Declaring a failure
On this page
A failure is a way an action or query can fail that its caller is
expected to handle, such as a todo that doesn't exist. You declare it as a
type whose base is Failure, written type TodoNotFound = Failure { ... }.
The base gives the type its settings: kind, message, client,
internal and, rarely, code. Here are two failures, with the Todo and
Product entities (stored records) and the Quantity type they refer to
declared elsewhere:
/// The todo does not exist.type TodoNotFound = Failure { kind: FailureKind.not_found message: "Todo not found." internal: { todo_id: Todo.id }}type QuantityUnavailable = Failure { kind: FailureKind.conflict message: "The requested quantity is unavailable." client: { available: Quantity } internal: { product_id: Product.id requested: Quantity }}
The /// line above TodoNotFound is a documentation comment. It is
optional, and becomes the failure's description in generated documentation
and OpenAPI. A field typed Todo.id has the type of Todo's id field.
kind:, which sets the status
kind: names one of the kinds, the categories a failure falls
into, such as not_found or conflict. The kind sets the HTTP status a
client gets, and how background work, such as a
job or an event subscriber, treats the failure. A failure never states a status itself.
The code clients see
A failure's public code is its name in lower_snake_case: TodoNotFound
is todo_not_found. Clients rely on it, so it stays the same. Write
code: "todo_missing" only to keep a code clients already use after you
rename the failure.
message:
message: is fixed text that is safe to show anyone. It can't include
values. Leave it out and the client gets the generic message Jadpo keeps
for the failure's kind.
client: and internal: fields
A failure can carry values that say what went wrong, such as which product ran out. These are its fields, and they go in one of two blocks:
client: { }holds the fields sent to the caller.internal: { }holds the fields that never leave the server. They are for your logs and traces.
A field name can't be in both. A failure has no fields outside these two
blocks. The caller gets a JSON body with code, message, request_id
and, when the failure has client fields, those fields under details
(How failures reach the client).
Leave client out and the body has only the code, the message and the
request id.
These fields are ordinary fields. A T? type, which may hold no
value, is allowed. optional and default are not, because only Input
types may have them (One form for every type).
Where the file goes
An app's code is split into domains: one business area each, such as
planning, in a folder under domains/
(Project layout). Each
failure has its own file in its domain's failures/ folder, such as
domains/planning/failures/todo_not_found.jadpo.
When the actions of two or more domains can fail with the same failure,
you put its file in shared/failures/ instead. When code in another
domain handles the failure, for example in a
match, write public before the
declaration: public type QuantityUnavailable = Failure { ... }.
Markdown for agents: /docs/declaring-a-failure.md.Built from Jadpo main at a707efb.