From 803f011e41ecb3da503ddb2b3286c6d7606d9c4b Mon Sep 17 00:00:00 2001 From: Anthony Skorupskyy <50280805+askorupskyy@users.noreply.github.com> Date: Fri, 13 Dec 2024 01:57:49 -0600 Subject: [PATCH] fix(zod-validator): support for case-insensitive `headers` target validation (#860) --- .changeset/odd-clocks-sin.md | 5 ++++ packages/zod-validator/src/index.ts | 24 +++++++++++++++--- packages/zod-validator/test/index.test.ts | 30 +++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 .changeset/odd-clocks-sin.md diff --git a/.changeset/odd-clocks-sin.md b/.changeset/odd-clocks-sin.md new file mode 100644 index 00000000..bde1f8e4 --- /dev/null +++ b/.changeset/odd-clocks-sin.md @@ -0,0 +1,5 @@ +--- +'@hono/zod-validator': patch +--- + +Case-insensitive Zod schemas for headers diff --git a/packages/zod-validator/src/index.ts b/packages/zod-validator/src/index.ts index 193378ec..bdccd3a8 100644 --- a/packages/zod-validator/src/index.ts +++ b/packages/zod-validator/src/index.ts @@ -1,6 +1,6 @@ -import type { Context, MiddlewareHandler, Env, ValidationTargets, TypedResponse, Input } from 'hono' +import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono' import { validator } from 'hono/validator' -import type { z, ZodSchema, ZodError } from 'zod' +import { ZodObject, type ZodError, type ZodSchema, type z } from 'zod' export type Hook< T, @@ -46,10 +46,26 @@ export const zValidator = < ): MiddlewareHandler => // @ts-expect-error not typed well validator(target, async (value, c) => { - const result = await schema.safeParseAsync(value) + let validatorValue = value + + // in case where our `target` === `header`, Hono parses all of the headers into lowercase. + // this might not match the Zod schema, so we want to make sure that we account for that when parsing the schema. + if (target === 'header' && schema instanceof ZodObject) { + // create an object that maps lowercase schema keys to lowercase + const schemaKeys = Object.keys(schema.shape) + const caseInsensitiveKeymap = Object.fromEntries( + schemaKeys.map((key) => [key.toLowerCase(), key]) + ) + + validatorValue = Object.fromEntries( + Object.entries(value).map(([key, value]) => [caseInsensitiveKeymap[key] || key, value]) + ) + } + + const result = await schema.safeParseAsync(validatorValue) if (hook) { - const hookResult = await hook({ data: value, ...result, target }, c) + const hookResult = await hook({ data: validatorValue, ...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 40add995..b0e86379 100644 --- a/packages/zod-validator/test/index.test.ts +++ b/packages/zod-validator/test/index.test.ts @@ -339,3 +339,33 @@ describe('Only Types', () => { type verify = Expect> }) }) + +describe('Case-Insensitive Headers', () => { + it('Should ignore the case for headers in the Zod schema and return 200', () => { + const app = new Hono() + const headerSchema = z.object({ + 'Content-Type': z.string(), + ApiKey: z.string(), + onlylowercase: z.string(), + ONLYUPPERCASE: z.string(), + }) + + const route = app.get('/', zValidator('header', headerSchema), (c) => { + const headers = c.req.valid('header') + return c.json(headers) + }) + + type Actual = ExtractSchema + type Expected = { + '/': { + $get: { + input: { + header: z.infer + } + output: z.infer + } + } + } + type verify = Expect> + }) +})