< Summary

Information
Class: AsiBackbone.Core.Outbox.GovernanceOutboxEntry
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Outbox/GovernanceOutboxEntry.cs
Line coverage
100%
Covered lines: 269
Uncovered lines: 0
Coverable lines: 269
Total lines: 582
Line coverage: 100%
Branch coverage
93%
Covered branches: 95
Total branches: 102
Branch coverage: 93.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%1414100%
get_OutboxEntryId()100%11100%
get_Envelope()100%11100%
get_Status()100%11100%
get_CreatedUtc()100%11100%
get_UpdatedUtc()100%11100%
get_RetryCount()100%11100%
get_MaxRetryCount()100%11100%
get_NextRetryUtc()100%11100%
get_LastError()100%11100%
get_ProviderName()100%11100%
get_ProviderRecordId()100%11100%
get_DeadLetterReason()100%11100%
get_Metadata()100%11100%
get_ClaimOwner()100%11100%
get_ClaimToken()100%11100%
get_ClaimedUtc()100%11100%
get_ClaimExpiresUtc()100%11100%
get_ClaimAttemptCount()100%11100%
get_IsDelivered()100%11100%
get_IsDeadLettered()100%11100%
get_HasMetadata()100%11100%
get_HasClaim()100%66100%
Create(...)100%22100%
Restore(...)100%11100%
IsRetryReady(...)94.44%1818100%
HasActiveClaim(...)75%44100%
CanBeClaimed(...)50%44100%
IsClaimedBy(...)100%44100%
MarkClaimed(...)87.5%88100%
ReleaseClaim(...)100%11100%
MarkDelivered(...)100%22100%
MarkFailed(...)100%88100%
MarkDeferred(...)100%22100%
MarkDeadLettered(...)100%22100%
Copy(...)100%22100%
NormalizeIdentifier(...)100%22100%
NormalizeOptional(...)100%22100%
MergeMetadata(...)100%88100%
NormalizeMetadata(...)85.71%1414100%

File(s)

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

