108 lines
2.6 KiB
TypeScript
108 lines
2.6 KiB
TypeScript
import { Ajv } from 'ajv'
|
|
import type { JSONSchemaType, ErrorObject } from 'ajv'
|
|
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono'
|
|
import { validator } from 'hono/validator'
|
|
|
|
type Hook<T, E extends Env, P extends string> = (
|
|
result: { success: true; data: T } | { success: false; errors: ErrorObject[] },
|
|
c: Context<E, P>
|
|
) => Response | Promise<Response> | void
|
|
|
|
/**
|
|
* Hono middleware that validates incoming data via an Ajv JSON schema.
|
|
*
|
|
* ---
|
|
*
|
|
* No Hook
|
|
*
|
|
* ```ts
|
|
* import { type JSONSchemaType } from 'ajv';
|
|
* import { ajvValidator } from '@hono/ajv-validator';
|
|
*
|
|
* const schema: JSONSchemaType<{ name: string; age: number }> = {
|
|
* type: 'object',
|
|
* properties: {
|
|
* name: { type: 'string' },
|
|
* age: { type: 'number' },
|
|
* },
|
|
* required: ['name', 'age'],
|
|
* additionalProperties: false,
|
|
* };
|
|
*
|
|
* const route = app.post('/user', ajvValidator('json', schema), (c) => {
|
|
* const user = c.req.valid('json');
|
|
* return c.json({ success: true, message: `${user.name} is ${user.age}` });
|
|
* });
|
|
* ```
|
|
*
|
|
* ---
|
|
* Hook
|
|
*
|
|
* ```ts
|
|
* import { type JSONSchemaType } from 'ajv';
|
|
* import { ajvValidator } from '@hono/ajv-validator';
|
|
*
|
|
* const schema: JSONSchemaType<{ name: string; age: number }> = {
|
|
* type: 'object',
|
|
* properties: {
|
|
* name: { type: 'string' },
|
|
* age: { type: 'number' },
|
|
* },
|
|
* required: ['name', 'age'],
|
|
* additionalProperties: false,
|
|
* };
|
|
*
|
|
* app.post(
|
|
* '/user',
|
|
* ajvValidator('json', schema, (result, c) => {
|
|
* if (!result.success) {
|
|
* return c.text('Invalid!', 400);
|
|
* }
|
|
* })
|
|
* //...
|
|
* );
|
|
* ```
|
|
*/
|
|
export function ajvValidator<
|
|
T,
|
|
Target extends keyof ValidationTargets,
|
|
E extends Env = Env,
|
|
P extends string = string
|
|
>(
|
|
target: Target,
|
|
schema: JSONSchemaType<T>,
|
|
hook?: Hook<T, E, P>
|
|
): MiddlewareHandler<
|
|
E,
|
|
P,
|
|
{
|
|
in: { [K in Target]: T }
|
|
out: { [K in Target]: T }
|
|
}
|
|
> {
|
|
const ajv = new Ajv()
|
|
const validate = ajv.compile(schema)
|
|
|
|
return validator(target, (data, c) => {
|
|
const valid = validate(data)
|
|
if (valid) {
|
|
if (hook) {
|
|
const hookResult = hook({ success: true, data: data as T }, c)
|
|
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
|
return hookResult
|
|
}
|
|
}
|
|
return data as T
|
|
}
|
|
|
|
const errors = validate.errors || []
|
|
if (hook) {
|
|
const hookResult = hook({ success: false, errors }, c)
|
|
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
|
return hookResult
|
|
}
|
|
}
|
|
return c.json({ success: false, errors }, 400)
|
|
})
|
|
}
|