Class Error
- Namespace
- Trellis
- Assembly
- Trellis.Core.dll
Closed discriminated union of Trellis error values. Domain-facing cases stay transport-neutral, while boundary-layer protocols can attach typed lower-level payloads through Error.TransportFault.
public abstract record Error : IEquatable<Error>
- Inheritance
-
Error
- Implements
- Derived
- Inherited Members
- Extension Methods
Remarks
Closure. The base record has a private constructor; only nested cases declared in this
file may inherit from Error. External code cannot extend the catalog, so
switch over an Error reference is exhaustive at the language level.
Identity. Kind is a stable domain slug suitable for telemetry and wire
serialization (e.g. "not-found"), fixed by the case. Code is the
per-instance reason the producer names, and lives on the base so that every case carries it
the same way: new Error.NotFound(resource) { Code = "account.closed" }. Cases whose
reason is required take it as their first positional parameter instead.
Detail. Every case inherits an optional Detail property from the base. Callers
supply it via object-initializer syntax: new Error.NotFound(resource) { Detail = "..." }.
The boundary renderer prefers Detail when present; otherwise it computes a localized
message from Kind, Code, and the typed payload.
Equality. Value-based equality over the discriminator, the typed payload, and Detail. Cause is intentionally excluded from equality so that two errors with identical surface payload compare equal regardless of how deeply they were wrapped — see the Equals(Error?) override for the rationale. Collection-bearing payloads use EquatableArray<T> for sequence equality.
Cause chain. Cause is a structured chain (never a live Exception).
Cycles are detected at init time and throw InvalidOperationException.
Properties
Cause
Gets the optional structured cause of this error. Never holds a live Exception; use a child Error to attach causal context.
public Error? Cause { get; init; }
Property Value
Code
Gets the machine-readable reason for this failure — the value a consumer sees, switches on, and carries from a bug report into a trace query. Defaults to Unspecified when the producer names no reason.
public string Code { get; init; }
Property Value
Remarks
The default is the sentinel meaning "no finer reason available", never Kind.
A code that fell back to the kind could not be told apart from a producer that deliberately
chose that string, and publishing it invited a consumer to branch on "not-found" as
though someone had meant it. Defaulting to the sentinel makes that mistake unreachable rather
than merely discouraged: the kind is not in this member for any boundary to leak.
This is the only code member, and it is storage rather than a per-case computed property.
Two members is how an HTTP body and a span tag come to disagree about the same failure; two
spellings of one member — a per-case ReasonCode payload behind a virtual Code —
is how three cases came to have no way of carrying a reason at all.
Cases whose reason is required (Error.InvariantViolation, Error.Conflict,
Error.Unexpected, Error.Forbidden) take it as their first positional parameter.
Every assigned code must be non-blank, including assignments through a with expression.
Other cases may leave the default unspecified sentinel; the Error.NotFound and
Error.Gone factories also normalize blank optional code arguments to that sentinel.
Exceptions
- ArgumentException
The assigned code is null, empty, or whitespace.
Detail
Gets the optional human-readable detail. When non-null the boundary renderer prefers this over the default template for Code.
public string? Detail { get; init; }
Property Value
Kind
Gets the stable domain slug for this case (e.g. "not-found",
"invalid-input"). Suitable for telemetry, observability dimensions, and as
the durable identifier that boundary layers translate into transport-specific
type identifiers.
public abstract string Kind { get; }
Property Value
Methods
Equals(Error?)
Value equality over the discriminator (Trellis.Error.EqualityContract), Code,
and Detail, plus each derived case's positional payload. Cause is
intentionally excluded from equality and hashing — two errors with identical kind, payload,
and detail represent the same logical failure regardless of how deeply they were
wrapped. This mirrors Exception, whose equality does not recurse
into InnerException, and keeps test assertions ergonomic (callers assert on
the surface error without reconstructing the entire causal chain).
public virtual bool Equals(Error? other)
Parameters
otherError
Returns
Remarks
How per-derived payload comparison works: this override deliberately checks
only the members declared on the base — EqualityContract, Code, and
Detail. Each derived sealed record
(e.g., Error.NotFound, Error.InvalidInput) gets a compiler-generated
Equals(Derived?) of the form
base.Equals(other) && Field1 == other.Field1 && ....
The base.Equals(other) call dispatches virtually to this override, contributing
the kind+code+detail check; the derived method then ANDs in its per-property comparison.
The net effect is element-wise equality across both base and derived fields, without
any per-derived override needed. Code must be named here precisely because it
is declared on the base: a derived record's generated Equals compares only its own
members, so a code omitted from this override would let two errors with different reasons
compare equal — and therefore collide in a cache key or a deduplicated log.
GetHashCode() uses the same compose-with-derived pattern: the override
hashes EqualityContract, Code, and Detail, and each derived
record's auto-generated GetHashCode combines base.GetHashCode() with hashes of
its own properties.
GetDisplayMessage()
Returns a human-readable message suitable for logging, tracing, and diagnostic surfaces. Prefers the explicit Detail when set; otherwise flattens any per-field violation messages (for Error.InvalidInput) before falling back to Code.
public virtual string GetDisplayMessage()
Returns
GetHashCode()
Serves as the default hash function.
public override int GetHashCode()
Returns
- int
A hash code for the current object.
ToString()
Returns a string that represents the current object.
public override string ToString()
Returns
- string
A string that represents the current object.