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

Information
Class: AsyncResponse.Channels.SqlServer.SqlServerRecoveryStateStore
Assembly: AsyncResponse.Channels.SqlServer
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/Channels/AsyncResponse.Channels.SqlServer/SqlServerRecoveryStateStore.cs
Line coverage
100%
Covered lines: 65
Uncovered lines: 0
Coverable lines: 65
Total lines: 124
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.SqlServer/SqlServerRecoveryStateStore.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2using System.Runtime.CompilerServices;
 3using System.Text.Json;
 4
 5namespace AsyncResponse.Channels.SqlServer;
 6
 7/// <summary>
 8/// Microsoft SQL Server implementation of <see cref="IRecoveryStateStore"/> and
 9/// <see cref="IRecoveryStateScanner"/>.
 10/// </summary>
 311internal sealed class SqlServerRecoveryStateStore(
 312    SqlServerChannelSql _sql,
 313    ILogger<SqlServerRecoveryStateStore> _logger) : IRecoveryStateStore, IRecoveryStateScanner
 14{
 15    /// <inheritdoc />
 16    public async Task SaveAsync(string correlationId, RecoveryState state, TimeSpan ttl, CancellationToken cancellationT
 17    {
 318        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 319        ArgumentNullException.ThrowIfNull(state);
 320        if (ttl <= TimeSpan.Zero)
 321            throw new ArgumentOutOfRangeException(nameof(ttl), "TTL must be greater than zero.");
 322        if (!string.Equals(state.CorrelationId, correlationId, StringComparison.Ordinal))
 323            throw new ArgumentException("The recovery-state correlation id must match the store key.", nameof(state));
 324        if (state.SchemaVersion != RecoveryStateSchema.Current)
 325            throw new ArgumentException("The recovery state must use the current schema version.", nameof(state));
 26
 127        cancellationToken.ThrowIfCancellationRequested();
 128        if (state.RegistrationId == Guid.Empty)
 129            state.RegistrationId = Guid.NewGuid();
 30
 131        await _sql.SaveRecoveryStateAsync(correlationId, state, ttl, cancellationToken).ConfigureAwait(false);
 132    }
 33
 34    /// <inheritdoc />
 35    public async Task<IReadOnlyList<RecoveryState>> GetAllAsync(string correlationId, CancellationToken cancellationToke
 36    {
 137        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 138        cancellationToken.ThrowIfCancellationRequested();
 39
 140        var jsonStates = await _sql.LoadRecoveryStatesAsync(correlationId, cancellationToken).ConfigureAwait(false);
 141        return DeserializeStates(jsonStates, correlationId);
 142    }
 43
 44    /// <inheritdoc />
 45    public Task<bool> TryDeleteAsync(string correlationId, Guid registrationId, CancellationToken cancellationToken = de
 46    {
 347        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);
 348        if (registrationId == Guid.Empty)
 349            throw new ArgumentException("Registration id cannot be empty.", nameof(registrationId));
 150        cancellationToken.ThrowIfCancellationRequested();
 151        return _sql.DeleteRecoveryStateAsync(correlationId, registrationId, cancellationToken);
 52    }
 53
 54    /// <inheritdoc />
 55    public async IAsyncEnumerable<RecoveryState> ScanAsync([EnumeratorCancellation] CancellationToken cancellationToken 
 56    {
 157        await foreach (var json in _sql.ScanRecoveryStateJsonAsync(cancellationToken).ConfigureAwait(false))
 58        {
 159            var state = DeserializeState(json, correlationId: null);
 160            if (state is not null)
 161                yield return state;
 62        }
 163    }
 64
 65    private IReadOnlyList<RecoveryState> DeserializeStates(IReadOnlyList<string> jsonStates, string correlationId)
 66    {
 167        if (jsonStates.Count == 0)
 168            return [];
 69
 170        var states = new List<RecoveryState>(jsonStates.Count);
 171        foreach (var json in jsonStates)
 72        {
 173            var state = DeserializeState(json, correlationId);
 174            if (state is not null)
 175                states.Add(state);
 76        }
 77
 178        return states;
 79    }
 80
 81    private RecoveryState? DeserializeState(string json, string? correlationId)
 82    {
 83        try
 84        {
 385            var state = AsyncResponseJson.Deserialize<RecoveryState>(json);
 386            if (state is null)
 387                return null;
 88
 389            if (state.RegistrationId == Guid.Empty || string.IsNullOrWhiteSpace(state.CorrelationId))
 90            {
 391                _logger.LogWarning(
 392                    "SQL Server recovery state for correlationId {CorrelationId} has an incomplete identity; rejecting i
 393                    correlationId ?? state.CorrelationId);
 394                return null;
 95            }
 96
 397            if (!RecoveryStateSchema.IsReadable(state.SchemaVersion))
 98            {
 399                _logger.LogWarning(
 3100                    "SQL Server recovery state for correlationId {CorrelationId} has unsupported schema version {SchemaV
 3101                    correlationId ?? state.CorrelationId,
 3102                    state.SchemaVersion,
 3103                    RecoveryStateSchema.Current);
 3104                return null;
 105            }
 106
 3107            if (!string.IsNullOrWhiteSpace(correlationId)
 3108                && !string.Equals(state.CorrelationId, correlationId, StringComparison.Ordinal))
 109            {
 3110                _logger.LogWarning(
 3111                    "SQL Server recovery state has correlationId {StoredCorrelationId}, expected {CorrelationId}; reject
 3112                    state.CorrelationId, correlationId);
 3113                return null;
 114            }
 115
 3116            return state;
 117        }
 3118        catch (JsonException ex)
 119        {
 3120            _logger.LogWarning(ex, "Unreadable SQL Server recovery state for correlationId {CorrelationId}; skipping.", 
 3121            return null;
 122        }
 3123    }
 124}