Table of Contents

Class ErrorRetryExtensions

Namespace
Trellis
Assembly
Trellis.Core.dll

Transport-neutral retry-classification helpers over the closed Error catalog. Lets workers, message consumers, and outbound-gateway callers ask "should I retry this, give up on this item, or halt the batch?" without hand-rolling parallel switch blocks against framework error types.

public static class ErrorRetryExtensions
Inheritance
ErrorRetryExtensions
Inherited Members

Remarks

Scope. These helpers answer the generic "retry the same operation later" question. Domain-specific retry shapes (optimistic-concurrency reload-and-retry for Error.Conflict; INSERT-with-regenerated-natural-key retry for SaveChangesWithRetryAsync) require their own primitives because they have to mutate state before retrying. Consumers writing such loops should switch on the concrete Error shape directly, not call Classify(Error).

Eventually-consistent reads. Error.NotFound and Error.Gone default to Permanent. Services whose reads are eventually consistent (read-your-own-writes against a replicated store, message consumers ahead of write replication) should override locally rather than expect the framework default to infer their model.

Override pattern. Consumers that disagree with the default mapping for one or more cases should branch on the concrete shape first and only fall back to Classify(Error) for the cases they don't override:

var classification = error switch
{
    Error.Conflict c when c.Code == FaultCodes.ConcurrentModification => RetryClassification.Transient,
    _ => error.Classify(),
};

Methods

Classify(Error)

Classifies an Error using the framework's default transport-neutral mapping. Use the result to decide whether the surrounding worker / consumer loop should retry this item, give up on it, or halt the batch entirely.

public static RetryClassification Classify(this Error error)

Parameters

error Error

The error to classify.

Returns

RetryClassification

The default RetryClassification for error.

Remarks

Default mapping (every nested case of Error is enumerated; the catalog is closed so the table is exhaustive at framework-publish time):

Error caseClassification
Error.UnavailableTransient
Error.RateLimitedTransient
Error.UnexpectedTransient — see below
Error.TransportFaultPermanent — see below
Error.AuthenticationRequiredFailFast
Error.ForbiddenPermanent
Error.InvalidInputPermanent
Error.InvariantViolationPermanent
Error.NotFoundPermanent
Error.GonePermanent
Error.ConflictPermanent — see below
Error.AggregateRecursive max-severity: FailFast if any inner classifies as FailFast; otherwise Permanent if any inner classifies as Permanent; otherwise Transient.

Why Error.Unexpected is Transient. Error.Unexpected wraps unhandled exceptions and other "this should not have happened" situations. A retry can hide a deterministic bug (the same code path will throw again the next time), but it can also recover a transient failure (a momentary serialization error, a stale connection). The default favours availability: producers should reserve Error.Unexpected for unknown internal faults and surface deterministic failures as Error.InvariantViolation, Error.InvalidInput, or Error.Conflict instead. Consumers must still cap retries.

Why Error.TransportFault is Permanent. ITransportFault is opaque from Trellis.Core's perspective; the concrete payload is defined by transport-specific packages (for example HttpError in Trellis.Http.Abstractions). The retryable transient outcomes those transports produce (HTTP 429, HTTP 503, gRPC UNAVAILABLE) are mapped at the boundary to Error.RateLimited and Error.Unavailable — which carry RetryAdvice — and never reach Error.TransportFault. Every HttpError case shipped today (405, 406, 412, 413, 415, 416, 428) is a caller-side error that will not succeed by waiting and retrying. Transport packages that surface their own retryable transient faults via Error.TransportFault should provide a transport-aware classification extension that overrides this default for those specific faults.

Why Error.Conflict is Permanent. Resolving a 409-shaped conflict typically requires conflict-specific work (reload the current rowversion, re-apply the change, regenerate a colliding natural key) that a generic outer retry loop cannot perform. Domains that have a meaningful retry strategy for a conflict should override locally as shown above, or use a dedicated primitive like DbContext.SaveChangesWithRetryAsync.

Aggregate semantics. The aggregate classification answers "should I retry the operation that produced this aggregate as an indivisible unit?" A mixed aggregate of a permanent and a transient failure is classified Permanent because retrying the unit will not change the permanent inner's outcome. Callers that need to retry the transient parts independently must iterate over Errors and classify each inner separately.

Exceptions

ArgumentNullException

Thrown when error is null.

GetRetryAdvice(Error)

Returns the RetryAdvice carried by the error, when the producer supplied one. Currently only Error.RateLimited and Error.Unavailable carry advice; all other cases (including Error.Aggregate) return null.

public static RetryAdvice? GetRetryAdvice(this Error error)

Parameters

error Error

The error to inspect.

Returns

RetryAdvice?

The producer-supplied RetryAdvice, or null when none was supplied.

Remarks

Aggregate returns null by design. Returning advice from the first transient inner would (a) contradict Classify(Error) when the aggregate classifies as Permanent or FailFast, and (b) silently under-wait when later inners suggest a longer delay. Callers that want a per-inner retry policy should iterate over Errors and apply their own merge rule.

Exceptions

ArgumentNullException

Thrown when error is null.

IsFailFast(Error)

Convenience predicate equivalent to Classify(Error)(error) == FailFast.

public static bool IsFailFast(this Error error)

Parameters

error Error

The error to classify.

Returns

bool

true if error is classified as FailFast.

Exceptions

ArgumentNullException

Thrown when error is null.

IsPermanent(Error)

Convenience predicate equivalent to Classify(Error)(error) == Permanent.

public static bool IsPermanent(this Error error)

Parameters

error Error

The error to classify.

Returns

bool

true if error is classified as Permanent.

Exceptions

ArgumentNullException

Thrown when error is null.

IsTransient(Error)

Convenience predicate equivalent to Classify(Error)(error) == Transient.

public static bool IsTransient(this Error error)

Parameters

error Error

The error to classify.

Returns

bool

true if error is classified as Transient.

Exceptions

ArgumentNullException

Thrown when error is null.