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

Information
Class: AsyncResponse.AsyncResponseContext
Assembly: AsyncResponse.Abstractions
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/AsyncResponse.Abstractions/AsyncResponseContext.cs
Line coverage
100%
Covered lines: 46
Uncovered lines: 0
Coverable lines: 46
Total lines: 140
Line coverage: 100%
Branch coverage
100%
Covered branches: 14
Total branches: 14
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_CorrelationId()100%11100%
get_ReplyTarget()100%11100%
CreateCorrelationId()100%11100%
GenerateCorrelationId()100%11100%
EnsureCorrelationId()100%22100%
SetCorrelationId(...)100%22100%
SetReplyTarget(...)100%11100%
PushCorrelationId(...)100%22100%
PushContext(...)100%44100%
ClearCorrelationId()100%11100%
ClearReplyTarget()100%11100%
ValidateReplyTarget(...)100%11100%
.ctor(...)100%11100%
Dispose()100%22100%
.ctor(...)100%11100%
Dispose()100%22100%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/src/AsyncResponse.Abstractions/AsyncResponseContext.cs

#LineLine coverage
 1namespace AsyncResponse;
 2
 3/// <summary>
 4/// Carries ambient async-response metadata that flows with the async call stack via
 5/// <see cref="AsyncLocal{T}"/>, so it is unique per logical operation.
 6/// <para>
 7/// Publishers fall back to this ambient value when <c>SetResponse</c>/<c>SetException</c> are
 8/// called without an explicit correlation id, and worker jobs restore it before executing so
 9/// downstream publishes correlate automatically. Reply targets are available to outbound
 10/// integration code that needs to tell a remote system where to publish its response.
 11/// Prefer passing values explicitly where practical; the ambient context exists for integration
 12/// points that cannot.
 13/// </para>
 14/// </summary>
 15public static class AsyncResponseContext
 16{
 317    private static readonly AsyncLocal<string?> _currentCorrelationId = new();
 318    private static readonly AsyncLocal<AsyncResponseReplyTarget?> _currentReplyTarget = new();
 19
 20    /// <summary>Gets the correlation id of the current logical operation, if any.</summary>
 321    public static string? CorrelationId => _currentCorrelationId.Value;
 22
 23    /// <summary>Gets the reply target selected for the current logical operation, if any.</summary>
 324    public static AsyncResponseReplyTarget? ReplyTarget => _currentReplyTarget.Value;
 25
 26    /// <summary>Generates a new correlation id, stores it in the ambient context, and returns it.</summary>
 27    public static string CreateCorrelationId()
 28    {
 329        var correlationId = GenerateCorrelationId();
 330        _currentCorrelationId.Value = correlationId;
 331        return correlationId;
 32    }
 33
 34    /// <summary>Generates a new correlation id without storing it.</summary>
 335    public static string GenerateCorrelationId() => Guid.NewGuid().ToString();
 36
 37    /// <summary>Ensures the ambient correlation id is non-empty, generating one if missing.</summary>
 38    public static string EnsureCorrelationId()
 39    {
 240        if (string.IsNullOrWhiteSpace(_currentCorrelationId.Value))
 41        {
 242            _currentCorrelationId.Value = GenerateCorrelationId();
 43        }
 44
 245        return _currentCorrelationId.Value!;
 46    }
 47
 48    /// <summary>Sets the ambient correlation id for the current logical operation.</summary>
 49    /// <exception cref="ArgumentException">Thrown when <paramref name="correlationId"/> is null or whitespace.</excepti
 50    public static void SetCorrelationId(string correlationId)
 51    {
 252        _currentCorrelationId.Value = !string.IsNullOrWhiteSpace(correlationId)
 253            ? correlationId
 254            : throw new ArgumentException("CorrelationId cannot be null or whitespace.", nameof(correlationId));
 255    }
 56
 57    /// <summary>Sets the ambient reply target for the current logical operation.</summary>
 58    public static void SetReplyTarget(AsyncResponseReplyTarget replyTarget)
 59    {
 260        ArgumentNullException.ThrowIfNull(replyTarget);
 261        ValidateReplyTarget(replyTarget);
 262        _currentReplyTarget.Value = replyTarget;
 363    }
 64
 65    /// <summary>
 66    /// Temporarily sets the ambient correlation id for the current logical operation and
 67    /// restores the previous value when the returned scope is disposed. Passing <c>null</c> or
 68    /// whitespace clears the ambient id for the scope.
 69    /// </summary>
 70    internal static IDisposable PushCorrelationId(string? correlationId)
 71    {
 372        var previousCorrelationId = _currentCorrelationId.Value;
 373        _currentCorrelationId.Value = !string.IsNullOrWhiteSpace(correlationId) ? correlationId : null;
 374        return new CorrelationScope(previousCorrelationId);
 75    }
 76
 77    /// <summary>
 78    /// Temporarily sets the ambient async-response context and restores the previous values when
 79    /// the returned scope is disposed.
 80    /// </summary>
 81    internal static IDisposable PushContext(string? correlationId, AsyncResponseReplyTarget? replyTarget)
 82    {
 383        if (replyTarget is not null)
 84        {
 385            ValidateReplyTarget(replyTarget);
 86        }
 87
 388        var previousCorrelationId = _currentCorrelationId.Value;
 389        var previousReplyTarget = _currentReplyTarget.Value;
 90
 391        _currentCorrelationId.Value = !string.IsNullOrWhiteSpace(correlationId) ? correlationId : null;
 392        _currentReplyTarget.Value = replyTarget;
 93
 394        return new ContextScope(previousCorrelationId, previousReplyTarget);
 95    }
 96
 97    /// <summary>Clears the ambient correlation id for the current logical operation.</summary>
 298    public static void ClearCorrelationId() => _currentCorrelationId.Value = null;
 99
 100    /// <summary>Clears the ambient reply target for the current logical operation.</summary>
 3101    public static void ClearReplyTarget() => _currentReplyTarget.Value = null;
 102
 103    private static void ValidateReplyTarget(AsyncResponseReplyTarget replyTarget)
 104    {
 3105        ArgumentException.ThrowIfNullOrWhiteSpace(replyTarget.Name);
 3106        ArgumentException.ThrowIfNullOrWhiteSpace(replyTarget.Transport);
 3107        ArgumentException.ThrowIfNullOrWhiteSpace(replyTarget.Address);
 3108    }
 109
 3110    private sealed class CorrelationScope(string? _previousCorrelationId) : IDisposable
 111    {
 112        private int _disposed;
 113
 114        /// <summary>Releases resources held by this instance.</summary>
 115        public void Dispose()
 116        {
 3117            if (Interlocked.Exchange(ref _disposed, 1) == 0)
 118            {
 3119                _currentCorrelationId.Value = _previousCorrelationId;
 120            }
 3121        }
 122    }
 123
 3124    private sealed class ContextScope(
 3125        string? _previousCorrelationId,
 3126        AsyncResponseReplyTarget? _previousReplyTarget) : IDisposable
 127    {
 128        private int _disposed;
 129
 130        /// <summary>Releases resources held by this instance.</summary>
 131        public void Dispose()
 132        {
 3133            if (Interlocked.Exchange(ref _disposed, 1) == 0)
 134            {
 3135                _currentCorrelationId.Value = _previousCorrelationId;
 3136                _currentReplyTarget.Value = _previousReplyTarget;
 137            }
 3138        }
 139    }
 140}