PhoenixKit.API.Problem (phoenix_kit v2.63.0)

Copy Markdown View Source

RFC 9457 application/problem+json errors for the shop API.

Every error the API sends, from the endpoint's PhoenixKit.API.BodyGuard to a tool handler's {:error, code, message, context}, goes through send/4, so a client sees one shape:

{"type": "https://phoenixkit.dev/api/errors/forbidden",
 "title": "Forbidden", "status": 403, "code": "forbidden",
 "detail": "This key lacks ecommerce.prices:write.",
 "context": {"required_scope": "ecommerce.prices:write"},
 "request_id": "F9x..."}

code is the contract: stable, machine-readable, and what an agent branches on. detail is always English prose for a human or an agent to relay. context carries the fields that make the error actionable (required_scope, errors, retry_after, ...).

The table of codes and statuses is statuses/0. A code that is not in it answers 500 and is logged: it means a handler returned a code this module does not know.

Summary

Functions

The problem document as a map with string keys.

Sends the problem document for code and halts the connection.

The HTTP status of an error code.

Every error code this module knows, with its HTTP status.

Functions

body(code, detail, context, request_id)

@spec body(atom(), String.t(), map(), String.t() | nil) :: map()

The problem document as a map with string keys.

request_id is left out of the map when it is nil.

send(conn, code, detail, context \\ %{})

@spec send(Plug.Conn.t(), atom(), String.t(), map()) :: Plug.Conn.t()

Sends the problem document for code and halts the connection.

The response carries content-type: application/problem+json and cache-control: no-store. Three codes add a header: unauthorized a www-authenticate challenge, forbidden with a required_scope in the context an insufficient_scope challenge, and any context with a retry_after (seconds) a retry-after header.

The request id is conn.assigns.phoenix_kit_api_request_id when the PhoenixKit.API.Auth plug has run, else the x-request-id response header that Plug.RequestId set. A header value with characters outside [A-Za-z0-9_=+/-] (Plug.RequestId accepts any client-sent id of 20..200 bytes) is replaced by a fresh id, in the header and in the body, the same rule Auth applies.

status(code)

@spec status(atom()) :: pos_integer()

The HTTP status of an error code.

A code that is not in statuses/0 answers 500.

statuses()

@spec statuses() :: %{required(atom()) => pos_integer()}

Every error code this module knows, with its HTTP status.