Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

feat: Add Model Schemas to OpenApi Docs #417

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/generator/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -41,6 +42,7 @@ export const generateOpenApiDocument = (
paths: getOpenApiPathsObject(appRouter, Object.keys(securitySchemes)),
components: {
securitySchemes,
schemas: zodComponentSchemaGenerator?.(),
responses: {
error: errorResponseObject,
},
Expand Down
22 changes: 18 additions & 4 deletions src/generator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = (
Expand Down Expand Up @@ -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] = {
Expand Down Expand Up @@ -189,7 +203,7 @@ export const errorResponseObject: OpenAPIV3.ResponseObject = {
export const getResponsesObject = (
schema: unknown,
example: Record<string, any> | undefined,
headers: Record<string, OpenAPIV3.HeaderObject | OpenAPIV3.ReferenceObject> | undefined
headers: Record<string, OpenAPIV3.HeaderObject | OpenAPIV3.ReferenceObject> | undefined,
): OpenAPIV3.ResponsesObject => {
if (!instanceofZodType(schema)) {
throw new TRPCError({
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import {
OpenApiRouter,
OpenApiSuccessResponse,
} from './types';
import {
experimentalZodSchemaGenerator,
setZodComponentDefinitions,
setZodComponentSchemaGenerator,
} from './utils/components';
import { ZodTypeLikeString, ZodTypeLikeVoid } from './utils/zod';

export {
Expand All @@ -51,4 +56,7 @@ export {
OpenApiErrorResponse,
ZodTypeLikeString,
ZodTypeLikeVoid,
setZodComponentDefinitions,
setZodComponentSchemaGenerator,
experimentalZodSchemaGenerator,
};
28 changes: 28 additions & 0 deletions src/utils/components.ts
Original file line number Diff line number Diff line change
@@ -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<string, z.ZodType> | undefined;

export const setZodComponentDefinitions = (definitions: Record<string, z.ZodType>) => {
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),
]),
)
: {};
};