From aa5a2e39608b54d4195cd39208c3e212fd340a18 Mon Sep 17 00:00:00 2001 From: callumjhays Date: Mon, 11 May 2026 15:02:15 +1000 Subject: [PATCH 1/3] fix(zod): generate z.discriminatedUnion for multi-value discriminator mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a discriminator maps multiple values to the same schema (e.g. mapping: { one: '#/Bar', two: '#/Bar' }), tryBuildDiscriminatedUnion() returned null because it only read .const from the discriminator property. The IR stores multiple values as { logicalOperator: 'or', items: [...] }, which the function didn't handle, causing a silent fallback to z.union. - Detect the logicalOperator: 'or' pattern and collect const values into an array - Emit z.enum([...]) for multi-value members, z.literal(...) for single - Add discriminator-mapped-many test scenario across all matrix cells (zod v3 + v4 × OpenAPI 3.0.x + 3.1.x) - Fix changeset scope to plugin(zod) per contributing guide --- ...fix-zod-discriminated-union-multi-value.md | 5 +++++ .../mini/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v3/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v4/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../mini/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v3/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v4/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../zod/v3/test/3.0.x.test.ts | 8 +++++++ .../zod/v3/test/3.1.x.test.ts | 8 +++++++ .../mini/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v3/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v4/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../mini/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v3/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../v4/discriminator-mapped-many/zod.gen.ts | 21 +++++++++++++++++++ .../zod/v4/test/3.0.x.test.ts | 8 +++++++ .../zod/v4/test/3.1.x.test.ts | 8 +++++++ .../src/plugins/zod/mini/toAst/union.ts | 4 +++- .../plugins/zod/shared/discriminated-union.ts | 11 +++++++++- .../src/plugins/zod/v3/toAst/union.ts | 4 +++- .../src/plugins/zod/v4/toAst/union.ts | 4 +++- 21 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-zod-discriminated-union-multi-value.md create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts diff --git a/.changeset/fix-zod-discriminated-union-multi-value.md b/.changeset/fix-zod-discriminated-union-multi-value.md new file mode 100644 index 0000000000..ffba685734 --- /dev/null +++ b/.changeset/fix-zod-discriminated-union-multi-value.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +**plugin(zod)**: fix: generate z.discriminatedUnion when discriminator maps multiple values to the same schema diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..a7e388b534 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zBar = z.object({ + foo: z.optional(z.enum(['one', 'two'])) +}); + +export const zBaz = z.object({ + foo: z.optional(z.enum(['three'])) +}); + +export const zSpæcial = z.object({ + foo: z.optional(z.enum(['four'])) +}); + +export const zFoo = z.discriminatedUnion('foo', [ + z.extend(zBar, { foo: z.enum(['one', 'two']) }), + z.extend(zBaz, { foo: z.literal('three') }), + z.extend(zSpæcial, { foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..5b51ab9abe --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..9994b91a08 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..a7e388b534 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zBar = z.object({ + foo: z.optional(z.enum(['one', 'two'])) +}); + +export const zBaz = z.object({ + foo: z.optional(z.enum(['three'])) +}); + +export const zSpæcial = z.object({ + foo: z.optional(z.enum(['four'])) +}); + +export const zFoo = z.discriminatedUnion('foo', [ + z.extend(zBar, { foo: z.enum(['one', 'two']) }), + z.extend(zBaz, { foo: z.literal('three') }), + z.extend(zSpæcial, { foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..5b51ab9abe --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..9994b91a08 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts b/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts index f498a05473..5510e42bab 100644 --- a/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts +++ b/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts @@ -34,6 +34,14 @@ for (const zodVersion of zodVersions) { }), description: 'generates circular schemas', }, + { + config: createConfig({ + input: 'discriminator-mapped-many.yaml', + output: 'discriminator-mapped-many', + }), + description: + 'generates discriminated union when multiple mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts b/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts index 5761d3ba64..2ed15ee169 100644 --- a/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts @@ -152,6 +152,14 @@ for (const zodVersion of zodVersions) { }), description: 'handles oneOf discriminator (falls back to z.union when needed)', }, + { + config: createConfig({ + input: 'discriminator-mapped-many.yaml', + output: 'discriminator-mapped-many', + }), + description: + 'generates discriminated union when multiple mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..a365be0f13 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zBar = z.object({ + foo: z.optional(z.enum(['one', 'two'])) +}); + +export const zBaz = z.object({ + foo: z.optional(z.enum(['three'])) +}); + +export const zSpæcial = z.object({ + foo: z.optional(z.enum(['four'])) +}); + +export const zFoo = z.discriminatedUnion('foo', [ + z.extend(zBar, { foo: z.enum(['one', 'two']) }), + z.extend(zBaz, { foo: z.literal('three') }), + z.extend(zSpæcial, { foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..1b25c19cad --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..facaac6e38 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..a365be0f13 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zBar = z.object({ + foo: z.optional(z.enum(['one', 'two'])) +}); + +export const zBaz = z.object({ + foo: z.optional(z.enum(['three'])) +}); + +export const zSpæcial = z.object({ + foo: z.optional(z.enum(['four'])) +}); + +export const zFoo = z.discriminatedUnion('foo', [ + z.extend(zBar, { foo: z.enum(['one', 'two']) }), + z.extend(zBaz, { foo: z.literal('three') }), + z.extend(zSpæcial, { foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..1b25c19cad --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts new file mode 100644 index 0000000000..facaac6e38 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many/zod.gen.ts @@ -0,0 +1,21 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod'; + +export const zBar = z.object({ + foo: z.enum(['one', 'two']).optional() +}); + +export const zBaz = z.object({ + foo: z.enum(['three']).optional() +}); + +export const zSpæcial = z.object({ + foo: z.enum(['four']).optional() +}); + +export const zFoo = z.discriminatedUnion('foo', [ + zBar.extend({ foo: z.enum(['one', 'two']) }), + zBaz.extend({ foo: z.literal('three') }), + zSpæcial.extend({ foo: z.literal('four') }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts b/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts index 18659712b5..136861b51d 100644 --- a/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts +++ b/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts @@ -42,6 +42,14 @@ for (const zodVersion of zodVersions) { description: 'falls back to z.union() when discriminated union members have allOf (intersection)', }, + { + config: createConfig({ + input: 'discriminator-mapped-many.yaml', + output: 'discriminator-mapped-many', + }), + description: + 'generates discriminated union when multiple mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts b/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts index 703c51f7ab..27104ef815 100644 --- a/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts @@ -185,6 +185,14 @@ for (const zodVersion of zodVersions) { }), description: 'handles oneOf discriminator (falls back to z.union when needed)', }, + { + config: createConfig({ + input: 'discriminator-mapped-many.yaml', + output: 'discriminator-mapped-many', + }), + description: + 'generates discriminated union when multiple mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts index 183ce382c8..a2fc98417e 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts @@ -49,7 +49,9 @@ function baseNode(ctx: UnionResolverContext): Chain { member.refExpression, $.object().prop( discriminatedExpression.discriminatorKey, - $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), + Array.isArray(member.discriminatedValue) + ? $(z).attr(identifiers.enum).call($.fromValue(member.discriminatedValue)) + : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), ), ), ); diff --git a/packages/openapi-ts/src/plugins/zod/shared/discriminated-union.ts b/packages/openapi-ts/src/plugins/zod/shared/discriminated-union.ts index 4e85cc1a13..f98153fe55 100644 --- a/packages/openapi-ts/src/plugins/zod/shared/discriminated-union.ts +++ b/packages/openapi-ts/src/plugins/zod/shared/discriminated-union.ts @@ -40,7 +40,16 @@ export function tryBuildDiscriminatedUnion({ if (schema.logicalOperator !== 'and' || !schema.items || schema.items.length !== 2) return null; const refPart = schema.items[1]!; - const discriminatedValue = schema.items[0]!.properties?.[discriminatorKey]?.const; + const discriminatorProp = schema.items[0]!.properties?.[discriminatorKey]; + let discriminatedValue: unknown; + if (discriminatorProp?.const !== undefined) { + discriminatedValue = discriminatorProp.const; + } else if ( + discriminatorProp?.logicalOperator === 'or' && + discriminatorProp.items?.every((item) => item.const !== undefined) + ) { + discriminatedValue = discriminatorProp.items.map((item) => item.const); + } // lazy references can't be used in a discriminated union directly if (discriminatedValue === undefined || items[index]!.meta.hasLazy) return null; diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts index d588b84f28..2b85eb5815 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts @@ -48,7 +48,9 @@ function baseNode(ctx: UnionResolverContext): Chain { .call( $.object().prop( discriminatedExpression.discriminatorKey, - $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), + Array.isArray(member.discriminatedValue) + ? $(z).attr(identifiers.enum).call($.fromValue(member.discriminatedValue)) + : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), ), ), ); diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts index 7ec7667344..e1486cea74 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts @@ -46,7 +46,9 @@ function baseNode(ctx: UnionResolverContext): Chain { .call( $.object().prop( discriminatedExpression.discriminatorKey, - $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), + Array.isArray(member.discriminatedValue) + ? $(z).attr(identifiers.enum).call($.fromValue(member.discriminatedValue)) + : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), ), ), ); From cef8ae517dd744de544cb13d8eba6f2b2f9e15a6 Mon Sep 17 00:00:00 2001 From: callumjhays Date: Mon, 11 May 2026 16:16:20 +1000 Subject: [PATCH 2/3] test(zod): add failing test for number-typed discriminator in mapped-many MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add OpenAPI specs and test scenarios for discriminator mappings where multiple integer values map to the same schema. The generated snapshots intentionally contain `z.enum([1, 2])` which is invalid — z.enum only accepts string tuples — demonstrating the type-safety gap fixed in the next commit. --- .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod/v3/test/3.0.x.test.ts | 8 +++++ .../zod/v3/test/3.1.x.test.ts | 8 +++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod.gen.ts | 16 ++++++++++ .../zod/v4/test/3.0.x.test.ts | 8 +++++ .../zod/v4/test/3.1.x.test.ts | 8 +++++ .../discriminator-mapped-many-number.yaml | 31 +++++++++++++++++++ .../discriminator-mapped-many-number.yaml | 31 +++++++++++++++++++ 18 files changed, 286 insertions(+) create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts create mode 100644 packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts create mode 100644 specs/3.0.x/discriminator-mapped-many-number.yaml create mode 100644 specs/3.1.x/discriminator-mapped-many-number.yaml diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..5535ccfc18 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zBar = z.object({ + code: z.optional(z.union([z.literal(1), z.literal(2)])) +}); + +export const zBaz = z.object({ + code: z.optional(z.literal(3)) +}); + +export const zFoo = z.discriminatedUnion('code', [ + z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBaz, { code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..6c7182a26f --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..29df77e7c5 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..5535ccfc18 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4-mini'; + +export const zBar = z.object({ + code: z.optional(z.union([z.literal(1), z.literal(2)])) +}); + +export const zBaz = z.object({ + code: z.optional(z.literal(3)) +}); + +export const zFoo = z.discriminatedUnion('code', [ + z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBaz, { code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..6c7182a26f --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..29df77e7c5 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/v4'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts b/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts index 5510e42bab..d2418c0cb3 100644 --- a/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts +++ b/packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts @@ -42,6 +42,14 @@ for (const zodVersion of zodVersions) { description: 'generates discriminated union when multiple mapping values point to same schema', }, + { + config: createConfig({ + input: 'discriminator-mapped-many-number.yaml', + output: 'discriminator-mapped-many-number', + }), + description: + 'generates discriminated union when multiple number mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts b/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts index 2ed15ee169..ed01c96e5f 100644 --- a/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts @@ -160,6 +160,14 @@ for (const zodVersion of zodVersions) { description: 'generates discriminated union when multiple mapping values point to same schema', }, + { + config: createConfig({ + input: 'discriminator-mapped-many-number.yaml', + output: 'discriminator-mapped-many-number', + }), + description: + 'generates discriminated union when multiple number mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..6cd7c118e6 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zBar = z.object({ + code: z.optional(z.union([z.literal(1), z.literal(2)])) +}); + +export const zBaz = z.object({ + code: z.optional(z.literal(3)) +}); + +export const zFoo = z.discriminatedUnion('code', [ + z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBaz, { code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..b64f5341fd --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..4287ed1b42 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..6cd7c118e6 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod/mini'; + +export const zBar = z.object({ + code: z.optional(z.union([z.literal(1), z.literal(2)])) +}); + +export const zBaz = z.object({ + code: z.optional(z.literal(3)) +}); + +export const zFoo = z.discriminatedUnion('code', [ + z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBaz, { code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..b64f5341fd --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { z } from 'zod/v3'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts new file mode 100644 index 0000000000..4287ed1b42 --- /dev/null +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -0,0 +1,16 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import * as z from 'zod'; + +export const zBar = z.object({ + code: z.union([z.literal(1), z.literal(2)]).optional() +}); + +export const zBaz = z.object({ + code: z.literal(3).optional() +}); + +export const zFoo = z.discriminatedUnion('code', [ + zBar.extend({ code: z.enum([1, 2]) }), + zBaz.extend({ code: z.literal(3) }) +]); diff --git a/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts b/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts index 136861b51d..0d004290f3 100644 --- a/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts +++ b/packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts @@ -50,6 +50,14 @@ for (const zodVersion of zodVersions) { description: 'generates discriminated union when multiple mapping values point to same schema', }, + { + config: createConfig({ + input: 'discriminator-mapped-many-number.yaml', + output: 'discriminator-mapped-many-number', + }), + description: + 'generates discriminated union when multiple number mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts b/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts index 27104ef815..3232387292 100644 --- a/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts @@ -193,6 +193,14 @@ for (const zodVersion of zodVersions) { description: 'generates discriminated union when multiple mapping values point to same schema', }, + { + config: createConfig({ + input: 'discriminator-mapped-many-number.yaml', + output: 'discriminator-mapped-many-number', + }), + description: + 'generates discriminated union when multiple number mapping values point to same schema', + }, { config: createConfig({ input: 'enum-null.json', diff --git a/specs/3.0.x/discriminator-mapped-many-number.yaml b/specs/3.0.x/discriminator-mapped-many-number.yaml new file mode 100644 index 0000000000..6c5fc9e8ba --- /dev/null +++ b/specs/3.0.x/discriminator-mapped-many-number.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: OpenAPI 3.0.0 discriminator mapped many (number) example + version: 1 +components: + schemas: + Foo: + oneOf: + - $ref: '#/components/schemas/Bar' + - $ref: '#/components/schemas/Baz' + discriminator: + propertyName: code + mapping: + '1': '#/components/schemas/Bar' + '2': '#/components/schemas/Bar' + '3': '#/components/schemas/Baz' + Bar: + type: object + properties: + code: + type: integer + enum: + - 1 + - 2 + Baz: + type: object + properties: + code: + type: integer + enum: + - 3 diff --git a/specs/3.1.x/discriminator-mapped-many-number.yaml b/specs/3.1.x/discriminator-mapped-many-number.yaml new file mode 100644 index 0000000000..54fa8ae32e --- /dev/null +++ b/specs/3.1.x/discriminator-mapped-many-number.yaml @@ -0,0 +1,31 @@ +openapi: 3.1.0 +info: + title: OpenAPI 3.1.0 discriminator mapped many (number) example + version: 1 +components: + schemas: + Foo: + oneOf: + - $ref: '#/components/schemas/Bar' + - $ref: '#/components/schemas/Baz' + discriminator: + propertyName: code + mapping: + '1': '#/components/schemas/Bar' + '2': '#/components/schemas/Bar' + '3': '#/components/schemas/Baz' + Bar: + type: object + properties: + code: + type: integer + enum: + - 1 + - 2 + Baz: + type: object + properties: + code: + type: integer + enum: + - 3 From 38101c47afe888174d63840f4444956b1a35d53b Mon Sep 17 00:00:00 2001 From: callumjhays Date: Mon, 11 May 2026 16:18:02 +1000 Subject: [PATCH 3/3] fix(zod): use z.union of z.literal for non-string multi-value discriminators z.enum only accepts [string, ...string[]], so emitting z.enum([1, 2]) for integer discriminator values is a TypeScript type error. When a discriminated union member maps to multiple non-string values, emit z.union([z.literal(v1), z.literal(v2)]) instead. Fixes the array branch in v3, v4, and mini union emitters. --- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../zod.gen.ts | 2 +- .../src/plugins/zod/mini/toAst/union.ts | 12 +++++++++- .../src/plugins/zod/v3/toAst/union.ts | 24 ++++++++++++------- .../src/plugins/zod/v4/toAst/union.ts | 24 ++++++++++++------- 15 files changed, 55 insertions(+), 29 deletions(-) diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts index 5535ccfc18..b3327ba5b3 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBar, { code: z.union([z.literal(1), z.literal(2)]) }), z.extend(zBaz, { code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts index 6c7182a26f..d8148660c1 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts index 29df77e7c5..0417778aea 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts index 5535ccfc18..b3327ba5b3 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBar, { code: z.union([z.literal(1), z.literal(2)]) }), z.extend(zBaz, { code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts index 6c7182a26f..d8148660c1 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts index 29df77e7c5..0417778aea 100644 --- a/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts index 6cd7c118e6..5fc0bdc133 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBar, { code: z.union([z.literal(1), z.literal(2)]) }), z.extend(zBaz, { code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts index b64f5341fd..da2629c131 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts index 4287ed1b42..b11c5e2390 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.0.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts index 6cd7c118e6..5fc0bdc133 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/mini/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - z.extend(zBar, { code: z.enum([1, 2]) }), + z.extend(zBar, { code: z.union([z.literal(1), z.literal(2)]) }), z.extend(zBaz, { code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts index b64f5341fd..da2629c131 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v3/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts index 4287ed1b42..b11c5e2390 100644 --- a/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts +++ b/packages/openapi-ts-tests/zod/v4/__snapshots__/3.1.x/v4/discriminator-mapped-many-number/zod.gen.ts @@ -11,6 +11,6 @@ export const zBaz = z.object({ }); export const zFoo = z.discriminatedUnion('code', [ - zBar.extend({ code: z.enum([1, 2]) }), + zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }), zBaz.extend({ code: z.literal(3) }) ]); diff --git a/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts b/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts index a2fc98417e..45cc711726 100644 --- a/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts +++ b/packages/openapi-ts/src/plugins/zod/mini/toAst/union.ts @@ -50,7 +50,17 @@ function baseNode(ctx: UnionResolverContext): Chain { $.object().prop( discriminatedExpression.discriminatorKey, Array.isArray(member.discriminatedValue) - ? $(z).attr(identifiers.enum).call($.fromValue(member.discriminatedValue)) + ? member.discriminatedValue.every((v) => typeof v === 'string') + ? $(z).attr(identifiers.enum).call($.fromValue(member.discriminatedValue)) + : $(z) + .attr(identifiers.union) + .call( + $.array( + ...member.discriminatedValue.map((v) => + $(z).attr(identifiers.literal).call($.fromValue(v)), + ), + ), + ) : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), ), ), diff --git a/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts b/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts index 2b85eb5815..0536cbe3f9 100644 --- a/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts +++ b/packages/openapi-ts/src/plugins/zod/v3/toAst/union.ts @@ -43,16 +43,24 @@ function baseNode(ctx: UnionResolverContext): Chain { if (discriminatedExpression) { const unionMembers = discriminatedExpression.members.map((member) => - member.refExpression - .attr(identifiers.extend) - .call( - $.object().prop( - discriminatedExpression.discriminatorKey, - Array.isArray(member.discriminatedValue) + member.refExpression.attr(identifiers.extend).call( + $.object().prop( + discriminatedExpression.discriminatorKey, + Array.isArray(member.discriminatedValue) + ? member.discriminatedValue.every((v) => typeof v === 'string') ? $(z).attr(identifiers.enum).call($.fromValue(member.discriminatedValue)) - : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), - ), + : $(z) + .attr(identifiers.union) + .call( + $.array( + ...member.discriminatedValue.map((v) => + $(z).attr(identifiers.literal).call($.fromValue(v)), + ), + ), + ) + : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), ), + ), ); return $(z) diff --git a/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts b/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts index e1486cea74..a6e8078c11 100644 --- a/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts +++ b/packages/openapi-ts/src/plugins/zod/v4/toAst/union.ts @@ -41,16 +41,24 @@ function baseNode(ctx: UnionResolverContext): Chain { if (discriminatedExpression) { const unionMembers = discriminatedExpression.members.map((member) => - member.refExpression - .attr(identifiers.extend) - .call( - $.object().prop( - discriminatedExpression.discriminatorKey, - Array.isArray(member.discriminatedValue) + member.refExpression.attr(identifiers.extend).call( + $.object().prop( + discriminatedExpression.discriminatorKey, + Array.isArray(member.discriminatedValue) + ? member.discriminatedValue.every((v) => typeof v === 'string') ? $(z).attr(identifiers.enum).call($.fromValue(member.discriminatedValue)) - : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), - ), + : $(z) + .attr(identifiers.union) + .call( + $.array( + ...member.discriminatedValue.map((v) => + $(z).attr(identifiers.literal).call($.fromValue(v)), + ), + ), + ) + : $(z).attr(identifiers.literal).call($.fromValue(member.discriminatedValue)), ), + ), ); expression = $(z)