From 43ff459fcc75dc78090502509e166fbfa959357f Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 23 Nov 2023 10:17:53 +0900 Subject: [PATCH 01/13] Remove `nullable` --- apps/example/src/main.spec.ts | 32 +- package-lock.json | 7 +- .../src/lib/zod-extensions.spec.ts | 6 +- .../zod-openapi/src/lib/zod-openapi.spec.ts | 290 +++++++++--------- packages/zod-openapi/src/lib/zod-openapi.ts | 48 +-- 5 files changed, 194 insertions(+), 189 deletions(-) diff --git a/apps/example/src/main.spec.ts b/apps/example/src/main.spec.ts index 05c9bd8..6a987d1 100644 --- a/apps/example/src/main.spec.ts +++ b/apps/example/src/main.spec.ts @@ -65,6 +65,7 @@ describe('Cats', () => { .set('Accept', 'application/json') .expect(200) .expect({ + // todo: this should report 3.1.0 openapi: '3.0.0', paths: { '/': { @@ -135,7 +136,8 @@ describe('Cats', () => { name: 'id', required: true, in: 'path', - schema: { type: 'string' }, + // schema: { type: 'array', items: {'type': 'string'} }, + schema: { type: ['string'] }, }, ], responses: { @@ -162,11 +164,11 @@ describe('Cats', () => { components: { schemas: { GetCatsDto: { - type: 'object', + type: ['object'], properties: { cats: { - type: 'array', - items: { type: 'string' }, + type: ['array'], + items: { type: ['string'] }, description: 'List of cats', }, }, @@ -174,30 +176,30 @@ describe('Cats', () => { title: 'Get Cat Response', }, CatDto: { - type: 'object', + type: ['object'], properties: { - name: { type: 'string' }, - age: { type: 'number' }, - breed: { type: 'string' }, + name: { type: ['string'] }, + age: { type: ['number'] }, + breed: { type: ['string'] }, }, required: ['name', 'age', 'breed'], title: 'Cat', description: 'A cat', }, CreateCatResponseDto: { - type: 'object', + type: ['object'], properties: { - success: { type: 'boolean' }, - message: { type: 'string' }, - name: { type: 'string' }, + success: { type: ['boolean'] }, + message: { type: ['string'] }, + name: { type: ['string'] }, }, required: ['success', 'message', 'name'], }, UpdateCatDto: { - type: 'object', + type: ['object'], properties: { - age: { type: 'number' }, - breed: { type: 'string' }, + age: { type: ['number'] }, + breed: { type: ['string'] }, }, required: ['age', 'breed'], }, diff --git a/package-lock.json b/package-lock.json index e2f7815..93c12aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17983,13 +17983,14 @@ }, "packages/zod-mock": { "name": "@anatine/zod-mock", - "version": "3.13.2", + "version": "3.13.3", "license": "MIT", "dependencies": { "randexp": "^0.5.3" }, "peerDependencies": { - "@faker-js/faker": "^7.0.0 || ^8.0.0" + "@faker-js/faker": "^7.0.0 || ^8.0.0", + "zod": "^3.21.4" } }, "packages/zod-nestjs": { @@ -18006,7 +18007,7 @@ }, "packages/zod-openapi": { "name": "@anatine/zod-openapi", - "version": "2.1.0", + "version": "2.2.1", "license": "MIT", "dependencies": { "ts-deepmerge": "^6.0.3" diff --git a/packages/zod-openapi/src/lib/zod-extensions.spec.ts b/packages/zod-openapi/src/lib/zod-extensions.spec.ts index e003ce3..4eae20f 100644 --- a/packages/zod-openapi/src/lib/zod-extensions.spec.ts +++ b/packages/zod-openapi/src/lib/zod-extensions.spec.ts @@ -25,17 +25,17 @@ describe('Zod Extensions', () => { "properties": { "one": { "example": "oneOne", - "type": "string" + "type": ["string"] }, "two": { - "type": "number" + "type": ["number"] } }, "required": [ "one", "two" ], - "type": "object" + "type": ["object"] }) }) diff --git a/packages/zod-openapi/src/lib/zod-openapi.spec.ts b/packages/zod-openapi/src/lib/zod-openapi.spec.ts index c3dfe71..3f5ae3f 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.spec.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.spec.ts @@ -24,13 +24,13 @@ describe('zodOpenapi', () => { const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - aString: { description: 'A test string', type: 'string' }, - aNumber: { type: 'number' }, - aBigInt: { type: 'integer', format: 'int64' }, - aBoolean: { type: 'boolean' }, - aDate: { type: 'string', format: 'date-time' }, + aString: { description: 'A test string', type: ['string'] }, + aNumber: { type: ['number'] }, + aBigInt: { type: ['integer'], format: 'int64' }, + aBoolean: { type: ['boolean'] }, + aDate: { type: ['string'], format: 'date-time' }, }, required: ['aBigInt', 'aBoolean', 'aDate', 'aNumber'], description: 'Primitives also testing overwriting of "required"', @@ -50,10 +50,10 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { aUndefined: {}, - aNull: { type: 'string', format: 'null', nullable: true }, + aNull: { type: ['string', 'null'], enum: ['null'] }, aVoid: {}, }, required: ['aNull'], @@ -67,9 +67,9 @@ describe('zodOpenapi', () => { { description: 'Will take in a string, returning the length' } ); const schemaIn = generateSchema(zodTransform); - expect(schemaIn.type).toEqual('string'); + expect(schemaIn.type).toEqual(['string']); const schemaOut = generateSchema(zodTransform, true); - expect(schemaOut.type).toEqual('number'); + expect(schemaOut.type).toEqual(['number']); }); it('should support catch-all types', () => { @@ -118,13 +118,13 @@ describe('zodOpenapi', () => { const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - aBrandedString: { description: 'A branded test string', type: 'string' }, - aBrandedNumber: { type: 'number' }, - aBrandedBigInt: { type: 'integer', format: 'int64' }, - aBrandedBoolean: { type: 'boolean' }, - aBrandedDate: { type: 'string', format: 'date-time' }, + aBrandedString: { description: 'A branded test string', type: ['string'] }, + aBrandedNumber: { type: ['number'] }, + aBrandedBigInt: { type: ['integer'], format: 'int64' }, + aBrandedBoolean: { type: ['boolean'] }, + aBrandedDate: { type: ['string'], format: 'date-time' }, }, required: ['aBrandedBigInt', 'aBrandedBoolean', 'aBrandedDate'], description: 'Branded primitives', @@ -153,18 +153,18 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - aStringMax: { type: 'string', maxLength: 5 }, - aStringMin: { type: 'string', minLength: 3 }, - aStringLength: { maxLength: 10, minLength: 10, type: 'string' }, - aStringEmail: { type: 'string', format: 'email' }, - aStringUrl: { type: 'string', format: 'uri' }, - aStringUUID: { type: 'string', format: 'uuid' }, - aStringCUID: { type: 'string', format: 'cuid' }, - aStringDatetime: { type: 'string', format: 'date-time' }, - aStringRegex: { type: 'string', pattern: '^[a-zA-Z]+$' }, - aStringNonEmpty: { type: 'string', minLength: 1 }, + aStringMax: { type: ['string'], maxLength: 5 }, + aStringMin: { type: ['string'], minLength: 3 }, + aStringLength: { maxLength: 10, minLength: 10, type: ['string'] }, + aStringEmail: { type: ['string'], format: 'email' }, + aStringUrl: { type: ['string'], format: 'uri' }, + aStringUUID: { type: ['string'], format: 'uuid' }, + aStringCUID: { type: ['string'], format: 'cuid' }, + aStringDatetime: { type: ['string'], format: 'date-time' }, + aStringRegex: { type: ['string'], pattern: '^[a-zA-Z]+$' }, + aStringNonEmpty: { type: ['string'], minLength: 1 }, }, description: 'This is totally unlike String Theory', }); @@ -192,18 +192,18 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - aNumberMin: { type: 'number', minimum: 3 }, - aNumberMax: { type: 'number', maximum: 8 }, - aNumberInt: { type: 'integer' }, - aNumberPositive: { type: 'number', minimum: 0, exclusiveMinimum: 0 }, - aNumberNonnegative: { type: 'number', minimum: 0 }, - aNumberNegative: { type: 'number', maximum: 0, exclusiveMaximum: 0 }, - aNumberNonpositive: { type: 'number', maximum: 0 }, - aNumberGt: { type: 'number', minimum: 5, exclusiveMinimum: 5 }, - aNumberLt: { type: 'number', maximum: 5, exclusiveMaximum: 5 }, - aNumberMultipleOf: { type: 'number', multipleOf: 2 }, + aNumberMin: { type: ['number'], minimum: 3 }, + aNumberMax: { type: ['number'], maximum: 8 }, + aNumberInt: { type: ['integer'] }, + aNumberPositive: { type: ['number'], minimum: 0, exclusiveMinimum: 0 }, + aNumberNonnegative: { type: ['number'], minimum: 0 }, + aNumberNegative: { type: ['number'], maximum: 0, exclusiveMaximum: 0 }, + aNumberNonpositive: { type: ['number'], maximum: 0 }, + aNumberGt: { type: ['number'], minimum: 5, exclusiveMinimum: 5 }, + aNumberLt: { type: ['number'], maximum: 5, exclusiveMaximum: 5 }, + aNumberMultipleOf: { type: ['number'], multipleOf: 2 }, }, description: 'Look mah, the horse can count higher than me!', }); @@ -226,26 +226,26 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - aArrayMin: { type: 'array', minItems: 3, items: { type: 'string' } }, - aArrayMax: { type: 'array', maxItems: 8, items: { type: 'number' } }, + aArrayMin: { type: ['array'], minItems: 3, items: { type: ['string'] } }, + aArrayMax: { type: ['array'], maxItems: 8, items: { type: ['number'] } }, aArrayLength: { - type: 'array', + type: ['array'], minItems: 10, maxItems: 10, - items: { type: 'boolean' }, + items: { type: ['boolean'] }, }, aArrayNonempty: { - type: 'array', + type: ['array'], minItems: 1, - items: { type: 'string', format: 'null', nullable: true }, + items: { type: ['string', 'null'], enum: ['null'] }, }, aArrayMinAndMax: { - type: 'array', + type: ['array'], minItems: 3, maxItems: 8, - items: { type: 'number' }, + items: { type: ['number'] }, }, }, description: 'I need arrays', @@ -271,17 +271,17 @@ describe('zodOpenapi', () => { const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { aArrayMin: { - type: 'array', + type: ['array'], minItems: 3, - items: { type: 'string' }, + items: { type: ['string'] }, }, aArrayMax: { - type: 'array', + type: ['array'], maxItems: 8, - items: { type: 'number' }, + items: { type: ['number'] }, }, }, "hideDefinitions": ["aArrayLength"], @@ -313,17 +313,17 @@ describe('zodOpenapi', () => { "additionalProperties": false, "properties": { "aString": { - "type": "string", + "type": ["string"], }, }, "hideDefinitions": ["aNumber"], "required": [ "aString", ], - "type": "object", + "type": ["object"], }, }, - "type": "object", + "type": ["object"], } ); }); @@ -337,8 +337,8 @@ describe('zodOpenapi', () => { }); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', - additionalProperties: { type: 'number', minimum: 2, maximum: 42 }, + type: ['object'], + additionalProperties: { type: ['number'], minimum: 2, maximum: 42 }, description: 'Record this one for me.', }); }); @@ -351,7 +351,7 @@ describe('zodOpenapi', () => { }); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], additionalProperties: {}, description: 'Record this one for me.', }); @@ -375,24 +375,24 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - aString: { type: 'string', default: 'hello' }, + aString: { type: ['string'], default: 'hello' }, aStringWithConstraints: { - type: 'string', + type: ['string'], format: 'email', maxLength: 100, default: 'hello', }, - aNumber: { type: 'number', default: 42 }, + aNumber: { type: ['number'], default: 42 }, aNumberWithRestrictions: { - type: 'number', + type: ['number'], minimum: 2, maximum: 42, default: 42, }, - aBoolean: { type: 'boolean', default: false }, - nonDefaulted: { type: 'string' }, + aBoolean: { type: ['boolean'], default: false }, + nonDefaulted: { type: ['string'] }, }, required: ['nonDefaulted'], description: 'I defaulted on my debt', @@ -416,10 +416,10 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - aString: { type: 'string' }, - aNumber: { type: 'number' }, + aString: { type: ['string'] }, + aNumber: { type: ['number'] }, }, default: { aString: 'hello', @@ -449,17 +449,17 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], required: ['aString', 'aNumber'], properties: { - aString: { type: 'string' }, - aNumber: { type: 'number' }, + aString: { type: ['string'] }, + aNumber: { type: ['number'] }, }, additionalProperties: { - type: 'object', + type: ['object'], properties: { - email: { type: 'string', format: 'email' }, - available: { type: 'boolean' }, + email: { type: ['string'], format: 'email' }, + available: { type: ['boolean'] }, }, required: ['email', 'available'], }, @@ -481,11 +481,11 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], required: ['aString', 'aNumber'], properties: { - aString: { type: 'string' }, - aNumber: { type: 'number' }, + aString: { type: ['string'] }, + aNumber: { type: ['number'] }, }, additionalProperties: true, description: "Gotta catch 'em all!", @@ -506,11 +506,11 @@ describe('zodOpenapi', () => { ); const apiSchema = generateSchema(zodSchema); expect(apiSchema).toEqual({ - type: 'object', + type: ['object'], required: ['aString', 'aNumber'], properties: { - aString: { type: 'string' }, - aNumber: { type: 'number' }, + aString: { type: ['string'] }, + aNumber: { type: ['number'] }, }, additionalProperties: false, description: "Super strict", @@ -601,66 +601,66 @@ describe('zodOpenapi', () => { const schemaTest = generateSchema(zodSchema, true); expect(schemaTest).toEqual({ - type: 'object', + type: ['object'], properties: { - name: { type: 'string', description: 'User full name' }, + name: { type: ['string'], description: 'User full name' }, email: { - type: 'string', + type: ['string'], format: 'email', minLength: 4, description: 'User email', }, - whatever: { type: 'string' }, + whatever: { type: ['string'] }, myCollection: { - type: 'array', + type: ['array'], items: { - type: 'object', - properties: { name: { type: 'string' }, count: { type: 'number' } }, + type: ['object'], + properties: { name: { type: ['string'] }, count: { type: ['number'] } }, required: ['name', 'count'], }, maxItems: 10, }, - timeStamp: { type: 'string' }, + timeStamp: { type: ['string'] }, literals: { - type: 'object', + type: ['object'], properties: { - wordOne: { nullable: true, type: 'string', enum: ['One'] }, - numberTwo: { type: 'number', enum: [2] }, - isThisTheEnd: { nullable: true, type: 'boolean', enum: [false] }, + wordOne: { type: ['string', 'null'], enum: ['One'] }, + numberTwo: { type: ['number'], enum: [2] }, + isThisTheEnd: { type: ['boolean', 'null'], enum: [false] }, }, required: ['wordOne'], }, catchall: { - type: 'object', + type: ['object'], properties: { - email: { type: 'string', format: 'email' }, - joined: { type: 'string', format: 'date-time' }, + email: { type: ['string'], format: 'email' }, + joined: { type: ['string'], format: 'date-time' }, }, required: ['email'], additionalProperties: { - type: 'object', + type: ['object'], properties: { - name: { type: 'string' }, - value: { type: 'string' }, + name: { type: ['string'] }, + value: { type: ['string'] }, }, required: ['name', 'value'], }, }, foodTest: { - type: 'object', + type: ['object'], properties: { fishEnum: { - type: 'string', + type: ['string'], enum: ['Salmon', 'Tuna', 'Trout'], description: 'Choose your fish', default: 'Salmon', }, fruitEnum: { - type: 'string', + type: ['string'], enum: ['Apple', 'Banana', 0, 1], default: 1, }, - moreFruitsEnum: { type: 'string', enum: ['pear', 'plumb', 3] }, + moreFruitsEnum: { type: ['string'], enum: ['pear', 'plumb', 3] }, }, required: ['fishEnum', 'moreFruitsEnum'], description: 'Have some lunch', @@ -668,15 +668,15 @@ describe('zodOpenapi', () => { employedPerson: { allOf: [ { - type: 'object', - properties: { name: { type: 'string' } }, + type: ['object'], + properties: { name: { type: ['string'] } }, required: ['name'], }, { - type: 'object', + type: ['object'], properties: { role: { - type: 'string', + type: ['string'], enum: ['manager', 'employee', 'intern', 'hopeful'], }, }, @@ -687,8 +687,8 @@ describe('zodOpenapi', () => { }, makeAChoice: { oneOf: [ - { type: 'string', enum: ['One'] }, - { type: 'number', enum: [2] }, + { type: ['string'], enum: ['One'] }, + { type: ['number'], enum: [2] }, ], }, chooseAPet: { @@ -700,54 +700,54 @@ describe('zodOpenapi', () => { properties: { animal: { enum: ['dog'], - type: 'string', + type: ['string'], }, theBestQuality: { enum: ['fleas'], - type: 'string', + type: ['string'], }, }, required: ['animal', 'theBestQuality'], - type: 'object', + type: ['object'], }, { properties: { animal: { enum: ['cat'], - type: 'string', + type: ['string'], }, theBestQuality: { enum: ['stink'], - type: 'string', + type: ['string'], }, }, required: ['animal', 'theBestQuality'], - type: 'object', + type: ['object'], }, ], }, openChoice: { - oneOf: [{ type: 'string' }, { type: 'string' }], + oneOf: [{ type: ['string'] }, { type: ['string'] }], description: 'Odd pattern here', }, - aNullish: { nullable: true, type: 'string' }, - stringLengthOutput: { type: 'number' }, + aNullish: { type: ['string', 'null'] }, + stringLengthOutput: { type: ['number'] }, favourites: { - type: 'object', + type: ['object'], additionalProperties: { - type: 'object', + type: ['object'], properties: { - name: { type: 'string' }, - watchCount: { type: 'number' }, + name: { type: ['string'] }, + watchCount: { type: ['number'] }, }, required: ['name', 'watchCount'], }, }, - limit: { type: 'number', default: 200 }, + limit: { type: ['number'], default: 200 }, freeform: { - type: 'object', + type: ['object'], properties: { - name: { type: 'string' }, + name: { type: ['string'] }, }, additionalProperties: true, required: ['name'], @@ -807,13 +807,13 @@ describe('zodOpenapi', () => { const myOpenApiSchema = generateSchema(aZodSchema); expect(myOpenApiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { - uid: { type: 'string', minLength: 1 }, - firstName: { type: 'string', minLength: 2 }, - lastName: { type: 'string' }, - email: { type: 'string', format: 'email' }, - phoneNumber: { type: 'string', minLength: 10 }, + uid: { type: ['string'], minLength: 1 }, + firstName: { type: ['string'], minLength: 2 }, + lastName: { type: ['string'] }, + email: { type: ['string'], format: 'email' }, + phoneNumber: { type: ['string'], minLength: 10 }, }, required: ['uid', 'firstName', 'email'], }); @@ -843,19 +843,19 @@ describe('zodOpenapi', () => { const myOpenApiSchema = generateSchema(aZodExtendedSchema); expect(myOpenApiSchema).toEqual({ - type: 'object', + type: ['object'], properties: { uid: { - type: 'string', + type: ['string'], minLength: 1, title: 'Unique ID', description: 'A UUID generated by the server', }, - firstName: { type: 'string', minLength: 2 }, - lastName: { type: 'string' }, - email: { type: 'string', format: 'email' }, + firstName: { type: ['string'], minLength: 2 }, + lastName: { type: ['string'] }, + email: { type: ['string'], format: 'email' }, phoneNumber: { - type: 'string', + type: ['string'], minLength: 10, description: 'US Phone numbers only', example: '555-555-5555', @@ -882,7 +882,7 @@ describe('zodOpenapi', () => { const openapiSchema = generateSchema(overriddenSchema); expect(openapiSchema).toMatchObject({ - type: 'string', + type: ['string'], format: 'date-time', description: newDescription, }); @@ -896,36 +896,36 @@ describe('zodOpenapi', () => { const openapiSchema = generateSchema(binarySchema); expect(openapiSchema).toMatchObject({ - type: 'string', + type: ['string'], format: 'binary', }); }); it('can summarize unions of zod literals as an enum', () => { expect(generateSchema(z.union([z.literal('h'), z.literal('i')]))).toEqual({ - type: 'string', + type: ['string'], enum: ['h', 'i'] }); expect(generateSchema(z.union([z.literal(3), z.literal(4)]))).toEqual({ - type: 'number', + type: ['number'], enum: [3, 4] }); // should this just remove the enum? true | false is exhaustive... expect(generateSchema(z.union([z.literal(true), z.literal(false)]))).toEqual({ - type: 'boolean', + type: ['boolean'], enum: [true, false] }); expect(generateSchema(z.union([z.literal(5), z.literal('i')]))).toEqual({ oneOf: [ { - type: 'number', + type: ['number'], enum: [5] }, { - type: 'string', + type: ['string'], enum: ['i'] } ] @@ -942,7 +942,7 @@ describe('zodOpenapi', () => { .pipe(z.number().min(0).max(10)) ) ).toEqual({ - type: 'string', + type: ['string'], pattern: '^\\d+$', } satisfies SchemaObject); @@ -956,7 +956,7 @@ describe('zodOpenapi', () => { true ) ).toEqual({ - type: 'number', + type: ['number'], minimum: 0, maximum: 10, } satisfies SchemaObject); diff --git a/packages/zod-openapi/src/lib/zod-openapi.ts b/packages/zod-openapi/src/lib/zod-openapi.ts index c8689a2..53fefb5 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.ts @@ -58,18 +58,21 @@ function parseTransformation({ zodRef._def.effect.type === 'transform' ? zodRef._def.effect : null; if (effect && 'transform' in effect) { try { + // todo: this doesn't deal with nullable types very well + // @ts-expect-error because we try/catch for a missing type + const type = input.type[0]; output = typeof effect.transform( - ['integer', 'number'].includes(`${input.type}`) + ['integer', 'number'].includes(`${type}`) ? 0 - : 'string' === input.type + : 'string' === type ? '' - : 'boolean' === input.type + : 'boolean' === type ? false - : 'object' === input.type + : 'object' === type ? {} - : 'null' === input.type + : 'null' === type ? null - : 'array' === input.type + : 'array' === type ? [] : undefined, { addIssue: () => undefined, path: [] } // TODO: Discover if context is necessary here @@ -85,7 +88,7 @@ function parseTransformation({ ...input, ...(['number', 'string', 'boolean', 'null'].includes(output) ? { - type: output as 'number' | 'string' | 'boolean' | 'null', + type: [output as 'number' | 'string' | 'boolean' | 'null'], } : {}), }, @@ -98,7 +101,7 @@ function parseString({ schemas, }: ParsingArgs): SchemaObject { const baseSchema: SchemaObject = { - type: 'string', + type: ['string'], }; const { checks = [] } = zodRef._def; checks.forEach((item) => { @@ -145,7 +148,7 @@ function parseNumber({ schemas, }: ParsingArgs): SchemaObject { const baseSchema: SchemaObject = { - type: 'number', + type: ['number'], }; const { checks = [] } = zodRef._def; checks.forEach((item) => { @@ -160,7 +163,7 @@ function parseNumber({ if (!item.inclusive) baseSchema.exclusiveMinimum = item.value; break; case 'int': - baseSchema.type = 'integer'; + baseSchema.type = ['integer']; break; case 'multipleOf': baseSchema.multipleOf = item.value; @@ -232,7 +235,7 @@ function parseObject({ return merge( { - type: 'object' as SchemaObjectType, + type: ['object' as SchemaObjectType], properties: iterateZodObject({ zodRef: zodRef as OpenApiZodAnyObject, schemas, @@ -255,7 +258,7 @@ function parseRecord({ }: ParsingArgs): SchemaObject { return merge( { - type: 'object' as SchemaObjectType, + type: ['object' as SchemaObjectType], additionalProperties: zodRef._def.valueType instanceof z.ZodUnknown ? {} @@ -271,7 +274,7 @@ function parseBigInt({ schemas, }: ParsingArgs): SchemaObject { return merge( - { type: 'integer' as SchemaObjectType, format: 'int64' }, + { type: ['integer' as SchemaObjectType], format: 'int64' }, zodRef.description ? { description: zodRef.description } : {}, ...schemas ); @@ -282,7 +285,7 @@ function parseBoolean({ schemas, }: ParsingArgs): SchemaObject { return merge( - { type: 'boolean' as SchemaObjectType }, + { type: ['boolean' as SchemaObjectType] }, zodRef.description ? { description: zodRef.description } : {}, ...schemas ); @@ -290,7 +293,7 @@ function parseBoolean({ function parseDate({ zodRef, schemas }: ParsingArgs): SchemaObject { return merge( - { type: 'string' as SchemaObjectType, format: 'date-time' }, + { type: ['string' as SchemaObjectType], format: 'date-time' }, zodRef.description ? { description: zodRef.description } : {}, ...schemas ); @@ -299,9 +302,8 @@ function parseDate({ zodRef, schemas }: ParsingArgs): SchemaObject { function parseNull({ zodRef, schemas }: ParsingArgs): SchemaObject { return merge( { - type: 'string' as SchemaObjectType, - format: 'null', - nullable: true, + type: ['string', 'null'] as SchemaObjectType[], + enum: ['null'], }, zodRef.description ? { description: zodRef.description } : {}, ...schemas @@ -355,7 +357,7 @@ function parseArray({ return merge( { - type: 'array' as SchemaObjectType, + type: ['array' as SchemaObjectType], items: generateSchema(zodRef.element, useOutput), ...constraints, }, @@ -370,7 +372,7 @@ function parseLiteral({ }: ParsingArgs>): SchemaObject { return merge( { - type: typeof zodRef._def.value as 'string' | 'number' | 'boolean', + type: [typeof zodRef._def.value as 'string' | 'number' | 'boolean'], enum: [zodRef._def.value], }, zodRef.description ? { description: zodRef.description } : {}, @@ -384,7 +386,7 @@ function parseEnum({ }: ParsingArgs | z.ZodNativeEnum>): SchemaObject { return merge( { - type: typeof Object.values(zodRef._def.values)[0] as 'string' | 'number', + type: [typeof Object.values(zodRef._def.values)[0] as 'string' | 'number'], enum: Object.values(zodRef._def.values), }, zodRef.description ? { description: zodRef.description } : {}, @@ -434,7 +436,7 @@ function parseUnion({ if (type) { return merge( { - type: type as 'string' | 'number' | 'boolean', + type: [type as 'string' | 'number' | 'boolean'], enum: literals.map((literal) => literal._def.value), }, zodRef.description ? { description: zodRef.description } : {}, @@ -565,7 +567,7 @@ export function generateSchema( ): SchemaObject { const { metaOpenApi = {} } = zodRef; const schemas = [ - zodRef.isNullable && zodRef.isNullable() ? { nullable: true } : {}, + zodRef.isNullable && zodRef.isNullable() ? { type: ['null'] } as SchemaObject : {}, ...(Array.isArray(metaOpenApi) ? metaOpenApi : [metaOpenApi]), ]; From 33a01e80a3b1309d2a5ae828c9e60d30ebbcffba Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 23 Nov 2023 10:21:07 +0900 Subject: [PATCH 02/13] exclusiveMinimum/exclusiveMaximum changes --- packages/zod-openapi/src/lib/zod-openapi.spec.ts | 8 ++++---- packages/zod-openapi/src/lib/zod-openapi.ts | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.spec.ts b/packages/zod-openapi/src/lib/zod-openapi.spec.ts index 3f5ae3f..61a9fdd 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.spec.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.spec.ts @@ -197,12 +197,12 @@ describe('zodOpenapi', () => { aNumberMin: { type: ['number'], minimum: 3 }, aNumberMax: { type: ['number'], maximum: 8 }, aNumberInt: { type: ['integer'] }, - aNumberPositive: { type: ['number'], minimum: 0, exclusiveMinimum: 0 }, + aNumberPositive: { type: ['number'], exclusiveMinimum: 0 }, aNumberNonnegative: { type: ['number'], minimum: 0 }, - aNumberNegative: { type: ['number'], maximum: 0, exclusiveMaximum: 0 }, + aNumberNegative: { type: ['number'], exclusiveMaximum: 0 }, aNumberNonpositive: { type: ['number'], maximum: 0 }, - aNumberGt: { type: ['number'], minimum: 5, exclusiveMinimum: 5 }, - aNumberLt: { type: ['number'], maximum: 5, exclusiveMaximum: 5 }, + aNumberGt: { type: ['number'], exclusiveMinimum: 5 }, + aNumberLt: { type: ['number'], exclusiveMaximum: 5 }, aNumberMultipleOf: { type: ['number'], multipleOf: 2 }, }, description: 'Look mah, the horse can count higher than me!', diff --git a/packages/zod-openapi/src/lib/zod-openapi.ts b/packages/zod-openapi/src/lib/zod-openapi.ts index 53fefb5..7356990 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.ts @@ -154,19 +154,19 @@ function parseNumber({ checks.forEach((item) => { switch (item.kind) { case 'max': - baseSchema.maximum = item.value; - // TODO: option to make this always explicit? (false instead of non-existent) - if (!item.inclusive) baseSchema.exclusiveMaximum = item.value; + if (item.inclusive) baseSchema.maximum = item.value; + else baseSchema.exclusiveMaximum = item.value; break; case 'min': - baseSchema.minimum = item.value; - if (!item.inclusive) baseSchema.exclusiveMinimum = item.value; + if (item.inclusive) baseSchema.minimum = item.value; + else baseSchema.exclusiveMinimum = item.value; break; case 'int': baseSchema.type = ['integer']; break; case 'multipleOf': baseSchema.multipleOf = item.value; + break; } }); return merge( From c1f26f18aed7a9783fffa084ad01944cae3beae2 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 23 Nov 2023 10:24:19 +0900 Subject: [PATCH 03/13] examples changes --- apps/example/src/app/cats/cats.dto.ts | 2 +- packages/zod-openapi/src/lib/zod-extensions.spec.ts | 10 +++++----- packages/zod-openapi/src/lib/zod-openapi.spec.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/example/src/app/cats/cats.dto.ts b/apps/example/src/app/cats/cats.dto.ts index 08369c3..2bd9915 100644 --- a/apps/example/src/app/cats/cats.dto.ts +++ b/apps/example/src/app/cats/cats.dto.ts @@ -40,7 +40,7 @@ export const GetCatsParamsZ = extendApi( id: z.string(), }), { - example: { id: 'mouse-terminator-2000' }, + examples: [{ id: 'mouse-terminator-2000' }], } ); diff --git a/packages/zod-openapi/src/lib/zod-extensions.spec.ts b/packages/zod-openapi/src/lib/zod-extensions.spec.ts index 4eae20f..963ae61 100644 --- a/packages/zod-openapi/src/lib/zod-extensions.spec.ts +++ b/packages/zod-openapi/src/lib/zod-extensions.spec.ts @@ -10,21 +10,21 @@ describe('Zod Extensions', () => { extendZodWithOpenApi(z, true) const schema = z.object({ - one: z.string().openapi({example: 'oneOne'}), + one: z.string().openapi({examples: ['oneOne']}), two: z.number(), - }).openapi({example: {one: 'oneOne', two: 42}}) + }).openapi({examples: [{one: 'oneOne', two: 42}]}) const apiSchema = generateSchema(schema); expect(apiSchema).toEqual({ - "example": { + "examples": [{ "one": "oneOne", "two": 42 - }, + }], "properties": { "one": { - "example": "oneOne", + "examples": ["oneOne"], "type": ["string"] }, "two": { diff --git a/packages/zod-openapi/src/lib/zod-openapi.spec.ts b/packages/zod-openapi/src/lib/zod-openapi.spec.ts index 61a9fdd..ee9dd94 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.spec.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.spec.ts @@ -831,7 +831,7 @@ describe('zodOpenapi', () => { email: z.string().email(), phoneNumber: extendApi(z.string().min(10), { description: 'US Phone numbers only', - example: '555-555-5555', + examples: ['555-555-5555'], }), }), { @@ -858,7 +858,7 @@ describe('zodOpenapi', () => { type: ['string'], minLength: 10, description: 'US Phone numbers only', - example: '555-555-5555', + examples: ['555-555-5555'], }, }, required: ['uid', 'firstName', 'email', 'phoneNumber'], From f1e887a98eb6e27350674a74637d1d5a440dd90e Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 23 Nov 2023 10:39:02 +0900 Subject: [PATCH 04/13] Fixup zod-nestjs --- apps/example/src/main.spec.ts | 5 ++--- packages/zod-nestjs/src/lib/create-zod-dto.ts | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/example/src/main.spec.ts b/apps/example/src/main.spec.ts index 6a987d1..38fcf69 100644 --- a/apps/example/src/main.spec.ts +++ b/apps/example/src/main.spec.ts @@ -65,7 +65,7 @@ describe('Cats', () => { .set('Accept', 'application/json') .expect(200) .expect({ - // todo: this should report 3.1.0 + // todo: this should report 3.1.0 (or use openapi 3.0.0 supporting zod-openapi) openapi: '3.0.0', paths: { '/': { @@ -136,8 +136,7 @@ describe('Cats', () => { name: 'id', required: true, in: 'path', - // schema: { type: 'array', items: {'type': 'string'} }, - schema: { type: ['string'] }, + schema: { type: 'string' }, }, ], responses: { diff --git a/packages/zod-nestjs/src/lib/create-zod-dto.ts b/packages/zod-nestjs/src/lib/create-zod-dto.ts index 91402c2..922b0e6 100644 --- a/packages/zod-nestjs/src/lib/create-zod-dto.ts +++ b/packages/zod-nestjs/src/lib/create-zod-dto.ts @@ -76,12 +76,13 @@ export const createZodDto = ( * array to a boolean. */ const schemaObject = properties[key] as SchemaObjectForMetadataFactory; - const schemaObjectWithRequiredField = { + const schemaObjectWithFixedFields = { ...schemaObject, + type: schemaObject.type ? schemaObject.type[0] : schemaObject.type }; - schemaObjectWithRequiredField.required = !!(generatedSchema.required !== undefined, + schemaObjectWithFixedFields.required = !!(generatedSchema.required !== undefined, generatedSchema.required?.includes(key)); - properties[key] = schemaObjectWithRequiredField as any; // TODO: Fix this + properties[key] = schemaObjectWithFixedFields as any; // TODO: Fix this } return properties as Record; } From e360646a8b3ad0dbbfe83605648b537389968419 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 23 Nov 2023 10:46:31 +0900 Subject: [PATCH 05/13] Some other last fixes I just realized --- packages/zod-nestjs/src/lib/create-zod-dto.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/zod-nestjs/src/lib/create-zod-dto.ts b/packages/zod-nestjs/src/lib/create-zod-dto.ts index 922b0e6..6a5a2b8 100644 --- a/packages/zod-nestjs/src/lib/create-zod-dto.ts +++ b/packages/zod-nestjs/src/lib/create-zod-dto.ts @@ -78,7 +78,10 @@ export const createZodDto = ( const schemaObject = properties[key] as SchemaObjectForMetadataFactory; const schemaObjectWithFixedFields = { ...schemaObject, - type: schemaObject.type ? schemaObject.type[0] : schemaObject.type + type: schemaObject.type ? schemaObject.type[0] : schemaObject.type, + nullable: schemaObject.type ? schemaObject.type.includes('null') : false, + exclusiveMinimum: schemaObject.exclusiveMinimum ? true : schemaObject.exclusiveMinimum, + exclusiveMaximum: schemaObject.exclusiveMaximum ? true : schemaObject.exclusiveMaximum, }; schemaObjectWithFixedFields.required = !!(generatedSchema.required !== undefined, generatedSchema.required?.includes(key)); From 89578a79b8919398134fe575b0ccfc0fbcfe82a2 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Thu, 23 Nov 2023 12:53:38 +0900 Subject: [PATCH 06/13] Fix tests --- packages/zod-nestjs/src/lib/create-zod-dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zod-nestjs/src/lib/create-zod-dto.ts b/packages/zod-nestjs/src/lib/create-zod-dto.ts index 6a5a2b8..51556fe 100644 --- a/packages/zod-nestjs/src/lib/create-zod-dto.ts +++ b/packages/zod-nestjs/src/lib/create-zod-dto.ts @@ -79,7 +79,7 @@ export const createZodDto = ( const schemaObjectWithFixedFields = { ...schemaObject, type: schemaObject.type ? schemaObject.type[0] : schemaObject.type, - nullable: schemaObject.type ? schemaObject.type.includes('null') : false, + nullable: schemaObject.type ? schemaObject.type.includes('null') || undefined : undefined, exclusiveMinimum: schemaObject.exclusiveMinimum ? true : schemaObject.exclusiveMinimum, exclusiveMaximum: schemaObject.exclusiveMaximum ? true : schemaObject.exclusiveMaximum, }; From 2fc3f480c041ecd130fb47f73110d57ab2b0accf Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Thu, 18 Jan 2024 14:28:05 -0500 Subject: [PATCH 07/13] Try fixing up the tests after merge --- .../zod-openapi/src/lib/zod-openapi.spec.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.spec.ts b/packages/zod-openapi/src/lib/zod-openapi.spec.ts index d1ebd85..55b3636 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.spec.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.spec.ts @@ -977,7 +977,7 @@ describe('zodOpenapi', () => { type: ['string', 'null'], }, }, - type: 'object', + type: ['object'], }); const schema2 = z.object({ item: extendApi( @@ -988,11 +988,11 @@ describe('zodOpenapi', () => { expect(generateSchema(schema2)).toEqual({ properties: { item: { - type: 'string', + type: ['string'], }, }, required: ['item'], - type: 'object', + type: ['object'], }); }); @@ -1003,13 +1003,17 @@ describe('zodOpenapi', () => { Object { "properties": Object { "field": Object { - "type": "string", + "type": Array [ + "string", + ], }, }, "required": Array [ "field", ], - "type": "object", + "type": Array [ + "object", + ], } `); @@ -1018,13 +1022,17 @@ describe('zodOpenapi', () => { Object { "properties": Object { "field": Object { - "type": "string", + "type": Array [ + "string", + ], }, }, "required": Array [ "field", ], - "type": "object", + "type": Array [ + "object", + ], } `); From 58a40a58f72588777bee85c2e5b289878faf35ad Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Thu, 18 Jan 2024 14:33:00 -0500 Subject: [PATCH 08/13] Partially revert last PR regarding nullability --- packages/zod-openapi/src/lib/zod-openapi.ts | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.ts b/packages/zod-openapi/src/lib/zod-openapi.ts index 4d9f116..4637b3d 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.ts @@ -310,7 +310,7 @@ function parseNull({ zodRef, schemas }: ParsingArgs): SchemaObject { ); } -function parseOptional({ +function parseOptionalNullable({ schemas, zodRef, useOutput, @@ -322,19 +322,6 @@ function parseOptional({ ); } -function parseNullable({ - schemas, - zodRef, - useOutput, -}: ParsingArgs>): SchemaObject { - const schema = generateSchema(zodRef.unwrap(), useOutput); - return merge( - { ...schema, type: [schema.type, 'null'] as SchemaObjectType[] }, - zodRef.description ? { description: zodRef.description } : {}, - ...schemas - ); -} - function parseDefault({ schemas, zodRef, @@ -555,8 +542,8 @@ const workerMap = { ZodBoolean: parseBoolean, ZodDate: parseDate, ZodNull: parseNull, - ZodOptional: parseOptional, - ZodNullable: parseNullable, + ZodOptional: parseOptionalNullable, + ZodNullable: parseOptionalNullable, ZodDefault: parseDefault, ZodArray: parseArray, ZodLiteral: parseLiteral, From 9fbe128867c15f799e6e6542538b8288b7a90ec6 Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Thu, 18 Jan 2024 14:35:42 -0500 Subject: [PATCH 09/13] Actually riff off of that PR --- packages/zod-openapi/src/lib/zod-openapi.ts | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.ts b/packages/zod-openapi/src/lib/zod-openapi.ts index 4637b3d..4e572f3 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.ts @@ -310,7 +310,7 @@ function parseNull({ zodRef, schemas }: ParsingArgs): SchemaObject { ); } -function parseOptionalNullable({ +function parseOptional({ schemas, zodRef, useOutput, @@ -322,6 +322,20 @@ function parseOptionalNullable({ ); } +function parseNullable({ + schemas, + zodRef, + useOutput, +}: ParsingArgs>): SchemaObject { + const schema = generateSchema(zodRef.unwrap(), useOutput); + return merge( + { type: ['null'] as SchemaObjectType[] }, + schema, + zodRef.description ? { description: zodRef.description } : {}, + ...schemas + ); +} + function parseDefault({ schemas, zodRef, @@ -542,8 +556,8 @@ const workerMap = { ZodBoolean: parseBoolean, ZodDate: parseDate, ZodNull: parseNull, - ZodOptional: parseOptionalNullable, - ZodNullable: parseOptionalNullable, + ZodOptional: parseOptional, + ZodNullable: parseNullable, ZodDefault: parseDefault, ZodArray: parseArray, ZodLiteral: parseLiteral, @@ -578,8 +592,6 @@ export function generateSchema( ): SchemaObject { const { metaOpenApi = {} } = zodRef; const schemas = [ - // todo: is this necessary? - zodRef.isNullable && zodRef.isNullable() ? { type: ['null'] } as SchemaObject : {}, ...(Array.isArray(metaOpenApi) ? metaOpenApi : [metaOpenApi]), ]; try { From fd03bed88940c43dc84fd59e35bd0aeb74c640dc Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Thu, 18 Jan 2024 14:38:09 -0500 Subject: [PATCH 10/13] Fix test ordering --- packages/zod-openapi/src/lib/zod-openapi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.ts b/packages/zod-openapi/src/lib/zod-openapi.ts index 4e572f3..e34c506 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.ts @@ -329,8 +329,8 @@ function parseNullable({ }: ParsingArgs>): SchemaObject { const schema = generateSchema(zodRef.unwrap(), useOutput); return merge( - { type: ['null'] as SchemaObjectType[] }, schema, + { type: ['null'] as SchemaObjectType[] }, zodRef.description ? { description: zodRef.description } : {}, ...schemas ); From ccc3490d645e4e9c6873470f6ddc105d3d01fbb8 Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Fri, 19 Jan 2024 00:32:13 -0500 Subject: [PATCH 11/13] Hopefully address PR review --- packages/zod-nestjs/src/lib/create-zod-dto.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/zod-nestjs/src/lib/create-zod-dto.ts b/packages/zod-nestjs/src/lib/create-zod-dto.ts index 51556fe..6842df7 100644 --- a/packages/zod-nestjs/src/lib/create-zod-dto.ts +++ b/packages/zod-nestjs/src/lib/create-zod-dto.ts @@ -78,10 +78,10 @@ export const createZodDto = ( const schemaObject = properties[key] as SchemaObjectForMetadataFactory; const schemaObjectWithFixedFields = { ...schemaObject, - type: schemaObject.type ? schemaObject.type[0] : schemaObject.type, + type: typeof schemaObject.type === "array" ? schemaObject.type.filter((t) => t !== "null")[0] : schemaObject.type, nullable: schemaObject.type ? schemaObject.type.includes('null') || undefined : undefined, - exclusiveMinimum: schemaObject.exclusiveMinimum ? true : schemaObject.exclusiveMinimum, - exclusiveMaximum: schemaObject.exclusiveMaximum ? true : schemaObject.exclusiveMaximum, + exclusiveMinimum: schemaObject.exclusiveMinimum !== undefined ? true : schemaObject.exclusiveMinimum, + exclusiveMaximum: schemaObject.exclusiveMaximum !== undefined ? true : schemaObject.exclusiveMaximum, }; schemaObjectWithFixedFields.required = !!(generatedSchema.required !== undefined, generatedSchema.required?.includes(key)); From 51367ca2a907f8b58edcde09a2ae7419d7b875f2 Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Fri, 19 Jan 2024 00:34:38 -0500 Subject: [PATCH 12/13] I forgot arrays were objects --- packages/zod-nestjs/src/lib/create-zod-dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zod-nestjs/src/lib/create-zod-dto.ts b/packages/zod-nestjs/src/lib/create-zod-dto.ts index 6842df7..3d4c132 100644 --- a/packages/zod-nestjs/src/lib/create-zod-dto.ts +++ b/packages/zod-nestjs/src/lib/create-zod-dto.ts @@ -78,7 +78,7 @@ export const createZodDto = ( const schemaObject = properties[key] as SchemaObjectForMetadataFactory; const schemaObjectWithFixedFields = { ...schemaObject, - type: typeof schemaObject.type === "array" ? schemaObject.type.filter((t) => t !== "null")[0] : schemaObject.type, + type: Array.isArray(schemaObject.type) ? schemaObject.type.filter((t) => t !== "null")[0] : schemaObject.type, nullable: schemaObject.type ? schemaObject.type.includes('null') || undefined : undefined, exclusiveMinimum: schemaObject.exclusiveMinimum !== undefined ? true : schemaObject.exclusiveMinimum, exclusiveMaximum: schemaObject.exclusiveMaximum !== undefined ? true : schemaObject.exclusiveMaximum, From 6fdba79bdcb7905b5883413338059dc5bef02f60 Mon Sep 17 00:00:00 2001 From: EXPLOSION Date: Wed, 20 Mar 2024 13:21:30 +0900 Subject: [PATCH 13/13] Fix test cases --- packages/zod-openapi/src/lib/zod-openapi.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.spec.ts b/packages/zod-openapi/src/lib/zod-openapi.spec.ts index 7054e2b..b3bc608 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.spec.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.spec.ts @@ -977,7 +977,7 @@ describe('zodOpenapi', () => { ) ) ).toEqual({ - type: 'string', + type: ['string'], pattern: '^\\d+$', description: 'Foo description', } satisfies SchemaObject); @@ -997,7 +997,7 @@ describe('zodOpenapi', () => { true ) ).toEqual({ - type: 'number', + type: ['number'], minimum: 0, maximum: 10, description: 'Foo description',