< Summary

Information
Class: AsiBackbone.Core.Metadata.DefaultGovernanceMetadataSanitizer
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Metadata/DefaultGovernanceMetadataSanitizer.cs
Line coverage
100%
Covered lines: 69
Uncovered lines: 0
Coverable lines: 69
Total lines: 139
Line coverage: 100%
Branch coverage
94%
Covered branches: 32
Total branches: 34
Branch coverage: 94.1%
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%88100%
SanitizeAsync()95.45%2222100%
GetStricterAction(...)75%44100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Metadata/DefaultGovernanceMetadataSanitizer.cs

#LineLine coverage
 1using AsiBackbone.Core.Results;
 2
 3namespace AsiBackbone.Core.Metadata;
 4
 5/// <summary>
 6/// Provides the default provider-neutral governance metadata classification, sanitation, and budget-validation pipeline
 7/// </summary>
 8/// <remarks>
 9/// The pipeline normalizes caller-owned metadata into a new collection, applies classifiers in registration order,
 10/// removes or redacts values as directed, and then applies <see cref="GovernanceMetadataBudgetValidator" /> to the
 11/// sanitized collection. Classifier exceptions are not converted into permissive results; they stop the pipeline so
 12/// hosts do not accidentally persist or emit metadata that was not successfully classified.
 13/// </remarks>
 14public sealed class DefaultGovernanceMetadataSanitizer : IGovernanceMetadataSanitizer
 15{
 316    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 317        GovernanceMetadataBudgetValidator.Normalize(null);
 18
 19    private readonly IReadOnlyList<IGovernanceMetadataClassifier> classifiers;
 20    private readonly GovernanceMetadataBudget budget;
 21
 22    /// <summary>
 23    /// Initializes a new instance of the <see cref="DefaultGovernanceMetadataSanitizer" /> class.
 24    /// </summary>
 25    /// <param name="classifiers">The host-owned classifiers applied in registration order.</param>
 26    /// <param name="budget">The metadata budget applied after classification and sanitation.</param>
 1727    public DefaultGovernanceMetadataSanitizer(
 1728        IEnumerable<IGovernanceMetadataClassifier>? classifiers = null,
 1729        GovernanceMetadataBudget? budget = null)
 30    {
 1731        IGovernanceMetadataClassifier[] resolvedClassifiers = classifiers?.ToArray() ?? [];
 32
 5233        foreach (IGovernanceMetadataClassifier classifier in resolvedClassifiers)
 34        {
 935            ArgumentNullException.ThrowIfNull(classifier);
 36        }
 37
 1738        this.classifiers = resolvedClassifiers;
 1739        this.budget = budget ?? GovernanceMetadataBudget.Recommended;
 1740    }
 41
 42    /// <inheritdoc />
 43    public async ValueTask<GovernanceMetadataSanitizationResult> SanitizeAsync(
 44        IReadOnlyDictionary<string, string>? metadata,
 45        CancellationToken cancellationToken = default)
 46    {
 1547        cancellationToken.ThrowIfCancellationRequested();
 48
 1549        IReadOnlyDictionary<string, string> normalizedMetadata =
 1550            GovernanceMetadataBudgetValidator.Normalize(metadata);
 1551        Dictionary<string, string> sanitizedMetadata = new(normalizedMetadata.Count, StringComparer.Ordinal);
 1552        List<OperationReason> reasons = [];
 1553        GovernanceMetadataSanitizationAction overallAction = GovernanceMetadataSanitizationAction.Allow;
 54
 6255        foreach (KeyValuePair<string, string> item in normalizedMetadata)
 56        {
 1657            cancellationToken.ThrowIfCancellationRequested();
 58
 1659            GovernanceMetadataSanitizationAction entryAction = GovernanceMetadataSanitizationAction.Allow;
 1660            string replacementValue = item.Value;
 1661            var context = new GovernanceMetadataClassificationContext(
 1662                item.Key,
 1663                item.Value,
 1664                normalizedMetadata);
 65
 5666            foreach (IGovernanceMetadataClassifier classifier in classifiers)
 67            {
 1268                GovernanceMetadataClassificationResult classification = await classifier
 1269                    .ClassifyAsync(context, cancellationToken)
 1270                    .ConfigureAwait(false);
 71
 1272                ArgumentNullException.ThrowIfNull(classification);
 73
 1274                entryAction = GetStricterAction(entryAction, classification.Action);
 75
 1276                if (classification.Reason is not null)
 77                {
 1278                    reasons.Add(classification.Reason);
 79                }
 80
 1281                if (classification.Action is GovernanceMetadataSanitizationAction.Redact)
 82                {
 483                    replacementValue = classification.ReplacementValue
 484                        ?? GovernanceMetadataClassificationResult.DefaultRedactedValue;
 85                }
 86            }
 87
 1688            overallAction = GetStricterAction(overallAction, entryAction);
 89
 1690            if (entryAction is GovernanceMetadataSanitizationAction.Allow
 1691                or GovernanceMetadataSanitizationAction.Warn)
 92            {
 493                sanitizedMetadata[item.Key] = item.Value;
 94            }
 1295            else if (entryAction is GovernanceMetadataSanitizationAction.Redact)
 96            {
 497                sanitizedMetadata[item.Key] = replacementValue;
 98            }
 1699        }
 100
 15101        GovernanceMetadataBudgetValidationResult budgetValidation =
 15102            GovernanceMetadataBudgetValidator.Validate(sanitizedMetadata, budget);
 103
 15104        if (!budgetValidation.IsValid)
 105        {
 4106            overallAction = GovernanceMetadataSanitizationAction.Deny;
 4107            reasons.Add(OperationReason.Create(
 4108                GovernanceMetadataSanitizationReasonCodes.BudgetViolation,
 4109                "Governance metadata failed the configured shape or reserved-key budget."));
 110        }
 111
 15112        IReadOnlyDictionary<string, string> forwardMetadata =
 15113            overallAction is GovernanceMetadataSanitizationAction.Deny
 15114                ? EmptyMetadata
 15115                : budgetValidation.NormalizedMetadata;
 116
 15117        IReadOnlyList<OperationReason> resultReasons = reasons.Count == 0
 15118            ? []
 15119            : reasons.AsReadOnly();
 120
 15121        return GovernanceMetadataSanitizationResult.Create(
 15122            overallAction,
 15123            forwardMetadata,
 15124            resultReasons,
 15125            budgetValidation);
 15126    }
 127
 128    private static GovernanceMetadataSanitizationAction GetStricterAction(
 129        GovernanceMetadataSanitizationAction current,
 130        GovernanceMetadataSanitizationAction candidate)
 131    {
 28132        return !Enum.IsDefined(candidate)
 28133            ? throw new ArgumentOutOfRangeException(
 28134                nameof(candidate),
 28135                candidate,
 28136                "Metadata sanitation action must be defined.")
 28137            : candidate > current ? candidate : current;
 138    }
 139}