PATTERN Cited by 1 source
Problem type for customer-actionable errors¶
Pattern¶
When the only party that can recover from a failure is
the end user (the Customer), model that failure as a
schema-level Problem type — a GraphQL object type that
appears as an arm of a
Result
union — rather than as an entry in response.errors.
Shape:
type RegisterProblem {
"translated message encompassing all invalid inputs."
title: String!
invalidInputs: [RegisterInvalidInput]
}
type RegisterInvalidInput {
field: RegisterInvalidInputField!
"translated message."
message: String!
}
enum RegisterInvalidInputField {
EMAIL
PASSWORD
}
The name Problem (not Error) is borrowed from RFC 7807. See concepts/problem-vs-error-distinction.
What the schema buys you¶
Because the Problem is a first-class schema type:
- Codegen produces typed client classes for
RegisterProblem,RegisterInvalidInput, and the enum — parseable by construction, no string matching. - Schema review applies: adding a new
RegisterInvalidInputFieldgoes through the same CR process as adding a new data field. - Introspection documentation surfaces the error vocabulary.
- i18n payload (the
titleand per-inputmessagefields) gets a typed home; the client UI renders the strings directly next to the form fields. - Deprecation tracking applies to error vocabulary the way it applies to data fields.
The tight scope¶
Zalando is explicit that this pattern should be used in one canonical situation: mutation input validation.
"Mutation Inputs is the only case where it is crucial to construct Problem types. It contains inputs directly from the Customer, and only the Customer can take action for this. So, we model these errors as Problems and not Errors."
(Source: sources/2021-04-12-zalando-modeling-errors-in-graphql)
Overusing Problem types (e.g. wrapping every query in a
...Success | ...Error union) forces the client into
... on fragment spreads at every level of the tree,
destroying GraphQL's query-shape ergonomics. See the
worked counter-example in
concepts/graphql-error-propagation.
Decision rule¶
The pattern fires when all of the following hold:
- The Customer is the action-taker (not the Developer, not an operator) — per concepts/error-action-taker-classification.
- The error is not a bug — a runtime exception or infrastructure failure stays an Error.
- The error must not propagate. Returning it as a Problem means the caller sees both arms of the union; GraphQL's null-propagation does not fire.
- The failure payload has structure — per-field codes, translated messages, nested shape — that justifies the schema weight.
Cost¶
- Every caller of the mutation must spread both union arms.
- Every new Problem type needs a schema CR.
- The i18n catalogue must be wired into the resolver
layer (the
titleandmessagefields are resolver-rendered, locale-aware strings).
Pairs with¶
- patterns/result-union-type-for-mutation-outcome — the outer union shape.
- patterns/error-extensions-code-for-developer-actionable-errors — the other arm of Zalando's discipline, used for everything that isn't Customer-actionable.
Seen in¶
- sources/2021-04-12-zalando-modeling-errors-in-graphql — the canonical reference.