63 lines
2.6 KiB
Markdown
63 lines
2.6 KiB
Markdown
# Error Responses — Derived Rules
|
|
|
|
> Derives from `domains/api/first-principles.md` P9 (Error Transparency) and `domains/errors/first-principles.md`.
|
|
|
|
## The Error Contract
|
|
|
|
Every error response is a JSON object with:
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "STRING_ERROR_CODE",
|
|
"message": "Human-readable description",
|
|
"details": {},
|
|
"request_id": "uuid"
|
|
}
|
|
}
|
|
```
|
|
|
|
- `code`: machine-consumable, stable, UPPER_SNAKE_CASE. Never a free-text message.
|
|
- `message`: human-readable, for logs and developers. Not for end users (see `domains/errors/` P8).
|
|
- `details`: structured, typed additional context (which field, what value, what constraint).
|
|
- `request_id`: correlation ID for tracing. Every error is traceable.
|
|
|
|
## Error Codes (P3 Predictability, P9)
|
|
|
|
- Codes are stable. Renaming an error code is a breaking change.
|
|
- Codes are specific: `VALIDATION_FAILED` not `BAD_REQUEST`. `DUPLICATE_EMAIL` not `CONFLICT`.
|
|
- Codes are namespaced: `USER_NOT_FOUND`, `ORDER_NOT_FOUND` — not just `NOT_FOUND`.
|
|
|
|
## Status Code Mapping (P1 Contract Fidelity)
|
|
|
|
| Code | Meaning | Error code example |
|
|
|------|---------|-------------------|
|
|
| 400 | Malformed request | `MALFORMED_REQUEST` |
|
|
| 401 | Auth required | `AUTH_REQUIRED` |
|
|
| 403 | Forbidden | `FORBIDDEN` |
|
|
| 404 | Not found | `<RESOURCE>_NOT_FOUND` |
|
|
| 409 | Conflict | `DUPLICATE_<RESOURCE>` |
|
|
| 422 | Semantic invalid | `VALIDATION_FAILED` |
|
|
| 429 | Rate limited | `RATE_LIMITED` |
|
|
| 500 | Server bug | `INTERNAL_ERROR` |
|
|
|
|
- Never return 200 with an error body. The status code is the first signal.
|
|
- Never return 500 for a client error. 500 means "the server has a bug."
|
|
|
|
## Information Disclosure (P8 Security, domains/security P9)
|
|
|
|
- Error messages do not leak internal state: no stack traces, no SQL fragments, no file paths.
|
|
- A 401 does not say "user not found" vs "wrong password" — both say "invalid credentials."
|
|
- A 404 does not confirm the resource exists but is forbidden — return 404, not 403, for unauthenticated requests to hidden resources.
|
|
- Detailed errors are logged server-side with `request_id`; the client gets the safe version.
|
|
|
|
## Retryability (P6 Idempotency)
|
|
|
|
- Errors that are safe to retry: 409, 422 (if the fix is applied), 429 (after backoff), 5xx.
|
|
- Errors that are not safe to retry: 400, 401 (without re-auth), 403.
|
|
- The error body indicates retryability: `retryable: true/false` or via the code's known semantics.
|
|
|
|
## Partial Errors (GraphQL, see `domains/api/graphql.md`)
|
|
|
|
- GraphQL returns data and errors together. Do not conflate.
|
|
- A null field with no error is a bug. A null field with an error is a partial failure. |