< Summary

Information
Class: AsiBackbone.Core.Metadata.GovernanceMetadataBudget
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Metadata/GovernanceMetadataBudget.cs
Line coverage
100%
Covered lines: 60
Uncovered lines: 0
Coverable lines: 60
Total lines: 160
Line coverage: 100%
Branch coverage
100%
Covered branches: 10
Total branches: 10
Branch coverage: 100%
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%
Create(...)100%22100%
NormalizeKeyForComparison(...)100%44100%
NormalizeReservedKeyFragments(...)100%22100%
ThrowIfLessThanOne(...)100%22100%

File(s)

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

#LineLine coverage
 1using System.Collections.ObjectModel;
 2
 3namespace AsiBackbone.Core.Metadata;
 4
 5/// <summary>
 6/// Defines optional host-owned budget limits for governance metadata dictionaries.
 7/// </summary>
 8/// <remarks>
 9/// The budget is intentionally advisory and helper-oriented. AsiBackbone cannot determine whether
 10/// host-provided metadata is sensitive in a specific deployment, but hosts can use these limits to
 11/// keep audit and governance metadata bounded before durable storage, emission, or signing.
 12/// </remarks>
 13public sealed class GovernanceMetadataBudget
 14{
 15    /// <summary>
 16    /// Gets the default recommended maximum number of metadata entries.
 17    /// </summary>
 18    public const int DefaultMaxCount = 32;
 19
 20    /// <summary>
 21    /// Gets the default recommended maximum metadata key length, in characters.
 22    /// </summary>
 23    public const int DefaultMaxKeyLength = 64;
 24
 25    /// <summary>
 26    /// Gets the default recommended maximum metadata value length, in characters.
 27    /// </summary>
 28    public const int DefaultMaxValueLength = 512;
 29
 30    /// <summary>
 31    /// Gets the default recommended estimated serialized metadata size, in UTF-8 bytes.
 32    /// </summary>
 33    public const int DefaultMaxSerializedBytes = 8192;
 34
 235    private static readonly ReadOnlyCollection<string> DefaultReservedKeyFragments =
 236        new(
 237            new[]
 238            {
 239                "accesstoken",
 240                "apikey",
 241                "authorization",
 242                "bearer",
 243                "connectionstring",
 244                "credential",
 245                "password",
 246                "privatekey",
 247                "refreshtoken",
 248                "secret",
 249                "socialsecurity",
 250                "ssn"
 251            });
 52
 853    private GovernanceMetadataBudget(
 854        int maxCount,
 855        int maxKeyLength,
 856        int maxValueLength,
 857        int maxSerializedBytes,
 858        IReadOnlyList<string> reservedKeyFragments)
 59    {
 860        ThrowIfLessThanOne(maxCount, nameof(maxCount));
 761        ThrowIfLessThanOne(maxKeyLength, nameof(maxKeyLength));
 762        ThrowIfLessThanOne(maxValueLength, nameof(maxValueLength));
 763        ThrowIfLessThanOne(maxSerializedBytes, nameof(maxSerializedBytes));
 764        ArgumentNullException.ThrowIfNull(reservedKeyFragments);
 65
 766        MaxCount = maxCount;
 767        MaxKeyLength = maxKeyLength;
 768        MaxValueLength = maxValueLength;
 769        MaxSerializedBytes = maxSerializedBytes;
 770        ReservedKeyFragments = reservedKeyFragments;
 771    }
 72
 73    /// <summary>
 74    /// Gets the recommended governance metadata budget.
 75    /// </summary>
 76    public static GovernanceMetadataBudget Recommended { get; } = Create();
 77
 78    /// <summary>
 79    /// Gets the maximum number of normalized metadata entries allowed by the budget.
 80    /// </summary>
 81    public int MaxCount { get; }
 82
 83    /// <summary>
 84    /// Gets the maximum normalized metadata key length allowed by the budget, in characters.
 85    /// </summary>
 86    public int MaxKeyLength { get; }
 87
 88    /// <summary>
 89    /// Gets the maximum normalized metadata value length allowed by the budget, in characters.
 90    /// </summary>
 91    public int MaxValueLength { get; }
 92
 93    /// <summary>
 94    /// Gets the maximum estimated serialized metadata size allowed by the budget, in UTF-8 bytes.
 95    /// </summary>
 96    public int MaxSerializedBytes { get; }
 97
 98    /// <summary>
 99    /// Gets normalized reserved or discouraged metadata key fragments.
 100    /// </summary>
 101    public IReadOnlyList<string> ReservedKeyFragments { get; }
 102
 103    /// <summary>
 104    /// Creates a governance metadata budget.
 105    /// </summary>
 106    public static GovernanceMetadataBudget Create(
 107        int maxCount = DefaultMaxCount,
 108        int maxKeyLength = DefaultMaxKeyLength,
 109        int maxValueLength = DefaultMaxValueLength,
 110        int maxSerializedBytes = DefaultMaxSerializedBytes,
 111        IEnumerable<string>? reservedKeyFragments = null)
 112    {
 8113        return new GovernanceMetadataBudget(
 8114            maxCount,
 8115            maxKeyLength,
 8116            maxValueLength,
 8117            maxSerializedBytes,
 8118            NormalizeReservedKeyFragments(reservedKeyFragments ?? DefaultReservedKeyFragments));
 119    }
 120
 121    internal static string NormalizeKeyForComparison(string key)
 122    {
 44123        string normalizedKey = key.Trim().ToLowerInvariant();
 44124        char[] buffer = new char[normalizedKey.Length];
 44125        int count = 0;
 126
 892127        foreach (char character in normalizedKey)
 128        {
 402129            if (char.IsLetterOrDigit(character))
 130            {
 397131                buffer[count] = character;
 397132                count++;
 133            }
 134        }
 135
 44136        return new string(buffer, 0, count);
 137    }
 138
 139    private static ReadOnlyCollection<string> NormalizeReservedKeyFragments(IEnumerable<string> reservedKeyFragments)
 140    {
 8141        string[] normalizedFragments = [.. reservedKeyFragments
 8142            .Where(fragment => !string.IsNullOrWhiteSpace(fragment))
 8143            .Select(NormalizeKeyForComparison)
 8144            .Where(fragment => fragment.Length > 0)
 8145            .Distinct(StringComparer.Ordinal)
 8146            .OrderBy(fragment => fragment, StringComparer.Ordinal)];
 147
 8148        return normalizedFragments.Length == 0
 8149            ? Array.AsReadOnly(Array.Empty<string>())
 8150            : Array.AsReadOnly(normalizedFragments);
 151    }
 152
 153    private static void ThrowIfLessThanOne(int value, string parameterName)
 154    {
 29155        if (value < 1)
 156        {
 1157            throw new ArgumentOutOfRangeException(parameterName, value, "Value must be greater than or equal to one.");
 158        }
 28159    }
 160}