< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.Internal.MongoNamespaceRegistry
Assembly: AsyncResponse.Channels.MongoDB
File(s): /_/src/Shared/MongoNamespaceRegistry.cs
Line coverage
100%
Covered lines: 24
Uncovered lines: 0
Coverable lines: 24
Total lines: 89
Line coverage: 100%
Branch coverage
100%
Covered branches: 8
Total branches: 8
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
Claim(...)100%66100%
ClusterKey(...)100%11100%
ValidateEffectiveNamespace(...)100%22100%

File(s)

/_/src/Shared/MongoNamespaceRegistry.cs

#LineLine coverage
 1using MongoDB.Driver;
 2using System.Text;
 3
 4namespace AsyncResponse.Internal;
 5
 6/// <summary>
 7/// Container-scoped ownership ledger for MongoDB collections. The channel, transport, and
 8/// durable-flow stores validate their own collection plans, but none can see the others' —
 9/// and MongoDB has no catalog "relation kind" to verify against after the fact: a durable-flow
 10/// store configured onto the channel's derived <c>{MessageCollection}_counters</c> collection
 11/// would happily create flow documents there, and its TTL index would then silently delete the
 12/// ack-sequence counter. Each store claims its effective collections (derived ones included) at
 13/// construction, keyed by cluster + database, so whichever component starts second fails with an
 14/// actionable error naming both claimants — in either startup order. Registered per container
 15/// (no static state), so independent hosts and test fixtures never see each other.
 16/// </summary>
 17/// <remarks>
 18/// Source-linked into the channel, transport, and durable-flow packages (matching
 19/// <c>MongoOwnershipLedger</c>), but the <see cref="IMongoNamespaceRegistry"/> seam it implements
 20/// lives in Core: registering <em>this</em> type directly under <c>TryAddSingleton</c> would key
 21/// the DI container on a per-package-compiled type, so two MongoDB packages sharing one container
 22/// would each install their own instance instead of sharing one — silently splitting the registry
 23/// and defeating cross-component collision detection. Resolving through the Core-defined interface
 24/// keeps one shared singleton regardless of which package's registration runs first.
 25/// </remarks>
 26internal sealed class MongoNamespaceRegistry : IMongoNamespaceRegistry
 27{
 38828    private readonly object _gate = new();
 38829    private readonly Dictionary<string, (string Component, string Purpose)> _claims = new(StringComparer.Ordinal);
 30
 31    /// <inheritdoc />
 32    public void Claim(
 33        string clusterKey,
 34        string databaseName,
 35        string componentName,
 36        IReadOnlyList<(string Collection, string Purpose)> collections)
 37    {
 45538        lock (_gate)
 39        {
 411840            foreach (var (collection, purpose) in collections)
 41            {
 160742                var key = $"{clusterKey}|{databaseName}|{collection}";
 160743                if (_claims.TryGetValue(key, out var existing)
 160744                    && !string.Equals(existing.Component, componentName, StringComparison.Ordinal))
 45                {
 646                    throw new InvalidOperationException(
 647                        $"MongoDB collection '{databaseName}.{collection}' is used by both the {existing.Component} " +
 648                        $"({existing.Purpose}) and the {componentName} ({purpose}). Components sharing a database must u
 649                        "distinct collections — including derived ones such as the channel's '{MessageCollection}_counte
 650                        "ack-sequence counter, whose documents another component's TTL index would silently delete. " +
 651                        "Rename one of the configured collection names.");
 52                }
 53
 160154                _claims[key] = (componentName, purpose);
 55            }
 56        }
 44957    }
 58
 59    /// <summary>
 60    /// Stable identity of the cluster a database handle points at, for cross-component
 61    /// collection-ownership claims: same servers + same database name = same namespace space.
 62    /// The one implementation every store's ownership claim calls, so a derivation drift (SRV
 63    /// seedlist normalization, <c>DirectConnection</c>, host casing) can no longer desync the
 64    /// keys and silently turn collision detection into a no-op.
 65    /// </summary>
 66    internal static string ClusterKey(IMongoDatabase database)
 115267        => string.Join(",", database.Client.Settings.Servers.Select(static s => s.ToString()).OrderBy(static s => s, Str
 68
 69    /// <summary>MongoDB's SHARDED namespace byte limit; see <see cref="ValidateEffectiveNamespace"/>.</summary>
 70    internal const int ShardedNamespaceByteLimit = 235;
 71
 72    /// <summary>
 73    /// Validates an effective namespace ("database.collection") against MongoDB's 235-byte
 74    /// SHARDED namespace limit — tighter than the 255-byte limit on an unsharded namespace, and
 75    /// enforced here even while unsharded so a later <c>shardCollection</c> cannot strand an
 76    /// already-created collection whose namespace fit under 255 but not 235. Only the store
 77    /// constructor knows the actual database name, so this cannot live in options validation.
 78    /// </summary>
 79    internal static void ValidateEffectiveNamespace(IMongoDatabase database, string collectionName, string description)
 80    {
 214881        var ns = $"{database.DatabaseNamespace.DatabaseName}.{collectionName}";
 214882        var byteLength = Encoding.UTF8.GetByteCount(ns);
 214883        if (byteLength > ShardedNamespaceByteLimit)
 484            throw new InvalidOperationException(
 485                $"The MongoDB namespace '{ns}' ({description}) is {byteLength} UTF-8 bytes; the store enforces MongoDB's
 486                "namespace limit of 235 bytes (unsharded allows 255) so a later shard-enable cannot strand the collectio
 487                "Shorten the database or collection name.");
 214488    }
 89}