using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace StellaOps.Scanner.Surface.Validation; internal sealed class SurfaceValidatorRunner : ISurfaceValidatorRunner { private readonly IReadOnlyList _validators; private readonly ILogger _logger; private readonly ISurfaceValidationReporter _reporter; private readonly SurfaceValidationOptions _options; public SurfaceValidatorRunner( IEnumerable validators, ILogger logger, ISurfaceValidationReporter reporter, IOptions options) { _validators = validators?.ToArray() ?? Array.Empty(); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _reporter = reporter ?? throw new ArgumentNullException(nameof(reporter)); _options = options?.Value ?? new SurfaceValidationOptions(); } public async ValueTask RunAllAsync( SurfaceValidationContext context, CancellationToken cancellationToken = default) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (_validators.Count == 0) { var success = SurfaceValidationResult.Success(); _reporter.Report(context, success); return success; } var issues = new List(); foreach (var validator in _validators) { cancellationToken.ThrowIfCancellationRequested(); try { var result = await validator.ValidateAsync(context, cancellationToken).ConfigureAwait(false); if (!result.IsSuccess) { issues.AddRange(result.Issues); if (!_options.ContinueOnError && result.Issues.Any(issue => issue.Severity == SurfaceValidationSeverity.Error)) { break; } } } catch (Exception ex) { _logger.LogError(ex, "Surface validator {Validator} threw an exception.", validator.GetType().FullName); issues.Add(SurfaceValidationIssue.Error( SurfaceValidationIssueCodes.ValidatorException, $"Validator '{validator.GetType().FullName}' threw an exception: {ex.Message}", "Inspect logs for stack trace.")); if (!_options.ContinueOnError) { break; } } } var resultAggregate = issues.Count == 0 ? SurfaceValidationResult.Success() : SurfaceValidationResult.FromIssues(issues); _reporter.Report(context, resultAggregate); return resultAggregate; } public async ValueTask EnsureAsync( SurfaceValidationContext context, CancellationToken cancellationToken = default) { var result = await RunAllAsync(context, cancellationToken).ConfigureAwait(false); if (!result.IsSuccess && _options.ThrowOnFailure) { throw new SurfaceValidationException( $"Surface validation failed for component '{context.ComponentName}'.", result.Issues); } } }