Results & errors
Result<T> is a lightweight success-or-failure return type, and AppError is the transport-agnostic failure model.
Handlers return outcomes as values, not exceptions. Result<T> carries either a success value or an
AppError, and the host maps that error to whatever the transport requires.
Result<T>
Result<T> is a readonly struct with a success value or an AppError:
public readonly struct Result<T> {
public bool IsSuccess { get; }
public T Value { get; }
public AppError Error { get; }
public static Result<T> Success(T value);
public static Result<T> Failure(AppError error);
}Implicit conversions let a handler return either a response or an error directly — no wrapping ceremony:
return new Response(project.Id); // implicit Success
return AppError.Validation("Name is required."); // implicit Failure
return AppError.Conflict("Invoice number is already reserved.");Because it is a struct with ValueTask-friendly ergonomics, the success path stays allocation-light.
AppError
AppError is a transport-agnostic record describing what kind of failure occurred, not how a
particular protocol should report it:
public sealed record AppError {
public required ErrorKind Kind { get; init; }
public required string Message { get; init; }
public object? Data { get; init; }
}Error kinds
ErrorKind | Meaning |
|---|---|
Validation | Invalid input or constraint violation. |
NotFound | The requested resource does not exist. |
Conflict | The operation conflicts with existing state (duplicate, concurrent modification). |
Unauthorized | The caller is not authenticated (missing or invalid credentials). Maps to HTTP 401. |
Forbidden | The caller is authenticated but not authorized to perform this operation. |
BusinessRule | A domain business rule was violated. |
Internal | An unexpected internal error occurred. |
Factory methods
Each kind has a factory; validation supports an optional structured payload:
AppError.Validation("Name is required.");
AppError.Validation("Invalid command.", new[] { "Name is required.", "Email is invalid." });
AppError.Validation("Invalid command.", new Dictionary<string, string[]> { // field-keyed: renders as
["name"] = ["Name is required."], // RFC 7807 `errors` over HTTP
});
AppError.NotFound($"Client {id} was not found.");
AppError.Conflict("Invoice number is already reserved.");
AppError.Unauthorized("Authentication required.");
AppError.Forbidden("You cannot modify this resource.");
AppError.BusinessRule("Credit limit exceeded.", data: limitInfo);
AppError.Internal("Failed to render the document.");AppError.InternalError is a shared instance for the generic internal-failure case.
Mapping to a transport
Kind is the seam between application semantics and protocol codes. Each host owns the mapping, so
different deployments can translate the same domain failure differently:
// The framework default: AppError → JSON-RPC error code (Elarion.AppErrorMapper)
var code = error.Kind switch {
ErrorKind.Validation => -32602, // spec "Invalid params"
ErrorKind.NotFound => -32001, // server range
ErrorKind.Conflict => -32002, // server range
ErrorKind.Forbidden => -32003, // server range
ErrorKind.BusinessRule => -32004, // server range
ErrorKind.Unauthorized => -32005, // server range
_ => -32603, // spec "Internal error"
};This keeps handlers free of transport details: the same AppError.NotFound(...) becomes a JSON-RPC
error in one host and an HTTP 404 in another. For the complete, authoritative tables — both the
JSON-RPC codes and the HTTP status mapping — see the
Result & error model reference.
Results in pipelines
Decorators can inspect a Result<T> through the non-generic IResultLike interface — for example a
transaction decorator that commits on success and rolls back on failure:
if (response is IResultLike { IsSuccess: true }) {
await transaction.CommitAsync(ct);
} else {
await transaction.RollbackAsync(ct);
}See Decorator pipelines for the full pattern, including how a
validation decorator turns failures into Result<T>.Failure(...).
Handlers
Handlers are Elarion's primary use-case unit — a request in, a Result out, with no transport concerns.
Modules
A module is an application boundary marked with [AppModule]. Its handlers, services, validation metadata, scheduled jobs, and event consumers are discovered and registered automatically, and feature-gated as one unit.