hono-ban 0.2.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -16
- package/dist/cjs/index.js +81 -42
- package/dist/esm/index.js +81 -42
- package/dist/types/formatters/rfc7807/formatter.d.ts +4 -4
- package/dist/types/formatters/rfc7807/hooks.d.ts +27 -2
- package/dist/types/formatters/rfc7807/index.d.ts +2 -2
- package/dist/types/formatters/rfc7807/schemas.d.ts +16 -16
- package/dist/types/types/rfc7807.d.ts +21 -38
- package/package.json +1 -1
- package/src/formatters/rfc7807/formatter.ts +16 -16
- package/src/formatters/rfc7807/hooks.ts +46 -20
- package/src/formatters/rfc7807/index.ts +7 -7
- package/src/formatters/rfc7807/schemas.ts +66 -35
- package/src/types/rfc7807.ts +31 -38
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ HTTP-friendly error objects for [Hono](https://hono.dev), inspired by [Boom](htt
|
|
|
17
17
|
- [Custom Error Data](#custom-error-data)
|
|
18
18
|
- [Error Conversion](#error-conversion)
|
|
19
19
|
- [Error Formatting](#error-formatting)
|
|
20
|
+
- [OpenAPI Integration](#openapi-integration)
|
|
20
21
|
- [Formatters](#formatters)
|
|
21
22
|
- [Default Formatter](#default-formatter)
|
|
22
23
|
- [RFC7807 (Problem Details) Formatter](#rfc7807-problem-details-formatter)
|
|
@@ -55,7 +56,14 @@ hono-ban provides a comprehensive error handling solution for Hono.js applicatio
|
|
|
55
56
|
### Basic Usage
|
|
56
57
|
|
|
57
58
|
```typescript
|
|
58
|
-
import {
|
|
59
|
+
import { Hono } from "hono";
|
|
60
|
+
import ban, { notFound, badRequest } from "hono-ban";
|
|
61
|
+
|
|
62
|
+
// Create a Hono app
|
|
63
|
+
const app = new Hono();
|
|
64
|
+
|
|
65
|
+
// IMPORTANT: Add the ban middleware for error handling to work
|
|
66
|
+
app.use(ban());
|
|
59
67
|
|
|
60
68
|
// Create a 404 Not Found error
|
|
61
69
|
const error = notFound("User not found");
|
|
@@ -66,10 +74,20 @@ const validationError = badRequest("Invalid input", {
|
|
|
66
74
|
invalidFields: ["email", "password"],
|
|
67
75
|
},
|
|
68
76
|
});
|
|
77
|
+
|
|
78
|
+
// Example route that throws an error
|
|
79
|
+
app.get("/users/:id", (c) => {
|
|
80
|
+
// The thrown error will be caught and formatted by the ban middleware
|
|
81
|
+
throw notFound(`User with ID ${c.req.param("id")} not found`);
|
|
82
|
+
});
|
|
69
83
|
```
|
|
70
84
|
|
|
85
|
+
> **Note**: The ban middleware is required for errors to be automatically caught and formatted. Without it, thrown errors won't be properly handled.
|
|
86
|
+
|
|
71
87
|
### Error Middleware
|
|
72
88
|
|
|
89
|
+
The ban middleware is **required** to catch and format errors thrown in your routes. Without this middleware, errors will not be properly handled.
|
|
90
|
+
|
|
73
91
|
#### Basic Usage
|
|
74
92
|
|
|
75
93
|
```typescript
|
|
@@ -80,6 +98,7 @@ import { notFound } from "hono-ban";
|
|
|
80
98
|
const app = new Hono();
|
|
81
99
|
|
|
82
100
|
// Add the error handling middleware with default options
|
|
101
|
+
// This is REQUIRED for error handling to work
|
|
83
102
|
app.use(ban());
|
|
84
103
|
|
|
85
104
|
// Routes can throw errors that will be handled automatically
|
|
@@ -189,6 +208,96 @@ const formatted = formatError(banError, myFormatter, {
|
|
|
189
208
|
const response = createErrorResponse(banError, formatted);
|
|
190
209
|
```
|
|
191
210
|
|
|
211
|
+
### OpenAPI Integration
|
|
212
|
+
|
|
213
|
+
hono-ban integrates seamlessly with [@hono/zod-openapi](https://github.com/honojs/middleware/tree/main/packages/zod-openapi) to provide standardized error handling for OpenAPI-validated routes.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
217
|
+
import { ban, createRFC7807Formatter, rfc7807Hook } from "hono-ban";
|
|
218
|
+
import { RFC7807DetailsSchema } from "hono-ban/formatters/rfc7807";
|
|
219
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
220
|
+
import type { Context } from "hono";
|
|
221
|
+
import type { Env } from "./config/env";
|
|
222
|
+
|
|
223
|
+
// Create an OpenAPIHono instance with the RFC7807 hook for validation errors
|
|
224
|
+
const app = new OpenAPIHono<Env>({ defaultHook: rfc7807Hook });
|
|
225
|
+
|
|
226
|
+
// IMPORTANT: Add the ban middleware with RFC7807 formatter
|
|
227
|
+
app.use(
|
|
228
|
+
ban({
|
|
229
|
+
formatter: createRFC7807Formatter({
|
|
230
|
+
baseUrl: "https://api.example.com/errors",
|
|
231
|
+
}),
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Define a schema for your data
|
|
236
|
+
const NoteSchema = z.object({
|
|
237
|
+
name: z.string().max(10),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Create an OpenAPI route with error handling
|
|
241
|
+
const route = createRoute({
|
|
242
|
+
method: "post",
|
|
243
|
+
path: "/notes",
|
|
244
|
+
request: {
|
|
245
|
+
body: {
|
|
246
|
+
content: {
|
|
247
|
+
"application/json": {
|
|
248
|
+
schema: NoteSchema,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
required: true,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
responses: {
|
|
255
|
+
200: {
|
|
256
|
+
description: "Successfully created note",
|
|
257
|
+
content: {
|
|
258
|
+
"application/json": {
|
|
259
|
+
schema: z.object({
|
|
260
|
+
data: z.object({
|
|
261
|
+
id: z.string(),
|
|
262
|
+
type: z.string(),
|
|
263
|
+
attributes: NoteSchema,
|
|
264
|
+
}),
|
|
265
|
+
}),
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
400: {
|
|
270
|
+
description: "Validation error",
|
|
271
|
+
content: {
|
|
272
|
+
"application/json": {
|
|
273
|
+
schema: RFC7807DetailsSchema, // Use the RFC7807 schema for errors
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Register the route
|
|
281
|
+
app.openapi(route, async (c) => {
|
|
282
|
+
// Handle the request
|
|
283
|
+
// Any validation errors will be automatically formatted using RFC7807
|
|
284
|
+
return c.json({
|
|
285
|
+
data: {
|
|
286
|
+
/* response data */
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
#### Key Benefits
|
|
293
|
+
|
|
294
|
+
1. **Automatic Validation Error Handling**: The `rfc7807Hook` automatically converts Zod validation errors to RFC7807 format.
|
|
295
|
+
2. **Standardized Error Responses**: All errors follow the RFC7807 specification.
|
|
296
|
+
3. **OpenAPI Documentation**: Error schemas are properly documented in your OpenAPI specification.
|
|
297
|
+
4. **Type Safety**: Full TypeScript support for request and response validation.
|
|
298
|
+
|
|
299
|
+
> **Note**: The ban middleware is still required when using OpenAPI integration. The `rfc7807Hook` handles validation errors, but the middleware is needed to catch and format other errors.
|
|
300
|
+
|
|
192
301
|
## Formatters
|
|
193
302
|
|
|
194
303
|
hono-ban includes several built-in formatters for common error response formats.
|
|
@@ -274,21 +383,7 @@ The RFC7807 formatter provides several helper functions:
|
|
|
274
383
|
- `createValidationError(params)`: Create validation error data
|
|
275
384
|
- `createZodValidationError(error)`: Convert Zod validation errors to RFC7807 format
|
|
276
385
|
- `createConstraintViolation(name, reason, resource, constraint)`: Create constraint violation data
|
|
277
|
-
- `createRFC7807Hook(options)`: Create a Hono hook for Zod OpenAPI validation
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
import { createRFC7807Hook } from "hono-ban/formatters/rfc7807";
|
|
281
|
-
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
282
|
-
|
|
283
|
-
const app = new OpenAPIHono();
|
|
284
|
-
|
|
285
|
-
// Add the RFC7807 hook for validation errors
|
|
286
|
-
app.openapi(
|
|
287
|
-
createRFC7807Hook({
|
|
288
|
-
baseUrl: "https://api.example.com/problems",
|
|
289
|
-
})
|
|
290
|
-
);
|
|
291
|
-
```
|
|
386
|
+
- `createRFC7807Hook(options)`: Create a Hono hook for Zod OpenAPI validation (see [OpenAPI Integration](#openapi-integration) for usage)
|
|
292
387
|
|
|
293
388
|
## API Reference
|
|
294
389
|
|
package/dist/cjs/index.js
CHANGED
|
@@ -69,13 +69,13 @@ __export(exports_src, {
|
|
|
69
69
|
entityTooLarge: () => entityTooLarge,
|
|
70
70
|
defaultFormatter: () => defaultFormatter,
|
|
71
71
|
default: () => src_default,
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
createRFC7807ZodValidationError: () => createRFC7807ZodValidationError,
|
|
73
|
+
createRFC7807ValidationError: () => createRFC7807ValidationError,
|
|
74
74
|
createRFC7807Hook: () => createRFC7807Hook,
|
|
75
75
|
createRFC7807Formatter: () => createRFC7807Formatter,
|
|
76
|
+
createRFC7807ConstraintViolation: () => createRFC7807ConstraintViolation,
|
|
76
77
|
createErrorResponse: () => createErrorResponse,
|
|
77
78
|
createError: () => createError,
|
|
78
|
-
createConstraintViolation: () => createConstraintViolation,
|
|
79
79
|
convertToBanError: () => convertToBanError,
|
|
80
80
|
conflict: () => conflict,
|
|
81
81
|
clientTimeout: () => clientTimeout,
|
|
@@ -83,11 +83,11 @@ __export(exports_src, {
|
|
|
83
83
|
badImplementation: () => badImplementation,
|
|
84
84
|
badGateway: () => badGateway,
|
|
85
85
|
badData: () => badData,
|
|
86
|
-
ValidationParamSchema: () => ValidationParamSchema,
|
|
87
86
|
STATUS_CODES: () => STATUS_CODES,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
RFC7807ValidationParamSchema: () => RFC7807ValidationParamSchema,
|
|
88
|
+
RFC7807ErrorDataSchema: () => RFC7807ErrorDataSchema,
|
|
89
|
+
RFC7807DetailsSchema: () => RFC7807DetailsSchema,
|
|
90
|
+
RFC7807ConstraintViolationSchema: () => RFC7807ConstraintViolationSchema
|
|
91
91
|
});
|
|
92
92
|
module.exports = __toCommonJS(exports_src);
|
|
93
93
|
|
|
@@ -282,28 +282,64 @@ var defaultFormatter = {
|
|
|
282
282
|
|
|
283
283
|
// src/formatters/rfc7807/schemas.ts
|
|
284
284
|
var import_zod_openapi = require("@hono/zod-openapi");
|
|
285
|
-
var
|
|
286
|
-
name: import_zod_openapi.z.string()
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}).openapi("ConstraintViolation");
|
|
295
|
-
var ProblemErrorDataSchema = import_zod_openapi.z.object({
|
|
296
|
-
"invalid-params": import_zod_openapi.z.array(ValidationParamSchema).optional(),
|
|
297
|
-
violations: import_zod_openapi.z.array(ConstraintViolationSchema).optional()
|
|
285
|
+
var RFC7807ValidationParamSchema = import_zod_openapi.z.object({
|
|
286
|
+
name: import_zod_openapi.z.string().openapi({
|
|
287
|
+
example: "username",
|
|
288
|
+
description: "The field name that failed validation"
|
|
289
|
+
}),
|
|
290
|
+
reason: import_zod_openapi.z.string().openapi({
|
|
291
|
+
example: "String must contain at least 3 character(s)",
|
|
292
|
+
description: "The reason for validation failure"
|
|
293
|
+
})
|
|
298
294
|
});
|
|
299
|
-
var
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
295
|
+
var RFC7807ConstraintViolationSchema = import_zod_openapi.z.object({
|
|
296
|
+
name: import_zod_openapi.z.string().openapi({
|
|
297
|
+
example: "email",
|
|
298
|
+
description: "The field name that violated a constraint"
|
|
299
|
+
}),
|
|
300
|
+
reason: import_zod_openapi.z.string().openapi({
|
|
301
|
+
example: "Email already exists",
|
|
302
|
+
description: "The reason for the constraint violation"
|
|
303
|
+
}),
|
|
304
|
+
resource: import_zod_openapi.z.string().openapi({
|
|
305
|
+
example: "user",
|
|
306
|
+
description: "The resource type that contains the constraint"
|
|
307
|
+
}),
|
|
308
|
+
constraint: import_zod_openapi.z.string().openapi({
|
|
309
|
+
example: "unique",
|
|
310
|
+
description: "The type of constraint that was violated"
|
|
311
|
+
})
|
|
312
|
+
});
|
|
313
|
+
var RFC7807ErrorDataSchema = import_zod_openapi.z.object({
|
|
314
|
+
"invalid-params": import_zod_openapi.z.array(RFC7807ValidationParamSchema).optional(),
|
|
315
|
+
violations: import_zod_openapi.z.array(RFC7807ConstraintViolationSchema).optional()
|
|
316
|
+
});
|
|
317
|
+
var RFC7807DetailsSchema = import_zod_openapi.z.object({
|
|
318
|
+
type: import_zod_openapi.z.string().url().openapi({
|
|
319
|
+
example: "https://api.example.com/problems/validation-error",
|
|
320
|
+
description: "A URI reference that identifies the problem type"
|
|
321
|
+
}),
|
|
322
|
+
title: import_zod_openapi.z.string().openapi({
|
|
323
|
+
example: "Validation Failed",
|
|
324
|
+
description: "A short, human-readable summary of the problem type"
|
|
325
|
+
}),
|
|
326
|
+
status: import_zod_openapi.z.number().int().min(400).max(599).openapi({
|
|
327
|
+
example: 400,
|
|
328
|
+
description: "The HTTP status code"
|
|
329
|
+
}),
|
|
330
|
+
detail: import_zod_openapi.z.string().optional().openapi({
|
|
331
|
+
example: "The request contains invalid fields",
|
|
332
|
+
description: "A human-readable explanation specific to this occurrence of the problem"
|
|
333
|
+
}),
|
|
334
|
+
instance: import_zod_openapi.z.string().url().optional().openapi({
|
|
335
|
+
example: "urn:uuid:6b56944d-5e89-4b4d-9ca7-c1be3d1f0e3f",
|
|
336
|
+
description: "A URI reference that identifies the specific occurrence of the problem"
|
|
337
|
+
}),
|
|
338
|
+
timestamp: import_zod_openapi.z.string().datetime().optional().openapi({
|
|
339
|
+
example: "2025-02-26T12:34:56.789Z",
|
|
340
|
+
description: "When the error occurred"
|
|
341
|
+
})
|
|
342
|
+
}).merge(RFC7807ErrorDataSchema);
|
|
307
343
|
|
|
308
344
|
// src/formatters/rfc7807/formatter.ts
|
|
309
345
|
function createRFC7807Formatter(options = {}) {
|
|
@@ -320,37 +356,37 @@ function createRFC7807Formatter(options = {}) {
|
|
|
320
356
|
timestamp: new Date().toISOString()
|
|
321
357
|
};
|
|
322
358
|
if (error.data?.["invalid-params"]) {
|
|
323
|
-
return
|
|
359
|
+
return RFC7807DetailsSchema.parse({
|
|
324
360
|
...base,
|
|
325
361
|
"invalid-params": error.data["invalid-params"]
|
|
326
362
|
});
|
|
327
363
|
}
|
|
328
364
|
if (error.data?.violations) {
|
|
329
|
-
return
|
|
365
|
+
return RFC7807DetailsSchema.parse({
|
|
330
366
|
...base,
|
|
331
367
|
violations: error.data.violations
|
|
332
368
|
});
|
|
333
369
|
}
|
|
334
|
-
return
|
|
370
|
+
return RFC7807DetailsSchema.parse(base);
|
|
335
371
|
}
|
|
336
372
|
};
|
|
337
373
|
}
|
|
338
|
-
function
|
|
374
|
+
function createRFC7807ValidationError(params) {
|
|
339
375
|
return {
|
|
340
|
-
"invalid-params":
|
|
376
|
+
"invalid-params": RFC7807ValidationParamSchema.array().parse(params)
|
|
341
377
|
};
|
|
342
378
|
}
|
|
343
|
-
function
|
|
379
|
+
function createRFC7807ZodValidationError(error) {
|
|
344
380
|
return {
|
|
345
|
-
"invalid-params":
|
|
381
|
+
"invalid-params": RFC7807ValidationParamSchema.array().parse(error.errors.map((e) => ({
|
|
346
382
|
name: e.path.join("."),
|
|
347
383
|
reason: e.message
|
|
348
384
|
})))
|
|
349
385
|
};
|
|
350
386
|
}
|
|
351
|
-
function
|
|
387
|
+
function createRFC7807ConstraintViolation(name, reason, resource, constraint = "unique") {
|
|
352
388
|
return {
|
|
353
|
-
violations:
|
|
389
|
+
violations: RFC7807ConstraintViolationSchema.array().parse([
|
|
354
390
|
{
|
|
355
391
|
name,
|
|
356
392
|
reason,
|
|
@@ -4353,14 +4389,17 @@ function badImplementation(messageOrOptions, options) {
|
|
|
4353
4389
|
|
|
4354
4390
|
// src/formatters/rfc7807/hooks.ts
|
|
4355
4391
|
function createRFC7807Hook(options) {
|
|
4356
|
-
const formatter = createRFC7807Formatter(options);
|
|
4357
4392
|
return (result, c) => {
|
|
4358
4393
|
if (!result.success && "error" in result && result.error instanceof ZodError) {
|
|
4359
|
-
const
|
|
4360
|
-
|
|
4394
|
+
const validationData = createRFC7807ZodValidationError(result.error);
|
|
4395
|
+
throw badRequest({
|
|
4396
|
+
message: options?.message || "Validation Error",
|
|
4397
|
+
data: validationData,
|
|
4398
|
+
formatter: options?.formatter,
|
|
4399
|
+
headers: options?.headers,
|
|
4400
|
+
sanitize: options?.sanitize,
|
|
4401
|
+
includeStackTrace: options?.includeStackTrace
|
|
4361
4402
|
});
|
|
4362
|
-
const formatted = formatError(error, formatter);
|
|
4363
|
-
return createErrorResponse(error, formatted);
|
|
4364
4403
|
}
|
|
4365
4404
|
};
|
|
4366
4405
|
}
|
package/dist/esm/index.js
CHANGED
|
@@ -189,28 +189,64 @@ var defaultFormatter = {
|
|
|
189
189
|
|
|
190
190
|
// src/formatters/rfc7807/schemas.ts
|
|
191
191
|
import { z } from "@hono/zod-openapi";
|
|
192
|
-
var
|
|
193
|
-
name: z.string()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}).openapi("ConstraintViolation");
|
|
202
|
-
var ProblemErrorDataSchema = z.object({
|
|
203
|
-
"invalid-params": z.array(ValidationParamSchema).optional(),
|
|
204
|
-
violations: z.array(ConstraintViolationSchema).optional()
|
|
192
|
+
var RFC7807ValidationParamSchema = z.object({
|
|
193
|
+
name: z.string().openapi({
|
|
194
|
+
example: "username",
|
|
195
|
+
description: "The field name that failed validation"
|
|
196
|
+
}),
|
|
197
|
+
reason: z.string().openapi({
|
|
198
|
+
example: "String must contain at least 3 character(s)",
|
|
199
|
+
description: "The reason for validation failure"
|
|
200
|
+
})
|
|
205
201
|
});
|
|
206
|
-
var
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
var RFC7807ConstraintViolationSchema = z.object({
|
|
203
|
+
name: z.string().openapi({
|
|
204
|
+
example: "email",
|
|
205
|
+
description: "The field name that violated a constraint"
|
|
206
|
+
}),
|
|
207
|
+
reason: z.string().openapi({
|
|
208
|
+
example: "Email already exists",
|
|
209
|
+
description: "The reason for the constraint violation"
|
|
210
|
+
}),
|
|
211
|
+
resource: z.string().openapi({
|
|
212
|
+
example: "user",
|
|
213
|
+
description: "The resource type that contains the constraint"
|
|
214
|
+
}),
|
|
215
|
+
constraint: z.string().openapi({
|
|
216
|
+
example: "unique",
|
|
217
|
+
description: "The type of constraint that was violated"
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
var RFC7807ErrorDataSchema = z.object({
|
|
221
|
+
"invalid-params": z.array(RFC7807ValidationParamSchema).optional(),
|
|
222
|
+
violations: z.array(RFC7807ConstraintViolationSchema).optional()
|
|
223
|
+
});
|
|
224
|
+
var RFC7807DetailsSchema = z.object({
|
|
225
|
+
type: z.string().url().openapi({
|
|
226
|
+
example: "https://api.example.com/problems/validation-error",
|
|
227
|
+
description: "A URI reference that identifies the problem type"
|
|
228
|
+
}),
|
|
229
|
+
title: z.string().openapi({
|
|
230
|
+
example: "Validation Failed",
|
|
231
|
+
description: "A short, human-readable summary of the problem type"
|
|
232
|
+
}),
|
|
233
|
+
status: z.number().int().min(400).max(599).openapi({
|
|
234
|
+
example: 400,
|
|
235
|
+
description: "The HTTP status code"
|
|
236
|
+
}),
|
|
237
|
+
detail: z.string().optional().openapi({
|
|
238
|
+
example: "The request contains invalid fields",
|
|
239
|
+
description: "A human-readable explanation specific to this occurrence of the problem"
|
|
240
|
+
}),
|
|
241
|
+
instance: z.string().url().optional().openapi({
|
|
242
|
+
example: "urn:uuid:6b56944d-5e89-4b4d-9ca7-c1be3d1f0e3f",
|
|
243
|
+
description: "A URI reference that identifies the specific occurrence of the problem"
|
|
244
|
+
}),
|
|
245
|
+
timestamp: z.string().datetime().optional().openapi({
|
|
246
|
+
example: "2025-02-26T12:34:56.789Z",
|
|
247
|
+
description: "When the error occurred"
|
|
248
|
+
})
|
|
249
|
+
}).merge(RFC7807ErrorDataSchema);
|
|
214
250
|
|
|
215
251
|
// src/formatters/rfc7807/formatter.ts
|
|
216
252
|
function createRFC7807Formatter(options = {}) {
|
|
@@ -227,37 +263,37 @@ function createRFC7807Formatter(options = {}) {
|
|
|
227
263
|
timestamp: new Date().toISOString()
|
|
228
264
|
};
|
|
229
265
|
if (error.data?.["invalid-params"]) {
|
|
230
|
-
return
|
|
266
|
+
return RFC7807DetailsSchema.parse({
|
|
231
267
|
...base,
|
|
232
268
|
"invalid-params": error.data["invalid-params"]
|
|
233
269
|
});
|
|
234
270
|
}
|
|
235
271
|
if (error.data?.violations) {
|
|
236
|
-
return
|
|
272
|
+
return RFC7807DetailsSchema.parse({
|
|
237
273
|
...base,
|
|
238
274
|
violations: error.data.violations
|
|
239
275
|
});
|
|
240
276
|
}
|
|
241
|
-
return
|
|
277
|
+
return RFC7807DetailsSchema.parse(base);
|
|
242
278
|
}
|
|
243
279
|
};
|
|
244
280
|
}
|
|
245
|
-
function
|
|
281
|
+
function createRFC7807ValidationError(params) {
|
|
246
282
|
return {
|
|
247
|
-
"invalid-params":
|
|
283
|
+
"invalid-params": RFC7807ValidationParamSchema.array().parse(params)
|
|
248
284
|
};
|
|
249
285
|
}
|
|
250
|
-
function
|
|
286
|
+
function createRFC7807ZodValidationError(error) {
|
|
251
287
|
return {
|
|
252
|
-
"invalid-params":
|
|
288
|
+
"invalid-params": RFC7807ValidationParamSchema.array().parse(error.errors.map((e) => ({
|
|
253
289
|
name: e.path.join("."),
|
|
254
290
|
reason: e.message
|
|
255
291
|
})))
|
|
256
292
|
};
|
|
257
293
|
}
|
|
258
|
-
function
|
|
294
|
+
function createRFC7807ConstraintViolation(name, reason, resource, constraint = "unique") {
|
|
259
295
|
return {
|
|
260
|
-
violations:
|
|
296
|
+
violations: RFC7807ConstraintViolationSchema.array().parse([
|
|
261
297
|
{
|
|
262
298
|
name,
|
|
263
299
|
reason,
|
|
@@ -4260,14 +4296,17 @@ function badImplementation(messageOrOptions, options) {
|
|
|
4260
4296
|
|
|
4261
4297
|
// src/formatters/rfc7807/hooks.ts
|
|
4262
4298
|
function createRFC7807Hook(options) {
|
|
4263
|
-
const formatter = createRFC7807Formatter(options);
|
|
4264
4299
|
return (result, c) => {
|
|
4265
4300
|
if (!result.success && "error" in result && result.error instanceof ZodError) {
|
|
4266
|
-
const
|
|
4267
|
-
|
|
4301
|
+
const validationData = createRFC7807ZodValidationError(result.error);
|
|
4302
|
+
throw badRequest({
|
|
4303
|
+
message: options?.message || "Validation Error",
|
|
4304
|
+
data: validationData,
|
|
4305
|
+
formatter: options?.formatter,
|
|
4306
|
+
headers: options?.headers,
|
|
4307
|
+
sanitize: options?.sanitize,
|
|
4308
|
+
includeStackTrace: options?.includeStackTrace
|
|
4268
4309
|
});
|
|
4269
|
-
const formatted = formatError(error, formatter);
|
|
4270
|
-
return createErrorResponse(error, formatted);
|
|
4271
4310
|
}
|
|
4272
4311
|
};
|
|
4273
4312
|
}
|
|
@@ -4353,13 +4392,13 @@ export {
|
|
|
4353
4392
|
entityTooLarge,
|
|
4354
4393
|
defaultFormatter,
|
|
4355
4394
|
src_default as default,
|
|
4356
|
-
|
|
4357
|
-
|
|
4395
|
+
createRFC7807ZodValidationError,
|
|
4396
|
+
createRFC7807ValidationError,
|
|
4358
4397
|
createRFC7807Hook,
|
|
4359
4398
|
createRFC7807Formatter,
|
|
4399
|
+
createRFC7807ConstraintViolation,
|
|
4360
4400
|
createErrorResponse,
|
|
4361
4401
|
createError,
|
|
4362
|
-
createConstraintViolation,
|
|
4363
4402
|
convertToBanError,
|
|
4364
4403
|
conflict,
|
|
4365
4404
|
clientTimeout,
|
|
@@ -4367,9 +4406,9 @@ export {
|
|
|
4367
4406
|
badImplementation,
|
|
4368
4407
|
badGateway,
|
|
4369
4408
|
badData,
|
|
4370
|
-
ValidationParamSchema,
|
|
4371
4409
|
STATUS_CODES,
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4410
|
+
RFC7807ValidationParamSchema,
|
|
4411
|
+
RFC7807ErrorDataSchema,
|
|
4412
|
+
RFC7807DetailsSchema,
|
|
4413
|
+
RFC7807ConstraintViolationSchema
|
|
4375
4414
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { z } from "@hono/zod-openapi";
|
|
6
6
|
import type { ErrorFormatter } from "../../types";
|
|
7
|
-
import type {
|
|
7
|
+
import type { RFC7807ValidationParam, RFC7807FormatterOptions as RFC7807Options } from "../../types";
|
|
8
8
|
/**
|
|
9
9
|
* Create an RFC 7807 Problem Details formatter
|
|
10
10
|
*/
|
|
@@ -12,7 +12,7 @@ export declare function createRFC7807Formatter(options?: RFC7807Options): ErrorF
|
|
|
12
12
|
/**
|
|
13
13
|
* Create validation error data in RFC 7807 format
|
|
14
14
|
*/
|
|
15
|
-
export declare function
|
|
15
|
+
export declare function createRFC7807ValidationError(params: RFC7807ValidationParam[]): {
|
|
16
16
|
"invalid-params": {
|
|
17
17
|
name: string;
|
|
18
18
|
reason: string;
|
|
@@ -21,7 +21,7 @@ export declare function createValidationError(params: ValidationParam[]): {
|
|
|
21
21
|
/**
|
|
22
22
|
* Convert Zod validation errors to RFC 7807 format
|
|
23
23
|
*/
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function createRFC7807ZodValidationError(error: z.ZodError): {
|
|
25
25
|
"invalid-params": {
|
|
26
26
|
name: string;
|
|
27
27
|
reason: string;
|
|
@@ -30,7 +30,7 @@ export declare function createZodValidationError(error: z.ZodError): {
|
|
|
30
30
|
/**
|
|
31
31
|
* Create constraint violation data in RFC 7807 format
|
|
32
32
|
*/
|
|
33
|
-
export declare function
|
|
33
|
+
export declare function createRFC7807ConstraintViolation(name: string, reason: string, resource: string, constraint?: string): {
|
|
34
34
|
violations: {
|
|
35
35
|
name: string;
|
|
36
36
|
reason: string;
|
|
@@ -4,12 +4,37 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Hook } from "@hono/zod-openapi";
|
|
6
6
|
import { Env } from "hono";
|
|
7
|
-
import type { RFC7807FormatterOptions as RFC7807Options } from "../../types";
|
|
7
|
+
import type { RFC7807FormatterOptions as RFC7807Options, BanOptions } from "../../types";
|
|
8
8
|
/**
|
|
9
9
|
* Create a Hono hook that formats validation errors using RFC 7807
|
|
10
|
+
*
|
|
11
|
+
* This hook throws a badRequest error that will be caught and processed by the ban middleware.
|
|
12
|
+
* You can provide options to override the default behavior of the middleware.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Basic usage - inherits all settings from middleware
|
|
16
|
+
* app.openapi(route, handler, { onError: createRFC7807Hook() });
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // With custom message
|
|
20
|
+
* app.openapi(route, handler, {
|
|
21
|
+
* onError: createRFC7807Hook({ message: "Custom validation error" })
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // With custom formatter and sanitization
|
|
26
|
+
* app.openapi(route, handler, {
|
|
27
|
+
* onError: createRFC7807Hook({
|
|
28
|
+
* formatter: customFormatter,
|
|
29
|
+
* sanitize: ['password', 'token']
|
|
30
|
+
* })
|
|
31
|
+
* });
|
|
10
32
|
*/
|
|
11
|
-
export declare function createRFC7807Hook(options?: RFC7807Options): Hook<any,
|
|
33
|
+
export declare function createRFC7807Hook<E extends Env = Env>(options?: RFC7807Options & Partial<BanOptions>): Hook<any, E, any, any>;
|
|
12
34
|
/**
|
|
13
35
|
* Pre-configured RFC 7807 hook with default options
|
|
36
|
+
*
|
|
37
|
+
* This is a convenience export that uses the default options.
|
|
38
|
+
* It will throw a badRequest error that will be caught and processed by the ban middleware.
|
|
14
39
|
*/
|
|
15
40
|
export declare const rfc7807Hook: Hook<any, Env, any, any>;
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* RFC 7807 Problem Details implementation for Hono Ban
|
|
3
3
|
* @module hono-ban/formatters/rfc7807
|
|
4
4
|
*/
|
|
5
|
-
export { createRFC7807Formatter,
|
|
5
|
+
export { createRFC7807Formatter, createRFC7807ValidationError, createRFC7807ZodValidationError, createRFC7807ConstraintViolation, } from "./formatter";
|
|
6
6
|
export { createRFC7807Hook, rfc7807Hook } from "./hooks";
|
|
7
|
-
export {
|
|
7
|
+
export { RFC7807ValidationParamSchema, RFC7807ConstraintViolationSchema, RFC7807ErrorDataSchema, RFC7807DetailsSchema, } from "./schemas";
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { z } from "@hono/zod-openapi";
|
|
6
6
|
/**
|
|
7
|
-
* Schema for validation error parameters
|
|
7
|
+
* Schema for RFC 7807 validation error parameters
|
|
8
8
|
*/
|
|
9
|
-
export declare const
|
|
9
|
+
export declare const RFC7807ValidationParamSchema: z.ZodObject<{
|
|
10
10
|
name: z.ZodString;
|
|
11
11
|
reason: z.ZodString;
|
|
12
12
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -17,9 +17,9 @@ export declare const ValidationParamSchema: z.ZodObject<{
|
|
|
17
17
|
reason: string;
|
|
18
18
|
}>;
|
|
19
19
|
/**
|
|
20
|
-
* Schema for constraint violations
|
|
20
|
+
* Schema for RFC 7807 constraint violations
|
|
21
21
|
*/
|
|
22
|
-
export declare const
|
|
22
|
+
export declare const RFC7807ConstraintViolationSchema: z.ZodObject<{
|
|
23
23
|
name: z.ZodString;
|
|
24
24
|
reason: z.ZodString;
|
|
25
25
|
resource: z.ZodString;
|
|
@@ -36,9 +36,9 @@ export declare const ConstraintViolationSchema: z.ZodObject<{
|
|
|
36
36
|
constraint: string;
|
|
37
37
|
}>;
|
|
38
38
|
/**
|
|
39
|
-
* Schema for additional error data in problem details
|
|
39
|
+
* Schema for additional error data in RFC 7807 problem details
|
|
40
40
|
*/
|
|
41
|
-
export declare const
|
|
41
|
+
export declare const RFC7807ErrorDataSchema: z.ZodObject<{
|
|
42
42
|
"invalid-params": z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
43
43
|
name: z.ZodString;
|
|
44
44
|
reason: z.ZodString;
|
|
@@ -91,13 +91,13 @@ export declare const ProblemErrorDataSchema: z.ZodObject<{
|
|
|
91
91
|
/**
|
|
92
92
|
* Schema for complete RFC 7807 problem details
|
|
93
93
|
*/
|
|
94
|
-
export declare const
|
|
94
|
+
export declare const RFC7807DetailsSchema: z.ZodObject<z.objectUtil.extendShape<{
|
|
95
95
|
type: z.ZodString;
|
|
96
96
|
title: z.ZodString;
|
|
97
97
|
status: z.ZodNumber;
|
|
98
|
-
detail: z.ZodString
|
|
99
|
-
instance: z.ZodString
|
|
100
|
-
timestamp: z.ZodString
|
|
98
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
99
|
+
instance: z.ZodOptional<z.ZodString>;
|
|
100
|
+
timestamp: z.ZodOptional<z.ZodString>;
|
|
101
101
|
}, {
|
|
102
102
|
"invalid-params": z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
103
103
|
name: z.ZodString;
|
|
@@ -129,9 +129,6 @@ export declare const ProblemDetailsSchema: z.ZodObject<z.objectUtil.extendShape<
|
|
|
129
129
|
type: string;
|
|
130
130
|
title: string;
|
|
131
131
|
status: number;
|
|
132
|
-
detail: string;
|
|
133
|
-
instance: string;
|
|
134
|
-
timestamp: string;
|
|
135
132
|
"invalid-params"?: {
|
|
136
133
|
name: string;
|
|
137
134
|
reason: string;
|
|
@@ -142,13 +139,13 @@ export declare const ProblemDetailsSchema: z.ZodObject<z.objectUtil.extendShape<
|
|
|
142
139
|
resource: string;
|
|
143
140
|
constraint: string;
|
|
144
141
|
}[] | undefined;
|
|
142
|
+
detail?: string | undefined;
|
|
143
|
+
instance?: string | undefined;
|
|
144
|
+
timestamp?: string | undefined;
|
|
145
145
|
}, {
|
|
146
146
|
type: string;
|
|
147
147
|
title: string;
|
|
148
148
|
status: number;
|
|
149
|
-
detail: string;
|
|
150
|
-
instance: string;
|
|
151
|
-
timestamp: string;
|
|
152
149
|
"invalid-params"?: {
|
|
153
150
|
name: string;
|
|
154
151
|
reason: string;
|
|
@@ -159,4 +156,7 @@ export declare const ProblemDetailsSchema: z.ZodObject<z.objectUtil.extendShape<
|
|
|
159
156
|
resource: string;
|
|
160
157
|
constraint: string;
|
|
161
158
|
}[] | undefined;
|
|
159
|
+
detail?: string | undefined;
|
|
160
|
+
instance?: string | undefined;
|
|
161
|
+
timestamp?: string | undefined;
|
|
162
162
|
}>;
|
|
@@ -8,87 +8,70 @@
|
|
|
8
8
|
* @module hono-ban/types/rfc7807
|
|
9
9
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc7807} RFC 7807 Problem Details
|
|
10
10
|
*/
|
|
11
|
+
import { z } from "@hono/zod-openapi";
|
|
12
|
+
import { RFC7807ValidationParamSchema, RFC7807ConstraintViolationSchema, RFC7807ErrorDataSchema, RFC7807DetailsSchema } from "../formatters/rfc7807/schemas";
|
|
11
13
|
/**
|
|
12
14
|
* Represents a validation error parameter in an RFC 7807 problem details object.
|
|
13
15
|
* Used to indicate specific validation failures in request parameters.
|
|
14
16
|
*
|
|
15
|
-
* @
|
|
17
|
+
* @type RFC7807ValidationParam
|
|
16
18
|
* @property {string} name - The name of the parameter that failed validation
|
|
17
19
|
* @property {string} reason - The reason why the parameter failed validation
|
|
18
20
|
*/
|
|
19
|
-
export
|
|
20
|
-
name: string;
|
|
21
|
-
reason: string;
|
|
22
|
-
}
|
|
21
|
+
export type RFC7807ValidationParam = z.infer<typeof RFC7807ValidationParamSchema>;
|
|
23
22
|
/**
|
|
24
23
|
* Represents a constraint violation in an RFC 7807 problem details object.
|
|
25
24
|
* Used to indicate violations of business rules or data constraints.
|
|
26
25
|
*
|
|
27
|
-
* @
|
|
26
|
+
* @type RFC7807ConstraintViolation
|
|
28
27
|
* @property {string} name - The name of the violated constraint
|
|
29
28
|
* @property {string} reason - The reason why the constraint was violated
|
|
30
29
|
* @property {string} resource - The resource or entity where the violation occurred
|
|
31
30
|
* @property {string} constraint - The specific constraint that was violated
|
|
32
31
|
*/
|
|
33
|
-
export
|
|
34
|
-
name: string;
|
|
35
|
-
reason: string;
|
|
36
|
-
resource: string;
|
|
37
|
-
constraint: string;
|
|
38
|
-
}
|
|
32
|
+
export type RFC7807ConstraintViolation = z.infer<typeof RFC7807ConstraintViolationSchema>;
|
|
39
33
|
/**
|
|
40
34
|
* Additional error data that can be included in an RFC 7807 problem details object.
|
|
41
35
|
* This interface extends the standard problem details with validation and constraint information.
|
|
42
36
|
*
|
|
43
|
-
* @
|
|
44
|
-
* @property {
|
|
45
|
-
* @property {
|
|
37
|
+
* @type RFC7807ErrorData
|
|
38
|
+
* @property {RFC7807ValidationParam[]} [invalid-params] - Array of validation errors
|
|
39
|
+
* @property {RFC7807ConstraintViolation[]} [violations] - Array of constraint violations
|
|
46
40
|
*/
|
|
47
|
-
export
|
|
48
|
-
"invalid-params"?: ValidationParam[];
|
|
49
|
-
violations?: ConstraintViolation[];
|
|
50
|
-
}
|
|
41
|
+
export type RFC7807ErrorData = z.infer<typeof RFC7807ErrorDataSchema>;
|
|
51
42
|
/**
|
|
52
43
|
* Complete RFC 7807 problem details object structure.
|
|
53
44
|
* This interface represents the full problem details format as defined in RFC 7807,
|
|
54
45
|
* with additional properties for validation and constraint violation data.
|
|
55
46
|
*
|
|
56
|
-
* @
|
|
57
|
-
* @extends ProblemErrorData
|
|
47
|
+
* @type RFC7807Details
|
|
58
48
|
* @property {string} type - URI reference that identifies the problem type
|
|
59
49
|
* @property {string} title - Short, human-readable summary of the problem
|
|
60
50
|
* @property {number} status - HTTP status code (400-599)
|
|
61
|
-
* @property {string} detail - Human-readable explanation specific to this occurrence
|
|
62
|
-
* @property {string} instance - URI reference that identifies the specific occurrence
|
|
63
|
-
* @property {string} timestamp - ISO 8601 datetime when the error occurred
|
|
51
|
+
* @property {string} [detail] - Human-readable explanation specific to this occurrence
|
|
52
|
+
* @property {string} [instance] - URI reference that identifies the specific occurrence
|
|
53
|
+
* @property {string} [timestamp] - ISO 8601 datetime when the error occurred
|
|
64
54
|
*/
|
|
65
|
-
export
|
|
66
|
-
type: string;
|
|
67
|
-
title: string;
|
|
68
|
-
status: number;
|
|
69
|
-
detail: string;
|
|
70
|
-
instance: string;
|
|
71
|
-
timestamp: string;
|
|
72
|
-
}
|
|
55
|
+
export type RFC7807Details = z.infer<typeof RFC7807DetailsSchema>;
|
|
73
56
|
/**
|
|
74
57
|
* Hook function for customizing RFC 7807 problem details before formatting.
|
|
75
58
|
* This type represents a function that can modify the problem details object
|
|
76
59
|
* before it is serialized into the final response.
|
|
77
60
|
*
|
|
78
|
-
* @callback
|
|
79
|
-
* @param {
|
|
80
|
-
* @returns {
|
|
61
|
+
* @callback RFC7807DetailsHook
|
|
62
|
+
* @param {RFC7807Details} details - The problem details object to modify
|
|
63
|
+
* @returns {RFC7807Details} The modified problem details object
|
|
81
64
|
*/
|
|
82
|
-
export type
|
|
65
|
+
export type RFC7807DetailsHook = (details: RFC7807Details) => RFC7807Details;
|
|
83
66
|
/**
|
|
84
67
|
* Configuration options for the RFC 7807 formatter.
|
|
85
68
|
* These options control how problem details are generated and formatted.
|
|
86
69
|
*
|
|
87
70
|
* @interface RFC7807FormatterOptions
|
|
88
71
|
* @property {string} [baseUrl="https://api.example.com/problems"] - Base URL for problem type URIs
|
|
89
|
-
* @property {
|
|
72
|
+
* @property {RFC7807DetailsHook[]} [hooks=[]] - Array of hooks for customizing problem details
|
|
90
73
|
*/
|
|
91
74
|
export interface RFC7807FormatterOptions {
|
|
92
75
|
baseUrl?: string;
|
|
93
|
-
hooks?:
|
|
76
|
+
hooks?: RFC7807DetailsHook[];
|
|
94
77
|
}
|
package/package.json
CHANGED
|
@@ -7,15 +7,15 @@ import { z } from "@hono/zod-openapi";
|
|
|
7
7
|
import { STATUS_CODES } from "../../constants";
|
|
8
8
|
import type { BanError, ErrorFormatter } from "../../types";
|
|
9
9
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
RFC7807Details,
|
|
11
|
+
RFC7807ErrorData,
|
|
12
|
+
RFC7807ValidationParam,
|
|
13
13
|
RFC7807FormatterOptions as RFC7807Options,
|
|
14
14
|
} from "../../types";
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
RFC7807ConstraintViolationSchema,
|
|
17
|
+
RFC7807DetailsSchema,
|
|
18
|
+
RFC7807ValidationParamSchema,
|
|
19
19
|
} from "./schemas";
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -29,7 +29,7 @@ export function createRFC7807Formatter(
|
|
|
29
29
|
return {
|
|
30
30
|
contentType: "application/problem+json",
|
|
31
31
|
|
|
32
|
-
format<T extends
|
|
32
|
+
format<T extends RFC7807ErrorData>(error: BanError<T>): RFC7807Details {
|
|
33
33
|
const base = {
|
|
34
34
|
type: `${baseUrl}/${error.status}`,
|
|
35
35
|
title: STATUS_CODES[error.status] || "Unknown Error",
|
|
@@ -41,7 +41,7 @@ export function createRFC7807Formatter(
|
|
|
41
41
|
|
|
42
42
|
// Handle validation errors
|
|
43
43
|
if (error.data?.["invalid-params"]) {
|
|
44
|
-
return
|
|
44
|
+
return RFC7807DetailsSchema.parse({
|
|
45
45
|
...base,
|
|
46
46
|
"invalid-params": error.data["invalid-params"],
|
|
47
47
|
});
|
|
@@ -49,13 +49,13 @@ export function createRFC7807Formatter(
|
|
|
49
49
|
|
|
50
50
|
// Handle constraint violations
|
|
51
51
|
if (error.data?.violations) {
|
|
52
|
-
return
|
|
52
|
+
return RFC7807DetailsSchema.parse({
|
|
53
53
|
...base,
|
|
54
54
|
violations: error.data.violations,
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
return
|
|
58
|
+
return RFC7807DetailsSchema.parse(base);
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
@@ -63,18 +63,18 @@ export function createRFC7807Formatter(
|
|
|
63
63
|
/**
|
|
64
64
|
* Create validation error data in RFC 7807 format
|
|
65
65
|
*/
|
|
66
|
-
export function
|
|
66
|
+
export function createRFC7807ValidationError(params: RFC7807ValidationParam[]) {
|
|
67
67
|
return {
|
|
68
|
-
"invalid-params":
|
|
68
|
+
"invalid-params": RFC7807ValidationParamSchema.array().parse(params),
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Convert Zod validation errors to RFC 7807 format
|
|
74
74
|
*/
|
|
75
|
-
export function
|
|
75
|
+
export function createRFC7807ZodValidationError(error: z.ZodError) {
|
|
76
76
|
return {
|
|
77
|
-
"invalid-params":
|
|
77
|
+
"invalid-params": RFC7807ValidationParamSchema.array().parse(
|
|
78
78
|
error.errors.map((e) => ({
|
|
79
79
|
name: e.path.join("."),
|
|
80
80
|
reason: e.message,
|
|
@@ -86,14 +86,14 @@ export function createZodValidationError(error: z.ZodError) {
|
|
|
86
86
|
/**
|
|
87
87
|
* Create constraint violation data in RFC 7807 format
|
|
88
88
|
*/
|
|
89
|
-
export function
|
|
89
|
+
export function createRFC7807ConstraintViolation(
|
|
90
90
|
name: string,
|
|
91
91
|
reason: string,
|
|
92
92
|
resource: string,
|
|
93
93
|
constraint: string = "unique"
|
|
94
94
|
) {
|
|
95
95
|
return {
|
|
96
|
-
violations:
|
|
96
|
+
violations: RFC7807ConstraintViolationSchema.array().parse([
|
|
97
97
|
{
|
|
98
98
|
name,
|
|
99
99
|
reason,
|
|
@@ -7,41 +7,67 @@ import { ZodError } from "zod";
|
|
|
7
7
|
import { Hook } from "@hono/zod-openapi";
|
|
8
8
|
import { Env } from "hono";
|
|
9
9
|
import { badRequest } from "../../factories";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import {
|
|
10
|
+
import type {
|
|
11
|
+
RFC7807FormatterOptions as RFC7807Options,
|
|
12
|
+
BanOptions,
|
|
13
|
+
} from "../../types";
|
|
14
|
+
import { createRFC7807ZodValidationError } from "./formatter";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Create a Hono hook that formats validation errors using RFC 7807
|
|
18
|
+
*
|
|
19
|
+
* This hook throws a badRequest error that will be caught and processed by the ban middleware.
|
|
20
|
+
* You can provide options to override the default behavior of the middleware.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Basic usage - inherits all settings from middleware
|
|
24
|
+
* app.openapi(route, handler, { onError: createRFC7807Hook() });
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // With custom message
|
|
28
|
+
* app.openapi(route, handler, {
|
|
29
|
+
* onError: createRFC7807Hook({ message: "Custom validation error" })
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // With custom formatter and sanitization
|
|
34
|
+
* app.openapi(route, handler, {
|
|
35
|
+
* onError: createRFC7807Hook({
|
|
36
|
+
* formatter: customFormatter,
|
|
37
|
+
* sanitize: ['password', 'token']
|
|
38
|
+
* })
|
|
39
|
+
* });
|
|
18
40
|
*/
|
|
19
|
-
export function createRFC7807Hook(
|
|
20
|
-
options?: RFC7807Options
|
|
21
|
-
): Hook<any,
|
|
22
|
-
const formatter = createRFC7807Formatter(options);
|
|
23
|
-
|
|
41
|
+
export function createRFC7807Hook<E extends Env = Env>(
|
|
42
|
+
options?: RFC7807Options & Partial<BanOptions>
|
|
43
|
+
): Hook<any, E, any, any> {
|
|
24
44
|
return (result, c) => {
|
|
25
45
|
if (
|
|
26
46
|
!result.success &&
|
|
27
47
|
"error" in result &&
|
|
28
48
|
result.error instanceof ZodError
|
|
29
49
|
) {
|
|
30
|
-
// Create the error
|
|
31
|
-
const
|
|
32
|
-
data: createZodValidationError(result.error),
|
|
33
|
-
});
|
|
50
|
+
// Create the validation error data
|
|
51
|
+
const validationData = createRFC7807ZodValidationError(result.error);
|
|
34
52
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
// Throw badRequest with both the validation data and any override options
|
|
54
|
+
throw badRequest({
|
|
55
|
+
message: options?.message || "Validation Error",
|
|
56
|
+
data: validationData,
|
|
57
|
+
// Pass through any override options
|
|
58
|
+
formatter: options?.formatter,
|
|
59
|
+
headers: options?.headers,
|
|
60
|
+
sanitize: options?.sanitize,
|
|
61
|
+
includeStackTrace: options?.includeStackTrace,
|
|
62
|
+
});
|
|
40
63
|
}
|
|
41
64
|
};
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
/**
|
|
45
68
|
* Pre-configured RFC 7807 hook with default options
|
|
69
|
+
*
|
|
70
|
+
* This is a convenience export that uses the default options.
|
|
71
|
+
* It will throw a badRequest error that will be caught and processed by the ban middleware.
|
|
46
72
|
*/
|
|
47
|
-
export const rfc7807Hook = createRFC7807Hook();
|
|
73
|
+
export const rfc7807Hook = createRFC7807Hook<Env>();
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
|
|
6
6
|
export {
|
|
7
7
|
createRFC7807Formatter,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
createRFC7807ValidationError,
|
|
9
|
+
createRFC7807ZodValidationError,
|
|
10
|
+
createRFC7807ConstraintViolation,
|
|
11
11
|
} from "./formatter";
|
|
12
12
|
|
|
13
13
|
export { createRFC7807Hook, rfc7807Hook } from "./hooks";
|
|
14
14
|
|
|
15
15
|
export {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
RFC7807ValidationParamSchema,
|
|
17
|
+
RFC7807ConstraintViolationSchema,
|
|
18
|
+
RFC7807ErrorDataSchema,
|
|
19
|
+
RFC7807DetailsSchema,
|
|
20
20
|
} from "./schemas";
|
|
@@ -4,54 +4,85 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { z } from "@hono/zod-openapi";
|
|
7
|
-
import type {
|
|
8
|
-
ValidationParam,
|
|
9
|
-
ConstraintViolation,
|
|
10
|
-
ProblemDetails,
|
|
11
|
-
ProblemErrorData,
|
|
12
|
-
} from "../../types";
|
|
13
7
|
|
|
14
8
|
/**
|
|
15
|
-
* Schema for validation error parameters
|
|
9
|
+
* Schema for RFC 7807 validation error parameters
|
|
16
10
|
*/
|
|
17
|
-
export const
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
})
|
|
22
|
-
.openapi(
|
|
11
|
+
export const RFC7807ValidationParamSchema = z.object({
|
|
12
|
+
name: z.string().openapi({
|
|
13
|
+
example: "username",
|
|
14
|
+
description: "The field name that failed validation",
|
|
15
|
+
}),
|
|
16
|
+
reason: z.string().openapi({
|
|
17
|
+
example: "String must contain at least 3 character(s)",
|
|
18
|
+
description: "The reason for validation failure",
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
23
21
|
|
|
24
22
|
/**
|
|
25
|
-
* Schema for constraint violations
|
|
23
|
+
* Schema for RFC 7807 constraint violations
|
|
26
24
|
*/
|
|
27
|
-
export const
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
export const RFC7807ConstraintViolationSchema = z.object({
|
|
26
|
+
name: z.string().openapi({
|
|
27
|
+
example: "email",
|
|
28
|
+
description: "The field name that violated a constraint",
|
|
29
|
+
}),
|
|
30
|
+
reason: z.string().openapi({
|
|
31
|
+
example: "Email already exists",
|
|
32
|
+
description: "The reason for the constraint violation",
|
|
33
|
+
}),
|
|
34
|
+
resource: z.string().openapi({
|
|
35
|
+
example: "user",
|
|
36
|
+
description: "The resource type that contains the constraint",
|
|
37
|
+
}),
|
|
38
|
+
constraint: z.string().openapi({
|
|
39
|
+
example: "unique",
|
|
40
|
+
description: "The type of constraint that was violated",
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
35
43
|
|
|
36
44
|
/**
|
|
37
|
-
* Schema for additional error data in problem details
|
|
45
|
+
* Schema for additional error data in RFC 7807 problem details
|
|
38
46
|
*/
|
|
39
|
-
export const
|
|
40
|
-
"invalid-params": z.array(
|
|
41
|
-
violations: z.array(
|
|
47
|
+
export const RFC7807ErrorDataSchema = z.object({
|
|
48
|
+
"invalid-params": z.array(RFC7807ValidationParamSchema).optional(),
|
|
49
|
+
violations: z.array(RFC7807ConstraintViolationSchema).optional(),
|
|
42
50
|
});
|
|
43
51
|
|
|
44
52
|
/**
|
|
45
53
|
* Schema for complete RFC 7807 problem details
|
|
46
54
|
*/
|
|
47
|
-
export const
|
|
55
|
+
export const RFC7807DetailsSchema = z
|
|
48
56
|
.object({
|
|
49
|
-
type: z.string().url()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
type: z.string().url().openapi({
|
|
58
|
+
example: "https://api.example.com/problems/validation-error",
|
|
59
|
+
description: "A URI reference that identifies the problem type",
|
|
60
|
+
}),
|
|
61
|
+
title: z.string().openapi({
|
|
62
|
+
example: "Validation Failed",
|
|
63
|
+
description: "A short, human-readable summary of the problem type",
|
|
64
|
+
}),
|
|
65
|
+
status: z.number().int().min(400).max(599).openapi({
|
|
66
|
+
example: 400,
|
|
67
|
+
description: "The HTTP status code",
|
|
68
|
+
}),
|
|
69
|
+
|
|
70
|
+
// Optional fields per RFC7807
|
|
71
|
+
detail: z.string().optional().openapi({
|
|
72
|
+
example: "The request contains invalid fields",
|
|
73
|
+
description:
|
|
74
|
+
"A human-readable explanation specific to this occurrence of the problem",
|
|
75
|
+
}),
|
|
76
|
+
instance: z.string().url().optional().openapi({
|
|
77
|
+
example: "urn:uuid:6b56944d-5e89-4b4d-9ca7-c1be3d1f0e3f",
|
|
78
|
+
description:
|
|
79
|
+
"A URI reference that identifies the specific occurrence of the problem",
|
|
80
|
+
}),
|
|
81
|
+
|
|
82
|
+
// Extensions for validation errors
|
|
83
|
+
timestamp: z.string().datetime().optional().openapi({
|
|
84
|
+
example: "2025-02-26T12:34:56.789Z",
|
|
85
|
+
description: "When the error occurred",
|
|
86
|
+
}),
|
|
55
87
|
})
|
|
56
|
-
.merge(
|
|
57
|
-
.openapi("ProblemDetails");
|
|
88
|
+
.merge(RFC7807ErrorDataSchema);
|
package/src/types/rfc7807.ts
CHANGED
|
@@ -9,82 +9,75 @@
|
|
|
9
9
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc7807} RFC 7807 Problem Details
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { z } from "@hono/zod-openapi";
|
|
13
|
+
import {
|
|
14
|
+
RFC7807ValidationParamSchema,
|
|
15
|
+
RFC7807ConstraintViolationSchema,
|
|
16
|
+
RFC7807ErrorDataSchema,
|
|
17
|
+
RFC7807DetailsSchema,
|
|
18
|
+
} from "../formatters/rfc7807/schemas";
|
|
19
|
+
|
|
12
20
|
/**
|
|
13
21
|
* Represents a validation error parameter in an RFC 7807 problem details object.
|
|
14
22
|
* Used to indicate specific validation failures in request parameters.
|
|
15
23
|
*
|
|
16
|
-
* @
|
|
24
|
+
* @type RFC7807ValidationParam
|
|
17
25
|
* @property {string} name - The name of the parameter that failed validation
|
|
18
26
|
* @property {string} reason - The reason why the parameter failed validation
|
|
19
27
|
*/
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
28
|
+
export type RFC7807ValidationParam = z.infer<
|
|
29
|
+
typeof RFC7807ValidationParamSchema
|
|
30
|
+
>;
|
|
24
31
|
|
|
25
32
|
/**
|
|
26
33
|
* Represents a constraint violation in an RFC 7807 problem details object.
|
|
27
34
|
* Used to indicate violations of business rules or data constraints.
|
|
28
35
|
*
|
|
29
|
-
* @
|
|
36
|
+
* @type RFC7807ConstraintViolation
|
|
30
37
|
* @property {string} name - The name of the violated constraint
|
|
31
38
|
* @property {string} reason - The reason why the constraint was violated
|
|
32
39
|
* @property {string} resource - The resource or entity where the violation occurred
|
|
33
40
|
* @property {string} constraint - The specific constraint that was violated
|
|
34
41
|
*/
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
resource: string;
|
|
39
|
-
constraint: string;
|
|
40
|
-
}
|
|
42
|
+
export type RFC7807ConstraintViolation = z.infer<
|
|
43
|
+
typeof RFC7807ConstraintViolationSchema
|
|
44
|
+
>;
|
|
41
45
|
|
|
42
46
|
/**
|
|
43
47
|
* Additional error data that can be included in an RFC 7807 problem details object.
|
|
44
48
|
* This interface extends the standard problem details with validation and constraint information.
|
|
45
49
|
*
|
|
46
|
-
* @
|
|
47
|
-
* @property {
|
|
48
|
-
* @property {
|
|
50
|
+
* @type RFC7807ErrorData
|
|
51
|
+
* @property {RFC7807ValidationParam[]} [invalid-params] - Array of validation errors
|
|
52
|
+
* @property {RFC7807ConstraintViolation[]} [violations] - Array of constraint violations
|
|
49
53
|
*/
|
|
50
|
-
export
|
|
51
|
-
"invalid-params"?: ValidationParam[];
|
|
52
|
-
violations?: ConstraintViolation[];
|
|
53
|
-
}
|
|
54
|
+
export type RFC7807ErrorData = z.infer<typeof RFC7807ErrorDataSchema>;
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
57
|
* Complete RFC 7807 problem details object structure.
|
|
57
58
|
* This interface represents the full problem details format as defined in RFC 7807,
|
|
58
59
|
* with additional properties for validation and constraint violation data.
|
|
59
60
|
*
|
|
60
|
-
* @
|
|
61
|
-
* @extends ProblemErrorData
|
|
61
|
+
* @type RFC7807Details
|
|
62
62
|
* @property {string} type - URI reference that identifies the problem type
|
|
63
63
|
* @property {string} title - Short, human-readable summary of the problem
|
|
64
64
|
* @property {number} status - HTTP status code (400-599)
|
|
65
|
-
* @property {string} detail - Human-readable explanation specific to this occurrence
|
|
66
|
-
* @property {string} instance - URI reference that identifies the specific occurrence
|
|
67
|
-
* @property {string} timestamp - ISO 8601 datetime when the error occurred
|
|
65
|
+
* @property {string} [detail] - Human-readable explanation specific to this occurrence
|
|
66
|
+
* @property {string} [instance] - URI reference that identifies the specific occurrence
|
|
67
|
+
* @property {string} [timestamp] - ISO 8601 datetime when the error occurred
|
|
68
68
|
*/
|
|
69
|
-
export
|
|
70
|
-
type: string;
|
|
71
|
-
title: string;
|
|
72
|
-
status: number;
|
|
73
|
-
detail: string;
|
|
74
|
-
instance: string;
|
|
75
|
-
timestamp: string;
|
|
76
|
-
}
|
|
69
|
+
export type RFC7807Details = z.infer<typeof RFC7807DetailsSchema>;
|
|
77
70
|
|
|
78
71
|
/**
|
|
79
72
|
* Hook function for customizing RFC 7807 problem details before formatting.
|
|
80
73
|
* This type represents a function that can modify the problem details object
|
|
81
74
|
* before it is serialized into the final response.
|
|
82
75
|
*
|
|
83
|
-
* @callback
|
|
84
|
-
* @param {
|
|
85
|
-
* @returns {
|
|
76
|
+
* @callback RFC7807DetailsHook
|
|
77
|
+
* @param {RFC7807Details} details - The problem details object to modify
|
|
78
|
+
* @returns {RFC7807Details} The modified problem details object
|
|
86
79
|
*/
|
|
87
|
-
export type
|
|
80
|
+
export type RFC7807DetailsHook = (details: RFC7807Details) => RFC7807Details;
|
|
88
81
|
|
|
89
82
|
/**
|
|
90
83
|
* Configuration options for the RFC 7807 formatter.
|
|
@@ -92,9 +85,9 @@ export type ProblemDetailsHook = (details: ProblemDetails) => ProblemDetails;
|
|
|
92
85
|
*
|
|
93
86
|
* @interface RFC7807FormatterOptions
|
|
94
87
|
* @property {string} [baseUrl="https://api.example.com/problems"] - Base URL for problem type URIs
|
|
95
|
-
* @property {
|
|
88
|
+
* @property {RFC7807DetailsHook[]} [hooks=[]] - Array of hooks for customizing problem details
|
|
96
89
|
*/
|
|
97
90
|
export interface RFC7807FormatterOptions {
|
|
98
91
|
baseUrl?: string;
|
|
99
|
-
hooks?:
|
|
92
|
+
hooks?: RFC7807DetailsHook[];
|
|
100
93
|
}
|