Skip to content

Commit 2420fa7

Browse files
committed
docs(errors): document problem-details and 422 mapping
- Describe the application/problem+json body following RFC 9457 - Document the 422 validation failure with reasons on error.cause - Mirror the default behavior changes in Indonesian
1 parent e628821 commit 2420fa7

3 files changed

Lines changed: 51 additions & 20 deletions

File tree

docs/error-handling/default-behavior.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ await router.serve(8000)
2828

2929
The default error response (without custom `router.catch()`) follows the client's `Accept` header:
3030

31-
- **Accept includes `application/json`**JSON body: `{ error, path, statusCode }`
31+
- **Accept includes `application/json` or `application/problem+json`**a problem-details body `{ type, title, status, instance }` sent as `application/problem+json`
3232
- **Otherwise** → HTML body: simple error page with status and message (escaped)
3333

3434
Also:
@@ -39,13 +39,16 @@ Also:
3939
```typescript
4040
// Example default response (client requests JSON)
4141
// Status: 404
42-
// Body: { "error": "...", "path": "/api/foo", "statusCode": 404 }
42+
// Content-Type: application/problem+json
43+
// Body: { "type": "about:blank", "title": "Not Found", "status": 404, "instance": "/api/foo" }
4344

4445
// Example default response (client does not request JSON)
4546
// Status: 404
4647
// Body: HTML with <title>404</title> and error message
4748
```
4849

50+
The problem-details shape follows [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457), where `type` is a problem URI, `title` is a short summary, `status` repeats the code, and `instance` carries the request path. A custom `router.catch()` replaces this body with whatever shape suits the client, covered in [Error Object Details](/error-handling/object-details).
51+
4952
## Error Scenarios
5053

5154
Default error handling covers all error types that can occur during request processing:
@@ -79,6 +82,20 @@ When the path matches a route but the method has no handler, the response is `40
7982

8083
`HEAD` is added automatically whenever a `GET` handler exists.
8184

85+
### 422 - Validation Failed
86+
87+
When a [validation](/middleware/validation/overview) contract rejects request input, the default response adds an `errors` array listing each failure reason:
88+
89+
```typescript
90+
// POST /users with an invalid body
91+
// Status: 422
92+
// Content-Type: application/problem+json
93+
// Body: { "type": "about:blank", "title": "...", "status": 422,
94+
// "instance": "/users", "errors": ["name must not be empty"] }
95+
```
96+
97+
Only a 422 carries `errors`, and every other status keeps a reason-free body. How a contract produces those reasons lives in [Reading Validated Data](/middleware/validation/reading-data#how-failures-surface).
98+
8299
### 500 - Server Errors
83100

84101
When a route handler throws any error or exception:

docs/error-handling/object-details.md

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -141,26 +141,23 @@ export async function POST(ctx: Context): Promise<Response> {
141141

142142
## Validation Errors
143143

144-
Return appropriate status codes for validation errors:
144+
A rejected [validation](/middleware/validation/overview) contract throws a **422 Unprocessable Content** and preserves the failure reasons on `error.cause` as a string array. The same `router.catch` handles it, so reading the reasons turns a failure into a field-level response:
145145

146146
```typescript twoslash
147-
import type { Context, DataRecord } from '@neabyte/deserve'
147+
import { Router } from '@neabyte/deserve'
148+
149+
const router = new Router()
148150
// ---cut---
149-
export async function POST(ctx: Context): Promise<Response> {
150-
const data = await ctx.body() as DataRecord
151-
if (!data.email) {
151+
router.catch((ctx, error) => {
152+
if (error.statusCode === 422 && Array.isArray(error.error.cause)) {
153+
// Surface each validation reason
152154
return ctx.send.json(
153-
{
154-
error: 'Email is required'
155-
},
156-
{
157-
status: 400
158-
}
155+
{ error: 'Validation failed', reasons: error.error.cause },
156+
{ status: 422 }
159157
)
160158
}
161-
// Process valid data...
162-
return ctx.send.json({
163-
success: true
164-
})
165-
}
159+
return null
160+
})
166161
```
162+
163+
How a contract produces those reasons lives in [Reading Validated Data](/middleware/validation/reading-data#how-failures-surface), which keeps the validation rules in one place and the response shaping here.

docs/id/error-handling/default-behavior.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ await router.serve(8000)
2828

2929
Response error default (tanpa `router.catch()` khusus) mengikuti header `Accept` klien:
3030

31-
- **Accept mencakup `application/json`** → body JSON: `{ error, path, statusCode }`
31+
- **Accept mencakup `application/json` atau `application/problem+json`** → body problem-details `{ type, title, status, instance }` dikirim sebagai `application/problem+json`
3232
- **Selain itu** → body HTML: halaman error sederhana dengan status dan pesan (di-escape)
3333

3434
Juga:
@@ -39,13 +39,16 @@ Juga:
3939
```typescript
4040
// Contoh response default (klien minta JSON)
4141
// Status: 404
42-
// Body: { "error": "...", "path": "/api/foo", "statusCode": 404 }
42+
// Content-Type: application/problem+json
43+
// Body: { "type": "about:blank", "title": "Not Found", "status": 404, "instance": "/api/foo" }
4344

4445
// Contoh response default (klien tidak minta JSON)
4546
// Status: 404
4647
// Body: HTML dengan <title>404</title> dan pesan error
4748
```
4849

50+
Bentuk problem-details mengikuti [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457), di mana `type` adalah URI masalah, `title` ringkasan singkat, `status` mengulang kodenya, dan `instance` membawa path request. Sebuah `router.catch()` khusus mengganti body ini dengan bentuk apa pun yang cocok untuk klien, dibahas di [Detail Objek Error](/id/error-handling/object-details).
51+
4952
## Skenario Error
5053

5154
Penanganan error default mencakup semua jenis error yang bisa terjadi selama pemrosesan request:
@@ -79,6 +82,20 @@ Ketika path cocok dengan sebuah rute tapi metodenya tidak punya handler, respons
7982

8083
`HEAD` ditambahkan otomatis setiap kali handler `GET` ada.
8184

85+
### 422 - Validasi Gagal
86+
87+
Ketika kontrak [validasi](/id/middleware/validation/overview) menolak input request, response default menambahkan array `errors` yang mendaftar tiap alasan kegagalan:
88+
89+
```typescript
90+
// POST /users dengan body tidak valid
91+
// Status: 422
92+
// Content-Type: application/problem+json
93+
// Body: { "type": "about:blank", "title": "...", "status": 422,
94+
// "instance": "/users", "errors": ["name must not be empty"] }
95+
```
96+
97+
Hanya 422 yang membawa `errors`, dan setiap status lain tetap berbody tanpa alasan. Bagaimana sebuah kontrak menghasilkan alasan itu ada di [Membaca Data Tervalidasi](/id/middleware/validation/reading-data#cara-kegagalan-muncul).
98+
8299
### 500 - Error Server
83100

84101
Ketika route handler melempar error atau exception apa pun:

0 commit comments

Comments
 (0)