LightningWeb.API.Errors (Lightning v2.20.1)

Copy Markdown View Source

Turns an invalid changeset into error entries and a problem.

A context function reports invalid data as a changeset. LightningWeb.API.FallbackController hands it to problem/3, which makes one entry per error and picks the response code from the entries. Each entry points at the part of the request it is about, such as a body key, a position in a list, or a path segment, using the request's key names rather than the schema's. Web code that checks the request itself builds entries with entry/2 and returns them through refuse/1.

An entry's code comes from the error's code: option, or from Ecto's own options. An error with neither raises. There is no fallback code.

Summary

Functions

One entry, for a producer that validates the request itself.

Every error in changeset and its children, as entries.

The error for a reference in the body that matches nothing the caller can see. Something missing and something hidden get the same error.

The 400 invalid_parameter problem for a refused changeset over a route's query parameters, with one entry per error, each naming its parameter.

An RFC 6901 JSON Pointer in URI-fragment form: each key has ~ written ~0 and / written ~1, then the whole is percent-encoded wherever a character isn't allowed in a fragment.

The problem for a refused changeset. The first rule that matches wins

:ok when there are no entries, otherwise a 422 invalid problem holding them all, for a producer that checks the request itself.

{:ok, value} passed through, or {:error, changeset} over a route's query parameters turned into its 400 invalid_parameter problem.

Types

entry()

@type entry() :: %{
  :code => atom(),
  :detail => String.t(),
  optional(:pointer | :parameter | :header | :path) => String.t()
}

location()

@type location() ::
  [segment()]
  | {:parameter, String.t()}
  | {:header, String.t()}
  | {:path, String.t()}

segment()

@type segment() :: atom() | String.t() | non_neg_integer()

Functions

entry(location, arg)

@spec entry(location(), {String.t(), keyword()}) :: entry()

One entry, for a producer that validates the request itself.

error is shaped as a changeset error, {message, opts}, and is coded and interpolated the same way. location is a list of the request's keys and positions, or {:parameter, name}, or {:header, name}, or {:path, name} for a path segment, named by its route parameter. A path names a segment, never carries its text.

from_changeset(changeset, renames \\ %{}, opts \\ [])

@spec from_changeset(Ecto.Changeset.t(), %{required(atom()) => atom()}, [
  {:path, [atom()]}
]) :: [
  entry()
]

Every error in changeset and its children, as entries.

renames maps an Ecto field to the request's key, at any depth: %{project_users: :members}. An error whose options carry key: is placed there instead of at its field: one request key, or a list of the request's keys and positions below the error's level.

path: lists the top-level request keys the request sent as path segments rather than in its body, so their errors name the segment: path: [:key].

Raises ArgumentError for an error with no code, and for an error on a child the request didn't send, since neither can be placed in the request.

not_visible()

@spec not_visible() :: {String.t(), keyword()}

The error for a reference in the body that matches nothing the caller can see. Something missing and something hidden get the same error.

parameters(changeset)

@spec parameters(Ecto.Changeset.t()) :: LightningWeb.API.Problem.t()

The 400 invalid_parameter problem for a refused changeset over a route's query parameters, with one entry per error, each naming its parameter.

pointer(segments)

@spec pointer([segment()]) :: String.t()

An RFC 6901 JSON Pointer in URI-fragment form: each key has ~ written ~0 and / written ~1, then the whole is percent-encoded wherever a character isn't allowed in a fragment.

problem(changeset, renames \\ %{}, opts \\ [])

@spec problem(Ecto.Changeset.t(), %{required(atom()) => atom()}, [{:path, [atom()]}]) ::
  LightningWeb.API.Problem.t()

The problem for a refused changeset. The first rule that matches wins:

  1. any error coded storage_failed: 500 internal, since the server failed, not the request;
  2. any error coded not_ready: 503 not_ready, with no entries;
  3. every error exceeds_limit: 402 exceeds_limit;
  4. otherwise 422 invalid, without any exceeds_limit entries.

A value in the body that must be unique and is already in use is a taken entry in the 422 like any other problem with the body. 409 is only for the state of the record the URL names.

refuse(entries)

@spec refuse([entry()]) :: :ok | {:error, LightningWeb.API.Problem.t()}

:ok when there are no entries, otherwise a 422 invalid problem holding them all, for a producer that checks the request itself.

refuse_parameters(ok)

@spec refuse_parameters({:ok, value} | {:error, Ecto.Changeset.t()}) ::
  {:ok, value} | {:error, LightningWeb.API.Problem.t()}
when value: term()

{:ok, value} passed through, or {:error, changeset} over a route's query parameters turned into its 400 invalid_parameter problem.