< Summary

Information
Class: AsiBackbone.AspNetCore.Endpoints.AsiBackboneEndpointGovernanceMiddleware
Assembly: AsiBackbone.AspNetCore
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Endpoints/AsiBackboneEndpointGovernanceMiddleware.cs
Line coverage
100%
Covered lines: 56
Uncovered lines: 0
Coverable lines: 56
Total lines: 118
Line coverage: 100%
Branch coverage
90%
Covered branches: 18
Total branches: 20
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
InvokeAsync()100%1212100%
CreateDefaultForbiddenResult(...)75%88100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Endpoints/AsiBackboneEndpointGovernanceMiddleware.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Http;
 2using Microsoft.Extensions.Options;
 3
 4namespace AsiBackbone.AspNetCore.Endpoints;
 5
 6/// <summary>
 7/// Middleware that evaluates AsiBackbone endpoint governance metadata before endpoint execution.
 8/// </summary>
 9public sealed class AsiBackboneEndpointGovernanceMiddleware
 10{
 11    // Example: use a precomputed static result for the most common forbidden response
 112    private static readonly IResult LightweightForbiddenResult =
 113        Microsoft.AspNetCore.Http.Results.StatusCode(StatusCodes.Status403Forbidden);
 14    // Use this for default cases instead of constructing new ProblemDetails every time.
 15
 16    private readonly RequestDelegate next;
 17    private readonly AsiBackboneEndpointGovernanceOptions options;
 18
 19    /// <summary>
 20    /// Initializes a new instance of the <see cref="AsiBackboneEndpointGovernanceMiddleware" /> class.
 21    /// </summary>
 22    /// <param name="next">The next request delegate.</param>
 23    /// <param name="endpointOptions">The endpoint governance options.</param>
 1324    public AsiBackboneEndpointGovernanceMiddleware(
 1325        RequestDelegate next,
 1326        IOptions<AsiBackboneEndpointGovernanceOptions> endpointOptions)
 27    {
 1328        ArgumentNullException.ThrowIfNull(next);
 1329        ArgumentNullException.ThrowIfNull(endpointOptions);
 30
 1331        this.next = next;
 1332        options = endpointOptions.Value;
 1333    }
 34
 35    /// <summary>
 36    /// Evaluates endpoint governance metadata and either continues execution or writes a failure result.
 37    /// </summary>
 38    /// <param name="httpContext">The current HTTP context.</param>
 39    /// <param name="governanceService">The endpoint governance service.</param>
 40    /// <returns>A task that completes when the middleware has run.</returns>
 41    public async Task InvokeAsync(
 42        HttpContext httpContext,
 43        IAsiBackboneEndpointGovernanceService governanceService)
 44    {
 1345        ArgumentNullException.ThrowIfNull(httpContext);
 1346        ArgumentNullException.ThrowIfNull(governanceService);
 47
 48        // Options validation is registered with ValidateOnStart in AddAsiBackboneAspNetCore.
 49        // IOptions<T> is singleton-backed, so keep the validated value from construction rather
 50        // than resolving IOptions<T> again through method injection on every request.
 1351        Endpoint? endpoint = httpContext.GetEndpoint();
 1352        var descriptor = AsiBackboneEndpointGovernanceDescriptor.FromEndpoint(endpoint);
 53
 1354        bool endpointAllowsMissingGovernance = endpoint?.Metadata
 1355            .GetMetadata<AllowMissingGovernanceMetadataAttribute>() is not null;
 56
 1357        if (!descriptor.HasGovernanceMetadata)
 58        {
 459            if (options.RequireGovernanceMetadata && !endpointAllowsMissingGovernance)
 60            {
 161                IResult missingGovernanceResult = CreateDefaultForbiddenResult(
 162                    httpContext,
 163                    options,
 164                    descriptor,
 165                    result: null,
 166                    decisionStage: "aspnetcore.endpoint.governance.metadata");
 67
 168                await missingGovernanceResult.ExecuteAsync(httpContext).ConfigureAwait(false);
 169                return;
 70            }
 71
 372            await next(httpContext).ConfigureAwait(false);
 373            return;
 74        }
 75
 976        AsiBackboneEndpointGovernanceResult result = await governanceService
 977            .EvaluateAsync(httpContext, descriptor, httpContext.RequestAborted)
 978            .ConfigureAwait(false);
 79
 980        if (result.CanExecute)
 81        {
 182            await next(httpContext).ConfigureAwait(false);
 183            return;
 84        }
 85
 886        IResult failureResult = result.FailureResult ?? CreateDefaultForbiddenResult(
 887            httpContext,
 888            options,
 889            descriptor,
 890            result,
 891            "aspnetcore.endpoint.governance.decision");
 92
 893        await failureResult.ExecuteAsync(httpContext).ConfigureAwait(false);
 1394    }
 95
 96    private static IResult CreateDefaultForbiddenResult(
 97        HttpContext httpContext,
 98        AsiBackboneEndpointGovernanceOptions options,
 99        AsiBackboneEndpointGovernanceDescriptor descriptor,
 100        AsiBackboneEndpointGovernanceResult? result,
 101        string decisionStage)
 102    {
 7103        return AsiBackboneEndpointGovernanceDevelopmentDiagnostics.IsEnabled(httpContext, options)
 7104            ? AsiBackboneEndpointGovernanceDevelopmentDiagnostics.CreateProblem(
 7105                httpContext,
 7106                options,
 7107                descriptor,
 7108                result?.Decision,
 7109                decisionStage,
 7110                title: "Endpoint governance blocked execution.",
 7111                detail: "Endpoint governance blocked this request before the selected endpoint executed.",
 7112                statusCode: StatusCodes.Status403Forbidden)
 7113            : options.DefaultForbiddenResultFactory is null
 7114            ? LightweightForbiddenResult
 7115            : options.DefaultForbiddenResultFactory(httpContext)
 7116                ?? throw new InvalidOperationException($"{nameof(AsiBackboneEndpointGovernanceOptions.DefaultForbiddenRe
 117    }
 118}