< Summary

Information
Class: AsiBackbone.AspNetCore.Endpoints.AsiBackboneEndpointGovernanceDescriptor
Assembly: AsiBackbone.AspNetCore
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Endpoints/AsiBackboneEndpointGovernanceDescriptor.cs
Line coverage
90%
Covered lines: 111
Uncovered lines: 11
Coverable lines: 122
Total lines: 289
Line coverage: 90.9%
Branch coverage
82%
Covered branches: 61
Total branches: 74
Branch coverage: 82.4%
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%
get_OperationName()100%11100%
get_PolicyTypes()100%11100%
get_ShortCircuitOnFirstDenial()100%11100%
get_RequiresLiabilityHandshake()100%11100%
get_CapabilityScopes()100%11100%
get_EmitGovernanceAudit()100%11100%
get_HasGovernanceMetadata()100%88100%
FromEndpoint(...)100%3232100%
None(...)100%11100%
ToMetadata()100%11100%
ToMetadata(...)75%4483.33%
ContainsPolicyType(...)16.66%19628.57%
ContainsScope(...)16.66%19628.57%
CreateFullMetadata()100%1212100%
CreateReducedMetadata(...)100%11100%
JoinPolicyTypeNames(...)75%44100%
ResolveOperationName(...)50%22100%

File(s)

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

