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

Information
Class: AsyncResponse.Channels.Redis.RedisKeySchema
Assembly: AsyncResponse.Channels.Redis
File(s): /_/src/Channels/AsyncResponse.Channels.Redis/RedisKeySchema.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 64
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
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%
ValidateKeyPrefix(...)100%1616100%
get_RecoveryKeyPattern()100%11100%
Channel(...)100%11100%
RecoveryKey(...)100%11100%
CorrelationIdFromRecoveryKey(...)100%11100%

File(s)

/_/src/Channels/AsyncResponse.Channels.Redis/RedisKeySchema.cs

#LineLine coverage
 1using StackExchange.Redis;
 2
 3namespace AsyncResponse.Channels.Redis;
 4
 5/// <summary>
 6/// The single source of truth for Redis key/channel shapes, shared by the channel and the
 7/// watchdog. Key shapes are a storage contract: changing them orphans in-flight recovery state.
 8/// </summary>
 9internal sealed class RedisKeySchema
 10{
 11    private readonly string _keyPrefix;
 12
 13    /// <summary>Validates the prefix once at the single choke point every consumer constructs through.</summary>
 220814    public RedisKeySchema(string keyPrefix) => _keyPrefix = ValidateKeyPrefix(keyPrefix);
 15
 16    /// <summary>
 17    /// The prefix feeds both literal keys and the recovery SCAN <c>MATCH</c> pattern, where
 18    /// <c>* ? [ ] \</c> are glob metacharacters: a prefix like <c>app[prod]</c> writes keys
 19    /// literally but scans a character class, so the watchdog silently finds nothing — and a
 20    /// <c>*</c> would sweep another deployment's keys instead.
 21    /// </summary>
 22    internal static string ValidateKeyPrefix(string keyPrefix)
 23    {
 110424        if (string.IsNullOrWhiteSpace(keyPrefix))
 25        {
 226            throw new InvalidOperationException(
 227                $"{nameof(RedisAsyncResponseOptions)}.{nameof(RedisAsyncResponseOptions.KeyPrefix)} must be a non-empty 
 28        }
 29
 3425630        foreach (var ch in keyPrefix)
 31        {
 1603032            if (ch is '*' or '?' or '[' or ']' or '\\' || char.IsWhiteSpace(ch))
 33            {
 834                throw new InvalidOperationException(
 835                    $"{nameof(RedisAsyncResponseOptions)}.{nameof(RedisAsyncResponseOptions.KeyPrefix)} must not contain
 836                    "Redis glob metacharacters '*', '?', '[', ']', '\\': keys are written literally, but the recovery sc
 837                    "prefix inside a SCAN MATCH pattern, so a metacharacter makes the watchdog miss this deployment's ke
 38            }
 39        }
 40
 109441        return keyPrefix;
 42    }
 43
 8944    public string RecoveryKeyPattern => $"{_keyPrefix}:recovery:*";
 45
 46    /// <summary>
 47    /// The response pub/sub channel for a correlation id. Key-routed: on Redis Cluster a plain
 48    /// channel lets each client pick its own node, and PUBLISH's reply counts only the
 49    /// subscribers on the node that received it — so a waiter subscribed elsewhere received the
 50    /// message while the publisher read 0, re-published a duplicate into the waiter's predicate
 51    /// (or fired lost-subscriber recovery for a delivered response). Routing SUBSCRIBE and
 52    /// PUBLISH to the channel's slot owner makes the count mean what the code reads it as; on a
 53    /// single node it changes nothing.
 54    /// </summary>
 55    public RedisChannel Channel(string correlationId)
 115156        => new RedisChannel($"{_keyPrefix}:response:{correlationId}", RedisChannel.PatternMode.Literal).WithKeyRouting()
 57
 58    /// <summary>Runs the RecoveryKey operation.</summary>
 120059    public string RecoveryKey(string correlationId) => $"{_keyPrefix}:recovery:{correlationId}";
 60
 61    /// <summary>Runs the CorrelationIdFromRecoveryKey operation.</summary>
 62    public string CorrelationIdFromRecoveryKey(string recoveryKey)
 93863        => recoveryKey[$"{_keyPrefix}:recovery:".Length..];
 64}