From 9a4d47e2c0c6bbfdbd0016229daca3095b6ae6d0 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sat, 3 Jun 2023 13:52:11 +0900 Subject: [PATCH 1/2] Turn unions of literals into enums --- .../zod-openapi/src/lib/zod-openapi.spec.ts | 31 +++++++++++++++++++ packages/zod-openapi/src/lib/zod-openapi.ts | 26 ++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.spec.ts b/packages/zod-openapi/src/lib/zod-openapi.spec.ts index e94f24a..d2c418c 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.spec.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.spec.ts @@ -827,4 +827,35 @@ describe('zodOpenapi', () => { 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', + enum: ['h', 'i'] + }); + + expect(generateSchema(z.union([z.literal(3), z.literal(4)]))).toEqual({ + 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', + enum: [true, false] + }); + + expect(generateSchema(z.union([z.literal(5), z.literal('i')]))).toEqual({ + oneOf: [ + { + type: 'number', + enum: [5] + }, + { + type: 'string', + enum: ['i'] + } + ] + }); + }) }); diff --git a/packages/zod-openapi/src/lib/zod-openapi.ts b/packages/zod-openapi/src/lib/zod-openapi.ts index e0b10fd..e270e84 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.ts @@ -389,11 +389,31 @@ function parseUnion({ zodRef, useOutput, }: ParsingArgs>): SchemaObject { + const contents = zodRef._def.options; + if (contents.reduce((prev, content) => prev && content._def.typeName === "ZodLiteral", true)) { + const literals = contents as unknown as z.ZodLiteral[]; + const type = literals + .reduce((prev, content) => + !prev || prev === typeof content._def.value ? + typeof content._def.value : + null, + null as null | string + ); + + if (type) { + return merge( + { + type: type as 'string' | 'number' | 'boolean', + enum: literals.map((literal) => literal._def.value) + }, + zodRef.description ? { description: zodRef.description } : {}, + ...schemas + ); + } + } return merge( { - oneOf: ( - zodRef as z.ZodUnion<[z.ZodTypeAny, ...z.ZodTypeAny[]]> - )._def.options.map((schema) => generateSchema(schema, useOutput)), + oneOf: contents.map((schema) => generateSchema(schema, useOutput)), }, zodRef.description ? { description: zodRef.description } : {}, ...schemas From 2b4981b134c52bfc53075826478492736fd23fa1 Mon Sep 17 00:00:00 2001 From: A5rocks Date: Sat, 3 Jun 2023 13:58:18 +0900 Subject: [PATCH 2/2] Clean up a tiny bit --- packages/zod-openapi/src/lib/zod-openapi.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/zod-openapi/src/lib/zod-openapi.ts b/packages/zod-openapi/src/lib/zod-openapi.ts index e270e84..84128c5 100644 --- a/packages/zod-openapi/src/lib/zod-openapi.ts +++ b/packages/zod-openapi/src/lib/zod-openapi.ts @@ -390,7 +390,8 @@ function parseUnion({ useOutput, }: ParsingArgs>): SchemaObject { const contents = zodRef._def.options; - if (contents.reduce((prev, content) => prev && content._def.typeName === "ZodLiteral", true)) { + if (contents.reduce((prev, content) => prev && content._def.typeName === 'ZodLiteral', true)) { + // special case to transform unions of literals into enums const literals = contents as unknown as z.ZodLiteral[]; const type = literals .reduce((prev, content) => @@ -411,6 +412,7 @@ function parseUnion({ ); } } + return merge( { oneOf: contents.map((schema) => generateSchema(schema, useOutput)),