< Summary

Information
Class: NexMediator.Pipeline.Behaviors.FluentValidationBehavior<T1, T2>
Assembly: NexMediator.Pipeline
File(s): /home/runner/work/NexMediator/NexMediator/src/NexMediator.Pipeline/Behaviors/FluentValidationBehavior.cs
Line coverage
100%
Covered lines: 24
Uncovered lines: 0
Coverable lines: 24
Total lines: 73
Line coverage: 100%
Branch coverage
100%
Covered branches: 14
Total branches: 14
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%44100%
Handle()100%1414100%

File(s)

/home/runner/work/NexMediator/NexMediator/src/NexMediator.Pipeline/Behaviors/FluentValidationBehavior.cs

#LineLine coverage
 1using FluentValidation;
 2using Microsoft.Extensions.Logging;
 3using NexMediator.Abstractions.Interfaces;
 4
 5namespace NexMediator.Pipeline.Behaviors;
 6
 7/// <summary>
 8/// Pipeline behavior that runs FluentValidation rules on the request.
 9/// </summary>
 10/// <typeparam name="TRequest">The request type.</typeparam>
 11/// <typeparam name="TResponse">The response type.</typeparam>
 12public class FluentValidationBehavior<TRequest, TResponse> : INexPipelineBehavior<TRequest, TResponse>
 13    where TRequest : INexRequest<TResponse>
 14{
 15    private readonly IEnumerable<IValidator<TRequest>> _validators;
 16    private readonly ILogger<FluentValidationBehavior<TRequest, TResponse>> _logger;
 17
 18    /// <summary>
 19    /// Create the validation behavior.
 20    /// </summary>
 21    /// <param name="validators">A list of validators from DI.</param>
 22    /// <param name="logger">Logger instance for messages.</param>
 623    public FluentValidationBehavior(
 624        IEnumerable<IValidator<TRequest>> validators,
 625        ILogger<FluentValidationBehavior<TRequest, TResponse>> logger)
 626    {
 627        _validators = validators ?? throw new ArgumentNullException(nameof(validators));
 528        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 429    }
 30
 31    /// <summary>
 32    /// Run all validators for the request. If any errors, throw exception.
 33    /// </summary>
 34    /// <param name="request">The request to validate.</param>
 35    /// <param name="next">Next handler in the pipeline.</param>
 36    /// <param name="cancellationToken">Token to cancel the work.</param>
 37    /// <returns>The response, or throws if request is invalid.</returns>
 38    public async Task<TResponse> Handle(
 39        TRequest request,
 40        RequestHandlerDelegate<TResponse> next,
 41        CancellationToken cancellationToken)
 442    {
 43        // If no validators, skip validation
 544        if (!_validators.Any()) return await next();
 45
 346        var context = new ValidationContext<TRequest>(request);
 47
 48        // Remove duplicated validators (by type)
 349        var distinctValidators = _validators
 450            .Where(v => v.GetType() != typeof(InlineValidator<TRequest>))
 451            .GroupBy(v => v.GetType())
 452            .Select(g => g.First())
 353            .ToList();
 54
 355        _logger.LogDebug("Running {ValidatorCount} validators for request {RequestType}", distinctValidators.Count, type
 56
 57        // Run all validations in parallel
 758        var validationResults = await Task.WhenAll(distinctValidators.Select(v => v.ValidateAsync(context, cancellationT
 59
 60        // Collect all errors
 1061        var failures = validationResults.Where(r => !r.IsValid).SelectMany(r => r.Errors).ToList();
 62
 63        // If any validation failed, log and throw
 364        if (failures.Any())
 265        {
 266            _logger.LogWarning("Validation failed for {RequestType} with {ErrorCount} errors", typeof(TRequest).Name, fa
 267            throw new ValidationException("One or more validation errors occurred.", failures);
 68        }
 69
 70        // All validations passed
 171        return await next();
 272    }
 73}