Error codes
The codes
| Code | Meaning | HTTP projection |
|---|---|---|
VALIDATION_FAILED | invalid input — details contains per-field { path, message } | 422 |
UNAUTHORIZED | no identity | 401 |
FORBIDDEN | identity present, right absent | 403 |
NOT_FOUND | designated thing does not exist | 404 |
CONFLICT | state refuses the transition (already published, slug taken) | 409 |
SERVICE_UNAVAILABLE | a remote frond is unreachable | 503 |
INTERNAL_ERROR | anything unexpected | 500 |
ErrorCode carries twelve more, each projecting to the HTTP status of the same name:
BAD_REQUEST 400, METHOD_NOT_ALLOWED 405, REQUEST_TIMEOUT 408, GONE 410,
PRECONDITION_FAILED 412, PAYLOAD_TOO_LARGE 413, UNPROCESSABLE_ENTITY 422,
LOCKED 423, TOO_MANY_REQUESTS 429, NOT_IMPLEMENTED 501, BAD_GATEWAY 502,
GATEWAY_TIMEOUT 504. The seven above are the ones a domain reaches for; these exist so a
surface never has to invent a status the vocabulary could carry.
INTERNAL_ERROR is the one code whose message never leaves
The others were written for the caller and travel whole. An INTERNAL_ERROR was not:
it may quote a path, a query or a row, so it is replaced by a constant before leaving the
process.
It is logged in the same place — masking and recording live in one function, so the
sentence exists exactly once, on the server. An operator asking "why this 500" finds it in
the logs with its cause, its address and its operation; an attacker never sees it. If your
message must reach the caller, then it is not an INTERNAL_ERROR: give it its code.