Structured error returned from the dispatcher pipeline.
Wire contract
Serialized to JSON as
{"code": string, "source"?: string, "message"?: string, "details"?: object}.
:codeis a machine-readable atom. Required.:sourceis the coarse provenance category (seesource/0). Optional.:messageis a human-readable string. Optional but recommended.:detailsis any extra structured context. Optional.:statusis the HTTP status to return. Set by typed handler errors and framework errors; not serialized to the wire.
Typed handler errors
When a handler's @spec declares {:error, atom_union()} or
{:error, %{code: atom_union(), message: String.t(), ...}}, the dispatcher
promotes that return value to a top-level RpcError:
{:error, :not_found}→%RpcError{code: :not_found, message: "not_found", details: nil}{:error, %{code: :not_found, message: "user X", field: "id"}}→%RpcError{code: :not_found, message: "user X", details: %{field: "id"}}
The :code and :message keys are pulled to the top level; everything else
flows through :details. This matches the JS Error contract on the
TypeScript client (err.message is populated for stack traces and logging).
Framework codes
Framework error codes and their default HTTP statuses are the single source
of truth defined in @framework_errors (see framework_errors/0 and
status_for/1). The transport layer reads these statuses; the dispatcher,
plug, and resolution build framework errors via framework/3.
:procedure_not_found— no procedure registered for the requested path:input_validation_failed— request input did not match the procedure schema:output_validation_failed— handler returned a value that failed output schema:handler_error— handler returned an unexpected value or raised:middleware_halted—Resolution.halt/2was called with a non-RpcErrorreason; the original term is stored underdetails.reason:unauthorized— the caller is not authenticated (HTTP 401):forbidden— the caller is authenticated but lacks permission (HTTP 403):payload_too_large— request body exceeded the configured byte cap:unsupported_media_type— request content-type was notapplication/json
Client visibility
Note: a typed error's :message and :details are always serialized to the
client by design (see RpcElixir.Dispatcher). Framework :details are gated
behind :expose_error_details for the internal exception/return paths, but
typed handler-error payloads are intentionally exposed.
Summary
Types
Machine-readable framework error code.
Coarse provenance category, paired with the fine-grained :code.
Functions
Builds a framework error, stamping the default HTTP status for code so the
status travels with the error rather than being re-derived downstream, and
tagging source: :framework.
The framework error code → default HTTP status map.
The default HTTP status for code, or nil when code is not a known
framework code (e.g. a user-defined typed error code).
Types
@type framework_code() ::
:procedure_not_found
| :input_validation_failed
| :output_validation_failed
| :handler_error
| :middleware_halted
| :unauthorized
| :forbidden
| :payload_too_large
| :unsupported_media_type
Machine-readable framework error code.
@type source() :: :transport | :framework | :middleware | :domain
Coarse provenance category, paired with the fine-grained :code.
Cannot be inferred from :code alone (e.g. :unauthorized may be a middleware halt
or a handler's typed error), so it is stamped at the layer that builds the
error:
:framework— built byframework/3(protocol/validation/transport-layer):middleware— produced byRpcElixir.Resolution.halt/2:domain— a typed{:error, ...}returned by a handler
A caller that constructs its own %RpcError{} may set :source explicitly; an
explicit value is preserved rather than overwritten (see Resolution.halt/2
and the dispatcher's typed-error promotion), so the layer default only applies
when :source is nil.
:transport is, by convention, reserved for failures the client synthesizes
(network/abort) before any server envelope exists; server code uses the other
three.
@type t() :: %RpcElixir.RpcError{ code: atom(), details: map() | nil, message: String.t() | nil, source: source() | nil, status: pos_integer() | nil }
Functions
@spec framework(framework_code(), String.t() | nil, map() | nil) :: t()
Builds a framework error, stamping the default HTTP status for code so the
status travels with the error rather than being re-derived downstream, and
tagging source: :framework.
@spec framework_errors() :: %{required(framework_code()) => pos_integer()}
The framework error code → default HTTP status map.
@spec status_for(atom()) :: pos_integer() | nil
The default HTTP status for code, or nil when code is not a known
framework code (e.g. a user-defined typed error code).