diff --git a/.changeset/chilly-tomatoes-suffer.md b/.changeset/chilly-tomatoes-suffer.md new file mode 100644 index 00000000..9f935041 --- /dev/null +++ b/.changeset/chilly-tomatoes-suffer.md @@ -0,0 +1,5 @@ +--- +"@hono/zod-validator": patch +--- + +fix(zod-validator): make validation input optional when schema is optional diff --git a/packages/zod-validator/src/index.ts b/packages/zod-validator/src/index.ts index 453094da..08fe64bf 100644 --- a/packages/zod-validator/src/index.ts +++ b/packages/zod-validator/src/index.ts @@ -7,17 +7,25 @@ export type Hook = ( c: Context ) => Response | Promise | void | Promise | TypedResponse +type HasUndefined = undefined extends T ? true : false + export const zValidator = < T extends ZodSchema, Target extends keyof ValidationTargets, E extends Env, P extends string, + I = z.input, + O = z.output, V extends { - in: { [K in Target]: z.input } - out: { [K in Target]: z.output } + in: HasUndefined extends true + ? { [K in Target]?: I } + : { [K in Target]: I }; + out: { [K in Target]: O }; } = { - in: { [K in Target]: z.input } - out: { [K in Target]: z.output } + in: HasUndefined extends true + ? { [K in Target]?: I } + : { [K in Target]: I }; + out: { [K in Target]: O }; } >( target: Target, diff --git a/packages/zod-validator/test/index.test.ts b/packages/zod-validator/test/index.test.ts index bf3c1615..2b56c810 100644 --- a/packages/zod-validator/test/index.test.ts +++ b/packages/zod-validator/test/index.test.ts @@ -9,16 +9,23 @@ type ExtractSchema = T extends Hono ? S : never describe('Basic', () => { const app = new Hono() - const schema = z.object({ + const jsonSchema = z.object({ name: z.string(), age: z.number(), }) - const route = app.post('/author', zValidator('json', schema), (c) => { + const querySchema = z.object({ + name: z.string().optional() + }).optional() + + const route = app.post('/author', zValidator('json', jsonSchema), zValidator('query', querySchema), (c) => { const data = c.req.valid('json') + const query = c.req.valid('query') + return c.jsonT({ success: true, message: `${data.name} is ${data.age}`, + queryName: query?.name, }) }) @@ -31,10 +38,15 @@ describe('Basic', () => { name: string age: number } + } & { + query?: { + name?: string | undefined + } | undefined } output: { success: boolean message: string + queryName: string | undefined } } } @@ -44,7 +56,7 @@ describe('Basic', () => { type verify = Expect> it('Should return 200 response', async () => { - const req = new Request('http://localhost/author', { + const req = new Request('http://localhost/author?name=Metallo', { body: JSON.stringify({ name: 'Superman', age: 20, @@ -57,6 +69,7 @@ describe('Basic', () => { expect(await res.json()).toEqual({ success: true, message: 'Superman is 20', + queryName: 'Metallo' }) })