// based on https://github.com/honojs/middleware/blob/main/packages/zod-validator/src/index.ts import { zValidator as zValidatorBase } from '@hono/zod-validator' import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono' import * as v3 from 'zod/v3' import type { ZodSafeParseResult as ZodSafeParseResult$1 } from 'zod/v4' import * as v4 from 'zod/v4/core' type Awaitable = T | Promise type HasUndefined = undefined extends T ? true : false type ZodSchema = v3.ZodType | v4.$ZodType type ZodError = T extends v4.$ZodType ? v4.$ZodError> : v3.ZodError type ZodSafeParseResult = T3 extends v4.$ZodType ? ZodSafeParseResult$1 : v3.SafeParseReturnType type zInput = T extends v3.ZodType ? v3.input : T extends v4.$ZodType ? v4.input : never type zOutput = T extends v3.ZodType ? v3.output : T extends v4.$ZodType ? v4.output : never type zInfer = T extends v3.ZodType ? v3.infer : T extends v4.$ZodType ? v4.infer : never type Hook = (result: ({ success: true data: T } | { success: false error: ZodError data: T }) & { target: Target }, c: Context) => Awaitable> type ValidationTargetsWithResponse = ValidationTargets & { response: any } /** * zValidator wraps `zValidator` from `@hono/zod-validator` and extends it to support response validation. It forwards * query parameter, path parameter, and request body validation to `@hono/zod-validator`. */ export const zValidator = < T extends ZodSchema, Target extends keyof ValidationTargetsWithResponse, E extends Env, P extends string, In = zInput, Out = zOutput, I extends Input = { in: HasUndefined extends true ? { [K in Target]?: In extends ValidationTargetsWithResponse[K] ? In : { [K2 in keyof In]?: In[K2] extends ValidationTargetsWithResponse[K][K2] ? In[K2] : ValidationTargetsWithResponse[K][K2] } } : { [K in Target]: In extends ValidationTargetsWithResponse[K] ? In : { [K2 in keyof In]: In[K2] extends ValidationTargetsWithResponse[K][K2] ? In[K2] : ValidationTargetsWithResponse[K][K2] } } out: { [K in Target]: Out } }, V extends I = I, InferredValue = zInfer > ( target: Target, schema: T, hook?: Hook ): MiddlewareHandler => async (c, next) => { if (target !== 'response'){ const baseTarget = target as keyof ValidationTargets; const baseHook = hook ? ( result: Parameters< Hook, Env, string, keyof ValidationTargets, {}, T> >[0], ctx: Context, ) => hook( { ...result, target } as Parameters[0], ctx as unknown as Context, ) : undefined; const validator = baseHook ? zValidatorBase(baseTarget, schema, baseHook) : zValidatorBase(baseTarget, schema); return validator( c as unknown as Context, next, ) as ReturnType>; } await next() if ( c.res.status !== 200 || !c.res.headers.get('Content-Type')?.includes('application/json') ) { return } let value: unknown try { value = await c.res.json() } catch { const message = 'Malformed JSON in response' c.res = new Response(message, { status: 400 }) return } const { success, data, error } = await (schema as v3.ZodType).safeParseAsync(value) as ZodSafeParseResult if (hook) { const hookResult = await hook({ target, success, data: data as InferredValue, error: error as ZodError }, c) if (typeof hookResult === 'object' && hookResult != null) { if (hookResult instanceof Response) { c.res = new Response(hookResult.body, hookResult) return } else if ('response' in hookResult && hookResult.response instanceof Response) { c.res = new Response(hookResult.response.body, hookResult.response) return } } } if (!success) { c.res = new Response(JSON.stringify({ success, data, error }), { status: 400, headers: { 'Content-Type': 'application/json' } }) } else { c.res = new Response(JSON.stringify(data), c.res) } }