< Summary

Information
Class: AsiBackbone.AspNetCore.Correlation.GovernanceHttpRequestCorrelation
Assembly: AsiBackbone.AspNetCore
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Correlation/GovernanceHttpRequestCorrelation.cs
Line coverage
93%
Covered lines: 44
Uncovered lines: 3
Coverable lines: 47
Total lines: 169
Line coverage: 93.6%
Branch coverage
77%
Covered branches: 31
Total branches: 40
Branch coverage: 77.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%11100%
get_HasMetadata()100%210%
ToEvaluationContext(...)100%22100%
NormalizeSuppliedMetadata(...)62.5%8887.5%
MergeMetadata(...)85.71%1414100%
NormalizeOptional(...)100%22100%
NormalizeMetadata(...)87.5%88100%
AddIfValid(...)50%7675%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Correlation/GovernanceHttpRequestCorrelation.cs

#LineLine coverage
 1using System.Collections.ObjectModel;
 2using AsiBackbone.Core.Constraints;
 3
 4namespace AsiBackbone.AspNetCore.Correlation;
 5
 6/// <summary>
 7/// Represents framework-neutral request correlation data resolved from the current ASP.NET Core HTTP request.
 8/// </summary>
 9/// <remarks>
 10/// Initializes a new instance of the <see cref="GovernanceHttpRequestCorrelation" /> class.
 11/// </remarks>
 12/// <param name="correlationId">The resolved correlation identifier, when available.</param>
 13/// <param name="traceId">The resolved trace identifier, when available.</param>
 14/// <param name="metadata">Safe request metadata resolved from the host.</param>
 5715public sealed class GovernanceHttpRequestCorrelation(
 5716    string? correlationId = null,
 5717    string? traceId = null,
 5718    IReadOnlyDictionary<string, string>? metadata = null)
 19{
 120    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 121        new ReadOnlyDictionary<string, string>(
 122            new Dictionary<string, string>(StringComparer.Ordinal));
 23
 24    /// <summary>
 25    /// Gets the request correlation identifier, when supplied by the host or propagated request headers.
 26    /// </summary>
 27    public string? CorrelationId { get; } = NormalizeOptional(correlationId);
 28
 29    /// <summary>
 30    /// Gets the request trace identifier, when supplied by the host or current activity.
 31    /// </summary>
 32    public string? TraceId { get; } = NormalizeOptional(traceId);
 33
 34    /// <summary>
 35    /// Gets safe request metadata supplied by the ASP.NET Core adapter.
 36    /// </summary>
 37    public IReadOnlyDictionary<string, string> Metadata { get; } = NormalizeMetadata(metadata);
 38
 39    /// <summary>
 40    /// Gets a value indicating whether safe request metadata is available.
 41    /// </summary>
 042    public bool HasMetadata => Metadata.Count > 0;
 43
 44    /// <summary>
 45    /// Creates a framework-neutral constraint evaluation context from the resolved request correlation data.
 46    /// </summary>
 47    /// <param name="policyVersion">Optional policy version.</param>
 48    /// <param name="policyHash">Optional policy hash.</param>
 49    /// <param name="metadata">Optional host-provided metadata to merge with safe request metadata.</param>
 50    /// <param name="mergeRequestMetadata">
 51    /// When <see langword="true" /> (the default), <see cref="Metadata" /> is merged underneath
 52    /// <paramref name="metadata" />. Pass <see langword="false" /> when <paramref name="metadata" /> already contains
 53    /// request metadata and is authoritative — for example after sanitization, where re-merging would reintroduce
 54    /// entries the sanitizer removed. <paramref name="metadata" /> is still key-normalized either way.
 55    /// </param>
 56    /// <returns>A framework-neutral constraint evaluation context.</returns>
 57    /// <remarks>
 58    /// Merging layers <paramref name="metadata" /> over <see cref="Metadata" /> by key, so a caller that rewrites a
 59    /// request-derived value overrides it, but a caller that <em>removes</em> one does not: with nothing to override
 60    /// the raw entry, the merge puts it back. Sanitized metadata must therefore be passed with
 61    /// <paramref name="mergeRequestMetadata" /> set to <see langword="false" />.
 62    /// </remarks>
 63    public GovernanceEvaluationContext ToEvaluationContext(
 64        string? policyVersion = null,
 65        string? policyHash = null,
 66        IReadOnlyDictionary<string, string>? metadata = null,
 67        bool mergeRequestMetadata = true)
 68    {
 2369        return new GovernanceEvaluationContext(
 2370            CorrelationId,
 2371            policyVersion,
 2372            policyHash,
 2373            mergeRequestMetadata ? MergeMetadata(metadata) : NormalizeSuppliedMetadata(metadata));
 74    }
 75
 76    /// <summary>
 77    /// Normalizes supplied metadata keys and values without merging request metadata underneath them.
 78    /// </summary>
 79    /// <param name="metadata">The metadata to normalize.</param>
 80    /// <returns>A normalized metadata dictionary containing only the supplied entries.</returns>
 81    private static IReadOnlyDictionary<string, string> NormalizeSuppliedMetadata(
 82        IReadOnlyDictionary<string, string>? metadata)
 83    {
 2184        if (metadata is null || metadata.Count == 0)
 85        {
 086            return EmptyMetadata;
 87        }
 88
 2189        Dictionary<string, string> normalized = new(StringComparer.Ordinal);
 90
 23491        foreach (KeyValuePair<string, string> item in metadata)
 92        {
 9693            AddIfValid(normalized, item.Key, item.Value);
 94        }
 95
 2196        return normalized.Count == 0
 2197            ? EmptyMetadata
 2198            : new ReadOnlyDictionary<string, string>(normalized);
 99    }
 100
 101    /// <summary>
 102    /// Merges safe request metadata with host-provided metadata.
 103    /// </summary>
 104    /// <param name="metadata">Optional host-provided metadata.</param>
 105    /// <returns>A normalized metadata dictionary.</returns>
 106    public IReadOnlyDictionary<string, string> MergeMetadata(IReadOnlyDictionary<string, string>? metadata)
 107    {
 32108        if ((metadata is null || metadata.Count == 0) && Metadata.Count == 0)
 109        {
 8110            return EmptyMetadata;
 111        }
 112
 24113        Dictionary<string, string> merged = new(StringComparer.Ordinal);
 114
 102115        foreach (KeyValuePair<string, string> item in Metadata)
 116        {
 27117            AddIfValid(merged, item.Key, item.Value);
 118        }
 119
 24120        if (metadata is not null)
 121        {
 214122            foreach (KeyValuePair<string, string> item in metadata)
 123            {
 83124                AddIfValid(merged, item.Key, item.Value);
 125            }
 126        }
 127
 24128        return merged.Count == 0
 24129            ? EmptyMetadata
 24130            : new ReadOnlyDictionary<string, string>(merged);
 131    }
 132
 133    private static string? NormalizeOptional(string? value)
 134    {
 114135        return string.IsNullOrWhiteSpace(value)
 114136            ? null
 114137            : value.Trim();
 138    }
 139
 140    private static IReadOnlyDictionary<string, string> NormalizeMetadata(
 141        IReadOnlyDictionary<string, string>? metadata)
 142    {
 57143        if (metadata is null || metadata.Count == 0)
 144        {
 9145            return EmptyMetadata;
 146        }
 147
 48148        Dictionary<string, string> normalizedMetadata = new(StringComparer.Ordinal);
 149
 216150        foreach (KeyValuePair<string, string> item in metadata)
 151        {
 60152            AddIfValid(normalizedMetadata, item.Key, item.Value);
 153        }
 154
 48155        return normalizedMetadata.Count == 0
 48156            ? EmptyMetadata
 48157            : new ReadOnlyDictionary<string, string>(normalizedMetadata);
 158    }
 159
 160    private static void AddIfValid(Dictionary<string, string> metadata, string key, string? value)
 161    {
 266162        if (string.IsNullOrWhiteSpace(key))
 163        {
 0164            return;
 165        }
 166
 266167        metadata[key.Trim()] = value?.Trim() ?? string.Empty;
 266168    }
 169}