#LineLine coverage
 1using System.Collections.ObjectModel;
 2using AsiBackbone.Core.Emissions;
 3
 4namespace AsiBackbone.Core.Outbox;
 5
 6/// <summary>
 7/// Represents a provider-neutral durable outbox entry for a governance emission envelope.
 8/// </summary>
 9/// <remarks>
 10/// Outbox entries are intended to be persisted before optional downstream provider delivery is attempted.
 11/// </remarks>
 12public sealed class GovernanceOutboxEntry
 13{
 14    private const int DefaultMaxRetryCount = 5;
 15
 516    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 517        new ReadOnlyDictionary<string, string>(
 518            new Dictionary<string, string>(StringComparer.Ordinal));
 19
 206620    private GovernanceOutboxEntry(
 206621        string outboxEntryId,
 206622        GovernanceEmissionEnvelope envelope,
 206623        GovernanceEmissionStatus status,
 206624        DateTimeOffset createdUtc,
 206625        DateTimeOffset updatedUtc,
 206626        int retryCount,
 206627        int maxRetryCount,
 206628        DateTimeOffset? nextRetryUtc,
 206629        GovernanceEmissionError? lastError,
 206630        string? providerName,
 206631        string? providerRecordId,
 206632        string? deadLetterReason,
 206633        IReadOnlyDictionary<string, string> metadata,
 206634        string? claimOwner,
 206635        string? claimToken,
 206636        DateTimeOffset? claimedUtc,
 206637        DateTimeOffset? claimExpiresUtc,
 206638        int claimAttemptCount)
 39    {
 206640        ArgumentException.ThrowIfNullOrWhiteSpace(outboxEntryId);
 206241        ArgumentNullException.ThrowIfNull(envelope);
 42
 206043        if (!Enum.IsDefined(status))
 44        {
 445            throw new ArgumentOutOfRangeException(nameof(status), status, "Outbox status must be defined.");
 46        }
 47
 205648        if (retryCount < 0)
 49        {
 450            throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "Retry count must be greater than or e
 51        }
 52
 205253        if (maxRetryCount < 0)
 54        {
 655            throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, "Maximum retry count must be gre
 56        }
 57
 204658        if (claimAttemptCount < 0)
 59        {
 260            throw new ArgumentOutOfRangeException(nameof(claimAttemptCount), claimAttemptCount, "Claim attempt count mus
 61        }
 62
 204463        OutboxEntryId = outboxEntryId.Trim();
 204464        Envelope = envelope;
 204465        Status = status;
 204466        CreatedUtc = createdUtc.ToUniversalTime();
 204467        UpdatedUtc = updatedUtc.ToUniversalTime();
 204468        RetryCount = retryCount;
 204469        MaxRetryCount = maxRetryCount;
 204470        NextRetryUtc = nextRetryUtc?.ToUniversalTime();
 204471        LastError = lastError;
 204472        ProviderName = NormalizeOptional(providerName);
 204473        ProviderRecordId = NormalizeOptional(providerRecordId);
 204474        DeadLetterReason = NormalizeOptional(deadLetterReason);
 204475        Metadata = metadata;
 204476        ClaimOwner = NormalizeOptional(claimOwner);
 204477        ClaimToken = NormalizeOptional(claimToken);
 204478        ClaimedUtc = claimedUtc?.ToUniversalTime();
 204479        ClaimExpiresUtc = claimExpiresUtc?.ToUniversalTime();
 204480        ClaimAttemptCount = claimAttemptCount;
 204481    }
 82
 83    /// <summary>
 84    /// Gets the stable outbox entry identifier.
 85    /// </summary>
 353986    public string OutboxEntryId { get; }
 87
 88    /// <summary>
 89    /// Gets the provider-neutral governance emission envelope being persisted for delivery.
 90    /// </summary>
 192091    public GovernanceEmissionEnvelope Envelope { get; }
 92
 93    /// <summary>
 94    /// Gets the current provider-neutral outbox status.
 95    /// </summary>
 439596    public GovernanceEmissionStatus Status { get; }
 97
 98    /// <summary>
 99    /// Gets the UTC timestamp when the outbox entry was created.
 100    /// </summary>
 1729101    public DateTimeOffset CreatedUtc { get; }
 102
 103    /// <summary>
 104    /// Gets the UTC timestamp when the outbox entry was last updated.
 105    /// </summary>
 994106    public DateTimeOffset UpdatedUtc { get; }
 107
 108    /// <summary>
 109    /// Gets the number of failed or deferred delivery attempts recorded for this entry.
 110    /// </summary>
 1825111    public int RetryCount { get; }
 112
 113    /// <summary>
 114    /// Gets the maximum retry count before the entry should transition to dead-lettered.
 115    /// </summary>
 1817116    public int MaxRetryCount { get; }
 117
 118    /// <summary>
 119    /// Gets the next UTC retry timestamp, when retry scheduling is active.
 120    /// </summary>
 1551121    public DateTimeOffset? NextRetryUtc { get; }
 122
 123    /// <summary>
 124    /// Gets the last provider-neutral emission error, when available.
 125    /// </summary>
 1547126    public GovernanceEmissionError? LastError { get; }
 127
 128    /// <summary>
 129    /// Gets the provider name associated with the most recent attempt, when available.
 130    /// </summary>
 1464131    public string? ProviderName { get; }
 132
 133    /// <summary>
 134    /// Gets the provider-side record identifier, when delivery returned one and it is safe to store.
 135    /// </summary>
 1454136    public string? ProviderRecordId { get; }
 137
 138    /// <summary>
 139    /// Gets the dead-letter reason, when the entry has reached a terminal dead-letter state.
 140    /// </summary>
 1444141    public string? DeadLetterReason { get; }
 142
 143    /// <summary>
 144    /// Gets minimized provider-neutral outbox metadata.
 145    /// </summary>
 1695146    public IReadOnlyDictionary<string, string> Metadata { get; }
 147
 148    /// <summary>
 149    /// Gets the current claim owner, when this row is leased by a worker.
 150    /// </summary>
 2144151    public string? ClaimOwner { get; }
 152
 153    /// <summary>
 154    /// Gets the opaque token for the current claim lease.
 155    /// </summary>
 1572156    public string? ClaimToken { get; }
 157
 158    /// <summary>
 159    /// Gets the UTC timestamp when the current claim was acquired.
 160    /// </summary>
 1442161    public DateTimeOffset? ClaimedUtc { get; }
 162
 163    /// <summary>
 164    /// Gets the UTC timestamp when the current claim lease expires.
 165    /// </summary>
 1460166    public DateTimeOffset? ClaimExpiresUtc { get; }
 167
 168    /// <summary>
 169    /// Gets the number of claim or reclaim attempts recorded for this entry.
 170    /// </summary>
 1969171    public int ClaimAttemptCount { get; }
 172
 173    /// <summary>
 174    /// Gets a value indicating whether this entry was delivered successfully.
 175    /// </summary>
 834176    public bool IsDelivered => Status is GovernanceEmissionStatus.Delivered;
 177
 178    /// <summary>
 179    /// Gets a value indicating whether this entry has reached a terminal dead-letter state.
 180    /// </summary>
 805181    public bool IsDeadLettered => Status is GovernanceEmissionStatus.DeadLettered;
 182
 183    /// <summary>
 184    /// Gets a value indicating whether metadata is present.
 185    /// </summary>
 12186    public bool HasMetadata => Metadata.Count > 0;
 187
 188    /// <summary>
 189    /// Gets a value indicating whether claim metadata is present.
 190    /// </summary>
 590191    public bool HasClaim => ClaimOwner is not null && ClaimToken is not null && ClaimedUtc.HasValue && ClaimExpiresUtc.H
 192
 193    /// <summary>
 194    /// Creates a pending durable governance outbox entry.
 195    /// </summary>
 196    public static GovernanceOutboxEntry Create(
 197        GovernanceEmissionEnvelope envelope,
 198        string? outboxEntryId = null,
 199        DateTimeOffset? createdUtc = null,
 200        int maxRetryCount = DefaultMaxRetryCount,
 201        IReadOnlyDictionary<string, string>? metadata = null)
 202    {
 645203        DateTimeOffset timestamp = createdUtc ?? DateTimeOffset.UtcNow;
 204
 645205        return new GovernanceOutboxEntry(
 645206            NormalizeIdentifier(outboxEntryId),
 645207            envelope,
 645208            GovernanceEmissionStatus.Pending,
 645209            timestamp,
 645210            timestamp,
 645211            retryCount: 0,
 645212            maxRetryCount,
 645213            nextRetryUtc: null,
 645214            lastError: null,
 645215            providerName: null,
 645216            providerRecordId: null,
 645217            deadLetterReason: null,
 645218            NormalizeMetadata(metadata),
 645219            claimOwner: null,
 645220            claimToken: null,
 645221            claimedUtc: null,
 645222            claimExpiresUtc: null,
 645223            claimAttemptCount: 0);
 224    }
 225
 226    /// <summary>
 227    /// Restores a durable outbox entry from provider-neutral storage.
 228    /// </summary>
 229    /// <remarks>
 230    /// This factory exists for storage adapters. It does not perform provider emission and does not add any provider de
 231    /// </remarks>
 232    public static GovernanceOutboxEntry Restore(
 233        GovernanceEmissionEnvelope envelope,
 234        GovernanceEmissionStatus status,
 235        string outboxEntryId,
 236        DateTimeOffset createdUtc,
 237        DateTimeOffset updatedUtc,
 238        int retryCount = 0,
 239        int maxRetryCount = DefaultMaxRetryCount,
 240        DateTimeOffset? nextRetryUtc = null,
 241        GovernanceEmissionError? lastError = null,
 242        string? providerName = null,
 243        string? providerRecordId = null,
 244        string? deadLetterReason = null,
 245        IReadOnlyDictionary<string, string>? metadata = null,
 246        string? claimOwner = null,
 247        string? claimToken = null,
 248        DateTimeOffset? claimedUtc = null,
 249        DateTimeOffset? claimExpiresUtc = null,
 250        int claimAttemptCount = 0)
 251    {
 734252        return new GovernanceOutboxEntry(
 734253            outboxEntryId,
 734254            envelope,
 734255            status,
 734256            createdUtc,
 734257            updatedUtc,
 734258            retryCount,
 734259            maxRetryCount,
 734260            nextRetryUtc,
 734261            lastError,
 734262            providerName,
 734263            providerRecordId,
 734264            deadLetterReason,
 734265            NormalizeMetadata(metadata),
 734266            claimOwner,
 734267            claimToken,
 734268            claimedUtc,
 734269            claimExpiresUtc,
 734270            claimAttemptCount);
 271    }
 272
 273    /// <summary>
 274    /// Determines whether the entry is ready for retry at the supplied UTC timestamp.
 275    /// </summary>
 276    public bool IsRetryReady(DateTimeOffset utcNow)
 277    {
 92278        return !IsDelivered && !IsDeadLettered && RetryCount < MaxRetryCount && Status is GovernanceEmissionStatus.Defer
 279    }
 280
 281    /// <summary>
 282    /// Determines whether the entry has an active claim lease at the supplied UTC timestamp.
 283    /// </summary>
 284    public bool HasActiveClaim(DateTimeOffset utcNow)
 285    {
 508286        return HasClaim && ClaimExpiresUtc > utcNow.ToUniversalTime();
 287    }
 288
 289    /// <summary>
 290    /// Determines whether the entry may be claimed by a cooperating worker at the supplied UTC timestamp.
 291    /// </summary>
 292    public bool CanBeClaimed(DateTimeOffset utcNow)
 293    {
 504294        return !IsDelivered && !IsDeadLettered && !HasActiveClaim(utcNow);
 295    }
 296
 297    /// <summary>
 298    /// Determines whether the entry is still owned by the supplied claim token.
 299    /// </summary>
 300    public bool IsClaimedBy(GovernanceOutboxClaim claim)
 301    {
 112302        ArgumentNullException.ThrowIfNull(claim);
 303
 112304        return string.Equals(OutboxEntryId, claim.OutboxEntryId, StringComparison.Ordinal)
 112305            && string.Equals(ClaimOwner, claim.WorkerId, StringComparison.Ordinal)
 112306            && string.Equals(ClaimToken, claim.ClaimToken, StringComparison.Ordinal);
 307    }
 308
 309    /// <summary>
 310    /// Returns a claimed copy of this entry.
 311    /// </summary>
 312    public GovernanceOutboxEntry MarkClaimed(
 313        string claimOwner,
 314        string? claimToken = null,
 315        DateTimeOffset? claimedUtc = null,
 316        TimeSpan? leaseDuration = null)
 317    {
 450318        ArgumentException.ThrowIfNullOrWhiteSpace(claimOwner);
 319
 450320        DateTimeOffset normalizedClaimedUtc = (claimedUtc ?? DateTimeOffset.UtcNow).ToUniversalTime();
 450321        TimeSpan normalizedLeaseDuration = leaseDuration ?? GovernanceOutboxClaimRequest.DefaultLeaseDuration;
 322
 450323        return normalizedLeaseDuration <= TimeSpan.Zero
 450324            ? throw new ArgumentOutOfRangeException(nameof(leaseDuration), leaseDuration, "Lease duration must be greate
 450325            : Copy(
 450326            Status,
 450327            normalizedClaimedUtc,
 450328            RetryCount,
 450329            NextRetryUtc,
 450330            LastError,
 450331            ProviderName,
 450332            ProviderRecordId,
 450333            DeadLetterReason,
 450334            Metadata,
 450335            claimOwner.Trim(),
 450336            string.IsNullOrWhiteSpace(claimToken) ? Guid.NewGuid().ToString("N") : claimToken.Trim(),
 450337            normalizedClaimedUtc,
 450338            normalizedClaimedUtc.Add(normalizedLeaseDuration),
 450339            ClaimAttemptCount + 1);
 340    }
 341
 342    /// <summary>
 343    /// Returns a copy with claim lease fields cleared.
 344    /// </summary>
 345    public GovernanceOutboxEntry ReleaseClaim(DateTimeOffset? updatedUtc = null)
 346    {
 10347        return Copy(
 10348            Status,
 10349            updatedUtc,
 10350            RetryCount,
 10351            NextRetryUtc,
 10352            LastError,
 10353            ProviderName,
 10354            ProviderRecordId,
 10355            DeadLetterReason,
 10356            Metadata,
 10357            claimOwner: null,
 10358            claimToken: null,
 10359            claimedUtc: null,
 10360            claimExpiresUtc: null,
 10361            ClaimAttemptCount);
 362    }
 363
 364    /// <summary>
 365    /// Returns a delivered copy of this entry.
 366    /// </summary>
 367    public GovernanceOutboxEntry MarkDelivered(
 368        GovernanceEmissionResult result,
 369        DateTimeOffset? updatedUtc = null)
 370    {
 76371        ArgumentNullException.ThrowIfNull(result);
 372
 76373        return !result.IsSuccess
 76374            ? throw new ArgumentException("Delivered outbox transitions require a successful emission result.", nameof(r
 76375            : Copy(
 76376            GovernanceEmissionStatus.Delivered,
 76377            updatedUtc,
 76378            retryCount: RetryCount,
 76379            nextRetryUtc: null,
 76380            lastError: null,
 76381            providerName: result.ProviderName,
 76382            providerRecordId: result.ProviderRecordId,
 76383            deadLetterReason: null,
 76384            metadata: MergeMetadata(Metadata, result.Metadata),
 76385            claimOwner: null,
 76386            claimToken: null,
 76387            claimedUtc: null,
 76388            claimExpiresUtc: null,
 76389            ClaimAttemptCount);
 390    }
 391
 392    /// <summary>
 393    /// Returns a failed or retryable-failure copy of this entry.
 394    /// </summary>
 395    public GovernanceOutboxEntry MarkFailed(
 396        GovernanceEmissionError governanceEmissionError,
 397        DateTimeOffset? nextRetryUtc = null,
 398        DateTimeOffset? updatedUtc = null)
 399    {
 89400        ArgumentNullException.ThrowIfNull(governanceEmissionError);
 401
 89402        int nextRetryCount = RetryCount + 1;
 89403        GovernanceEmissionStatus nextStatus = nextRetryCount >= MaxRetryCount
 89404            ? GovernanceEmissionStatus.DeadLettered
 89405            : governanceEmissionError.IsRetryable
 89406                ? GovernanceEmissionStatus.RetryableFailure
 89407                : GovernanceEmissionStatus.Failed;
 408
 89409        return Copy(
 89410            nextStatus,
 89411            updatedUtc,
 89412            nextRetryCount,
 89413            nextStatus is GovernanceEmissionStatus.DeadLettered ? null : nextRetryUtc,
 89414            governanceEmissionError,
 89415            governanceEmissionError.ProviderName,
 89416            providerRecordId: null,
 89417            nextStatus is GovernanceEmissionStatus.DeadLettered ? governanceEmissionError.Message : null,
 89418            Metadata,
 89419            claimOwner: null,
 89420            claimToken: null,
 89421            claimedUtc: null,
 89422            claimExpiresUtc: null,
 89423            ClaimAttemptCount);
 424    }
 425
 426    /// <summary>
 427    /// Returns a deferred copy of this entry.
 428    /// </summary>
 429    public GovernanceOutboxEntry MarkDeferred(
 430        GovernanceEmissionError? governanceEmissionError = null,
 431        DateTimeOffset? nextRetryUtc = null,
 432        DateTimeOffset? updatedUtc = null)
 433    {
 40434        return Copy(
 40435            GovernanceEmissionStatus.Deferred,
 40436            updatedUtc,
 40437            retryCount: RetryCount,
 40438            nextRetryUtc,
 40439            governanceEmissionError,
 40440            governanceEmissionError?.ProviderName,
 40441            providerRecordId: null,
 40442            deadLetterReason: null,
 40443            metadata: Metadata,
 40444            claimOwner: null,
 40445            claimToken: null,
 40446            claimedUtc: null,
 40447            claimExpiresUtc: null,
 40448            ClaimAttemptCount);
 449    }
 450
 451    /// <summary>
 452    /// Returns a dead-lettered copy of this entry.
 453    /// </summary>
 454    public GovernanceOutboxEntry MarkDeadLettered(
 455        GovernanceEmissionError governanceEmissionError,
 456        string? deadLetterReason = null,
 457        DateTimeOffset? updatedUtc = null)
 458    {
 30459        ArgumentNullException.ThrowIfNull(governanceEmissionError);
 460
 30461        return Copy(
 30462            GovernanceEmissionStatus.DeadLettered,
 30463            updatedUtc,
 30464            retryCount: RetryCount,
 30465            nextRetryUtc: null,
 30466            lastError: governanceEmissionError,
 30467            providerName: governanceEmissionError.ProviderName,
 30468            providerRecordId: null,
 30469            deadLetterReason: deadLetterReason ?? governanceEmissionError.Message,
 30470            metadata: Metadata,
 30471            claimOwner: null,
 30472            claimToken: null,
 30473            claimedUtc: null,
 30474            claimExpiresUtc: null,
 30475            ClaimAttemptCount);
 476    }
 477
 478    private GovernanceOutboxEntry Copy(
 479        GovernanceEmissionStatus status,
 480        DateTimeOffset? updatedUtc,
 481        int retryCount,
 482        DateTimeOffset? nextRetryUtc,
 483        GovernanceEmissionError? lastError,
 484        string? providerName,
 485        string? providerRecordId,
 486        string? deadLetterReason,
 487        IReadOnlyDictionary<string, string> metadata,
 488        string? claimOwner,
 489        string? claimToken,
 490        DateTimeOffset? claimedUtc,
 491        DateTimeOffset? claimExpiresUtc,
 492        int claimAttemptCount)
 493    {
 687494        return new GovernanceOutboxEntry(
 687495            OutboxEntryId,
 687496            Envelope,
 687497            status,
 687498            CreatedUtc,
 687499            updatedUtc ?? DateTimeOffset.UtcNow,
 687500            retryCount,
 687501            MaxRetryCount,
 687502            nextRetryUtc,
 687503            lastError,
 687504            providerName,
 687505            providerRecordId,
 687506            deadLetterReason,
 687507            metadata,
 687508            claimOwner,
 687509            claimToken,
 687510            claimedUtc,
 687511            claimExpiresUtc,
 687512            claimAttemptCount);
 513    }
 514
 515    private static string NormalizeIdentifier(string? identifier)
 516    {
 645517        return string.IsNullOrWhiteSpace(identifier)
 645518            ? Guid.NewGuid().ToString("N")
 645519            : identifier.Trim();
 520    }
 521
 522    private static string? NormalizeOptional(string? value)
 523    {
 10220524        return string.IsNullOrWhiteSpace(value)
 10220525            ? null
 10220526            : value.Trim();
 527    }
 528
 529    private static IReadOnlyDictionary<string, string> MergeMetadata(
 530        IReadOnlyDictionary<string, string> originalMetadata,
 531        IReadOnlyDictionary<string, string> resultMetadata)
 532    {
 72533        if (resultMetadata.Count == 0)
 534        {
 58535            return originalMetadata;
 536        }
 537
 14538        if (originalMetadata.Count == 0)
 539        {
 10540            return resultMetadata;
 541        }
 542
 4543        Dictionary<string, string> mergedMetadata = new(originalMetadata.Count + resultMetadata.Count, StringComparer.Or
 544
 20545        foreach (KeyValuePair<string, string> item in originalMetadata)
 546        {
 6547            mergedMetadata[item.Key] = item.Value;
 548        }
 549
 20550        foreach (KeyValuePair<string, string> item in resultMetadata)
 551        {
 6552            mergedMetadata[item.Key] = item.Value;
 553        }
 554
 4555        return new ReadOnlyDictionary<string, string>(mergedMetadata);
 556    }
 557
 558    private static IReadOnlyDictionary<string, string> NormalizeMetadata(
 559        IReadOnlyDictionary<string, string>? metadata)
 560    {
 1379561        if (metadata is null || metadata.Count == 0)
 562        {
 1251563            return EmptyMetadata;
 564        }
 565
 128566        Dictionary<string, string> normalizedMetadata = new(metadata.Count, StringComparer.Ordinal);
 567
 556568        foreach (KeyValuePair<string, string> item in metadata)
 569        {
 150570            if (string.IsNullOrWhiteSpace(item.Key))
 571            {
 572                continue;
 573            }
 574
 144575            normalizedMetadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 576        }
 577
 128578        return normalizedMetadata.Count == 0
 128579            ? EmptyMetadata
 128580            : new ReadOnlyDictionary<string, string>(normalizedMetadata);
 581    }
 582}

Methods/Properties

.cctor()
.ctor(System.String,AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope,AsiBackbone.Core.Emissions.GovernanceEmissionStatus,System.DateTimeOffset,System.DateTimeOffset,System.Int32,System.Int32,System.Nullable`1<System.DateTimeOffset>,AsiBackbone.Core.Emissions.GovernanceEmissionError,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.String,System.String,System.Nullable`1<System.DateTimeOffset>,System.Nullable`1<System.DateTimeOffset>,System.Int32)
get_OutboxEntryId()
get_Envelope()
get_Status()
get_CreatedUtc()
get_UpdatedUtc()
get_RetryCount()
get_MaxRetryCount()
get_NextRetryUtc()
get_LastError()
get_ProviderName()
get_ProviderRecordId()
get_DeadLetterReason()
get_Metadata()
get_ClaimOwner()
get_ClaimToken()
get_ClaimedUtc()
get_ClaimExpiresUtc()
get_ClaimAttemptCount()
get_IsDelivered()
get_IsDeadLettered()
get_HasMetadata()
get_HasClaim()
Create(AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope,System.String,System.Nullable`1<System.DateTimeOffset>,System.Int32,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
Restore(AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope,AsiBackbone.Core.Emissions.GovernanceEmissionStatus,System.String,System.DateTimeOffset,System.DateTimeOffset,System.Int32,System.Int32,System.Nullable`1<System.DateTimeOffset>,AsiBackbone.Core.Emissions.GovernanceEmissionError,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.String,System.String,System.Nullable`1<System.DateTimeOffset>,System.Nullable`1<System.DateTimeOffset>,System.Int32)
IsRetryReady(System.DateTimeOffset)
HasActiveClaim(System.DateTimeOffset)
CanBeClaimed(System.DateTimeOffset)
IsClaimedBy(AsiBackbone.Core.Outbox.GovernanceOutboxClaim)
MarkClaimed(System.String,System.String,System.Nullable`1<System.DateTimeOffset>,System.Nullable`1<System.TimeSpan>)
ReleaseClaim(System.Nullable`1<System.DateTimeOffset>)
MarkDelivered(AsiBackbone.Core.Emissions.GovernanceEmissionResult,System.Nullable`1<System.DateTimeOffset>)
MarkFailed(AsiBackbone.Core.Emissions.GovernanceEmissionError,System.Nullable`1<System.DateTimeOffset>,System.Nullable`1<System.DateTimeOffset>)
MarkDeferred(AsiBackbone.Core.Emissions.GovernanceEmissionError,System.Nullable`1<System.DateTimeOffset>,System.Nullable`1<System.DateTimeOffset>)
MarkDeadLettered(AsiBackbone.Core.Emissions.GovernanceEmissionError,System.String,System.Nullable`1<System.DateTimeOffset>)
Copy(AsiBackbone.Core.Emissions.GovernanceEmissionStatus,System.Nullable`1<System.DateTimeOffset>,System.Int32,System.Nullable`1<System.DateTimeOffset>,AsiBackbone.Core.Emissions.GovernanceEmissionError,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.String,System.String,System.Nullable`1<System.DateTimeOffset>,System.Nullable`1<System.DateTimeOffset>,System.Int32)
NormalizeIdentifier(System.String)
NormalizeOptional(System.String)
MergeMetadata(System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
NormalizeMetadata(System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)