fix(zod-validator): support for case-insensitive `headers` target validation (#860)

pull/885/head
Anthony Skorupskyy 2024-12-13 01:57:49 -06:00 committed by GitHub
parent 27ff98f26e
commit 803f011e41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 4 deletions

View File

@ -0,0 +1,5 @@
---
'@hono/zod-validator': patch
---
Case-insensitive Zod schemas for headers

View File

@ -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 { validator } from 'hono/validator'
import type { z, ZodSchema, ZodError } from 'zod' import { ZodObject, type ZodError, type ZodSchema, type z } from 'zod'
export type Hook< export type Hook<
T, T,
@ -46,10 +46,26 @@ export const zValidator = <
): MiddlewareHandler<E, P, V> => ): MiddlewareHandler<E, P, V> =>
// @ts-expect-error not typed well // @ts-expect-error not typed well
validator(target, async (value, c) => { 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) { if (hook) {
const hookResult = await hook({ data: value, ...result, target }, c) const hookResult = await hook({ data: validatorValue, ...result, target }, c)
if (hookResult) { if (hookResult) {
if (hookResult instanceof Response) { if (hookResult instanceof Response) {
return hookResult return hookResult

View File

@ -339,3 +339,33 @@ describe('Only Types', () => {
type verify = Expect<Equal<Expected, Actual>> type verify = Expect<Equal<Expected, Actual>>
}) })
}) })
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<typeof route>
type Expected = {
'/': {
$get: {
input: {
header: z.infer<typeof headerSchema>
}
output: z.infer<typeof headerSchema>
}
}
}
type verify = Expect<Equal<Expected, Actual>>
})
})