diff --git a/.changeset/olive-hounds-shout.md b/.changeset/olive-hounds-shout.md new file mode 100644 index 00000000..4b6696d6 --- /dev/null +++ b/.changeset/olive-hounds-shout.md @@ -0,0 +1,5 @@ +--- +'@hono/zod-validator': minor +--- + +feat(zod-validator): pass target to a hook diff --git a/packages/zod-validator/src/index.ts b/packages/zod-validator/src/index.ts index 40777017..65c5d4f7 100644 --- a/packages/zod-validator/src/index.ts +++ b/packages/zod-validator/src/index.ts @@ -2,9 +2,9 @@ import type { Context, MiddlewareHandler, Env, ValidationTargets, TypedResponse, import { validator } from 'hono/validator' import type { z, ZodSchema, ZodError } from 'zod' -export type Hook = ( - result: { success: true; data: T } | { success: false; error: ZodError; data: T }, - c: Context +export type Hook = ( + result: ({ success: true; data: T} | { success: false; error: ZodError; data: T }) & {target: Target }, + c: Context, ) => Response | void | TypedResponse | Promise> type HasUndefined = undefined extends T ? true : false @@ -38,14 +38,14 @@ export const zValidator = < >( target: Target, schema: T, - hook?: Hook, E, P> + hook?: Hook, E, P, Target> ): MiddlewareHandler => // @ts-expect-error not typed well validator(target, async (value, c) => { const result = await schema.safeParseAsync(value) if (hook) { - const hookResult = await hook({ data: value, ...result }, c) + const hookResult = await hook({ data: value, ...result, target, }, c) if (hookResult) { if (hookResult instanceof Response) { return hookResult diff --git a/packages/zod-validator/test/index.test.ts b/packages/zod-validator/test/index.test.ts index 25d57e04..e68892e7 100644 --- a/packages/zod-validator/test/index.test.ts +++ b/packages/zod-validator/test/index.test.ts @@ -2,6 +2,8 @@ import { Hono } from 'hono' import type { Equal, Expect } from 'hono/utils/types' import { z } from 'zod' import { zValidator } from '../src' +import {vi} from 'vitest' + // eslint-disable-next-line @typescript-eslint/no-unused-vars type ExtractSchema = T extends Hono ? S : never @@ -256,3 +258,46 @@ describe('With Async Hook', () => { expect(await res.text()).toBe('123 is invalid!') }) }) + + +describe('With target', () => { + it('should call hook for correctly validated target', async () => { + const app = new Hono() + + const schema = z.object({ + id: z.string(), + }) + + const jsonHook = vi.fn() + const paramHook = vi.fn() + const queryHook = vi.fn() + app.post( + '/:id/post', + zValidator('json', schema, jsonHook), + zValidator('param', schema, paramHook), + zValidator('query', schema, queryHook), + (c) => { + return c.text('ok') + } + ) + + const req = new Request('http://localhost/1/post?id=2', { + body: JSON.stringify({ + id: '3', + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(200) + expect(await res.text()).toBe('ok') + expect(paramHook).toHaveBeenCalledWith({data: {id: '1'}, success: true, target: + 'param'}, expect.anything()) + expect(queryHook).toHaveBeenCalledWith({data: {id: '2'}, success: true, target: 'query'}, expect.anything()) + expect(jsonHook).toHaveBeenCalledWith({data: {id: '3'}, success: true, target: 'json'}, expect.anything()) + }) +}) \ No newline at end of file