< Summary

Information
Class: AsiBackbone.Storage.InMemory.CapabilityTokens.InMemoryCapabilityGrantUseStore
Assembly: AsiBackbone.Storage.InMemory
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Storage.InMemory/CapabilityTokens/InMemoryCapabilityGrantUseStore.cs
Line coverage
86%
Covered lines: 38
Uncovered lines: 6
Coverable lines: 44
Total lines: 125
Line coverage: 86.3%
Branch coverage
87%
Covered branches: 7
Total branches: 8
Branch coverage: 87.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%
GetUseCount(...)50%22100%
StopGrant(...)100%11100%
CancelGrant(...)100%11100%
Clear()100%210%
TryConsumeAsync(...)100%66100%
NormalizeGrantId(...)100%11100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Storage.InMemory/CapabilityTokens/InMemoryCapabilityGrantUseStore.cs

#LineLine coverage
 1using AsiBackbone.Core.CapabilityTokens;
 2
 3namespace AsiBackbone.Storage.InMemory.CapabilityTokens;
 4
 5/// <summary>
 6/// Provides a non-durable, in-process capability grant use store for tests, samples, and local validation.
 7/// </summary>
 8/// <remarks>
 9/// This store is thread-safe within a single process, but it is not durable, distributed, replicated, or suitable for
 10/// production replay protection. Hosts that require production single-use or bounded-use guarantees should provide a
 11/// durable implementation of <see cref="ICapabilityGrantUseStore" /> with documented transaction, locking, retention,
 12/// and failure semantics.
 13/// </remarks>
 14public sealed class InMemoryCapabilityGrantUseStore : ICapabilityGrantUseStore
 15{
 1016    private readonly Lock syncRoot = new();
 1017    private readonly Dictionary<string, int> useCounts = new(StringComparer.Ordinal);
 1018    private readonly HashSet<string> stoppedGrantIds = new(StringComparer.Ordinal);
 1019    private readonly HashSet<string> cancelledGrantIds = new(StringComparer.Ordinal);
 20
 21    /// <summary>
 22    /// Gets the observed use count for a grant identifier.
 23    /// </summary>
 24    /// <param name="grantId">The stable capability grant identifier.</param>
 25    /// <returns>The observed use count, or zero when the grant has not been consumed by this store instance.</returns>
 26    public int GetUseCount(string grantId)
 27    {
 628        string normalizedGrantId = NormalizeGrantId(grantId);
 29
 30        lock (syncRoot)
 31        {
 632            return useCounts.TryGetValue(normalizedGrantId, out int useCount)
 633                ? useCount
 634                : 0;
 35        }
 636    }
 37
 38    /// <summary>
 39    /// Marks a grant as stopped for subsequent local validation attempts.
 40    /// </summary>
 41    /// <param name="grantId">The stable capability grant identifier.</param>
 42    public void StopGrant(string grantId)
 43    {
 244        string normalizedGrantId = NormalizeGrantId(grantId);
 45
 46        lock (syncRoot)
 47        {
 248            _ = stoppedGrantIds.Add(normalizedGrantId);
 249            _ = cancelledGrantIds.Remove(normalizedGrantId);
 250        }
 251    }
 52
 53    /// <summary>
 54    /// Marks a grant as cancelled for subsequent local validation attempts.
 55    /// </summary>
 56    /// <param name="grantId">The stable capability grant identifier.</param>
 57    public void CancelGrant(string grantId)
 58    {
 259        string normalizedGrantId = NormalizeGrantId(grantId);
 60
 61        lock (syncRoot)
 62        {
 263            _ = cancelledGrantIds.Add(normalizedGrantId);
 264            _ = stoppedGrantIds.Remove(normalizedGrantId);
 265        }
 266    }
 67
 68    /// <summary>
 69    /// Clears use-count and stopped/cancelled state from this in-memory store instance.
 70    /// </summary>
 71    public void Clear()
 072    {
 73        lock (syncRoot)
 74        {
 075            useCounts.Clear();
 076            stoppedGrantIds.Clear();
 077            cancelledGrantIds.Clear();
 078        }
 079    }
 80
 81    /// <inheritdoc />
 82    public ValueTask<CapabilityGrantUseResult> TryConsumeAsync(
 83        CapabilityTokenGrant grant,
 84        int maxUseCount,
 85        DateTimeOffset usedUtc,
 86        CancellationToken cancellationToken = default)
 87    {
 1688        ArgumentNullException.ThrowIfNull(grant);
 1689        ArgumentOutOfRangeException.ThrowIfLessThan(maxUseCount, 1);
 1690        _ = usedUtc.ToUniversalTime();
 1691        cancellationToken.ThrowIfCancellationRequested();
 92
 93        lock (syncRoot)
 94        {
 1695            if (stoppedGrantIds.Contains(grant.TokenId))
 96            {
 297                return ValueTask.FromResult(CapabilityGrantUseResult.Stopped("The in-memory capability grant use store m
 98            }
 99
 14100            if (cancelledGrantIds.Contains(grant.TokenId))
 101            {
 2102                return ValueTask.FromResult(CapabilityGrantUseResult.Cancelled("The in-memory capability grant use store
 103            }
 104
 12105            _ = useCounts.TryGetValue(grant.TokenId, out int currentCount);
 106
 12107            if (currentCount >= maxUseCount)
 108            {
 4109                return ValueTask.FromResult(CapabilityGrantUseResult.UseLimitExceeded(
 4110                    currentCount,
 4111                    "The in-memory capability grant use limit was exceeded."));
 112            }
 113
 8114            int nextCount = currentCount + 1;
 8115            useCounts[grant.TokenId] = nextCount;
 8116            return ValueTask.FromResult(CapabilityGrantUseResult.Accepted(nextCount));
 117        }
 16118    }
 119
 120    private static string NormalizeGrantId(string grantId)
 121    {
 10122        ArgumentException.ThrowIfNullOrWhiteSpace(grantId);
 10123        return grantId.Trim();
 124    }
 125}