< Summary

Information
Class: AsiBackbone.AspNetCore.Endpoints.AsiBackboneEndpointGovernanceDevelopmentDiagnostics
Assembly: AsiBackbone.AspNetCore
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Endpoints/AsiBackboneEndpointGovernanceDevelopmentDiagnostics.cs
Line coverage
91%
Covered lines: 76
Uncovered lines: 7
Coverable lines: 83
Total lines: 176
Line coverage: 91.5%
Branch coverage
63%
Covered branches: 24
Total branches: 38
Branch coverage: 63.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsEnabled(...)75%44100%
CreateProblem(...)100%22100%
CreateExtensions(...)50%4493.1%
RedactMetadata(...)75%44100%
ShouldRedactMetadataValue(...)50%271869.23%
CreateDocumentationUrl(...)75%4483.33%
AddIfPresent(...)100%22100%

File(s)

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

#LineLine coverage
 1using AsiBackbone.Core.Decisions;
 2using Microsoft.AspNetCore.Hosting;
 3using Microsoft.AspNetCore.Http;
 4using Microsoft.Extensions.DependencyInjection;
 5using Microsoft.Extensions.Hosting;
 6
 7namespace AsiBackbone.AspNetCore.Endpoints;
 8
 9internal static class AsiBackboneEndpointGovernanceDevelopmentDiagnostics
 10{
 11    private const string RedactedValue = "[redacted]";
 12    private const string DocumentationArticleName = "endpoint-governance-development-diagnostics.html";
 13
 14    public static bool IsEnabled(
 15        HttpContext httpContext,
 16        AsiBackboneEndpointGovernanceOptions options)
 17    {
 1618        ArgumentNullException.ThrowIfNull(httpContext);
 1619        ArgumentNullException.ThrowIfNull(options);
 20
 1621        if (!options.EnableDevelopmentDiagnostics)
 22        {
 1023            return false;
 24        }
 25
 626        IWebHostEnvironment? environment = httpContext.RequestServices.GetService<IWebHostEnvironment>();
 27
 628        return environment?.IsDevelopment() == true;
 29    }
 30
 31    public static IResult CreateProblem(
 32        HttpContext httpContext,
 33        AsiBackboneEndpointGovernanceOptions options,
 34        AsiBackboneEndpointGovernanceDescriptor descriptor,
 35        GovernanceDecision? decision,
 36        string decisionStage,
 37        string title,
 38        string detail,
 39        int statusCode,
 40        IReadOnlyDictionary<string, string>? metadata = null)
 41    {
 442        ArgumentNullException.ThrowIfNull(httpContext);
 443        ArgumentNullException.ThrowIfNull(options);
 444        ArgumentNullException.ThrowIfNull(descriptor);
 445        ArgumentException.ThrowIfNullOrWhiteSpace(decisionStage);
 446        ArgumentException.ThrowIfNullOrWhiteSpace(title);
 447        ArgumentException.ThrowIfNullOrWhiteSpace(detail);
 48
 449        Dictionary<string, string> diagnosticMetadata = metadata is null
 450            ? new Dictionary<string, string>(descriptor.ToMetadata(), StringComparer.Ordinal)
 451            : new Dictionary<string, string>(metadata, StringComparer.Ordinal);
 452        Dictionary<string, object?> extensions = CreateExtensions(
 453            options,
 454            descriptor,
 455            decision,
 456            decisionStage,
 457            diagnosticMetadata);
 58
 459        return Microsoft.AspNetCore.Http.Results.Problem(
 460            title: title,
 461            detail: detail,
 462            statusCode: statusCode,
 463            extensions: extensions);
 64    }
 65
 66    private static Dictionary<string, object?> CreateExtensions(
 67        AsiBackboneEndpointGovernanceOptions options,
 68        AsiBackboneEndpointGovernanceDescriptor descriptor,
 69        GovernanceDecision? decision,
 70        string decisionStage,
 71        IReadOnlyDictionary<string, string> metadata)
 72    {
 473        var extensions = new Dictionary<string, object?>(StringComparer.Ordinal)
 474        {
 475            ["decisionStage"] = decisionStage,
 476            ["endpointOperationName"] = descriptor.OperationName,
 477            ["endpointPolicyTypes"] = descriptor.PolicyTypes
 278                .Select(static policyType => policyType.FullName ?? policyType.Name)
 079                .OrderBy(static policyType => policyType, StringComparer.Ordinal)
 480                .ToArray(),
 481            ["capabilityScopes"] = descriptor.CapabilityScopes
 082                .OrderBy(static scope => scope, StringComparer.Ordinal)
 483                .ToArray(),
 484            ["metadataKeys"] = metadata.Keys
 1885                .OrderBy(static key => key, StringComparer.Ordinal)
 486                .ToArray(),
 487            ["metadata"] = RedactMetadata(options, metadata)
 488        };
 89
 490        if (decision is not null)
 91        {
 492            extensions["outcome"] = decision.Outcome.ToString();
 493            extensions["reasonCodes"] = decision.ReasonCodes.ToArray();
 494            extensions["reasonMessages"] = decision.Reasons
 495                .Select(static reason => reason.Message)
 496                .ToArray();
 97
 498            AddIfPresent(extensions, "correlationId", decision.CorrelationId);
 499            AddIfPresent(extensions, "traceId", decision.TraceId);
 4100            AddIfPresent(extensions, "policyVersion", decision.PolicyVersion);
 4101            AddIfPresent(extensions, "policyHash", decision.PolicyHash);
 102        }
 103
 4104        string? documentationUrl = CreateDocumentationUrl(options.DevelopmentDiagnosticsDocumentationBaseUrl);
 4105        AddIfPresent(extensions, "documentationUrl", documentationUrl);
 106
 4107        return extensions;
 108    }
 109
 110    private static Dictionary<string, string> RedactMetadata(
 111        AsiBackboneEndpointGovernanceOptions options,
 112        IReadOnlyDictionary<string, string> metadata)
 113    {
 4114        Dictionary<string, string> redacted = new(StringComparer.Ordinal);
 115
 62116        foreach (KeyValuePair<string, string> item in metadata.OrderBy(static pair => pair.Key, StringComparer.Ordinal))
 117        {
 18118            redacted[item.Key] = ShouldRedactMetadataValue(options, item.Key)
 18119                ? RedactedValue
 18120                : item.Value;
 121        }
 122
 4123        return redacted;
 124    }
 125
 126    private static bool ShouldRedactMetadataValue(
 127        AsiBackboneEndpointGovernanceOptions options,
 128        string key)
 129    {
 18130        if (!options.IncludeDevelopmentDiagnosticsMetadataValues)
 131        {
 0132            return true;
 133        }
 134
 36135        foreach (string sensitiveKey in options.DevelopmentDiagnosticsRedactedMetadataKeys)
 136        {
 0137            if (string.Equals(key, sensitiveKey, StringComparison.OrdinalIgnoreCase))
 138            {
 0139                return true;
 140            }
 141        }
 142
 18143        return key.Contains("secret", StringComparison.OrdinalIgnoreCase)
 18144            || key.Contains("token", StringComparison.OrdinalIgnoreCase)
 18145            || key.Contains("password", StringComparison.OrdinalIgnoreCase)
 18146            || key.Contains("credential", StringComparison.OrdinalIgnoreCase)
 18147            || key.Contains("cookie", StringComparison.OrdinalIgnoreCase)
 18148            || key.Contains("authorization", StringComparison.OrdinalIgnoreCase)
 18149            || key.Contains("key", StringComparison.OrdinalIgnoreCase);
 0150    }
 151
 152    private static string? CreateDocumentationUrl(string? baseUrl)
 153    {
 4154        if (string.IsNullOrWhiteSpace(baseUrl))
 155        {
 0156            return null;
 157        }
 158
 4159        string trimmedBaseUrl = baseUrl.Trim();
 160
 4161        return trimmedBaseUrl.EndsWith('/')
 4162            ? trimmedBaseUrl + DocumentationArticleName
 4163            : trimmedBaseUrl + "/" + DocumentationArticleName;
 164    }
 165
 166    private static void AddIfPresent(
 167        Dictionary<string, object?> extensions,
 168        string key,
 169        string? value)
 170    {
 20171        if (!string.IsNullOrWhiteSpace(value))
 172        {
 16173            extensions[key] = value;
 174        }
 20175    }
 176}