1+ import type { SwaggerTransform , SwaggerTransformObject } from '@fastify/swagger'
12import type {
23 FastifyPluginAsync ,
34 FastifyPluginCallback ,
@@ -9,14 +10,11 @@ import type {
910 RawServerDefault ,
1011} from 'fastify'
1112import type { FastifySerializerCompiler } from 'fastify/types/schema'
12- import type { OpenAPIV2 , OpenAPIV3 , OpenAPIV3_1 } from 'openapi-types'
13- import type { z } from 'zod'
13+ import { z } from 'zod/v4'
1414
1515import { InvalidSchemaError , ResponseSerializationError , createValidationError } from './errors'
16- import { resolveRefs } from './ref'
17- import { convertZodToJsonSchema } from './zod-to-json'
16+ import { zodRegistryToJson , zodSchemaToJson } from './zod-to-json'
1817
19- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2018type FreeformRecord = Record < string , any >
2119
2220const defaultSkipList = [
@@ -38,8 +36,16 @@ interface Schema extends FastifySchema {
3836 hide ?: boolean
3937}
4038
41- export const createJsonSchemaTransform = ( { skipList } : { skipList : readonly string [ ] } ) => {
42- return ( { schema, url } : { schema : Schema ; url : string } ) => {
39+ type CreateJsonSchemaTransformOptions = {
40+ skipList ?: readonly string [ ]
41+ schemaRegistry ?: z . core . $ZodRegistry < { id ?: string | undefined } >
42+ }
43+
44+ export const createJsonSchemaTransform = ( {
45+ skipList = defaultSkipList ,
46+ schemaRegistry = z . globalRegistry ,
47+ } : CreateJsonSchemaTransformOptions ) : SwaggerTransform < Schema > => {
48+ return ( { schema, url } ) => {
4349 if ( ! schema ) {
4450 return {
4551 schema,
@@ -61,20 +67,17 @@ export const createJsonSchemaTransform = ({ skipList }: { skipList: readonly str
6167 for ( const prop in zodSchemas ) {
6268 const zodSchema = zodSchemas [ prop ]
6369 if ( zodSchema ) {
64- transformed [ prop ] = convertZodToJsonSchema ( zodSchema )
70+ transformed [ prop ] = zodSchemaToJson ( zodSchema , schemaRegistry , 'input' )
6571 }
6672 }
6773
6874 if ( response ) {
6975 transformed . response = { }
7076
71- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7277 for ( const prop in response as any ) {
73- // eslint-disable-next-line @typescript-eslint/no-explicit-any
74- const schema = resolveSchema ( ( response as any ) [ prop ] )
78+ const zodSchema = resolveSchema ( ( response as any ) [ prop ] )
7579
76- const transformedResponse = convertZodToJsonSchema ( schema )
77- transformed . response [ prop ] = transformedResponse
80+ transformed . response [ prop ] = zodSchemaToJson ( zodSchema , schemaRegistry , 'output' )
7881 }
7982 }
8083
@@ -89,25 +92,48 @@ export const createJsonSchemaTransform = ({ skipList }: { skipList: readonly str
8992 }
9093}
9194
92- export const jsonSchemaTransform = createJsonSchemaTransform ( {
93- skipList : defaultSkipList ,
94- } )
95+ export const jsonSchemaTransform = createJsonSchemaTransform ( { } )
96+
97+ type CreateJsonSchemaTransformObjectOptions = {
98+ schemaRegistry ?: z . core . $ZodRegistry < { id ?: string | undefined } >
99+ }
95100
96101export const createJsonSchemaTransformObject =
97- ( { schemas } : { schemas : Record < string , z . ZodTypeAny > } ) =>
98- (
99- input :
100- | { swaggerObject : Partial < OpenAPIV2 . Document > }
101- | { openapiObject : Partial < OpenAPIV3 . Document | OpenAPIV3_1 . Document > } ,
102- ) => {
102+ ( {
103+ schemaRegistry = z . globalRegistry ,
104+ } : CreateJsonSchemaTransformObjectOptions ) : SwaggerTransformObject =>
105+ ( input ) => {
103106 if ( 'swaggerObject' in input ) {
104107 console . warn ( 'This package currently does not support component references for Swagger 2.0' )
105108 return input . swaggerObject
106109 }
107110
108- return resolveRefs ( input . openapiObject , schemas )
111+ const inputSchemas = zodRegistryToJson ( schemaRegistry , 'input' )
112+ const outputSchemas = zodRegistryToJson ( schemaRegistry , 'output' )
113+
114+ for ( const key in outputSchemas ) {
115+ if ( inputSchemas [ key ] ) {
116+ throw new Error (
117+ `Collision detected for schema "${ key } ". The is already an input schema with the same name.` ,
118+ )
119+ }
120+ }
121+
122+ return {
123+ ...input . openapiObject ,
124+ components : {
125+ ...input . openapiObject . components ,
126+ schemas : {
127+ ...input . openapiObject . components ?. schemas ,
128+ ...inputSchemas ,
129+ ...outputSchemas ,
130+ } ,
131+ } ,
132+ } as ReturnType < SwaggerTransformObject >
109133 }
110134
135+ export const jsonSchemaTransformObject = createJsonSchemaTransformObject ( { } )
136+
111137export const validatorCompiler : FastifySchemaCompiler < z . ZodTypeAny > =
112138 ( { schema } ) =>
113139 ( data ) => {
@@ -129,7 +155,6 @@ function resolveSchema(maybeSchema: z.ZodTypeAny | { properties: z.ZodTypeAny })
129155 throw new InvalidSchemaError ( JSON . stringify ( maybeSchema ) )
130156}
131157
132- // eslint-disable-next-line @typescript-eslint/no-explicit-any
133158type ReplacerFunction = ( this : any , key : string , value : any ) => any
134159
135160export type ZodSerializerCompilerOptions = {
0 commit comments