< Summary

Information
Class: ClutterStock.Api.Filters.DataAnnotationsValidationFilter
Assembly: Api
File(s): /home/runner/work/ClutterStock/ClutterStock/backend/src/Api/Filters/DataAnnotationsValidationFilter.cs
Tag: 58_25416222083
Line coverage
92%
Covered lines: 50
Uncovered lines: 4
Coverable lines: 54
Total lines: 107
Line coverage: 92.5%
Branch coverage
79%
Covered branches: 43
Total branches: 54
Branch coverage: 79.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
InvokeAsync()71.42%1414100%
ValidateArgument(...)58.33%131280.95%
AddResults(...)83.33%1212100%
IsSimpleType(...)100%1616100%

File(s)

/home/runner/work/ClutterStock/ClutterStock/backend/src/Api/Filters/DataAnnotationsValidationFilter.cs

#LineLine coverage
 1using System.ComponentModel.DataAnnotations;
 2using System.Reflection;
 3
 4namespace ClutterStock.Api.Filters;
 5
 6/// <summary>
 7///     Endpoint filter that runs <see cref="System.ComponentModel.DataAnnotations" /> validation
 8///     on every endpoint argument. Returns an RFC 7807 <c>ValidationProblemDetails</c> response
 9///     when validation fails. Applied group-wide by the endpoint source generator.
 10/// </summary>
 11/// <remarks>
 12///     This filter is the project's validation strategy because the .NET 10 minimal-API
 13///     <c>AddValidation()</c> source generator can't statically resolve our endpoints — they
 14///     are mapped via <c>MapPost(route, EndpointType.Handler)</c> where <c>Handler</c> is a
 15///     <see cref="Delegate" />-typed property. The generator skips such call sites and emits
 16///     no validatable-type info, leaving validation as a no-op.
 17/// </remarks>
 18internal sealed class DataAnnotationsValidationFilter : IEndpointFilter
 19{
 20    public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
 21    {
 4222        var parameters = context.HttpContext.GetEndpoint()
 4223                                ?.Metadata
 4224                                .GetMetadata<MethodInfo>()
 4225                                ?.GetParameters();
 26
 4227        Dictionary<string, List<string>>? errors = null;
 28
 34229        for (var i = 0; i < context.Arguments.Count; i++)
 30        {
 12931            var argument = context.Arguments[i];
 12932            if (argument is null)
 33                continue;
 34
 12935            var parameter = parameters is not null && i < parameters.Length ? parameters[i] : null;
 12936            ValidateArgument(argument, parameter, ref errors);
 37        }
 38
 4239        if (errors is not null)
 40        {
 941            var formatted = errors.ToDictionary(static kv => kv.Key, static kv => kv.Value.ToArray());
 942            return Results.ValidationProblem(formatted);
 43        }
 44
 3345        return await next(context);
 4246    }
 47
 48    private static void ValidateArgument(object argument, ParameterInfo? parameter, ref Dictionary<string, List<string>>
 49    {
 12950        if (IsSimpleType(argument.GetType()))
 51        {
 1852            if (parameter is null)
 053                return;
 54
 1855            var attributes = parameter.GetCustomAttributes<ValidationAttribute>(inherit: true).ToArray();
 1856            if (attributes.Length == 0)
 057                return;
 58
 1859            var results = new List<ValidationResult>();
 1860            var ctx = new ValidationContext(argument)
 1861            {
 1862                MemberName = parameter.Name
 1863            };
 1864            if (Validator.TryValidateValue(argument, ctx, results, attributes))
 1865                return;
 66
 067            AddResults(results, parameter.Name ?? string.Empty, ref errors);
 068            return;
 69        }
 70
 11171        var objectResults = new List<ValidationResult>();
 11172        var objectContext = new ValidationContext(argument);
 11173        if (Validator.TryValidateObject(argument, objectContext, objectResults, validateAllProperties: true))
 10274            return;
 75
 976        AddResults(objectResults, fallbackKey: argument.GetType().Name, ref errors);
 977    }
 78
 79    private static void AddResults(IEnumerable<ValidationResult> results, string fallbackKey, ref Dictionary<string, Lis
 80    {
 3881        foreach (var result in results)
 82        {
 1083            var message = result.ErrorMessage ?? "Invalid value";
 1084            var members = result.MemberNames.Any() ? result.MemberNames : [fallbackKey];
 4085            foreach (var member in members)
 86            {
 1087                errors ??= new Dictionary<string, List<string>>();
 1088                if (!errors.TryGetValue(member, out var list))
 1089                    errors[member] = list = [];
 1090                list.Add(message);
 91            }
 92        }
 993    }
 94
 95    private static bool IsSimpleType(Type type)
 96    {
 12997        var underlying = Nullable.GetUnderlyingType(type) ?? type;
 12998        return underlying.IsPrimitive
 12999               || underlying.IsEnum
 129100               || underlying == typeof(string)
 129101               || underlying == typeof(decimal)
 129102               || underlying == typeof(DateTime)
 129103               || underlying == typeof(DateTimeOffset)
 129104               || underlying == typeof(TimeSpan)
 129105               || underlying == typeof(Guid);
 106    }
 107}