#LineLine coverage
 1using System.Collections.ObjectModel;
 2using Microsoft.AspNetCore.Http;
 3
 4namespace AsiBackbone.AspNetCore.Endpoints;
 5
 6/// <summary>
 7/// Represents normalized AsiBackbone governance metadata resolved from an ASP.NET Core endpoint.
 8/// </summary>
 9public sealed class AsiBackboneEndpointGovernanceDescriptor
 10{
 311    private static readonly ReadOnlyCollection<Type> EmptyPolicyTypes = Array.AsReadOnly(Array.Empty<Type>());
 312    private static readonly ReadOnlyCollection<string> EmptyScopes = Array.AsReadOnly(Array.Empty<string>());
 13
 14    private readonly IReadOnlyDictionary<string, string> fullMetadata;
 15    private readonly IReadOnlyDictionary<string, string> reducedMetadata;
 16
 5017    private AsiBackboneEndpointGovernanceDescriptor(
 5018        string operationName,
 5019        IReadOnlyList<Type> policyTypes,
 5020        bool? shortCircuitOnFirstDenial,
 5021        bool requiresLiabilityHandshake,
 5022        IReadOnlyList<string> capabilityScopes,
 5023        bool emitGovernanceAudit)
 24    {
 5025        ArgumentException.ThrowIfNullOrWhiteSpace(operationName);
 26
 5027        OperationName = operationName.Trim();
 5028        PolicyTypes = policyTypes;
 5029        ShortCircuitOnFirstDenial = shortCircuitOnFirstDenial;
 5030        RequiresLiabilityHandshake = requiresLiabilityHandshake;
 5031        CapabilityScopes = capabilityScopes;
 5032        EmitGovernanceAudit = emitGovernanceAudit;
 5033        reducedMetadata = CreateReducedMetadata(OperationName);
 5034        fullMetadata = CreateFullMetadata();
 5035    }
 36
 37    /// <summary>
 38    /// Gets the operation name used for audit residue and acknowledgment challenge construction.
 39    /// </summary>
 10740    public string OperationName { get; }
 41
 42    /// <summary>
 43    /// Gets the policy marker or resolver types attached to the endpoint.
 44    /// </summary>
 18245    public IReadOnlyList<Type> PolicyTypes { get; }
 46
 47    /// <summary>
 48    /// Gets an endpoint-scoped first-denial short-circuit preference, when endpoint metadata supplied one.
 49    /// </summary>
 12050    public bool? ShortCircuitOnFirstDenial { get; }
 51
 52    /// <summary>
 53    /// Gets a value indicating whether liability-handshake support is requested.
 54    /// </summary>
 6555    public bool RequiresLiabilityHandshake { get; }
 56
 57    /// <summary>
 58    /// Gets the required capability-grant scopes attached to the endpoint.
 59    /// </summary>
 15560    public IReadOnlyList<string> CapabilityScopes { get; }
 61
 62    /// <summary>
 63    /// Gets a value indicating whether governance audit emission is requested.
 64    /// </summary>
 6965    public bool EmitGovernanceAudit { get; }
 66
 67    /// <summary>
 68    /// Gets a value indicating whether the endpoint contains any AsiBackbone governance metadata.
 69    /// </summary>
 3670    public bool HasGovernanceMetadata => PolicyTypes.Count > 0
 3671        || ShortCircuitOnFirstDenial.HasValue
 3672        || RequiresLiabilityHandshake
 3673        || CapabilityScopes.Count > 0
 3674        || EmitGovernanceAudit;
 75
 76    /// <summary>
 77    /// Creates a descriptor from the selected ASP.NET Core endpoint.
 78    /// </summary>
 79    /// <param name="endpoint">The selected endpoint.</param>
 80    /// <returns>A normalized descriptor.</returns>
 81    public static AsiBackboneEndpointGovernanceDescriptor FromEndpoint(Endpoint? endpoint)
 82    {
 4983        if (endpoint is null)
 84        {
 185            return None("unresolved-endpoint");
 86        }
 87
 4888        List<Type>? policyTypes = null;
 14489        foreach (IAsiBackboneEndpointGovernancePolicyMetadata metadata in endpoint.Metadata.GetOrderedMetadata<IAsiBackb
 90        {
 2491            if (metadata.PolicyType is not Type policyType || ContainsPolicyType(policyTypes, policyType))
 92            {
 93                continue;
 94            }
 95
 2496            policyTypes ??= [];
 2497            policyTypes.Add(policyType);
 98        }
 99
 48100        List<string>? capabilityScopes = null;
 152101        foreach (IAsiBackboneEndpointCapabilityGrantMetadata metadata in endpoint.Metadata.GetOrderedMetadata<IAsiBackbo
 102        {
 28103            if (string.IsNullOrWhiteSpace(metadata.Scope))
 104            {
 105                continue;
 106            }
 107
 28108            string scope = metadata.Scope.Trim();
 28109            if (ContainsScope(capabilityScopes, scope))
 110            {
 111                continue;
 112            }
 113
 28114            capabilityScopes ??= [];
 28115            capabilityScopes.Add(scope);
 116        }
 117
 48118        bool? shortCircuitOnFirstDenial = null;
 102119        foreach (IAsiBackboneEndpointPolicyEvaluationOptionsMetadata metadata in endpoint.Metadata.GetOrderedMetadata<IA
 120        {
 3121            shortCircuitOnFirstDenial = metadata.ShortCircuitOnFirstDenial;
 122        }
 123
 48124        bool requiresLiabilityHandshake = false;
 102125        foreach (IAsiBackboneEndpointLiabilityHandshakeMetadata metadata in endpoint.Metadata.GetOrderedMetadata<IAsiBac
 126        {
 6127            if (metadata.RequiresLiabilityHandshake)
 128            {
 6129                requiresLiabilityHandshake = true;
 6130                break;
 131            }
 132        }
 133
 48134        bool emitGovernanceAudit = false;
 103135        foreach (IAsiBackboneEndpointAuditEmissionMetadata metadata in endpoint.Metadata.GetOrderedMetadata<IAsiBackbone
 136        {
 7137            if (metadata.EmitGovernanceAudit)
 138            {
 7139                emitGovernanceAudit = true;
 7140                break;
 141            }
 142        }
 143
 48144        return new AsiBackboneEndpointGovernanceDescriptor(
 48145            ResolveOperationName(endpoint),
 48146            policyTypes is null ? EmptyPolicyTypes : Array.AsReadOnly(policyTypes.ToArray()),
 48147            shortCircuitOnFirstDenial,
 48148            requiresLiabilityHandshake,
 48149            capabilityScopes is null ? EmptyScopes : Array.AsReadOnly(capabilityScopes.ToArray()),
 48150            emitGovernanceAudit);
 151    }
 152
 153    /// <summary>
 154    /// Creates a descriptor that does not request governance handling.
 155    /// </summary>
 156    /// <param name="operationName">The operation name to associate with the descriptor.</param>
 157    /// <returns>A descriptor with no governance metadata.</returns>
 158    public static AsiBackboneEndpointGovernanceDescriptor None(string operationName)
 159    {
 2160        return new AsiBackboneEndpointGovernanceDescriptor(
 2161            operationName,
 2162            EmptyPolicyTypes,
 2163            shortCircuitOnFirstDenial: null,
 2164            requiresLiabilityHandshake: false,
 2165            EmptyScopes,
 2166            emitGovernanceAudit: false);
 167    }
 168
 169    /// <summary>
 170    /// Converts descriptor values into safe metadata for framework-neutral governance evaluation and audit residue.
 171    /// </summary>
 172    /// <returns>A normalized metadata dictionary.</returns>
 173    public IReadOnlyDictionary<string, string> ToMetadata()
 174    {
 6175        return ToMetadata(AsiBackboneEndpointGovernanceMetadataMode.Full);
 176    }
 177
 178    /// <summary>
 179    /// Converts descriptor values into safe metadata for framework-neutral governance evaluation and audit residue.
 180    /// </summary>
 181    /// <param name="metadataMode">The endpoint metadata mode to apply.</param>
 182    /// <returns>A normalized metadata dictionary.</returns>
 183    public IReadOnlyDictionary<string, string> ToMetadata(AsiBackboneEndpointGovernanceMetadataMode metadataMode)
 184    {
 29185        return metadataMode switch
 29186        {
 24187            AsiBackboneEndpointGovernanceMetadataMode.Full => fullMetadata,
 5188            AsiBackboneEndpointGovernanceMetadataMode.Reduced => reducedMetadata,
 0189            _ => throw new ArgumentOutOfRangeException(nameof(metadataMode), metadataMode, "Endpoint governance metadata
 29190        };
 191    }
 192
 193    private static bool ContainsPolicyType(List<Type>? policyTypes, Type policyType)
 194    {
 24195        if (policyTypes is null)
 196        {
 24197            return false;
 198        }
 199
 0200        foreach (Type currentPolicyType in policyTypes)
 201        {
 0202            if (currentPolicyType == policyType)
 203            {
 0204                return true;
 205            }
 206        }
 207
 0208        return false;
 0209    }
 210
 211    private static bool ContainsScope(List<string>? capabilityScopes, string scope)
 212    {
 28213        if (capabilityScopes is null)
 214        {
 28215            return false;
 216        }
 217
 0218        foreach (string currentScope in capabilityScopes)
 219        {
 0220            if (string.Equals(currentScope, scope, StringComparison.Ordinal))
 221            {
 0222                return true;
 223            }
 224        }
 225
 0226        return false;
 0227    }
 228
 229    private ReadOnlyDictionary<string, string> CreateFullMetadata()
 230    {
 50231        int metadataCapacity = 3
 50232            + (PolicyTypes.Count > 0 ? 1 : 0)
 50233            + (ShortCircuitOnFirstDenial.HasValue ? 1 : 0)
 50234            + (CapabilityScopes.Count > 0 ? 1 : 0);
 235
 50236        var metadata = new Dictionary<string, string>(metadataCapacity, StringComparer.Ordinal)
 50237        {
 50238            ["endpoint.operation_name"] = OperationName,
 50239            ["endpoint.requires_liability_handshake"] = RequiresLiabilityHandshake ? "true" : "false",
 50240            ["endpoint.emit_governance_audit"] = EmitGovernanceAudit ? "true" : "false"
 50241        };
 242
 50243        if (PolicyTypes.Count > 0)
 244        {
 24245            metadata["endpoint.policy_types"] = JoinPolicyTypeNames(PolicyTypes);
 246        }
 247
 50248        if (ShortCircuitOnFirstDenial.HasValue)
 249        {
 2250            metadata["endpoint.short_circuit_on_first_denial"] = ShortCircuitOnFirstDenial.Value ? "true" : "false";
 251        }
 252
 50253        if (CapabilityScopes.Count > 0)
 254        {
 28255            metadata["endpoint.capability_scopes"] = string.Join(",", CapabilityScopes);
 256        }
 257
 50258        return new ReadOnlyDictionary<string, string>(metadata);
 259    }
 260
 261    private static ReadOnlyDictionary<string, string> CreateReducedMetadata(string operationName)
 262    {
 50263        return new ReadOnlyDictionary<string, string>(
 50264            new Dictionary<string, string>(1, StringComparer.Ordinal)
 50265            {
 50266                ["endpoint.operation_name"] = operationName
 50267            });
 268    }
 269
 270    private static string JoinPolicyTypeNames(IReadOnlyList<Type> policyTypes)
 271    {
 24272        string[] policyTypeNames = new string[policyTypes.Count];
 273
 96274        for (int index = 0; index < policyTypeNames.Length; index++)
 275        {
 24276            Type policyType = policyTypes[index];
 24277            policyTypeNames[index] = policyType.FullName ?? policyType.Name;
 278        }
 279
 24280        return string.Join(",", policyTypeNames);
 281    }
 282
 283    private static string ResolveOperationName(Endpoint endpoint)
 284    {
 48285        return string.IsNullOrWhiteSpace(endpoint.DisplayName)
 48286            ? "aspnetcore.endpoint"
 48287            : endpoint.DisplayName.Trim();
 288    }
 289}