< Summary

Information
Class: AsiBackbone.Core.Outbox.GovernanceOutboxOptions
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Outbox/GovernanceOutboxOptions.cs
Line coverage
91%
Covered lines: 22
Uncovered lines: 2
Coverable lines: 24
Total lines: 199
Line coverage: 91.6%
Branch coverage
90%
Covered branches: 20
Total branches: 22
Branch coverage: 90.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Validate()90%202090.48%
ValidateDelay(...)100%22100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Outbox/GovernanceOutboxOptions.cs

#LineLine coverage
 1namespace AsiBackbone.Core.Outbox;
 2
 3/// <summary>
 4/// Provides provider-neutral retry timing, poison-message, and optional claim/lease options for outbox drain processing
 5/// </summary>
 6/// <remarks>
 7/// These options control default retry timestamps when a downstream emitter does not provide its own retry-after value.
 8/// </remarks>
 9public sealed class GovernanceOutboxOptions
 10{
 11    /// <summary>
 12    /// Gets the stable default reason code used when the drain dead-letters an entry after the configured retry thresho
 13    /// </summary>
 14    public const string DefaultDeadLetterReasonCode = "outbox.max_retry_attempts_exceeded";
 15
 16    /// <summary>
 17    /// Gets the stable default reason message used when the drain dead-letters an entry after the configured retry thre
 18    /// </summary>
 19    public const string DefaultDeadLetterReasonMessage = "Governance outbox entry exceeded the configured maximum retry 
 20
 21    /// <summary>
 22    /// Gets the stable default reason code used when the drain dead-letters an entry after the configured claim thresho
 23    /// </summary>
 24    public const string DefaultMaxClaimAttemptsReasonCode = "outbox.max_claim_attempts_exceeded";
 25
 26    /// <summary>
 27    /// Gets the stable default reason message used when the drain dead-letters an entry after the configured claim thre
 28    /// </summary>
 29    public const string DefaultMaxClaimAttemptsReasonMessage = "Governance outbox entry exceeded the configured maximum 
 30
 31    /// <summary>
 32    /// Gets the default number of claimed entries drained under a single lease before the drain claims again.
 33    /// </summary>
 34    public const int DefaultClaimPageSize = 10;
 35
 36    /// <summary>
 37    /// Gets the default number of claims permitted before the drain dead-letters an entry that never reaches a terminal
 38    /// </summary>
 39    public const int DefaultMaxClaimAttempts = 5;
 40
 41    /// <summary>
 42    /// Gets the default worker identifier used when claim leases are enabled and the host does not supply one.
 43    /// </summary>
 44    /// <remarks>
 45    /// The value combines the machine name and the current process identifier so that replicas of the same deployment d
 46    /// </remarks>
 47    public static string DefaultClaimWorkerId { get; } = $"{Environment.MachineName}:{Environment.ProcessId}";
 48
 49    /// <summary>
 50    /// Gets or sets the default delay applied after a transient emission failure or unexpected emitter exception.
 51    /// </summary>
 52    public TimeSpan RetryDelay { get; set; } = TimeSpan.FromMinutes(1);
 53
 54    /// <summary>
 55    /// Gets or sets the default delay applied when an emitter returns a pending or deferred result without a retry-afte
 56    /// </summary>
 57    public TimeSpan DeferredDelay { get; set; } = TimeSpan.FromMinutes(1);
 58
 59    /// <summary>
 60    /// Gets or sets the maximum number of failed emission attempts permitted before the drain applies its poison-messag
 61    /// </summary>
 62    /// <remarks>
 63    /// The threshold counts the failure currently being processed. A value of <c>1</c> dead-letters the first failed at
 64    /// </remarks>
 65    public int MaxRetryAttempts { get; set; } = 5;
 66
 67    /// <summary>
 68    /// Gets or sets a value indicating whether entries are dead-lettered when <see cref="MaxRetryAttempts" /> is reache
 69    /// </summary>
 70    public bool DeadLetterOnMaxRetryAttempts { get; set; } = true;
 71
 72    /// <summary>
 73    /// Gets or sets the provider-neutral reason code recorded when the configured retry threshold is reached.
 74    /// </summary>
 75    public string DeadLetterReasonCode { get; set; } = DefaultDeadLetterReasonCode;
 76
 77    /// <summary>
 78    /// Gets or sets the provider-neutral reason message recorded when the configured retry threshold is reached.
 79    /// </summary>
 80    public string DeadLetterReasonMessage { get; set; } = DefaultDeadLetterReasonMessage;
 81
 82    /// <summary>
 83    /// Gets or sets a value indicating whether the drain should claim outbox entries before provider emission when the 
 84    /// </summary>
 85    /// <remarks>
 86    /// This is enabled by default because the alternative path allows two hosts draining the same durable outbox to emi
 87    /// </remarks>
 88    public bool UseClaimLeases { get; set; } = true;
 89
 90    /// <summary>
 91    /// Gets or sets the worker, process, node, or partition identifier used when <see cref="UseClaimLeases" /> is enabl
 92    /// </summary>
 93    /// <remarks>
 94    /// Defaults to <see cref="DefaultClaimWorkerId" />. Setting this to <see langword="null" /> or whitespace while <se
 95    /// </remarks>
 96    public string? ClaimWorkerId { get; set; } = DefaultClaimWorkerId;
 97
 98    /// <summary>
 99    /// Gets or sets the lease duration used when <see cref="UseClaimLeases" /> is enabled.
 100    /// </summary>
 101    public TimeSpan ClaimLeaseDuration { get; set; } = GovernanceOutboxClaimRequest.DefaultLeaseDuration;
 102
 103    /// <summary>
 104    /// Gets or sets the maximum number of claimed entries drained under a single lease before the drain claims again.
 105    /// </summary>
 106    /// <remarks>
 107    /// A drain pass claims entries in pages of this size rather than leasing an entire batch at once, so a slow emitter
 108    /// </remarks>
 109    public int ClaimPageSize { get; set; } = DefaultClaimPageSize;
 110
 111    /// <summary>
 112    /// Gets or sets the number of claims permitted before the drain dead-letters an entry that has never reached a term
 113    /// </summary>
 114    /// <remarks>
 115    /// An emitter that hangs or is killed mid-emission leaves the entry claimed but not failed, so its retry count neve
 116    /// </remarks>
 117    public int MaxClaimAttempts { get; set; } = DefaultMaxClaimAttempts;
 118
 119    /// <summary>
 120    /// Gets or sets a value indicating whether entries are dead-lettered when <see cref="MaxClaimAttempts" /> is reache
 121    /// </summary>
 122    /// <remarks>
 123    /// When disabled, an entry that exceeds the claim threshold is still drained normally, which restores the unbounded
 124    /// </remarks>
 125    public bool DeadLetterOnMaxClaimAttempts { get; set; } = true;
 126
 127    /// <summary>
 128    /// Gets or sets the provider-neutral reason code recorded when the configured claim threshold is reached.
 129    /// </summary>
 130    public string MaxClaimAttemptsReasonCode { get; set; } = DefaultMaxClaimAttemptsReasonCode;
 131
 132    /// <summary>
 133    /// Gets or sets the provider-neutral reason message recorded when the configured claim threshold is reached.
 134    /// </summary>
 135    public string MaxClaimAttemptsReasonMessage { get; set; } = DefaultMaxClaimAttemptsReasonMessage;
 136
 137    /// <summary>
 138    /// Validates the configured outbox retry, poison-message, timing, and claim options.
 139    /// </summary>
 140    /// <exception cref="InvalidOperationException">Thrown when a retry threshold, reason, delay, or claim lease option 
 141    public void Validate()
 142    {
 119143        ValidateDelay(RetryDelay, nameof(RetryDelay));
 116144        ValidateDelay(DeferredDelay, nameof(DeferredDelay));
 145
 113146        if (MaxRetryAttempts <= 0)
 147        {
 1148            throw new InvalidOperationException($"{nameof(MaxRetryAttempts)} must be greater than zero.");
 149        }
 150
 112151        if (string.IsNullOrWhiteSpace(DeadLetterReasonCode))
 152        {
 1153            throw new InvalidOperationException($"{nameof(DeadLetterReasonCode)} is required.");
 154        }
 155
 111156        if (string.IsNullOrWhiteSpace(DeadLetterReasonMessage))
 157        {
 1158            throw new InvalidOperationException($"{nameof(DeadLetterReasonMessage)} is required.");
 159        }
 160
 110161        if (ClaimLeaseDuration <= TimeSpan.Zero)
 162        {
 2163            throw new InvalidOperationException($"{nameof(ClaimLeaseDuration)} must be greater than TimeSpan.Zero.");
 164        }
 165
 108166        if (ClaimPageSize <= 0)
 167        {
 1168            throw new InvalidOperationException($"{nameof(ClaimPageSize)} must be greater than zero.");
 169        }
 170
 107171        if (MaxClaimAttempts <= 0)
 172        {
 1173            throw new InvalidOperationException($"{nameof(MaxClaimAttempts)} must be greater than zero.");
 174        }
 175
 106176        if (string.IsNullOrWhiteSpace(MaxClaimAttemptsReasonCode))
 177        {
 0178            throw new InvalidOperationException($"{nameof(MaxClaimAttemptsReasonCode)} is required.");
 179        }
 180
 106181        if (string.IsNullOrWhiteSpace(MaxClaimAttemptsReasonMessage))
 182        {
 0183            throw new InvalidOperationException($"{nameof(MaxClaimAttemptsReasonMessage)} is required.");
 184        }
 185
 106186        if (UseClaimLeases && string.IsNullOrWhiteSpace(ClaimWorkerId))
 187        {
 3188            throw new InvalidOperationException($"{nameof(ClaimWorkerId)} is required when {nameof(UseClaimLeases)} is e
 189        }
 103190    }
 191
 192    private static void ValidateDelay(TimeSpan delay, string propertyName)
 193    {
 235194        if (delay < TimeSpan.Zero)
 195        {
 6196            throw new InvalidOperationException($"{propertyName} must be greater than or equal to TimeSpan.Zero.");
 197        }
 229198    }
 199}