From ad7ca96e498476a5609d4ee91ca548ecd82192ad Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Mon, 17 Apr 2023 20:36:34 +1000 Subject: [PATCH 1/7] Add support for ZodEffects --- README.md | 11 +- src/create/components.test.ts | 3 + src/create/components.ts | 19 ++- src/create/content.test.ts | 3 + src/create/content.ts | 13 +- src/create/parameters.ts | 5 +- src/create/paths.ts | 2 +- src/create/responses.ts | 7 +- src/create/schema/array.test.ts | 8 +- src/create/schema/array.ts | 8 +- src/create/schema/catch.test.ts | 4 +- src/create/schema/catch.ts | 8 +- src/create/schema/default.test.ts | 6 +- src/create/schema/default.ts | 8 +- src/create/schema/discriminatedUnion.test.ts | 17 +-- src/create/schema/discriminatedUnion.ts | 10 +- src/create/schema/effects.test.ts | 35 ------ src/create/schema/effects.ts | 12 -- src/create/schema/index.test.ts | 124 ++++++++++++++----- src/create/schema/index.ts | 109 +++++++++++----- src/create/schema/intersection.test.ts | 4 +- src/create/schema/intersection.ts | 10 +- src/create/schema/metadata.test.ts | 18 +-- src/create/schema/metadata.ts | 10 +- src/create/schema/nativeEnum.test.ts | 13 +- src/create/schema/nativeEnum.ts | 7 +- src/create/schema/nullable.test.ts | 30 ++--- src/create/schema/nullable.ts | 15 ++- src/create/schema/number.test.ts | 20 ++- src/create/schema/number.ts | 9 +- src/create/schema/object.test.ts | 8 +- src/create/schema/object.ts | 22 ++-- src/create/schema/optional.test.ts | 4 +- src/create/schema/optional.ts | 8 +- src/create/schema/pipeline.test.ts | 65 ++++++++++ src/create/schema/pipeline.ts | 14 +++ src/create/schema/preprocess.test.ts | 57 +++++++++ src/create/schema/preprocess.ts | 17 +++ src/create/schema/record.test.ts | 4 +- src/create/schema/record.ts | 8 +- src/create/schema/refine.test.ts | 22 ++++ src/create/schema/refine.ts | 10 ++ src/create/schema/transform.test.ts | 48 +++++++ src/create/schema/transform.ts | 17 +++ src/create/schema/tuple.test.ts | 10 +- src/create/schema/tuple.ts | 23 ++-- src/create/schema/union.test.ts | 4 +- src/create/schema/union.ts | 10 +- src/create/schema/unknown.test.ts | 21 ++++ src/create/schema/unknown.ts | 25 ++++ src/extendZod.ts | 15 +++ src/test/state.ts | 17 +++ 52 files changed, 674 insertions(+), 303 deletions(-) delete mode 100644 src/create/schema/effects.test.ts delete mode 100644 src/create/schema/effects.ts create mode 100644 src/create/schema/pipeline.test.ts create mode 100644 src/create/schema/pipeline.ts create mode 100644 src/create/schema/preprocess.test.ts create mode 100644 src/create/schema/preprocess.ts create mode 100644 src/create/schema/refine.test.ts create mode 100644 src/create/schema/refine.ts create mode 100644 src/create/schema/transform.test.ts create mode 100644 src/create/schema/transform.ts create mode 100644 src/create/schema/unknown.test.ts create mode 100644 src/create/schema/unknown.ts create mode 100644 src/test/state.ts diff --git a/README.md b/README.md index 22a87ab..4ebe401 100644 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ Wherever `title` is used in schemas across the document, it will instead be crea This can be an extremely powerful way to generate better Open API documentation. There are some Open API features like [discriminator mapping](https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/) which require all schemas in the union to contain a ref. -To display components which are not referenced by simply add the Zod Schema to the schema components directly. +To display components which are not referenced in the responses or requests simply add the Zod Schema to the schema components directly. eg. @@ -306,12 +306,14 @@ eg. { "components": { "schemas": { - MyJobSchema // note: this will register this Zod Schema as MyJobSchema unless `ref` is specified on the type + MyJobSchema // note: this will register this Zod Schema as MyJobSchema unless `ref` in `openapi()` is specified on the type } } } ``` +Please note: if your schema contains a ZodEffect you may need to declare `refType` as `input` or `output` in `openapi()` to declare how to create the component. This defaults to `output` by default. + #### Parameters Query, Path, Header & Cookie parameters can be similarly registered: @@ -356,7 +358,9 @@ const header = z.string().openapi({ - ZodDiscriminatedUnion - `discriminator` mapping when all schemas in the union contain a `ref`. - ZodEffects - - `pre-process` and `refine` support + - `transform` support for request schemas. Wrap your transform in a ZodPipeline to enable response schema creation or declare a manual `type` in the `.openapi()` section of that schema. + - `pre-process` support for response schemas. Wrap your transform in a ZodPipeline to enable request schema creation or declare a manual `type` in the `.openapi()` section of that schema. + - `refine` support - ZodEnum - ZodLiteral - ZodNativeEnum @@ -368,6 +372,7 @@ const header = z.string().openapi({ - `exclusiveMin`/`min`/`exclusiveMax`/`max` mapping for `.min()`, `.max()`, `lt()`, `gt()` - ZodObject - ZodOptional +- ZodPipeline - ZodRecord - ZodString - `format` mapping for `.url()`, `.uuid()`, `.email()`, `.datetime()` diff --git a/src/create/components.test.ts b/src/create/components.test.ts index 491b42c..c5750de 100644 --- a/src/create/components.test.ts +++ b/src/create/components.test.ts @@ -67,6 +67,7 @@ describe('getDefaultComponents', () => { type: 'string', }, zodSchema: aSchema, + types: ['input', 'output'], }, b: { schemaObject: { @@ -149,6 +150,7 @@ describe('createComponents', () => { type: 'string', }, zodSchema: z.string().openapi({ ref: 'a' }), + types: ['output'], }, }, headers: { @@ -224,6 +226,7 @@ describe('createComponents', () => { type: 'string', }, zodSchema: z.string().openapi({ ref: 'a' }), + types: ['output'], }, }, headers: { diff --git a/src/create/components.ts b/src/create/components.ts index 5292eb3..cec3de4 100644 --- a/src/create/components.ts +++ b/src/create/components.ts @@ -2,22 +2,26 @@ import { oas30, oas31 } from 'openapi3-ts'; import { ZodType } from 'zod'; import { ZodOpenApiComponentsObject, ZodOpenApiVersion } from './document'; +import { SchemaState } from './schema'; import { createSchemaWithMetadata } from './schema/metadata'; -export interface Schema { +export type CreationType = 'input' | 'output'; + +export interface SchemaComponent { zodSchema?: ZodType; schemaObject: | oas31.SchemaObject | oas31.ReferenceObject | oas30.SchemaObject | oas30.ReferenceObject; + types?: [CreationType, ...CreationType[]]; } interface SchemaComponentObject { - [ref: string]: Schema | undefined; + [ref: string]: SchemaComponent | undefined; } -export interface Parameter { +export interface ParameterComponent { zodSchema?: ZodType; paramObject: | oas31.ParameterObject @@ -27,7 +31,7 @@ export interface Parameter { } interface ParametersComponentObject { - [ref: string]: Parameter | undefined; + [ref: string]: ParameterComponent | undefined; } export interface Header { @@ -90,9 +94,14 @@ const createSchemas = ( } if (schema instanceof ZodType) { + const state: SchemaState = { + components, + type: schema._def.openapi?.refType ?? 'output', + }; components.schemas[ref] = { - schemaObject: createSchemaWithMetadata(schema, components), + schemaObject: createSchemaWithMetadata(schema, state), zodSchema: schema, + types: state.effectType ? [state.effectType] : ['input', 'output'], }; return; } diff --git a/src/create/content.test.ts b/src/create/content.test.ts index 22bd176..5f9a0c0 100644 --- a/src/create/content.test.ts +++ b/src/create/content.test.ts @@ -31,6 +31,7 @@ describe('createContent', () => { }, }, getDefaultComponents(), + 'output', ); expect(result).toStrictEqual(expectedResult); @@ -66,6 +67,7 @@ describe('createContent', () => { }, }, getDefaultComponents(), + 'output', ); expect(result).toStrictEqual(expectedResult); @@ -99,6 +101,7 @@ describe('createContent', () => { }, }, getDefaultComponents(), + 'output', ); expect(result).toStrictEqual(expectedResult); diff --git a/src/create/content.ts b/src/create/content.ts index 187a4b1..459778a 100644 --- a/src/create/content.ts +++ b/src/create/content.ts @@ -1,7 +1,7 @@ import { oas31 } from 'openapi3-ts'; import { AnyZodObject, ZodType } from 'zod'; -import { ComponentsObject } from './components'; +import { ComponentsObject, CreationType } from './components'; import { ZodOpenApiContentObject, ZodOpenApiMediaTypeObject } from './document'; import { createSchemaOrRef } from './schema'; @@ -12,6 +12,7 @@ const createMediaTypeSchema = ( | oas31.ReferenceObject | undefined, components: ComponentsObject, + type: CreationType, ): oas31.SchemaObject | oas31.ReferenceObject | undefined => { if (!schemaObject) { return undefined; @@ -21,12 +22,16 @@ const createMediaTypeSchema = ( return schemaObject; } - return createSchemaOrRef(schemaObject, components); + return createSchemaOrRef(schemaObject, { + components, + type, + }); }; const createMediaTypeObject = ( mediaTypeObject: ZodOpenApiMediaTypeObject | undefined, components: ComponentsObject, + type: CreationType, ): oas31.MediaTypeObject | undefined => { if (!mediaTypeObject) { return undefined; @@ -34,19 +39,21 @@ const createMediaTypeObject = ( return { ...mediaTypeObject, - schema: createMediaTypeSchema(mediaTypeObject.schema, components), + schema: createMediaTypeSchema(mediaTypeObject.schema, components, type), }; }; export const createContent = ( contentObject: ZodOpenApiContentObject, components: ComponentsObject, + type: CreationType, ): oas31.ContentObject => Object.entries(contentObject).reduce( (acc, [path, zodOpenApiMediaTypeObject]): oas31.ContentObject => { const mediaTypeObject = createMediaTypeObject( zodOpenApiMediaTypeObject, components, + type, ); if (mediaTypeObject) { diff --git a/src/create/parameters.ts b/src/create/parameters.ts index a79a380..7d33c23 100644 --- a/src/create/parameters.ts +++ b/src/create/parameters.ts @@ -13,7 +13,10 @@ export const createBaseParameter = ( components: ComponentsObject, ): oas31.BaseParameterObject => { const { ref, ...rest } = schema._def.openapi?.param ?? {}; - const schemaOrRef = createSchemaOrRef(schema, components); + const schemaOrRef = createSchemaOrRef(schema, { + components, + type: 'input', + }); const required = !schema.isOptional(); return { ...rest, diff --git a/src/create/paths.ts b/src/create/paths.ts index 440b1c7..a3feb49 100644 --- a/src/create/paths.ts +++ b/src/create/paths.ts @@ -21,7 +21,7 @@ const createRequestBody = ( } return { ...requestBodyObject, - content: createContent(requestBodyObject.content, components), + content: createContent(requestBodyObject.content, components, 'input'), }; }; diff --git a/src/create/responses.ts b/src/create/responses.ts index 50b61c3..15fd324 100644 --- a/src/create/responses.ts +++ b/src/create/responses.ts @@ -45,7 +45,10 @@ export const createBaseHeader = ( components: ComponentsObject, ): oas31.BaseParameterObject => { const { ref, ...rest } = schema._def.openapi?.header ?? {}; - const schemaOrRef = createSchemaOrRef(schema, components); + const schemaOrRef = createSchemaOrRef(schema, { + components, + type: 'input', + }); const required = !schema.isOptional(); return { ...rest, @@ -123,7 +126,7 @@ const createResponse = ( return { ...rest, ...(maybeHeaders && { headers: maybeHeaders }), - ...(content && { content: createContent(content, components) }), + ...(content && { content: createContent(content, components, 'output') }), }; }; diff --git a/src/create/schema/array.test.ts b/src/create/schema/array.test.ts index dada204..75adb7f 100644 --- a/src/create/schema/array.test.ts +++ b/src/create/schema/array.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createArraySchema } from './array'; @@ -18,7 +18,7 @@ describe('createArraySchema', () => { }; const schema = z.array(z.string()); - const result = createArraySchema(schema, getDefaultComponents()); + const result = createArraySchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -34,7 +34,7 @@ describe('createArraySchema', () => { }; const schema = z.array(z.string()).min(0).max(10); - const result = createArraySchema(schema, getDefaultComponents()); + const result = createArraySchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -50,7 +50,7 @@ describe('createArraySchema', () => { }; const schema = z.array(z.string()).length(10); - const result = createArraySchema(schema, getDefaultComponents()); + const result = createArraySchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/array.ts b/src/create/schema/array.ts index abc72fb..aafe0bc 100644 --- a/src/create/schema/array.ts +++ b/src/create/schema/array.ts @@ -1,13 +1,11 @@ import { oas31 } from 'openapi3-ts'; import { ZodArray, ZodTypeAny } from 'zod'; -import { ComponentsObject } from '../components'; - -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createArraySchema = ( zodArray: ZodArray, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { const zodType = zodArray._def.type as ZodTypeAny; const minItems = @@ -16,7 +14,7 @@ export const createArraySchema = ( zodArray._def.exactLength?.value ?? zodArray._def.maxLength?.value; return { type: 'array', - items: createSchemaOrRef(zodType, components), + items: createSchemaOrRef(zodType, state), ...(minItems !== undefined && { minItems }), ...(maxItems !== undefined && { maxItems }), }; diff --git a/src/create/schema/catch.test.ts b/src/create/schema/catch.test.ts index 72f7633..db851e2 100644 --- a/src/create/schema/catch.test.ts +++ b/src/create/schema/catch.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createCatchSchema } from './catch'; @@ -15,7 +15,7 @@ describe('createCatchSchema', () => { }; const schema = z.string().catch('bob'); - const result = createCatchSchema(schema, getDefaultComponents()); + const result = createCatchSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/catch.ts b/src/create/schema/catch.ts index 2eb5572..81fce95 100644 --- a/src/create/schema/catch.ts +++ b/src/create/schema/catch.ts @@ -1,12 +1,10 @@ import { oas31 } from 'openapi3-ts'; import { ZodCatch, ZodType } from 'zod'; -import { ComponentsObject } from '../components'; - -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createCatchSchema = ( zodCatch: ZodCatch, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => - createSchemaOrRef(zodCatch._def.innerType as ZodType, components); + createSchemaOrRef(zodCatch._def.innerType as ZodType, state); diff --git a/src/create/schema/default.test.ts b/src/create/schema/default.test.ts index d673622..0a71359 100644 --- a/src/create/schema/default.test.ts +++ b/src/create/schema/default.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createDefaultSchema } from './default'; @@ -16,7 +16,7 @@ describe('createDefaultSchema', () => { }; const schema = z.string().default('a'); - const result = createDefaultSchema(schema, getDefaultComponents()); + const result = createDefaultSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -34,7 +34,7 @@ describe('createDefaultSchema', () => { }; const schema = z.string().openapi({ ref: 'ref' }).optional().default('a'); - const result = createDefaultSchema(schema, getDefaultComponents()); + const result = createDefaultSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/default.ts b/src/create/schema/default.ts index 87d3019..2988c44 100644 --- a/src/create/schema/default.ts +++ b/src/create/schema/default.ts @@ -1,19 +1,17 @@ import { oas31 } from 'openapi3-ts'; import { ZodDefault, ZodTypeAny } from 'zod'; -import { ComponentsObject } from '../components'; - import { enhanceWithMetadata } from './metadata'; -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createDefaultSchema = ( zodDefault: ZodDefault, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => { const schemaOrRef = createSchemaOrRef( zodDefault._def.innerType as ZodTypeAny, - components, + state, ); return enhanceWithMetadata(schemaOrRef, { diff --git a/src/create/schema/discriminatedUnion.test.ts b/src/create/schema/discriminatedUnion.test.ts index 34646d2..3d3dc45 100644 --- a/src/create/schema/discriminatedUnion.test.ts +++ b/src/create/schema/discriminatedUnion.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createDiscriminatedUnionSchema } from './discriminatedUnion'; @@ -43,10 +43,7 @@ describe('createDiscriminatedUnionSchema', () => { }), ]); - const result = createDiscriminatedUnionSchema( - schema, - getDefaultComponents(), - ); + const result = createDiscriminatedUnionSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -78,10 +75,7 @@ describe('createDiscriminatedUnionSchema', () => { .openapi({ ref: 'b' }), ]); - const result = createDiscriminatedUnionSchema( - schema, - getDefaultComponents(), - ); + const result = createDiscriminatedUnionSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -114,10 +108,7 @@ describe('createDiscriminatedUnionSchema', () => { .openapi({ ref: 'd' }), ]); - const result = createDiscriminatedUnionSchema( - schema, - getDefaultComponents(), - ); + const result = createDiscriminatedUnionSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/discriminatedUnion.ts b/src/create/schema/discriminatedUnion.ts index 77461b4..02975f6 100644 --- a/src/create/schema/discriminatedUnion.ts +++ b/src/create/schema/discriminatedUnion.ts @@ -7,18 +7,16 @@ import { ZodRawShape, } from 'zod'; -import { ComponentsObject, createComponentSchemaRef } from '../components'; +import { createComponentSchemaRef } from '../components'; -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createDiscriminatedUnionSchema = ( zodDiscriminatedUnion: ZodDiscriminatedUnion, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { const options = zodDiscriminatedUnion.options as AnyZodObject[]; - const schemas = options.map((option) => - createSchemaOrRef(option, components), - ); + const schemas = options.map((option) => createSchemaOrRef(option, state)); const discriminator = mapDiscriminator( options, zodDiscriminatedUnion.discriminator as string, diff --git a/src/create/schema/effects.test.ts b/src/create/schema/effects.test.ts deleted file mode 100644 index ca7add7..0000000 --- a/src/create/schema/effects.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { oas31 } from 'openapi3-ts'; -import { z } from 'zod'; - -import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; - -import { createEffectsSchema } from './effects'; - -extendZodWithOpenApi(z); - -describe('createEffectsSchema', () => { - it('creates a schema from preprocess', () => { - const expected: oas31.SchemaObject = { - type: 'string', - }; - const schema = z.preprocess((arg) => String(arg), z.string()); - - const result = createEffectsSchema(schema, getDefaultComponents()); - - expect(result).toStrictEqual(expected); - }); - - it('creates a schema from refine', () => { - const expected: oas31.SchemaObject = { - type: 'string', - }; - const schema = z.string().refine((str) => { - str.startsWith('bla'); - }); - - const result = createEffectsSchema(schema, getDefaultComponents()); - - expect(result).toStrictEqual(expected); - }); -}); diff --git a/src/create/schema/effects.ts b/src/create/schema/effects.ts deleted file mode 100644 index 7316703..0000000 --- a/src/create/schema/effects.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { oas31 } from 'openapi3-ts'; -import { ZodEffects, ZodType } from 'zod'; - -import { ComponentsObject } from '../components'; - -import { createSchemaOrRef } from '.'; - -export const createEffectsSchema = ( - zodEffects: ZodEffects, - components: ComponentsObject, -): oas31.SchemaObject | oas31.ReferenceObject => - createSchemaOrRef(zodEffects._def.schema as ZodType, components); diff --git a/src/create/schema/index.test.ts b/src/create/schema/index.test.ts index fd6a655..1655eab 100644 --- a/src/create/schema/index.test.ts +++ b/src/create/schema/index.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createInputState, createOutputState } from '../../test/state'; import { createSchemaOrRef } from '.'; @@ -65,11 +65,6 @@ const expectedZodDiscriminatedUnion: oas31.SchemaObject = { ], }; -const zodEffect = z.preprocess((arg) => String(arg), z.string()); -const expectedZodEffect: oas31.SchemaObject = { - type: 'string', -}; - const zodEnum = z.enum(['a', 'b']); const expectedZodEnum: oas31.SchemaObject = { type: 'string', @@ -189,32 +184,101 @@ const expectedZodCatch: oas31.SchemaObject = { type: 'string', }; +const zodPipeline = z + .string() + .transform((arg) => arg.length) + .pipe(z.number()); +const expectedZodPipelineOutput: oas31.SchemaObject = { + type: 'number', +}; +const expectedZodPipelineInput: oas31.SchemaObject = { + type: 'string', +}; + +const zodTransform = z.string().transform((str) => str.length); +const expectedZodTransform: oas31.SchemaObject = { + type: 'string', +}; + +const zodPreprocess = z.preprocess( + (arg) => (typeof arg === 'string' ? arg.split(',') : arg), + z.string(), +); +const expectedZodPreprocess: oas31.SchemaObject = { + type: 'string', +}; + +const zodRefine = z.string().refine((arg) => typeof arg === 'string'); +const expectedZodRefine: oas31.SchemaObject = { + type: 'string', +}; + +const zodUnknown = z.unknown().openapi({ type: 'string' }); +const expectedZodUnknown: oas31.SchemaObject = { + type: 'string', +}; + describe('createSchemaOrRef', () => { it.each` - zodType | schema | expected - ${'ZodArray'} | ${zodArray} | ${expectedZodArray} - ${'ZodBoolean'} | ${zodBoolean} | ${expectedZodBoolean} - ${'ZodDate'} | ${zodDate} | ${expectedZodDate} - ${'ZodDefault'} | ${zodDefault} | ${expectedZodDefault} - ${'ZodDiscriminatedUnion'} | ${zodDiscriminatedUnion} | ${expectedZodDiscriminatedUnion} - ${'ZodEffect'} | ${zodEffect} | ${expectedZodEffect} - ${'ZodEnum'} | ${zodEnum} | ${expectedZodEnum} - ${'ZodIntersection'} | ${zodIntersection} | ${expectedZodIntersection} - ${'ZodLiteral'} | ${zodLiteral} | ${expectedZodLiteral} - ${'ZodMetadata'} | ${zodMetadata} | ${expectedZodMetadata} - ${'ZodNativeEnum'} | ${zodNativeEnum} | ${expectedZodNativeEnum} - ${'ZodNull'} | ${zodNull} | ${expectedZodNull} - ${'ZodNullable'} | ${zodNullable} | ${expectedZodNullable} - ${'ZodNumber'} | ${zodNumber} | ${expectedZodNumber} - ${'ZodObject'} | ${zodObject} | ${expectedZodObject} - ${'ZodOptional'} | ${zodOptional} | ${expectedZodOptional} - ${'ZodRecord'} | ${zodRecord} | ${expectedZodReord} - ${'ZodString'} | ${zodString} | ${expectedZodString} - ${'ZodTuple'} | ${zodTuple} | ${expectedZodTuple} - ${'ZodUnion'} | ${zodUnion} | ${expectedZodUnion} - ${'ZodCatch'} | ${zodCatch} | ${expectedZodCatch} - `('creates a schema for $zodType', ({ schema, expected }) => { - expect(createSchemaOrRef(schema, getDefaultComponents())).toStrictEqual( + zodType | schema | expected + ${'ZodArray'} | ${zodArray} | ${expectedZodArray} + ${'ZodBoolean'} | ${zodBoolean} | ${expectedZodBoolean} + ${'ZodDate'} | ${zodDate} | ${expectedZodDate} + ${'ZodDefault'} | ${zodDefault} | ${expectedZodDefault} + ${'ZodDiscriminatedUnion'} | ${zodDiscriminatedUnion} | ${expectedZodDiscriminatedUnion} + ${'ZodEnum'} | ${zodEnum} | ${expectedZodEnum} + ${'ZodIntersection'} | ${zodIntersection} | ${expectedZodIntersection} + ${'ZodLiteral'} | ${zodLiteral} | ${expectedZodLiteral} + ${'ZodMetadata'} | ${zodMetadata} | ${expectedZodMetadata} + ${'ZodNativeEnum'} | ${zodNativeEnum} | ${expectedZodNativeEnum} + ${'ZodNull'} | ${zodNull} | ${expectedZodNull} + ${'ZodNullable'} | ${zodNullable} | ${expectedZodNullable} + ${'ZodNumber'} | ${zodNumber} | ${expectedZodNumber} + ${'ZodObject'} | ${zodObject} | ${expectedZodObject} + ${'ZodOptional'} | ${zodOptional} | ${expectedZodOptional} + ${'ZodRecord'} | ${zodRecord} | ${expectedZodReord} + ${'ZodString'} | ${zodString} | ${expectedZodString} + ${'ZodTuple'} | ${zodTuple} | ${expectedZodTuple} + ${'ZodUnion'} | ${zodUnion} | ${expectedZodUnion} + ${'ZodCatch'} | ${zodCatch} | ${expectedZodCatch} + ${'ZodPipeline'} | ${zodPipeline} | ${expectedZodPipelineOutput} + ${'ZodEffects - Preprocess'} | ${zodPreprocess} | ${expectedZodPreprocess} + ${'ZodEffects - Refine'} | ${zodRefine} | ${expectedZodRefine} + ${'unknown'} | ${zodUnknown} | ${expectedZodUnknown} + `('creates an output schema for $zodType', ({ schema, expected }) => { + expect(createSchemaOrRef(schema, createOutputState())).toStrictEqual( + expected, + ); + }); + + it.each` + zodType | schema | expected + ${'ZodArray'} | ${zodArray} | ${expectedZodArray} + ${'ZodBoolean'} | ${zodBoolean} | ${expectedZodBoolean} + ${'ZodDate'} | ${zodDate} | ${expectedZodDate} + ${'ZodDefault'} | ${zodDefault} | ${expectedZodDefault} + ${'ZodDiscriminatedUnion'} | ${zodDiscriminatedUnion} | ${expectedZodDiscriminatedUnion} + ${'ZodEnum'} | ${zodEnum} | ${expectedZodEnum} + ${'ZodIntersection'} | ${zodIntersection} | ${expectedZodIntersection} + ${'ZodLiteral'} | ${zodLiteral} | ${expectedZodLiteral} + ${'ZodMetadata'} | ${zodMetadata} | ${expectedZodMetadata} + ${'ZodNativeEnum'} | ${zodNativeEnum} | ${expectedZodNativeEnum} + ${'ZodNull'} | ${zodNull} | ${expectedZodNull} + ${'ZodNullable'} | ${zodNullable} | ${expectedZodNullable} + ${'ZodNumber'} | ${zodNumber} | ${expectedZodNumber} + ${'ZodObject'} | ${zodObject} | ${expectedZodObject} + ${'ZodOptional'} | ${zodOptional} | ${expectedZodOptional} + ${'ZodRecord'} | ${zodRecord} | ${expectedZodReord} + ${'ZodString'} | ${zodString} | ${expectedZodString} + ${'ZodTuple'} | ${zodTuple} | ${expectedZodTuple} + ${'ZodUnion'} | ${zodUnion} | ${expectedZodUnion} + ${'ZodCatch'} | ${zodCatch} | ${expectedZodCatch} + ${'ZodPipeline'} | ${zodPipeline} | ${expectedZodPipelineInput} + ${'ZodEffects - Transform'} | ${zodTransform} | ${expectedZodTransform} + ${'ZodEffects - Refine'} | ${zodRefine} | ${expectedZodRefine} + ${'unknown'} | ${zodUnknown} | ${expectedZodUnknown} + `('creates an input schema for $zodType', ({ schema, expected }) => { + expect(createSchemaOrRef(schema, createInputState())).toStrictEqual( expected, ); }); diff --git a/src/create/schema/index.ts b/src/create/schema/index.ts index d3d3571..999d1a1 100644 --- a/src/create/schema/index.ts +++ b/src/create/schema/index.ts @@ -16,6 +16,7 @@ import { ZodNumber, ZodObject, ZodOptional, + ZodPipeline, ZodRecord, ZodString, ZodTuple, @@ -24,7 +25,11 @@ import { ZodUnion, } from 'zod'; -import { ComponentsObject, createComponentSchemaRef } from '../components'; +import { + ComponentsObject, + CreationType, + createComponentSchemaRef, +} from '../components'; import { createArraySchema } from './array'; import { createBooleanSchema } from './boolean'; @@ -32,7 +37,6 @@ import { createCatchSchema } from './catch'; import { createDateSchema } from './date'; import { createDefaultSchema } from './default'; import { createDiscriminatedUnionSchema } from './discriminatedUnion'; -import { createEffectsSchema } from './effects'; import { createEnumSchema } from './enum'; import { createIntersectionSchema } from './intersection'; import { createLiteralSchema } from './literal'; @@ -43,10 +47,21 @@ import { createNullableSchema } from './nullable'; import { createNumberSchema } from './number'; import { createObjectSchema } from './object'; import { createOptionalSchema } from './optional'; +import { createPipelineSchema } from './pipeline'; +import { createPreprocessSchema } from './preprocess'; import { createRecordSchema } from './record'; +import { createRefineSchema } from './refine'; import { createStringSchema } from './string'; +import { createTransformSchema } from './transform'; import { createTupleSchema } from './tuple'; import { createUnionSchema } from './union'; +import { createUnknownSchema } from './unknown'; + +export interface SchemaState { + components: ComponentsObject; + type: CreationType; + effectType?: CreationType; +} export const createSchema = < Output = any, @@ -54,14 +69,14 @@ export const createSchema = < Input = Output, >( zodSchema: ZodType, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => { if (zodSchema instanceof ZodString) { return createStringSchema(zodSchema); } if (zodSchema instanceof ZodNumber) { - return createNumberSchema(zodSchema, components); + return createNumberSchema(zodSchema, state); } if (zodSchema instanceof ZodBoolean) { @@ -77,23 +92,23 @@ export const createSchema = < } if (zodSchema instanceof ZodNativeEnum) { - return createNativeEnumSchema(zodSchema, components); + return createNativeEnumSchema(zodSchema, state); } if (zodSchema instanceof ZodArray) { - return createArraySchema(zodSchema, components); + return createArraySchema(zodSchema, state); } if (zodSchema instanceof ZodObject) { - return createObjectSchema(zodSchema, components); + return createObjectSchema(zodSchema, state); } if (zodSchema instanceof ZodUnion) { - return createUnionSchema(zodSchema, components); + return createUnionSchema(zodSchema, state); } if (zodSchema instanceof ZodDiscriminatedUnion) { - return createDiscriminatedUnionSchema(zodSchema, components); + return createDiscriminatedUnionSchema(zodSchema, state); } if (zodSchema instanceof ZodNull) { @@ -101,56 +116,67 @@ export const createSchema = < } if (zodSchema instanceof ZodNullable) { - return createNullableSchema(zodSchema, components); + return createNullableSchema(zodSchema, state); } if (zodSchema instanceof ZodOptional) { - return createOptionalSchema(zodSchema, components); + return createOptionalSchema(zodSchema, state); } if (zodSchema instanceof ZodDefault) { - return createDefaultSchema(zodSchema, components); + return createDefaultSchema(zodSchema, state); } if (zodSchema instanceof ZodRecord) { - return createRecordSchema(zodSchema, components); + return createRecordSchema(zodSchema, state); } if (zodSchema instanceof ZodTuple) { - return createTupleSchema(zodSchema, components); + return createTupleSchema(zodSchema, state); } if (zodSchema instanceof ZodDate) { return createDateSchema(zodSchema); } + if (zodSchema instanceof ZodPipeline) { + return createPipelineSchema(zodSchema, state); + } + if ( zodSchema instanceof ZodEffects && - (zodSchema._def.effect.type === 'refinement' || - zodSchema._def.effect.type === 'preprocess') + zodSchema._def.effect.type === 'transform' ) { - return createEffectsSchema(zodSchema, components); + return createTransformSchema(zodSchema, state); + } + + if ( + zodSchema instanceof ZodEffects && + zodSchema._def.effect.type === 'preprocess' + ) { + return createPreprocessSchema(zodSchema, state); + } + + if ( + zodSchema instanceof ZodEffects && + zodSchema._def.effect.type === 'refinement' + ) { + return createRefineSchema(zodSchema, state); } if (zodSchema instanceof ZodNativeEnum) { - return createNativeEnumSchema(zodSchema, components); + return createNativeEnumSchema(zodSchema, state); } if (zodSchema instanceof ZodIntersection) { - return createIntersectionSchema(zodSchema, components); + return createIntersectionSchema(zodSchema, state); } if (zodSchema instanceof ZodCatch) { - return createCatchSchema(zodSchema, components); - } - - if (!zodSchema._def.openapi?.type) { - throw new Error( - `Unknown schema ${zodSchema.toString()}. Please assign it a manual type`, - ); + return createCatchSchema(zodSchema, state); } - return {}; + return createUnknownSchema(zodSchema); }; export const createRegisteredSchema = < @@ -160,29 +186,44 @@ export const createRegisteredSchema = < >( zodSchema: ZodType, schemaRef: string, - components: ComponentsObject, + state: SchemaState, ): oas31.ReferenceObject => { - const component = components.schemas[schemaRef]; + const component = state.components.schemas[schemaRef]; if (component) { if (component.zodSchema !== zodSchema) { throw new Error(`schemaRef "${schemaRef}" is already registered`); } + if (!component.types?.includes(state.type)) { + throw new Error( + `schemaRef ${schemaRef} was generated with a ZodEffect meaning that the input type is different from the output type. This type is currently being referenced in a response and request. Wrap the ZodEffect in a ZodPipeline to verify the contents of the transform`, + ); + } return { $ref: createComponentSchemaRef(schemaRef), }; } + const newState: SchemaState = { + components: state.components, + type: state.type, + }; + + const schemaOrRef = createSchemaWithMetadata(zodSchema, newState); // Optional Objects can return a reference object - const schemaOrRef = createSchemaWithMetadata(zodSchema, components); if ('$ref' in schemaOrRef) { throw new Error('Unexpected Error: received a reference object'); } - components.schemas[schemaRef] = { + state.components.schemas[schemaRef] = { schemaObject: schemaOrRef, zodSchema, + types: newState?.effectType ? [newState.effectType] : ['input', 'output'], }; + if (newState.effectType) { + state.effectType = newState.effectType; + } + return { $ref: createComponentSchemaRef(schemaRef), }; @@ -194,12 +235,12 @@ export const createSchemaOrRef = < Input = Output, >( zodSchema: ZodType, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => { const schemaRef = zodSchema._def.openapi?.ref; if (schemaRef) { - return createRegisteredSchema(zodSchema, schemaRef, components); + return createRegisteredSchema(zodSchema, schemaRef, state); } - return createSchemaWithMetadata(zodSchema, components); + return createSchemaWithMetadata(zodSchema, state); }; diff --git a/src/create/schema/intersection.test.ts b/src/create/schema/intersection.test.ts index fdac2cb..3824d36 100644 --- a/src/create/schema/intersection.test.ts +++ b/src/create/schema/intersection.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createIntersectionSchema } from './intersection'; @@ -22,7 +22,7 @@ describe('createIntersectionSchema', () => { }; const schema = z.intersection(z.string(), z.number()); - const result = createIntersectionSchema(schema, getDefaultComponents()); + const result = createIntersectionSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/intersection.ts b/src/create/schema/intersection.ts index 7f7ed63..d9c741c 100644 --- a/src/create/schema/intersection.ts +++ b/src/create/schema/intersection.ts @@ -1,16 +1,14 @@ import { oas31 } from 'openapi3-ts'; import { ZodIntersection, ZodType } from 'zod'; -import { ComponentsObject } from '../components'; - -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createIntersectionSchema = ( zodIntersection: ZodIntersection, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => ({ allOf: [ - createSchemaOrRef(zodIntersection._def.left as ZodType, components), - createSchemaOrRef(zodIntersection._def.right as ZodType, components), + createSchemaOrRef(zodIntersection._def.left as ZodType, state), + createSchemaOrRef(zodIntersection._def.right as ZodType, state), ], }); diff --git a/src/create/schema/metadata.test.ts b/src/create/schema/metadata.test.ts index 819e764..7d08ddc 100644 --- a/src/create/schema/metadata.test.ts +++ b/src/create/schema/metadata.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createSchemaWithMetadata } from './metadata'; @@ -16,7 +16,7 @@ describe('createSchemaWithMetadata', () => { }; const schema = z.string().openapi({ description: 'bla' }); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -28,7 +28,7 @@ describe('createSchemaWithMetadata', () => { }; const schema = z.string().describe('bla'); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -41,7 +41,7 @@ describe('createSchemaWithMetadata', () => { const schema = z.string().describe('bla').openapi({ description: 'foo' }); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -52,7 +52,7 @@ describe('createSchemaWithMetadata', () => { }; const schema = z.string().openapi({ type: 'integer' }); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -73,7 +73,7 @@ describe('createSchemaWithMetadata', () => { const schema = ref.optional().openapi({ description: 'hello' }); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -87,7 +87,7 @@ describe('createSchemaWithMetadata', () => { const schema = ref.optional(); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -100,7 +100,7 @@ describe('createSchemaWithMetadata', () => { const ref = z.string().openapi({ ref: 'ref2' }); const schema = ref.optional().default('a'); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -129,7 +129,7 @@ describe('createSchemaWithMetadata', () => { b: object2.openapi({ description: 'jello' }), }); - const result = createSchemaWithMetadata(schema, getDefaultComponents()); + const result = createSchemaWithMetadata(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/metadata.ts b/src/create/schema/metadata.ts index 821ba00..3e378e1 100644 --- a/src/create/schema/metadata.ts +++ b/src/create/schema/metadata.ts @@ -1,9 +1,7 @@ import { oas31 } from 'openapi3-ts'; import { ZodType, ZodTypeDef } from 'zod'; -import { ComponentsObject } from '../components'; - -import { createSchema } from '.'; +import { SchemaState, createSchema } from '.'; export const createSchemaWithMetadata = < Output = any, @@ -11,11 +9,11 @@ export const createSchemaWithMetadata = < Input = Output, >( zodSchema: ZodType, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => { - const { ref, param, header, ...additionalMetadata } = + const { ref, refType, param, header, ...additionalMetadata } = zodSchema._def.openapi ?? {}; - const schemaOrRef = createSchema(zodSchema, components); + const schemaOrRef = createSchema(zodSchema, state); const description = zodSchema.description; return enhanceWithMetadata(schemaOrRef, { diff --git a/src/create/schema/nativeEnum.test.ts b/src/create/schema/nativeEnum.test.ts index 3eec3c5..4e5ad7e 100644 --- a/src/create/schema/nativeEnum.test.ts +++ b/src/create/schema/nativeEnum.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputOpenapi3State, createOutputState } from '../../test/state'; import { createNativeEnumSchema } from './nativeEnum'; @@ -24,7 +24,7 @@ describe('createNativeEnumSchema', () => { const schema = z.nativeEnum(Direction); - const result = createNativeEnumSchema(schema, getDefaultComponents()); + const result = createNativeEnumSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -43,7 +43,7 @@ describe('createNativeEnumSchema', () => { } const schema = z.nativeEnum(Direction); - const result = createNativeEnumSchema(schema, getDefaultComponents()); + const result = createNativeEnumSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -63,7 +63,7 @@ describe('createNativeEnumSchema', () => { const schema = z.nativeEnum(Direction); - const result = createNativeEnumSchema(schema, getDefaultComponents()); + const result = createNativeEnumSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -91,10 +91,7 @@ describe('createNativeEnumSchema', () => { const schema = z.nativeEnum(Direction); - const result = createNativeEnumSchema( - schema, - getDefaultComponents({}, '3.0.0'), - ); + const result = createNativeEnumSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/nativeEnum.ts b/src/create/schema/nativeEnum.ts index eb5ad66..f8a6519 100644 --- a/src/create/schema/nativeEnum.ts +++ b/src/create/schema/nativeEnum.ts @@ -2,17 +2,18 @@ import { oas31 } from 'openapi3-ts'; import { EnumLike, ZodNativeEnum } from 'zod'; import { satisfiesVersion } from '../../openapi'; -import { ComponentsObject } from '../components'; + +import { SchemaState } from '.'; export const createNativeEnumSchema = ( zodEnum: ZodNativeEnum, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => { const enumValues = getValidEnumValues(zodEnum._def.values); const { numbers, strings } = sortStringsAndNumbers(enumValues); if (strings.length && numbers.length) { - if (satisfiesVersion(components.openapi, '3.1.0')) + if (satisfiesVersion(state.components.openapi, '3.1.0')) return { type: ['string', 'number'], enum: [...strings, ...numbers], diff --git a/src/create/schema/nullable.test.ts b/src/create/schema/nullable.test.ts index ca2951c..3bce421 100644 --- a/src/create/schema/nullable.test.ts +++ b/src/create/schema/nullable.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputOpenapi3State, createOutputState } from '../../test/state'; import { createNullableSchema } from './nullable'; @@ -17,10 +17,7 @@ describe('createNullableSchema', () => { }; const schema = z.string().nullable(); - const result = createNullableSchema( - schema, - getDefaultComponents({}, '3.0.0'), - ); + const result = createNullableSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); @@ -39,10 +36,7 @@ describe('createNullableSchema', () => { const registered = z.string().openapi({ ref: 'a' }); const schema = registered.optional().nullable(); - const result = createNullableSchema( - schema, - getDefaultComponents({}, '3.0.0'), - ); + const result = createNullableSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); @@ -77,10 +71,7 @@ describe('createNullableSchema', () => { .union([z.object({ a: z.string() }), z.object({ b: z.string() })]) .nullable(); - const result = createNullableSchema( - schema, - getDefaultComponents({}, '3.0.0'), - ); + const result = createNullableSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); @@ -112,10 +103,7 @@ describe('createNullableSchema', () => { const object2 = object1.extend({ b: z.string() }); const schema = z.object({ b: object2.nullable() }).nullable(); - const result = createNullableSchema( - schema, - getDefaultComponents({}, '3.0.0'), - ); + const result = createNullableSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); @@ -128,7 +116,7 @@ describe('createNullableSchema', () => { }; const schema = z.string().nullable(); - const result = createNullableSchema(schema, getDefaultComponents()); + const result = createNullableSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -147,7 +135,7 @@ describe('createNullableSchema', () => { const registered = z.string().openapi({ ref: 'a' }); const schema = registered.optional().nullable(); - const result = createNullableSchema(schema, getDefaultComponents()); + const result = createNullableSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -182,7 +170,7 @@ describe('createNullableSchema', () => { .union([z.object({ a: z.string() }), z.object({ b: z.string() })]) .nullable(); - const result = createNullableSchema(schema, getDefaultComponents()); + const result = createNullableSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -213,7 +201,7 @@ describe('createNullableSchema', () => { const object2 = object1.extend({ b: z.string() }); const schema = z.object({ b: object2.nullable() }).nullable(); - const result = createNullableSchema(schema, getDefaultComponents()); + const result = createNullableSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/nullable.ts b/src/create/schema/nullable.ts index 360dd09..98cd5e1 100644 --- a/src/create/schema/nullable.ts +++ b/src/create/schema/nullable.ts @@ -2,30 +2,29 @@ import { oas31 } from 'openapi3-ts'; import { ZodNullable, ZodTypeAny } from 'zod'; import { satisfiesVersion } from '../../openapi'; -import { ComponentsObject } from '../components'; import { ZodOpenApiVersion } from '../document'; -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createNullableSchema = ( zodNullable: ZodNullable, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { const schemaOrReference = createSchemaOrRef( zodNullable.unwrap() as ZodTypeAny, - components, + state, ); if ('$ref' in schemaOrReference || schemaOrReference.allOf) { return { - oneOf: mapNullOf([schemaOrReference], components.openapi), + oneOf: mapNullOf([schemaOrReference], state.components.openapi), }; } if (schemaOrReference.oneOf) { const { oneOf, ...schema } = schemaOrReference; return { - oneOf: mapNullOf(oneOf, components.openapi), + oneOf: mapNullOf(oneOf, state.components.openapi), ...schema, }; } @@ -33,14 +32,14 @@ export const createNullableSchema = ( if (schemaOrReference.anyOf) { const { anyOf, ...schema } = schemaOrReference; return { - anyOf: mapNullOf(anyOf, components.openapi), + anyOf: mapNullOf(anyOf, state.components.openapi), ...schema, }; } const { type, ...schema } = schemaOrReference; - if (satisfiesVersion(components.openapi, '3.1.0')) { + if (satisfiesVersion(state.components.openapi, '3.1.0')) { return { type: mapNullType(type), ...schema, diff --git a/src/create/schema/number.test.ts b/src/create/schema/number.test.ts index 2a04996..71930f1 100644 --- a/src/create/schema/number.test.ts +++ b/src/create/schema/number.test.ts @@ -2,7 +2,7 @@ import { oas30, oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputOpenapi3State, createOutputState } from '../../test/state'; import { createNumberSchema } from './number'; @@ -15,7 +15,7 @@ describe('createNumberSchema', () => { }; const schema = z.number(); - const result = createNumberSchema(schema, getDefaultComponents()); + const result = createNumberSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -26,7 +26,7 @@ describe('createNumberSchema', () => { }; const schema = z.number().int(); - const result = createNumberSchema(schema, getDefaultComponents()); + const result = createNumberSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -39,7 +39,7 @@ describe('createNumberSchema', () => { }; const schema = z.number().lt(10).gt(0); - const result = createNumberSchema(schema, getDefaultComponents()); + const result = createNumberSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -52,7 +52,7 @@ describe('createNumberSchema', () => { }; const schema = z.number().lte(10).gte(0); - const result = createNumberSchema(schema, getDefaultComponents()); + const result = createNumberSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -65,10 +65,7 @@ describe('createNumberSchema', () => { }; const schema = z.number().lte(10).gte(0); - const result = createNumberSchema( - schema, - getDefaultComponents({}, '3.0.0'), - ); + const result = createNumberSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); @@ -83,10 +80,7 @@ describe('createNumberSchema', () => { }; const schema = z.number().lt(10).gt(0); - const result = createNumberSchema( - schema, - getDefaultComponents({}, '3.0.0'), - ); + const result = createNumberSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/number.ts b/src/create/schema/number.ts index 5d61951..b15359d 100644 --- a/src/create/schema/number.ts +++ b/src/create/schema/number.ts @@ -2,17 +2,18 @@ import { oas30, oas31 } from 'openapi3-ts'; import { ZodNumber, ZodNumberCheck } from 'zod'; import { satisfiesVersion } from '../../openapi'; -import { ComponentsObject } from '../components'; import { ZodOpenApiVersion } from '../document'; +import { SchemaState } from '.'; + export const createNumberSchema = ( zodNumber: ZodNumber, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { const zodNumberChecks = getZodNumberChecks(zodNumber); - const minimum = mapMinimum(zodNumberChecks, components.openapi); - const maximum = mapMaximum(zodNumberChecks, components.openapi); + const minimum = mapMinimum(zodNumberChecks, state.components.openapi); + const maximum = mapMaximum(zodNumberChecks, state.components.openapi); return { type: mapNumberType(zodNumberChecks), diff --git a/src/create/schema/object.test.ts b/src/create/schema/object.test.ts index 1055389..a775548 100644 --- a/src/create/schema/object.test.ts +++ b/src/create/schema/object.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createObjectSchema } from './object'; @@ -23,7 +23,7 @@ describe('createObjectSchema', () => { b: z.string().optional(), }); - const result = createObjectSchema(schema, getDefaultComponents()); + const result = createObjectSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -43,7 +43,7 @@ describe('createObjectSchema', () => { a: z.string(), }); - const result = createObjectSchema(schema, getDefaultComponents()); + const result = createObjectSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -73,7 +73,7 @@ describe('createObjectSchema', () => { obj2: object2, }); - const result = createObjectSchema(schema, getDefaultComponents()); + const result = createObjectSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/object.ts b/src/create/schema/object.ts index 4becfab..1794aac 100644 --- a/src/create/schema/object.ts +++ b/src/create/schema/object.ts @@ -1,30 +1,30 @@ import { oas31 } from 'openapi3-ts'; import { UnknownKeysParam, ZodObject, ZodRawShape } from 'zod'; -import { ComponentsObject, createComponentSchemaRef } from '../components'; +import { createComponentSchemaRef } from '../components'; -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createObjectSchema = < T extends ZodRawShape, UnknownKeys extends UnknownKeysParam = UnknownKeysParam, >( zodObject: ZodObject, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { if (zodObject._def.extendMetadata?.extendsRef) { return createExtendedSchema( zodObject, zodObject._def.extendMetadata.extends, zodObject._def.extendMetadata.extendsRef, - components, + state, ); } return createObjectSchemaFromShape( zodObject.shape, zodObject._def.unknownKeys === 'strict', - components, + state, ); }; @@ -32,7 +32,7 @@ export const createExtendedSchema = ( zodObject: ZodObject, baseZodObject: ZodObject, schemaRef: string, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { const diffShape = createShapeDiff( baseZodObject._def.shape() as ZodRawShape, @@ -42,7 +42,7 @@ export const createExtendedSchema = ( return { allOf: [ { $ref: createComponentSchemaRef(schemaRef) }, - createObjectSchemaFromShape(diffShape, false, components), + createObjectSchemaFromShape(diffShape, false, state), ], }; }; @@ -62,10 +62,10 @@ const createShapeDiff = ( export const createObjectSchemaFromShape = ( shape: ZodRawShape, strict: boolean, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => ({ type: 'object', - properties: mapProperties(shape, components), + properties: mapProperties(shape, state), required: mapRequired(shape), ...(strict && { additionalProperties: false }), }); @@ -86,11 +86,11 @@ export const mapRequired = ( export const mapProperties = ( shape: ZodRawShape, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject['properties'] => Object.entries(shape).reduce>( (acc, [key, zodSchema]): NonNullable => { - acc[key] = createSchemaOrRef(zodSchema, components); + acc[key] = createSchemaOrRef(zodSchema, state); return acc; }, {}, diff --git a/src/create/schema/optional.test.ts b/src/create/schema/optional.test.ts index 1d4d0f0..d7e593c 100644 --- a/src/create/schema/optional.test.ts +++ b/src/create/schema/optional.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createOptionalSchema } from './optional'; @@ -15,7 +15,7 @@ describe('createOptionalSchema', () => { }; const schema = z.string().optional(); - const result = createOptionalSchema(schema, getDefaultComponents()); + const result = createOptionalSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/optional.ts b/src/create/schema/optional.ts index bb65e39..eeb7e51 100644 --- a/src/create/schema/optional.ts +++ b/src/create/schema/optional.ts @@ -1,13 +1,11 @@ import { oas31 } from 'openapi3-ts'; import { ZodOptional, ZodTypeAny } from 'zod'; -import { ComponentsObject } from '../components'; - -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createOptionalSchema = ( zodOptional: ZodOptional, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => // Optional doesn't change OpenAPI schema - createSchemaOrRef(zodOptional.unwrap() as ZodTypeAny, components); + createSchemaOrRef(zodOptional.unwrap() as ZodTypeAny, state); diff --git a/src/create/schema/pipeline.test.ts b/src/create/schema/pipeline.test.ts new file mode 100644 index 0000000..23cd51f --- /dev/null +++ b/src/create/schema/pipeline.test.ts @@ -0,0 +1,65 @@ +import { oas31 } from 'openapi3-ts'; +import { z } from 'zod'; + +import { extendZodWithOpenApi } from '../../extendZod'; +import { createInputState, createOutputState } from '../../test/state'; + +import { createPipelineSchema } from './pipeline'; + +extendZodWithOpenApi(z); + +describe('createTransformSchema', () => { + describe('input', () => { + it('creates a schema from a simple pipeline', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z.string().pipe(z.string()); + + const result = createPipelineSchema(schema, createInputState()); + + expect(result).toStrictEqual(expected); + }); + + it('creates a schema from a transform pipeline', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z + .string() + .transform((arg) => arg.length) + .pipe(z.number()); + + const result = createPipelineSchema(schema, createInputState()); + + expect(result).toStrictEqual(expected); + }); + }); + + describe('output', () => { + it('creates a schema from a simple pipeline', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z.string().pipe(z.string()); + + const result = createPipelineSchema(schema, createOutputState()); + + expect(result).toStrictEqual(expected); + }); + + it('creates a schema from a transform pipeline', () => { + const expected: oas31.SchemaObject = { + type: 'number', + }; + const schema = z + .string() + .transform((arg) => arg.length) + .pipe(z.number()); + + const result = createPipelineSchema(schema, createOutputState()); + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/src/create/schema/pipeline.ts b/src/create/schema/pipeline.ts new file mode 100644 index 0000000..efa2c59 --- /dev/null +++ b/src/create/schema/pipeline.ts @@ -0,0 +1,14 @@ +import { oas31 } from 'openapi3-ts'; +import { ZodPipeline, ZodTypeAny } from 'zod'; + +import { SchemaState, createSchemaOrRef } from '.'; + +export const createPipelineSchema = ( + zodPipeline: ZodPipeline, + state: SchemaState, +): oas31.SchemaObject | oas31.ReferenceObject => { + if (state.type === 'input') { + return createSchemaOrRef(zodPipeline._def.in as ZodTypeAny, state); + } + return createSchemaOrRef(zodPipeline._def.out as ZodTypeAny, state); +}; diff --git a/src/create/schema/preprocess.test.ts b/src/create/schema/preprocess.test.ts new file mode 100644 index 0000000..f71e500 --- /dev/null +++ b/src/create/schema/preprocess.test.ts @@ -0,0 +1,57 @@ +import { oas31 } from 'openapi3-ts'; +import { z } from 'zod'; + +import { extendZodWithOpenApi } from '../../extendZod'; +import { createInputState, createOutputState } from '../../test/state'; + +import { createPreprocessSchema } from './preprocess'; + +extendZodWithOpenApi(z); + +describe('createPreprocessSchema', () => { + describe('input', () => { + it('throws an error when creating an input schema with preprocess', () => { + const schema = z.preprocess( + (arg) => (typeof arg === 'string' ? arg.split(',') : arg), + z.string(), + ); + expect(() => + createPreprocessSchema(schema, createInputState()), + ).toThrow(); + }); + + it('returns a manually declared type when creating an input schema with preprocess', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z + .preprocess( + (arg) => (typeof arg === 'string' ? arg.split(',') : arg), + z.string(), + ) + .openapi({ type: 'string' }); + + const result = createPreprocessSchema(schema, createInputState()); + + expect(result).toStrictEqual(expected); + }); + }); + + describe('output', () => { + it('returns a schema when creating an output schema with preprocess', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z + .preprocess( + (arg) => (typeof arg === 'string' ? arg.split(',') : arg), + z.string(), + ) + .openapi({ type: 'string' }); + + const result = createPreprocessSchema(schema, createOutputState()); + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/src/create/schema/preprocess.ts b/src/create/schema/preprocess.ts new file mode 100644 index 0000000..cc42393 --- /dev/null +++ b/src/create/schema/preprocess.ts @@ -0,0 +1,17 @@ +import { oas31 } from 'openapi3-ts'; +import { ZodEffects, ZodType } from 'zod'; + +import { createUnknownSchema } from './unknown'; + +import { SchemaState, createSchemaOrRef } from '.'; + +export const createPreprocessSchema = ( + zodPreprocess: ZodEffects, + state: SchemaState, +): oas31.SchemaObject | oas31.ReferenceObject => { + if (state.type === 'output') { + return createSchemaOrRef(zodPreprocess._def.schema as ZodType, state); + } + + return createUnknownSchema(zodPreprocess); +}; diff --git a/src/create/schema/record.test.ts b/src/create/schema/record.test.ts index 94af6a0..029e90b 100644 --- a/src/create/schema/record.test.ts +++ b/src/create/schema/record.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createRecordSchema } from './record'; @@ -18,7 +18,7 @@ describe('createRecordSchema', () => { }; const schema = z.record(z.string()); - const result = createRecordSchema(schema, getDefaultComponents()); + const result = createRecordSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/record.ts b/src/create/schema/record.ts index c7cf2d5..29dad6f 100644 --- a/src/create/schema/record.ts +++ b/src/create/schema/record.ts @@ -1,17 +1,15 @@ import { oas31 } from 'openapi3-ts'; import { ZodRecord, ZodTypeAny } from 'zod'; -import { ComponentsObject } from '../components'; - -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createRecordSchema = ( zodRecord: ZodRecord, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => ({ type: 'object', additionalProperties: createSchemaOrRef( zodRecord.valueSchema as ZodTypeAny, - components, + state, ), }); diff --git a/src/create/schema/refine.test.ts b/src/create/schema/refine.test.ts new file mode 100644 index 0000000..21c8e27 --- /dev/null +++ b/src/create/schema/refine.test.ts @@ -0,0 +1,22 @@ +import { oas31 } from 'openapi3-ts'; +import { z } from 'zod'; + +import { extendZodWithOpenApi } from '../../extendZod'; +import { createOutputState } from '../../test/state'; + +import { createRefineSchema } from './refine'; + +extendZodWithOpenApi(z); + +describe('createRefineSchema', () => { + it('returns a schema when creating an output schema with preprocess', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z.string().refine((check) => typeof check === 'string'); + + const result = createRefineSchema(schema, createOutputState()); + + expect(result).toStrictEqual(expected); + }); +}); diff --git a/src/create/schema/refine.ts b/src/create/schema/refine.ts new file mode 100644 index 0000000..96876e7 --- /dev/null +++ b/src/create/schema/refine.ts @@ -0,0 +1,10 @@ +import { oas31 } from 'openapi3-ts'; +import { ZodEffects, ZodType } from 'zod'; + +import { SchemaState, createSchemaOrRef } from '.'; + +export const createRefineSchema = ( + zodRefine: ZodEffects, + state: SchemaState, +): oas31.SchemaObject | oas31.ReferenceObject => + createSchemaOrRef(zodRefine._def.schema as ZodType, state); diff --git a/src/create/schema/transform.test.ts b/src/create/schema/transform.test.ts new file mode 100644 index 0000000..347d2cd --- /dev/null +++ b/src/create/schema/transform.test.ts @@ -0,0 +1,48 @@ +import { oas31 } from 'openapi3-ts'; +import { z } from 'zod'; + +import { extendZodWithOpenApi } from '../../extendZod'; +import { createInputState, createOutputState } from '../../test/state'; + +import { createTransformSchema } from './transform'; + +extendZodWithOpenApi(z); + +describe('createTransformSchema', () => { + describe('input', () => { + it('creates a schema from transform', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z.string().transform((str) => str.length); + + const result = createTransformSchema(schema, createInputState()); + + expect(result).toStrictEqual(expected); + }); + }); + + describe('output', () => { + it('throws an error with a schema with transform', () => { + const schema = z.string().transform((str) => str.length); + + expect(() => + createTransformSchema(schema, createOutputState()), + ).toThrow(); + }); + + it('creates an empty schema when a type is manually specified', () => { + const expected: oas31.SchemaObject = { + type: 'number', + }; + const schema = z + .string() + .transform((str) => str.length) + .openapi({ type: 'number' }); + + const result = createTransformSchema(schema, createOutputState()); + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/src/create/schema/transform.ts b/src/create/schema/transform.ts new file mode 100644 index 0000000..01bb0f5 --- /dev/null +++ b/src/create/schema/transform.ts @@ -0,0 +1,17 @@ +import { oas31 } from 'openapi3-ts'; +import { ZodEffects, ZodType } from 'zod'; + +import { createUnknownSchema } from './unknown'; + +import { SchemaState, createSchemaOrRef } from '.'; + +export const createTransformSchema = ( + zodTransform: ZodEffects, + state: SchemaState, +): oas31.SchemaObject | oas31.ReferenceObject => { + if (state.type === 'input') { + return createSchemaOrRef(zodTransform._def.schema as ZodType, state); + } + + return createUnknownSchema(zodTransform); +}; diff --git a/src/create/schema/tuple.test.ts b/src/create/schema/tuple.test.ts index 47dc50e..328e7cf 100644 --- a/src/create/schema/tuple.test.ts +++ b/src/create/schema/tuple.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputOpenapi3State, createOutputState } from '../../test/state'; import { createTupleSchema } from './tuple'; @@ -25,7 +25,7 @@ describe('createTupleSchema', () => { }; const schema = z.tuple([z.string(), z.number()]); - const result = createTupleSchema(schema, getDefaultComponents()); + const result = createTupleSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -47,7 +47,7 @@ describe('createTupleSchema', () => { }; const schema = z.tuple([z.string(), z.number()]).rest(z.boolean()); - const result = createTupleSchema(schema, getDefaultComponents()); + const result = createTupleSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -60,7 +60,7 @@ describe('createTupleSchema', () => { }; const schema = z.tuple([]); - const result = createTupleSchema(schema, getDefaultComponents()); + const result = createTupleSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); @@ -84,7 +84,7 @@ describe('createTupleSchema', () => { }; const schema = z.tuple([z.string(), z.number()]).rest(z.boolean()); - const result = createTupleSchema(schema, getDefaultComponents({}, '3.0.0')); + const result = createTupleSchema(schema, createOutputOpenapi3State()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/tuple.ts b/src/create/schema/tuple.ts index d24fab8..2569a00 100644 --- a/src/create/schema/tuple.ts +++ b/src/create/schema/tuple.ts @@ -2,28 +2,27 @@ import { oas31 } from 'openapi3-ts'; import { ZodTuple, ZodTypeAny } from 'zod'; import { satisfiesVersion } from '../../openapi'; -import { ComponentsObject } from '../components'; -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createTupleSchema = ( zodTuple: ZodTuple, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { const items = zodTuple.items as ZodTypeAny[]; const rest = zodTuple._def.rest as ZodTypeAny; return { type: 'array', - ...mapItemProperties(items, rest, components), + ...mapItemProperties(items, rest, state), } as oas31.SchemaObject; }; const mapPrefixItems = ( items: ZodTypeAny[], - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject['prefixItems'] | undefined => { if (items.length) { - return items.map((item) => createSchemaOrRef(item, components)); + return items.map((item) => createSchemaOrRef(item, state)); } return undefined; }; @@ -31,14 +30,14 @@ const mapPrefixItems = ( const mapItemProperties = ( items: ZodTypeAny[], rest: ZodTypeAny, - components: ComponentsObject, + state: SchemaState, ): Pick< oas31.SchemaObject, 'items' | 'minItems' | 'maxItems' | 'prefixItems' > => { - const prefixItems = mapPrefixItems(items, components); + const prefixItems = mapPrefixItems(items, state); - if (satisfiesVersion(components.openapi, '3.1.0')) { + if (satisfiesVersion(state.components.openapi, '3.1.0')) { if (!rest) { return { maxItems: items.length, @@ -48,7 +47,7 @@ const mapItemProperties = ( } return { - items: createSchemaOrRef(rest, components), + items: createSchemaOrRef(rest, state), ...(prefixItems && { prefixItems }), }; } @@ -63,7 +62,9 @@ const mapItemProperties = ( return { ...(prefixItems && { - items: { oneOf: [...prefixItems, createSchemaOrRef(rest, components)] }, + items: { + oneOf: [...prefixItems, createSchemaOrRef(rest, state)], + }, }), }; }; diff --git a/src/create/schema/union.test.ts b/src/create/schema/union.test.ts index 65a2af3..f310559 100644 --- a/src/create/schema/union.test.ts +++ b/src/create/schema/union.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { getDefaultComponents } from '../components'; +import { createOutputState } from '../../test/state'; import { createUnionSchema } from './union'; @@ -22,7 +22,7 @@ describe('createUnionSchema', () => { }; const schema = z.union([z.string(), z.number()]); - const result = createUnionSchema(schema, getDefaultComponents()); + const result = createUnionSchema(schema, createOutputState()); expect(result).toStrictEqual(expected); }); diff --git a/src/create/schema/union.ts b/src/create/schema/union.ts index 6fbce7d..0c8e613 100644 --- a/src/create/schema/union.ts +++ b/src/create/schema/union.ts @@ -1,18 +1,14 @@ import { oas31 } from 'openapi3-ts'; import { ZodTypeAny, ZodUnion } from 'zod'; -import { ComponentsObject } from '../components'; - -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; export const createUnionSchema = ( zodUnion: ZodUnion, - components: ComponentsObject, + state: SchemaState, ): oas31.SchemaObject => { const options = zodUnion.options as ZodTypeAny[]; - const schemas = options.map((option) => - createSchemaOrRef(option, components), - ); + const schemas = options.map((option) => createSchemaOrRef(option, state)); return { anyOf: schemas, }; diff --git a/src/create/schema/unknown.test.ts b/src/create/schema/unknown.test.ts new file mode 100644 index 0000000..d0fd70f --- /dev/null +++ b/src/create/schema/unknown.test.ts @@ -0,0 +1,21 @@ +import { oas31 } from 'openapi3-ts'; +import { z } from 'zod'; + +import { extendZodWithOpenApi } from '../../extendZod'; + +import { createUnknownSchema } from './unknown'; + +extendZodWithOpenApi(z); + +describe('createUnknownSchema', () => { + it('creates a simple string schema for an optional string', () => { + const expected: oas31.SchemaObject = { + type: 'string', + }; + const schema = z.unknown().openapi({ type: 'string' }); + + const result = createUnknownSchema(schema); + + expect(result).toStrictEqual(expected); + }); +}); diff --git a/src/create/schema/unknown.ts b/src/create/schema/unknown.ts new file mode 100644 index 0000000..880c4dc --- /dev/null +++ b/src/create/schema/unknown.ts @@ -0,0 +1,25 @@ +import { oas31 } from 'openapi3-ts'; +import { ZodEffects, ZodType, ZodTypeDef } from 'zod'; + +export const createUnknownSchema = < + Output = any, + Def extends ZodTypeDef = ZodTypeDef, + Input = Output, +>( + zodSchema: ZodType, +): oas31.SchemaObject => { + if (!zodSchema._def.openapi?.type) { + const zodType = zodSchema.constructor.name; + const schemaName = + zodSchema instanceof ZodEffects + ? `${zodType} - ${zodSchema._def.effect.type}` + : zodType; + throw new Error( + `Unknown schema ${schemaName}. Please assign it a manual type`, + ); + } + + return { + type: zodSchema._def.openapi.type, + }; +}; diff --git a/src/extendZod.ts b/src/extendZod.ts index 7a20ba4..173a529 100644 --- a/src/extendZod.ts +++ b/src/extendZod.ts @@ -8,6 +8,8 @@ import { z, } from 'zod'; +import { CreationType } from './create/components'; + type SchemaObject = oas30.SchemaObject & oas31.SchemaObject; interface ZodOpenApiMetadata> @@ -15,7 +17,14 @@ interface ZodOpenApiMetadata> example?: TInferred; examples?: [TInferred, ...TInferred[]]; default?: T extends ZodDate ? string : TInferred; + /** + * Use this field to output this Zod Schema in the components schemas section. Any usage of this Zod Schema will then be transformed into a $ref. + */ ref?: string; + /** + * Use this field when you are manually adding a Zod Schema to the components section. This controls whether this should be rendered as request (`input`) or response (`output`). Defaults to `output` + */ + refType?: CreationType; param?: Partial & { example?: TInferred; examples?: { @@ -23,9 +32,15 @@ interface ZodOpenApiMetadata> | (oas31.ExampleObject & { value: TInferred }) | oas31.ReferenceObject; }; + /** + * Use this field to output this Zod Schema in the components parameters section. Any usage of this Zod Schema will then be transformed into a $ref. + */ ref?: string; }; header?: Partial & { + /** + * Use this field to output this Zod Schema in the components headers section. Any usage of this Zod Schema will then be transformed into a $ref. + */ ref?: string; }; } diff --git a/src/test/state.ts b/src/test/state.ts new file mode 100644 index 0000000..39476af --- /dev/null +++ b/src/test/state.ts @@ -0,0 +1,17 @@ +import { getDefaultComponents } from '../create/components'; +import { SchemaState } from '../create/schema'; + +export const createOutputState = (): SchemaState => ({ + components: getDefaultComponents(), + type: 'output', +}); + +export const createInputState = (): SchemaState => ({ + components: getDefaultComponents(), + type: 'input', +}); + +export const createOutputOpenapi3State = (): SchemaState => ({ + components: { ...getDefaultComponents(), openapi: '3.0.0' }, + type: 'output', +}); From 54f50595ef307c397ef145221b6ea7f38538f816 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Mon, 17 Apr 2023 20:38:35 +1000 Subject: [PATCH 2/7] fix typo --- src/create/schema/index.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/create/schema/index.test.ts b/src/create/schema/index.test.ts index 1655eab..238a9f3 100644 --- a/src/create/schema/index.test.ts +++ b/src/create/schema/index.test.ts @@ -140,7 +140,7 @@ const expectedZodOptional: oas31.SchemaObject = { }; const zodRecord = z.record(z.string()); -const expectedZodReord: oas31.SchemaObject = { +const expectedZodRecord: oas31.SchemaObject = { type: 'object', additionalProperties: { type: 'string', @@ -236,7 +236,7 @@ describe('createSchemaOrRef', () => { ${'ZodNumber'} | ${zodNumber} | ${expectedZodNumber} ${'ZodObject'} | ${zodObject} | ${expectedZodObject} ${'ZodOptional'} | ${zodOptional} | ${expectedZodOptional} - ${'ZodRecord'} | ${zodRecord} | ${expectedZodReord} + ${'ZodRecord'} | ${zodRecord} | ${expectedZodRecord} ${'ZodString'} | ${zodString} | ${expectedZodString} ${'ZodTuple'} | ${zodTuple} | ${expectedZodTuple} ${'ZodUnion'} | ${zodUnion} | ${expectedZodUnion} @@ -268,7 +268,7 @@ describe('createSchemaOrRef', () => { ${'ZodNumber'} | ${zodNumber} | ${expectedZodNumber} ${'ZodObject'} | ${zodObject} | ${expectedZodObject} ${'ZodOptional'} | ${zodOptional} | ${expectedZodOptional} - ${'ZodRecord'} | ${zodRecord} | ${expectedZodReord} + ${'ZodRecord'} | ${zodRecord} | ${expectedZodRecord} ${'ZodString'} | ${zodString} | ${expectedZodString} ${'ZodTuple'} | ${zodTuple} | ${expectedZodTuple} ${'ZodUnion'} | ${zodUnion} | ${expectedZodUnion} From 975c3fd4b9eaab769ae9f650d8e6b14d98e8c198 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Mon, 17 Apr 2023 20:55:56 +1000 Subject: [PATCH 3/7] adjust effectType --- src/create/schema/index.test.ts | 25 ++++++++++++++++++++++++- src/create/schema/index.ts | 2 +- src/create/schema/preprocess.test.ts | 15 +++++++++++++++ src/create/schema/preprocess.ts | 1 + src/create/schema/transform.test.ts | 9 +++++++++ src/create/schema/transform.ts | 1 + 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/create/schema/index.test.ts b/src/create/schema/index.test.ts index 238a9f3..f8207d1 100644 --- a/src/create/schema/index.test.ts +++ b/src/create/schema/index.test.ts @@ -3,8 +3,9 @@ import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; import { createInputState, createOutputState } from '../../test/state'; +import { getDefaultComponents } from '../components'; -import { createSchemaOrRef } from '.'; +import { SchemaState, createSchemaOrRef } from '.'; extendZodWithOpenApi(z); @@ -282,4 +283,26 @@ describe('createSchemaOrRef', () => { expected, ); }); + + it('throws an error when an ZodEffect input component is referenced in an output', () => { + const inputSchema = z + .object({ a: z.string().transform((arg) => arg.length) }) + .openapi({ ref: 'a' }); + const components = getDefaultComponents(); + const state: SchemaState = { + components, + type: 'input', + }; + createSchemaOrRef(inputSchema, state); + + const outputState: SchemaState = { + components, + type: 'output', + }; + + const outputSchema = z.object({ a: inputSchema }); + expect(() => createSchemaOrRef(outputSchema, outputState)).toThrow( + 'schemaRef "a" was created with a ZodEffect meaning that the input type is different from the output type. This type is currently being referenced in a response and request. Wrap the ZodEffect in a ZodPipeline to verify the contents of the effect', + ); + }); }); diff --git a/src/create/schema/index.ts b/src/create/schema/index.ts index 999d1a1..bdeb52f 100644 --- a/src/create/schema/index.ts +++ b/src/create/schema/index.ts @@ -195,7 +195,7 @@ export const createRegisteredSchema = < } if (!component.types?.includes(state.type)) { throw new Error( - `schemaRef ${schemaRef} was generated with a ZodEffect meaning that the input type is different from the output type. This type is currently being referenced in a response and request. Wrap the ZodEffect in a ZodPipeline to verify the contents of the transform`, + `schemaRef "${schemaRef}" was created with a ZodEffect meaning that the input type is different from the output type. This type is currently being referenced in a response and request. Wrap the ZodEffect in a ZodPipeline to verify the contents of the effect`, ); } return { diff --git a/src/create/schema/preprocess.test.ts b/src/create/schema/preprocess.test.ts index f71e500..5b7aa84 100644 --- a/src/create/schema/preprocess.test.ts +++ b/src/create/schema/preprocess.test.ts @@ -53,5 +53,20 @@ describe('createPreprocessSchema', () => { expect(result).toStrictEqual(expected); }); + + it('changes the state effectType to output', () => { + const schema = z + .preprocess( + (arg) => (typeof arg === 'string' ? arg.split(',') : arg), + z.string(), + ) + .openapi({ type: 'string' }); + + const state = createOutputState(); + + createPreprocessSchema(schema, state); + + expect(state.effectType).toBe('output'); + }); }); }); diff --git a/src/create/schema/preprocess.ts b/src/create/schema/preprocess.ts index cc42393..195c4f1 100644 --- a/src/create/schema/preprocess.ts +++ b/src/create/schema/preprocess.ts @@ -10,6 +10,7 @@ export const createPreprocessSchema = ( state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => { if (state.type === 'output') { + state.effectType = 'output'; return createSchemaOrRef(zodPreprocess._def.schema as ZodType, state); } diff --git a/src/create/schema/transform.test.ts b/src/create/schema/transform.test.ts index 347d2cd..b6cd62d 100644 --- a/src/create/schema/transform.test.ts +++ b/src/create/schema/transform.test.ts @@ -20,6 +20,15 @@ describe('createTransformSchema', () => { expect(result).toStrictEqual(expected); }); + + it('changes the state effectType to input', () => { + const schema = z.string().transform((str) => str.length); + const state = createInputState(); + + createTransformSchema(schema, state); + + expect(state.effectType).toBe('input'); + }); }); describe('output', () => { diff --git a/src/create/schema/transform.ts b/src/create/schema/transform.ts index 01bb0f5..7911c23 100644 --- a/src/create/schema/transform.ts +++ b/src/create/schema/transform.ts @@ -10,6 +10,7 @@ export const createTransformSchema = ( state: SchemaState, ): oas31.SchemaObject | oas31.ReferenceObject => { if (state.type === 'input') { + state.effectType = 'input'; return createSchemaOrRef(zodTransform._def.schema as ZodType, state); } From 164d80871c48af5561f29d48ab4f1a0457b1fe82 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Mon, 17 Apr 2023 22:21:55 +1000 Subject: [PATCH 4/7] rename test file --- src/create/schema/array.test.ts | 2 +- src/create/schema/catch.test.ts | 2 +- src/create/schema/default.test.ts | 2 +- src/create/schema/discriminatedUnion.test.ts | 2 +- src/create/schema/index.test.ts | 2 +- src/create/schema/intersection.test.ts | 2 +- src/create/schema/metadata.test.ts | 2 +- src/create/schema/nativeEnum.test.ts | 5 ++++- src/create/schema/nullable.test.ts | 5 ++++- src/create/schema/number.test.ts | 5 ++++- src/create/schema/object.test.ts | 2 +- src/create/schema/optional.test.ts | 2 +- src/create/schema/pipeline.test.ts | 2 +- src/create/schema/preprocess.test.ts | 2 +- src/create/schema/record.test.ts | 2 +- src/create/schema/refine.test.ts | 2 +- src/create/schema/transform.test.ts | 2 +- src/create/schema/tuple.test.ts | 5 ++++- src/create/schema/union.test.ts | 2 +- src/{test => testing}/state.ts | 0 20 files changed, 31 insertions(+), 19 deletions(-) rename src/{test => testing}/state.ts (100%) diff --git a/src/create/schema/array.test.ts b/src/create/schema/array.test.ts index 75adb7f..187a24d 100644 --- a/src/create/schema/array.test.ts +++ b/src/create/schema/array.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createArraySchema } from './array'; diff --git a/src/create/schema/catch.test.ts b/src/create/schema/catch.test.ts index db851e2..b53ed91 100644 --- a/src/create/schema/catch.test.ts +++ b/src/create/schema/catch.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createCatchSchema } from './catch'; diff --git a/src/create/schema/default.test.ts b/src/create/schema/default.test.ts index 0a71359..9b59939 100644 --- a/src/create/schema/default.test.ts +++ b/src/create/schema/default.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createDefaultSchema } from './default'; diff --git a/src/create/schema/discriminatedUnion.test.ts b/src/create/schema/discriminatedUnion.test.ts index 3d3dc45..5dd3708 100644 --- a/src/create/schema/discriminatedUnion.test.ts +++ b/src/create/schema/discriminatedUnion.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createDiscriminatedUnionSchema } from './discriminatedUnion'; diff --git a/src/create/schema/index.test.ts b/src/create/schema/index.test.ts index f8207d1..955c23b 100644 --- a/src/create/schema/index.test.ts +++ b/src/create/schema/index.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createInputState, createOutputState } from '../../test/state'; +import { createInputState, createOutputState } from '../../testing/state'; import { getDefaultComponents } from '../components'; import { SchemaState, createSchemaOrRef } from '.'; diff --git a/src/create/schema/intersection.test.ts b/src/create/schema/intersection.test.ts index 3824d36..c248c3b 100644 --- a/src/create/schema/intersection.test.ts +++ b/src/create/schema/intersection.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createIntersectionSchema } from './intersection'; diff --git a/src/create/schema/metadata.test.ts b/src/create/schema/metadata.test.ts index 7d08ddc..9fbd3f0 100644 --- a/src/create/schema/metadata.test.ts +++ b/src/create/schema/metadata.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createSchemaWithMetadata } from './metadata'; diff --git a/src/create/schema/nativeEnum.test.ts b/src/create/schema/nativeEnum.test.ts index 4e5ad7e..104e16c 100644 --- a/src/create/schema/nativeEnum.test.ts +++ b/src/create/schema/nativeEnum.test.ts @@ -2,7 +2,10 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputOpenapi3State, createOutputState } from '../../test/state'; +import { + createOutputOpenapi3State, + createOutputState, +} from '../../testing/state'; import { createNativeEnumSchema } from './nativeEnum'; diff --git a/src/create/schema/nullable.test.ts b/src/create/schema/nullable.test.ts index 3bce421..5e9702a 100644 --- a/src/create/schema/nullable.test.ts +++ b/src/create/schema/nullable.test.ts @@ -2,7 +2,10 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputOpenapi3State, createOutputState } from '../../test/state'; +import { + createOutputOpenapi3State, + createOutputState, +} from '../../testing/state'; import { createNullableSchema } from './nullable'; diff --git a/src/create/schema/number.test.ts b/src/create/schema/number.test.ts index 71930f1..87c8ead 100644 --- a/src/create/schema/number.test.ts +++ b/src/create/schema/number.test.ts @@ -2,7 +2,10 @@ import { oas30, oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputOpenapi3State, createOutputState } from '../../test/state'; +import { + createOutputOpenapi3State, + createOutputState, +} from '../../testing/state'; import { createNumberSchema } from './number'; diff --git a/src/create/schema/object.test.ts b/src/create/schema/object.test.ts index a775548..284d06e 100644 --- a/src/create/schema/object.test.ts +++ b/src/create/schema/object.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createObjectSchema } from './object'; diff --git a/src/create/schema/optional.test.ts b/src/create/schema/optional.test.ts index d7e593c..307d0ad 100644 --- a/src/create/schema/optional.test.ts +++ b/src/create/schema/optional.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createOptionalSchema } from './optional'; diff --git a/src/create/schema/pipeline.test.ts b/src/create/schema/pipeline.test.ts index 23cd51f..71d5ae4 100644 --- a/src/create/schema/pipeline.test.ts +++ b/src/create/schema/pipeline.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createInputState, createOutputState } from '../../test/state'; +import { createInputState, createOutputState } from '../../testing/state'; import { createPipelineSchema } from './pipeline'; diff --git a/src/create/schema/preprocess.test.ts b/src/create/schema/preprocess.test.ts index 5b7aa84..7dc155b 100644 --- a/src/create/schema/preprocess.test.ts +++ b/src/create/schema/preprocess.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createInputState, createOutputState } from '../../test/state'; +import { createInputState, createOutputState } from '../../testing/state'; import { createPreprocessSchema } from './preprocess'; diff --git a/src/create/schema/record.test.ts b/src/create/schema/record.test.ts index 029e90b..98edcb8 100644 --- a/src/create/schema/record.test.ts +++ b/src/create/schema/record.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createRecordSchema } from './record'; diff --git a/src/create/schema/refine.test.ts b/src/create/schema/refine.test.ts index 21c8e27..c7a324c 100644 --- a/src/create/schema/refine.test.ts +++ b/src/create/schema/refine.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createRefineSchema } from './refine'; diff --git a/src/create/schema/transform.test.ts b/src/create/schema/transform.test.ts index b6cd62d..ddab947 100644 --- a/src/create/schema/transform.test.ts +++ b/src/create/schema/transform.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createInputState, createOutputState } from '../../test/state'; +import { createInputState, createOutputState } from '../../testing/state'; import { createTransformSchema } from './transform'; diff --git a/src/create/schema/tuple.test.ts b/src/create/schema/tuple.test.ts index 328e7cf..45e454a 100644 --- a/src/create/schema/tuple.test.ts +++ b/src/create/schema/tuple.test.ts @@ -2,7 +2,10 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputOpenapi3State, createOutputState } from '../../test/state'; +import { + createOutputOpenapi3State, + createOutputState, +} from '../../testing/state'; import { createTupleSchema } from './tuple'; diff --git a/src/create/schema/union.test.ts b/src/create/schema/union.test.ts index f310559..ab6df7a 100644 --- a/src/create/schema/union.test.ts +++ b/src/create/schema/union.test.ts @@ -2,7 +2,7 @@ import { oas31 } from 'openapi3-ts'; import { z } from 'zod'; import { extendZodWithOpenApi } from '../../extendZod'; -import { createOutputState } from '../../test/state'; +import { createOutputState } from '../../testing/state'; import { createUnionSchema } from './union'; diff --git a/src/test/state.ts b/src/testing/state.ts similarity index 100% rename from src/test/state.ts rename to src/testing/state.ts From 169f737c317baadd3158dabaa2e926257edfd081 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Mon, 17 Apr 2023 22:24:02 +1000 Subject: [PATCH 5/7] change validate workflow --- .github/workflows/validate.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index bbea1ba..331c668 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,8 +1,10 @@ name: Validate on: - - pull_request - - push + pull_request: + push: + branches: + - 'master' permissions: {} jobs: From b17a00c3dbd6a81d28e2b6eb24795a2db37ba9a2 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Mon, 17 Apr 2023 22:26:38 +1000 Subject: [PATCH 6/7] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ebe401..f9814f7 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,7 @@ const header = z.string().openapi({ - ZodEffects - `transform` support for request schemas. Wrap your transform in a ZodPipeline to enable response schema creation or declare a manual `type` in the `.openapi()` section of that schema. - `pre-process` support for response schemas. Wrap your transform in a ZodPipeline to enable request schema creation or declare a manual `type` in the `.openapi()` section of that schema. - - `refine` support + - `refine` full support. - ZodEnum - ZodLiteral - ZodNativeEnum From 93b4d32a63bc569655229ff057534f0b1c5d3866 Mon Sep 17 00:00:00 2001 From: Sam Chung Date: Mon, 17 Apr 2023 22:36:48 +1000 Subject: [PATCH 7/7] update readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f9814f7..1d961bb 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,11 @@ eg. } ``` -Please note: if your schema contains a ZodEffect you may need to declare `refType` as `input` or `output` in `openapi()` to declare how to create the component. This defaults to `output` by default. +##### Zod Effects + +`.transform()` and `.preprocess()` are complicated because they are technically two types (input & output). This means that we need to understand which type you are after. This means if you are adding the ZodSchema directly to the `components` section, we need to know whether you want the response or request type created. You can do this by setting the `refType` field to `input` or `output` in `.openapi()`. This defaults to `output` by default. + +If you use a registered schema with a ZodEffect in both a request and response schema you will receive an error because we cannot register two different schemas under the same `ref`. #### Parameters