diff --git a/src/generator/index.ts b/src/generator/index.ts index 3f54871c..a21a930f 100644 --- a/src/generator/index.ts +++ b/src/generator/index.ts @@ -1,6 +1,7 @@ import { OpenAPIV3 } from 'openapi-types'; import { OpenApiRouter } from '../types'; +import { zodComponentSchemaGenerator } from '../utils/components'; import { getOpenApiPathsObject } from './paths'; import { errorResponseObject } from './schema'; @@ -41,6 +42,7 @@ export const generateOpenApiDocument = ( paths: getOpenApiPathsObject(appRouter, Object.keys(securitySchemes)), components: { securitySchemes, + schemas: zodComponentSchemaGenerator?.(), responses: { error: errorResponseObject, }, diff --git a/src/generator/schema.ts b/src/generator/schema.ts index d3f41e38..018ba29b 100644 --- a/src/generator/schema.ts +++ b/src/generator/schema.ts @@ -4,6 +4,7 @@ import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import { OpenApiContentType } from '../types'; +import { zodComponentDefinitions } from '../utils/components'; import { instanceofZodType, instanceofZodTypeCoercible, @@ -15,9 +16,22 @@ import { zodSupportsCoerce, } from '../utils/zod'; -const zodSchemaToOpenApiSchemaObject = (zodSchema: z.ZodType): OpenAPIV3.SchemaObject => { +export const zodSchemaToOpenApiSchemaObject = ( + zodSchema: z.ZodType, + suppressObjectReferences = false, +): OpenAPIV3.SchemaObject => { // FIXME: https://github.com/StefanTerdell/zod-to-json-schema/issues/35 - return zodToJsonSchema(zodSchema, { target: 'openApi3', $refStrategy: 'none' }) as any; + const result = zodToJsonSchema(zodSchema, { + target: 'openApi3', + definitions: + zodComponentDefinitions && !suppressObjectReferences ? zodComponentDefinitions : {}, + definitionPath: 'components/schemas', + }) as OpenAPIV3.SchemaObject & { + 'components/schemas': unknown; + }; + + delete result['components/schemas']; + return result; }; export const getParameterObjects = ( @@ -156,7 +170,7 @@ export const getRequestBodyObject = ( return undefined; } - const openApiSchemaObject = zodSchemaToOpenApiSchemaObject(dedupedSchema); + const openApiSchemaObject = zodSchemaToOpenApiSchemaObject(unwrappedSchema); const content: OpenAPIV3.RequestBodyObject['content'] = {}; for (const contentType of contentTypes) { content[contentType] = { @@ -189,7 +203,7 @@ export const errorResponseObject: OpenAPIV3.ResponseObject = { export const getResponsesObject = ( schema: unknown, example: Record | undefined, - headers: Record | undefined + headers: Record | undefined, ): OpenAPIV3.ResponsesObject => { if (!instanceofZodType(schema)) { throw new TRPCError({ diff --git a/src/index.ts b/src/index.ts index 4d89b3ff..8b3127f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,11 @@ import { OpenApiRouter, OpenApiSuccessResponse, } from './types'; +import { + experimentalZodSchemaGenerator, + setZodComponentDefinitions, + setZodComponentSchemaGenerator, +} from './utils/components'; import { ZodTypeLikeString, ZodTypeLikeVoid } from './utils/zod'; export { @@ -51,4 +56,7 @@ export { OpenApiErrorResponse, ZodTypeLikeString, ZodTypeLikeVoid, + setZodComponentDefinitions, + setZodComponentSchemaGenerator, + experimentalZodSchemaGenerator, }; diff --git a/src/utils/components.ts b/src/utils/components.ts new file mode 100644 index 00000000..c3ddbe0b --- /dev/null +++ b/src/utils/components.ts @@ -0,0 +1,28 @@ +import { OpenAPIV3 } from 'openapi-types'; +import { z } from 'zod'; + +import { zodSchemaToOpenApiSchemaObject } from '../generator/schema'; + +export let zodComponentSchemaGenerator: (() => { [key: string]: any }) | undefined; + +export let zodComponentDefinitions: Record | undefined; + +export const setZodComponentDefinitions = (definitions: Record) => { + zodComponentDefinitions = definitions; +}; + +export const setZodComponentSchemaGenerator = (generator: typeof zodComponentSchemaGenerator) => { + zodComponentSchemaGenerator = generator; +}; + +// Does not support references (breaks in weird ways if references are used) +export const experimentalZodSchemaGenerator = (): { [key: string]: OpenAPIV3.SchemaObject } => { + return zodComponentDefinitions + ? Object.fromEntries( + Object.entries(zodComponentDefinitions).map(([key, value]) => [ + key, + zodSchemaToOpenApiSchemaObject(value, true), + ]), + ) + : {}; +};