Caravela.Error (Caravela v0.6.0)

Copy Markdown View Source

Uniform error shape emitted by every Caravela read / write path.

Context functions and generated LiveViews can return any of four {:error, reason} shapes today:

{:error, :unauthorized}
{:error, :not_found}
{:error, %Ecto.Changeset{}}
{:error, other}

…and every handler has to match on all four. Caravela.Error collapses them into one struct so upstream code (a LiveView event handler, a JSON-API controller, a telemetry reporter) can pattern match once and discriminate by :kind:

case MyApp.Library.create_book(attrs, context) do
  {:ok, book} ->
    ...

  {:error, %Caravela.Error{kind: :unauthorized}} ->
    ...

  {:error, %Caravela.Error{kind: :invalid, details: changeset}} ->
    ...
end

Kinds

  • :unauthorized — an authorization rule (can_read / can_create / can_update / can_delete) returned false. details is the entity atom that failed the check.
  • :not_found — a get_* / delete_*(id, _) lookup returned nil (either missing or hidden by can_read). details is the id.
  • :invalid — input failed changeset validation. details is the %Ecto.Changeset{}.
  • :internal — anything else: a raised exception, a hook that returned a non-:ok tuple, a downstream integration failure. details is the original reason term.

The generated contexts continue to emit the plain atoms / changesets for backwards compatibility. Use wrap/1 to lift an existing error into the struct form when routing through a boundary that prefers the uniform shape.

Summary

Functions

Short human-friendly string describing the error. Handy as a default flash message when a LiveView doesn't care about the discrimination.

Lift a "plain" error reason into a %Caravela.Error{} struct.

Types

kind()

@type kind() :: :unauthorized | :not_found | :invalid | :internal

t()

@type t() :: %Caravela.Error{details: term(), kind: kind()}

Functions

message(error)

@spec message(t()) :: String.t()

Short human-friendly string describing the error. Handy as a default flash message when a LiveView doesn't care about the discrimination.

wrap(err)

@spec wrap(term()) :: t()

Lift a "plain" error reason into a %Caravela.Error{} struct.

iex> Caravela.Error.wrap(:unauthorized)
%Caravela.Error{kind: :unauthorized, details: nil}

iex> Caravela.Error.wrap(%Ecto.Changeset{})
%Caravela.Error{kind: :invalid, details: %Ecto.Changeset{}}

Accepts the raw error term or an {:error, reason} tuple. Already- wrapped %Caravela.Error{} values pass through unchanged.