-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ajv-validator): Add Ajv validator middleware (#794)
- Loading branch information
Showing
10 changed files
with
500 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@hono/ajv-validator': patch | ||
--- | ||
|
||
Add Ajv validator middleware |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: ci-ajv-validator | ||
on: | ||
push: | ||
branches: [main] | ||
paths: | ||
- 'packages/ajv-validator/**' | ||
pull_request: | ||
branches: ['*'] | ||
paths: | ||
- 'packages/ajv-validator/**' | ||
|
||
jobs: | ||
ci: | ||
runs-on: ubuntu-latest | ||
defaults: | ||
run: | ||
working-directory: ./packages/ajv-validator | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-node@v4 | ||
with: | ||
node-version: 20.x | ||
- run: yarn install --frozen-lockfile | ||
- run: yarn build | ||
- run: yarn test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Ajv validator middleware for Hono | ||
|
||
Validator middleware using [Ajv](https://github.com/ajv-validator/ajv) for [Hono](https://honojs.dev) applications. | ||
Define your schema with Ajv and validate incoming requests. | ||
|
||
## Usage | ||
|
||
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, | ||
} as const; | ||
|
||
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); | ||
} | ||
}) | ||
//... | ||
); | ||
``` | ||
|
||
## Author | ||
|
||
Illia Khvost <https://github.com/ikhvost> | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
{ | ||
"name": "@hono/ajv-validator", | ||
"version": "0.0.0", | ||
"description": "Validator middleware using Ajv", | ||
"type": "module", | ||
"module": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"test": "vitest --run", | ||
"build": "tsup ./src/index.ts --format esm,cjs --dts", | ||
"publint": "publint", | ||
"release": "yarn build && yarn test && yarn publint && yarn publish" | ||
}, | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
} | ||
}, | ||
"license": "MIT", | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.org", | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/honojs/middleware.git" | ||
}, | ||
"homepage": "https://github.com/honojs/middleware", | ||
"peerDependencies": { | ||
"ajv": ">=8.12.0", | ||
"hono": ">=3.9.0" | ||
}, | ||
"devDependencies": { | ||
"ajv": ">=8.12.0", | ||
"hono": "^4.4.12", | ||
"tsup": "^8.1.0", | ||
"vitest": "^1.6.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
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); | ||
}); | ||
} |
Oops, something went wrong.