RpcElixir.RpcError (elixir_ts_rpc v0.0.1)

Copy Markdown View Source

Structured error returned from the dispatcher pipeline.

Wire contract

Serialized to JSON as {"code": string, "source"?: string, "message"?: string, "details"?: object}.

  • :code is a machine-readable atom. Required.
  • :source is the coarse provenance category (see source/0). Optional.
  • :message is a human-readable string. Optional but recommended.
  • :details is any extra structured context. Optional.
  • :status is 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_haltedResolution.halt/2 was called with a non-RpcError reason; the original term is stored under details.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 not application/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.

t()

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

framework_code()

@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.

source()

@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:

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.

t()

@type t() :: %RpcElixir.RpcError{
  code: atom(),
  details: map() | nil,
  message: String.t() | nil,
  source: source() | nil,
  status: pos_integer() | nil
}

Functions

framework(code, message, details \\ nil)

@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.

framework_errors()

@spec framework_errors() :: %{required(framework_code()) => pos_integer()}

The framework error code → default HTTP status map.

status_for(code)

@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).