107 lines
2.6 KiB
TypeScript
107 lines
2.6 KiB
TypeScript
|
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono';
|
||
|
import { validator } from 'hono/validator';
|
||
|
import { Ajv, type JSONSchemaType, type ErrorObject } from 'ajv';
|
||
|
|
||
|
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);
|
||
|
});
|
||
|
}
|