You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Design proposal / discussion. Filing to track the option; not a commitment to implement. The hand-rolled validation path stays the default unless we decide otherwise.
Motivation
The generated Fastify router uses none of Fastify 5's native validation/serialization pipeline. It hand-rolls Zod safeParse per param/body and does reply.type().send(). That hand-rolled path is the root cause of several currently-shipping bugs (#313 header casing, #314 query coercion) and the Body: unknown type erasure on request handlers.
fastify-type-provider-zod is the idiomatic way to support modern Fastify when you already have Zod schemas. It wires:
validatorCompiler (request validation from Zod),
serializerCompiler (response serialization/validation from Zod),
withTypeProvider<ZodTypeProvider>() for full request/response type inference.
Crucially, the generated router already imports the generated Zod schemas (e.g. CreatePetRequestSchema), so the schemas needed to drive this mode are already on hand.
Proposed approach (opt-in mode)
Behind a config flag, emit a Fastify router that:
Registers setValidatorCompiler / setSerializerCompiler from fastify-type-provider-zod once.
Uses app.withTypeProvider<ZodTypeProvider>().
Attaches a route schema: { params, querystring, headers, body, response } per operation, built from the generated Zod schemas, instead of inline safeParse blocks.
Keep the current hand-rolled path as the default so the core router stays dependency-free; this is an additional mode, not a forced migration.
Caveats / open questions (must be resolved before this is actionable)
Error-contract change. Current major fastify-type-provider-zod throws a FastifyError / ResponseSerializationError on validation failure rather than returning the existing 422 { error, issues } envelope. Adopting this mode silently changes the error contract unless we also generate a setErrorHandler that reshapes those errors back into the documented envelope. This also interacts with [openapi-server] README correction: the generated router DOES wrap service calls in try/catch; document the error-propagation contract #311 (try/catch contract) and the user-owned setErrorHandler documented in the README.
deepObject / delimited query styles.spaceDelimited / pipeDelimited / deepObject query handling (router.ts:~1102-1160) cannot be reproduced by a plain schema.querystring + ajv. These need a pre-parse step or bespoke handling that survives the move to native validation.
Dependency / version pinning.openapi-server declares no fastify peer today. This mode adds fastify-type-provider-zod (peer zod >=4.1.5; the repo catalog is on zod ^4.4.3, so Zod 4 is fine). Pin exact compatible majors at build time.
Labels:
enhancement,openapi-server,fastify,designMotivation
The generated Fastify router uses none of Fastify 5's native validation/serialization pipeline. It hand-rolls Zod
safeParseper param/body and doesreply.type().send(). That hand-rolled path is the root cause of several currently-shipping bugs (#313 header casing, #314 query coercion) and theBody: unknowntype erasure on request handlers.fastify-type-provider-zodis the idiomatic way to support modern Fastify when you already have Zod schemas. It wires:validatorCompiler(request validation from Zod),serializerCompiler(response serialization/validation from Zod),withTypeProvider<ZodTypeProvider>()for full request/response type inference.Crucially, the generated router already imports the generated Zod schemas (e.g.
CreatePetRequestSchema), so the schemas needed to drive this mode are already on hand.Proposed approach (opt-in mode)
Behind a config flag, emit a Fastify router that:
setValidatorCompiler/setSerializerCompilerfromfastify-type-provider-zodonce.app.withTypeProvider<ZodTypeProvider>().schema: { params, querystring, headers, body, response }per operation, built from the generated Zod schemas, instead of inlinesafeParseblocks.Keep the current hand-rolled path as the default so the core router stays dependency-free; this is an additional mode, not a forced migration.
What this would fix (structurally)
coerceTypescoerces query strings to numbers/booleans, so the spurious422class disappears at the source.request.headers(lowercased) and the manualreq.headers['X-Lab-Token']lookup goes away entirely.Body: unknowntype erasure (router.ts:~1077-1079): handlers get typedreq.body/req.query/req.params.safeParseboilerplate and unifies request + response validation from one schema source (overlaps the response side of [openapi-server] Fastify: wire schema.response and optional serializerCompiler so response drift is caught at runtime #308).Caveats / open questions (must be resolved before this is actionable)
fastify-type-provider-zodthrows aFastifyError/ResponseSerializationErroron validation failure rather than returning the existing422 { error, issues }envelope. Adopting this mode silently changes the error contract unless we also generate asetErrorHandlerthat reshapes those errors back into the documented envelope. This also interacts with [openapi-server] README correction: the generated router DOES wrap service calls in try/catch; document the error-propagation contract #311 (try/catch contract) and the user-ownedsetErrorHandlerdocumented in the README.deepObject/ delimited query styles.spaceDelimited/pipeDelimited/deepObjectquery handling (router.ts:~1102-1160) cannot be reproduced by a plainschema.querystring+ ajv. These need a pre-parse step or bespoke handling that survives the move to native validation.HttpErrormapping ([openapi-server] Fastify: async HttpError throws return 500 instead of the mapped status because the 200/201 JSON branches don't await the service call #315). The per-routetry/catchthat mapsHttpErrorto its status would likely move into a centralsetErrorHandler; the mapping itself still has to be emitted (andHttpErrorexposesstatus, notstatusCode).openapi-serverdeclares nofastifypeer today. This mode addsfastify-type-provider-zod(peerzod >=4.1.5; the repo catalog is onzod ^4.4.3, so Zod 4 is fine). Pin exact compatible majors at build time.Relationship to existing issues
serializerCompiler). This proposal is broader: it also covers the request side and the type-provider wiring.File refs
packages/openapi-server/src/plugins/router.ts:1087-1089(bare route registration, noschema/config),~1048-1086(manual generics,Body: unknown),~1092-1201(inlinesafeParse),~1102-1160(deepObject/delimited query handling),~1372-1375(Zod schema imports already wired)