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

Information
Class: AsyncResponse.Channels.MongoDB.MongoDbRecoveryStateStore
Assembly: AsyncResponse.Channels.MongoDB
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Channels/AsyncResponse.Channels.MongoDB/MongoDbRecoveryStateStore.cs
Line coverage
100%
Covered lines: 65
Uncovered lines: 0
Coverable lines: 65
Total lines: 125
Line coverage: 100%
Branch coverage
97%
Covered branches: 35
Total branches: 36
Branch coverage: 97.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
SaveAsync()100%88100%
GetAllAsync()100%11100%
TryDeleteAsync(...)100%22100%
ScanAsync()100%44100%
DeserializeStates(...)100%66100%
DeserializeState(...)87.5%1616100%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/src/Channels/AsyncResponse.Channels.MongoDB/MongoDbRecoveryStateStore.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2using System.Runtime.CompilerServices;
 3using System.Text.Json;
 4
 5namespace AsyncResponse.Channels.MongoDB;
 6
 7/// <summary>
 8/// MongoDB implementation of <see cref="IRecoveryStateStore"/> and
 9/// <see cref="IRecoveryStateScanner"/>. Entries live in a TTL-indexed collection, so MongoDB itself
 10/// reaps expired registrations.
 11/// </summary>
 312internal sealed class MongoDbRecoveryStateStore(
 313    MongoDbChannelStore _store,
 314    ILogger<MongoDbRecoveryStateStore> _logger) : IRecoveryStateStore, IRecoveryStateScanner
 15{
 16    /// <inheritdoc />
 17    public async Task SaveAsync(string correlationId, RecoveryState state, TimeSpan ttl, CancellationToken cancellationT
 18    {
 319        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 320        ArgumentNullException.ThrowIfNull(state);
 321        if (ttl <= TimeSpan.Zero)
 322            throw new ArgumentOutOfRangeException(nameof(ttl), "TTL must be greater than zero.");
 323        if (!string.Equals(state.CorrelationId, correlationId, StringComparison.Ordinal))
 324            throw new ArgumentException("The recovery-state correlation id must match the store key.", nameof(state));
 325        if (state.SchemaVersion != RecoveryStateSchema.Current)
 326            throw new ArgumentException("The recovery state must use the current schema version.", nameof(state));
 27
 128        cancellationToken.ThrowIfCancellationRequested();
 129        if (state.RegistrationId == Guid.Empty)
 130            state.RegistrationId = Guid.NewGuid();
 31
 132        await _store.SaveRecoveryStateAsync(correlationId, state, ttl, cancellationToken).ConfigureAwait(false);
 133    }
 34
 35    /// <inheritdoc />
 36    public async Task<IReadOnlyList<RecoveryState>> GetAllAsync(string correlationId, CancellationToken cancellationToke
 37    {
 138        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 139        cancellationToken.ThrowIfCancellationRequested();
 40
 141        var jsonStates = await _store.LoadRecoveryStatesAsync(correlationId, cancellationToken).ConfigureAwait(false);
 142        return DeserializeStates(jsonStates, correlationId);
 143    }
 44
 45    /// <inheritdoc />
 46    public Task<bool> TryDeleteAsync(string correlationId, Guid registrationId, CancellationToken cancellationToken = de
 47    {
 348        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 349        if (registrationId == Guid.Empty)
 350            throw new ArgumentException("Registration id cannot be empty.", nameof(registrationId));
 151        cancellationToken.ThrowIfCancellationRequested();
 152        return _store.DeleteRecoveryStateAsync(correlationId, registrationId, cancellationToken);
 53    }
 54
 55    /// <inheritdoc />
 56    public async IAsyncEnumerable<RecoveryState> ScanAsync([EnumeratorCancellation] CancellationToken cancellationToken 
 57    {
 158        await foreach (var json in _store.ScanRecoveryStateJsonAsync(cancellationToken).ConfigureAwait(false))
 59        {
 160            var state = DeserializeState(json, correlationId: null);
 161            if (state is not null)
 162                yield return state;
 63        }
 164    }
 65
 66    private IReadOnlyList<RecoveryState> DeserializeStates(IReadOnlyList<string> jsonStates, string correlationId)
 67    {
 168        if (jsonStates.Count == 0)
 169            return [];
 70
 171        var states = new List<RecoveryState>(jsonStates.Count);
 172        foreach (var json in jsonStates)
 73        {
 174            var state = DeserializeState(json, correlationId);
 175            if (state is not null)
 176                states.Add(state);
 77        }
 78
 179        return states;
 80    }
 81
 82    private RecoveryState? DeserializeState(string json, string? correlationId)
 83    {
 84        try
 85        {
 386            var state = AsyncResponseJson.Deserialize<RecoveryState>(json);
 387            if (state is null)
 388                return null;
 89
 390            if (state.RegistrationId == Guid.Empty || string.IsNullOrWhiteSpace(state.CorrelationId))
 91            {
 392                _logger.LogWarning(
 393                    "MongoDB recovery state for correlationId {CorrelationId} has an incomplete identity; rejecting it."
 394                    correlationId ?? state.CorrelationId);
 395                return null;
 96            }
 97
 398            if (!RecoveryStateSchema.IsReadable(state.SchemaVersion))
 99            {
 1100                _logger.LogWarning(
 1101                    "MongoDB recovery state for correlationId {CorrelationId} has unsupported schema version {SchemaVers
 1102                    correlationId ?? state.CorrelationId,
 1103                    state.SchemaVersion,
 1104                    RecoveryStateSchema.Current);
 1105                return null;
 106            }
 107
 3108            if (!string.IsNullOrWhiteSpace(correlationId)
 3109                && !string.Equals(state.CorrelationId, correlationId, StringComparison.Ordinal))
 110            {
 3111                _logger.LogWarning(
 3112                    "MongoDB recovery state has correlationId {StoredCorrelationId}, expected {CorrelationId}; rejecting
 3113                    state.CorrelationId, correlationId);
 3114                return null;
 115            }
 116
 3117            return state;
 118        }
 3119        catch (JsonException ex)
 120        {
 3121            _logger.LogWarning(ex, "Unreadable MongoDB recovery state for correlationId {CorrelationId}; skipping.", cor
 3122            return null;
 123        }
 3124    }
 125}