Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation/SurfaceValidatorRunner.cs
2026-02-01 21:37:40 +02:00

100 lines
3.5 KiB
C#

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<ISurfaceValidator> _validators;
private readonly ILogger<SurfaceValidatorRunner> _logger;
private readonly ISurfaceValidationReporter _reporter;
private readonly SurfaceValidationOptions _options;
public SurfaceValidatorRunner(
IEnumerable<ISurfaceValidator> validators,
ILogger<SurfaceValidatorRunner> logger,
ISurfaceValidationReporter reporter,
IOptions<SurfaceValidationOptions> options)
{
_validators = validators?.ToArray() ?? Array.Empty<ISurfaceValidator>();
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_reporter = reporter ?? throw new ArgumentNullException(nameof(reporter));
_options = options?.Value ?? new SurfaceValidationOptions();
}
public async ValueTask<SurfaceValidationResult> 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<SurfaceValidationIssue>();
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);
}
}
}