Skip to content

Commit 6de0b51

Browse files
committed
fix(zod): generate z.discriminatedUnion for multi-value discriminator mappings
When an OpenAPI discriminator maps multiple values to the same schema (e.g. mapping: { one: Bar, two: Bar }), the zod plugin previously fell back to z.union(), losing discriminator-aware narrowing. - Extend tryBuildDiscriminatedUnion to recognise the IR's logicalOperator:'or' of const-items shape for multi-value mappings - Add buildDiscriminatorExpression helper: z.literal for scalars, z.enum for all-string arrays, z.union of literals for non-string arrays - v3 dialect expands non-string multi-value members to one branch per literal (ZodUnion is not a valid v3 discriminator node) - v4 and mini emit z.union of literals directly (valid via _zod.values flattening in $ZodDiscriminatedUnion) - Add discriminator-mapped-many and discriminator-mapped-many-number scenarios to all four test cells (zod v3/v4 x OpenAPI 3.0.x/3.1.x)
1 parent 66720d9 commit 6de0b51

35 files changed

Lines changed: 675 additions & 16 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hey-api/openapi-ts': patch
3+
---
4+
5+
fix(zod): generate z.discriminatedUnion when discriminator maps multiple values to the same schema
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4-mini';
4+
5+
export const zBar = z.object({
6+
code: z.optional(z.union([z.literal(1), z.literal(2)]))
7+
});
8+
9+
export const zBaz = z.object({
10+
code: z.optional(z.literal(3))
11+
});
12+
13+
export const zFoo = z.discriminatedUnion('code', [
14+
z.extend(zBar, { code: z.union([z.literal(1), z.literal(2)]) }),
15+
z.extend(zBaz, { code: z.literal(3) })
16+
]);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4-mini';
4+
5+
export const zBar = z.object({
6+
foo: z.optional(z.enum(['one', 'two']))
7+
});
8+
9+
export const zBaz = z.object({
10+
foo: z.optional(z.enum(['three']))
11+
});
12+
13+
export const zSpæcial = z.object({
14+
foo: z.optional(z.enum(['four']))
15+
});
16+
17+
export const zFoo = z.discriminatedUnion('foo', [
18+
z.extend(zBar, { foo: z.enum(['one', 'two']) }),
19+
z.extend(zBaz, { foo: z.literal('three') }),
20+
z.extend(zSpæcial, { foo: z.literal('four') })
21+
]);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { z } from 'zod';
4+
5+
export const zBar = z.object({
6+
code: z.union([z.literal(1), z.literal(2)]).optional()
7+
});
8+
9+
export const zBaz = z.object({
10+
code: z.literal(3).optional()
11+
});
12+
13+
export const zFoo = z.discriminatedUnion('code', [
14+
zBar.extend({ code: z.literal(1) }),
15+
zBar.extend({ code: z.literal(2) }),
16+
zBaz.extend({ code: z.literal(3) })
17+
]);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { z } from 'zod';
4+
5+
export const zBar = z.object({
6+
foo: z.enum(['one', 'two']).optional()
7+
});
8+
9+
export const zBaz = z.object({
10+
foo: z.enum(['three']).optional()
11+
});
12+
13+
export const zSpæcial = z.object({
14+
foo: z.enum(['four']).optional()
15+
});
16+
17+
export const zFoo = z.discriminatedUnion('foo', [
18+
zBar.extend({ foo: z.enum(['one', 'two']) }),
19+
zBaz.extend({ foo: z.literal('three') }),
20+
zSpæcial.extend({ foo: z.literal('four') })
21+
]);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4';
4+
5+
export const zBar = z.object({
6+
code: z.union([z.literal(1), z.literal(2)]).optional()
7+
});
8+
9+
export const zBaz = z.object({
10+
code: z.literal(3).optional()
11+
});
12+
13+
export const zFoo = z.discriminatedUnion('code', [
14+
zBar.extend({ code: z.union([z.literal(1), z.literal(2)]) }),
15+
zBaz.extend({ code: z.literal(3) })
16+
]);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4';
4+
5+
export const zBar = z.object({
6+
foo: z.enum(['one', 'two']).optional()
7+
});
8+
9+
export const zBaz = z.object({
10+
foo: z.enum(['three']).optional()
11+
});
12+
13+
export const zSpæcial = z.object({
14+
foo: z.enum(['four']).optional()
15+
});
16+
17+
export const zFoo = z.discriminatedUnion('foo', [
18+
zBar.extend({ foo: z.enum(['one', 'two']) }),
19+
zBaz.extend({ foo: z.literal('three') }),
20+
zSpæcial.extend({ foo: z.literal('four') })
21+
]);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4-mini';
4+
5+
export const zBar = z.object({
6+
code: z.optional(z.union([z.literal(1), z.literal(2)]))
7+
});
8+
9+
export const zBaz = z.object({
10+
code: z.optional(z.literal(3))
11+
});
12+
13+
export const zFoo = z.discriminatedUnion('code', [
14+
z.extend(zBar, { code: z.union([z.literal(1), z.literal(2)]) }),
15+
z.extend(zBaz, { code: z.literal(3) })
16+
]);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4-mini';
4+
5+
export const zBar = z.object({
6+
foo: z.optional(z.enum(['one', 'two']))
7+
});
8+
9+
export const zBaz = z.object({
10+
foo: z.optional(z.enum(['three']))
11+
});
12+
13+
export const zSpæcial = z.object({
14+
foo: z.optional(z.enum(['four']))
15+
});
16+
17+
export const zFoo = z.discriminatedUnion('foo', [
18+
z.extend(zBar, { foo: z.enum(['one', 'two']) }),
19+
z.extend(zBaz, { foo: z.literal('three') }),
20+
z.extend(zSpæcial, { foo: z.literal('four') })
21+
]);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { z } from 'zod';
4+
5+
export const zBar = z.object({
6+
code: z.union([z.literal(1), z.literal(2)]).optional()
7+
});
8+
9+
export const zBaz = z.object({
10+
code: z.literal(3).optional()
11+
});
12+
13+
export const zFoo = z.discriminatedUnion('code', [
14+
zBar.extend({ code: z.literal(1) }),
15+
zBar.extend({ code: z.literal(2) }),
16+
zBaz.extend({ code: z.literal(3) })
17+
]);

0 commit comments

Comments
 (0)