Skip to content

Commit eb1756d

Browse files
Merge pull request #129 from A5rocks/summarize-union-of-literals
Turn unions of literals into enums
2 parents aa7b7d8 + 2b4981b commit eb1756d

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

packages/zod-openapi/src/lib/zod-openapi.spec.ts

+31
Original file line numberDiff line numberDiff line change
@@ -823,4 +823,35 @@ describe('zodOpenapi', () => {
823823
format: 'binary',
824824
});
825825
});
826+
827+
it('can summarize unions of zod literals as an enum', () => {
828+
expect(generateSchema(z.union([z.literal('h'), z.literal('i')]))).toEqual({
829+
type: 'string',
830+
enum: ['h', 'i']
831+
});
832+
833+
expect(generateSchema(z.union([z.literal(3), z.literal(4)]))).toEqual({
834+
type: 'number',
835+
enum: [3, 4]
836+
});
837+
838+
// should this just remove the enum? true | false is exhaustive...
839+
expect(generateSchema(z.union([z.literal(true), z.literal(false)]))).toEqual({
840+
type: 'boolean',
841+
enum: [true, false]
842+
});
843+
844+
expect(generateSchema(z.union([z.literal(5), z.literal('i')]))).toEqual({
845+
oneOf: [
846+
{
847+
type: 'number',
848+
enum: [5]
849+
},
850+
{
851+
type: 'string',
852+
enum: ['i']
853+
}
854+
]
855+
});
856+
})
826857
});

packages/zod-openapi/src/lib/zod-openapi.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -388,11 +388,33 @@ function parseUnion({
388388
zodRef,
389389
useOutput,
390390
}: ParsingArgs<z.ZodUnion<[z.ZodTypeAny, ...z.ZodTypeAny[]]>>): SchemaObject {
391+
const contents = zodRef._def.options;
392+
if (contents.reduce((prev, content) => prev && content._def.typeName === 'ZodLiteral', true)) {
393+
// special case to transform unions of literals into enums
394+
const literals = contents as unknown as z.ZodLiteral<OpenApiZodAny>[];
395+
const type = literals
396+
.reduce((prev, content) =>
397+
!prev || prev === typeof content._def.value ?
398+
typeof content._def.value :
399+
null,
400+
null as null | string
401+
);
402+
403+
if (type) {
404+
return merge(
405+
{
406+
type: type as 'string' | 'number' | 'boolean',
407+
enum: literals.map((literal) => literal._def.value)
408+
},
409+
zodRef.description ? { description: zodRef.description } : {},
410+
...schemas
411+
);
412+
}
413+
}
414+
391415
return merge(
392416
{
393-
oneOf: (
394-
zodRef as z.ZodUnion<[z.ZodTypeAny, ...z.ZodTypeAny[]]>
395-
)._def.options.map((schema) => generateSchema(schema, useOutput)),
417+
oneOf: contents.map((schema) => generateSchema(schema, useOutput)),
396418
},
397419
zodRef.description ? { description: zodRef.description } : {},
398420
...schemas

0 commit comments

Comments
 (0)