Skip to content

Commit 5c25117

Browse files
authored
feat(zod): generate z.discriminatedUnion() for oneOf/anyOf with discriminator mapping
Agent-Logs-Url: https://github.com/hey-api/openapi-ts/sessions/355f99e2-8a8e-4191-a822-7de8f991a49c
1 parent 810eb32 commit 5c25117

35 files changed

Lines changed: 956 additions & 70 deletions

File tree

packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v3/default/zod.gen.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,9 @@ export const zModelSquare = z.object({
423423
/**
424424
* This is a model with one property with a 'one of' relationship where the options are not $ref
425425
*/
426-
export const zCompositionWithOneOfDiscriminator = z.union([
427-
z.object({
428-
kind: z.literal('circle')
429-
}).and(zModelCircle),
430-
z.object({
431-
kind: z.literal('square')
432-
}).and(zModelSquare)
426+
export const zCompositionWithOneOfDiscriminator = z.discriminatedUnion('kind', [
427+
zModelCircle.extend({ kind: z.literal('circle') }),
428+
zModelSquare.extend({ kind: z.literal('square') })
433429
]);
434430

435431
/**

packages/openapi-ts-tests/zod/v3/__snapshots__/3.0.x/v4/default/zod.gen.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,9 @@ export const zModelSquare = z.object({
423423
/**
424424
* This is a model with one property with a 'one of' relationship where the options are not $ref
425425
*/
426-
export const zCompositionWithOneOfDiscriminator = z.union([
427-
z.object({
428-
kind: z.literal('circle')
429-
}).and(zModelCircle),
430-
z.object({
431-
kind: z.literal('square')
432-
}).and(zModelSquare)
426+
export const zCompositionWithOneOfDiscriminator = z.discriminatedUnion('kind', [
427+
zModelCircle.extend({ kind: z.literal('circle') }),
428+
zModelSquare.extend({ kind: z.literal('square') })
433429
]);
434430

435431
/**
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4-mini';
4+
5+
export const zFoo = z.object({
6+
id: z.string()
7+
});
8+
9+
export const zBar = z.intersection(zFoo, z.object({
10+
bar: z.optional(z.string()),
11+
id: z.literal('Bar')
12+
}));
13+
14+
export const zBaz = z.intersection(zFoo, z.object({
15+
baz: z.optional(z.string()),
16+
id: z.literal('Baz')
17+
}));
18+
19+
export const zQux = z.intersection(zFoo, z.object({
20+
qux: z.optional(z.boolean()),
21+
id: z.literal('Qux')
22+
}));
23+
24+
export const zFooMapped = z.object({
25+
id: z.string()
26+
});
27+
28+
export const zBarMapped = z.intersection(zFooMapped, z.object({
29+
bar: z.optional(z.string()),
30+
id: z.literal('bar')
31+
}));
32+
33+
export const zBazMapped = z.intersection(zFooMapped, z.object({
34+
baz: z.optional(z.string()),
35+
id: z.literal('baz')
36+
}));
37+
38+
export const zQuxMapped = z.intersection(zFooMapped, z.object({
39+
qux: z.optional(z.boolean()),
40+
id: z.literal('QuxMapped')
41+
}));
42+
43+
export const zBarUnion = z.object({
44+
id: z.optional(z.string()),
45+
bar: z.optional(z.string())
46+
});
47+
48+
export const zBazUnion = z.object({
49+
id: z.optional(z.string()),
50+
baz: z.optional(z.string())
51+
});
52+
53+
export const zFooUnion = z.union([
54+
z.intersection(z.object({
55+
id: z.literal('bar')
56+
}), zBarUnion),
57+
z.intersection(z.object({
58+
id: z.literal('baz')
59+
}), zBazUnion)
60+
]);
61+
62+
export const zQuxExtend = zFooUnion;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4-mini';
4+
5+
export const zQuux = z.enum(['Bar', 'Baz']);
6+
7+
export const zQux = z.object({
8+
id: z.string(),
9+
type: zQuux
10+
});
11+
12+
export const zBaz = zQux;
13+
14+
export const zBar = zQux;
15+
16+
export const zFoo = z.union([
17+
z.intersection(z.object({
18+
type: z.optional(z.literal('Bar'))
19+
}), zBar),
20+
z.intersection(z.object({
21+
type: z.optional(z.literal('Baz'))
22+
}), zBaz)
23+
]);
24+
25+
export const zSpæcial = zQux;
26+
27+
export const zQuuz = z.union([
28+
z.intersection(z.object({
29+
type: z.optional(z.literal('bar'))
30+
}), zBar),
31+
z.intersection(z.object({
32+
type: z.optional(z.literal('baz'))
33+
}), zBaz),
34+
z.intersection(z.object({
35+
type: z.optional(z.literal('non-ascii'))
36+
}), zSpæcial)
37+
]);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import * as z from 'zod/v4-mini';
4+
5+
export const zQuux = z.enum(['Bar', 'Baz']);
6+
7+
export const zQux = z.object({
8+
id: z.string(),
9+
type: zQuux
10+
});
11+
12+
export const zBaz = zQux;
13+
14+
export const zBar = zQux;
15+
16+
export const zFoo = z.union([
17+
z.intersection(z.object({
18+
type: z.literal('Bar')
19+
}), zBar),
20+
z.intersection(z.object({
21+
type: z.literal('Baz')
22+
}), zBaz)
23+
]);
24+
25+
export const zSpæcial = zQux;
26+
27+
export const zQuuz = z.union([
28+
z.intersection(z.object({
29+
type: z.literal('bar')
30+
}), zBar),
31+
z.intersection(z.object({
32+
type: z.literal('baz')
33+
}), zBaz),
34+
z.intersection(z.object({
35+
type: z.literal('non-ascii')
36+
}), zSpæcial)
37+
]);

packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v3/default/zod.gen.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,9 @@ export const zModelSquare = z.object({
423423
/**
424424
* This is a model with one property with a 'one of' relationship where the options are not $ref
425425
*/
426-
export const zCompositionWithOneOfDiscriminator = z.union([
427-
z.object({
428-
kind: z.literal('circle')
429-
}).and(zModelCircle),
430-
z.object({
431-
kind: z.literal('square')
432-
}).and(zModelSquare)
426+
export const zCompositionWithOneOfDiscriminator = z.discriminatedUnion('kind', [
427+
zModelCircle.extend({ kind: z.literal('circle') }),
428+
zModelSquare.extend({ kind: z.literal('square') })
433429
]);
434430

435431
/**
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { z } from 'zod';
4+
5+
export const zFoo = z.object({
6+
id: z.string()
7+
});
8+
9+
export const zBar = zFoo.and(z.object({
10+
bar: z.string().optional(),
11+
id: z.literal('Bar')
12+
}));
13+
14+
export const zBaz = zFoo.and(z.object({
15+
baz: z.string().optional(),
16+
id: z.literal('Baz')
17+
}));
18+
19+
export const zQux = zFoo.and(z.object({
20+
qux: z.boolean().optional(),
21+
id: z.literal('Qux')
22+
}));
23+
24+
export const zFooMapped = z.object({
25+
id: z.string()
26+
});
27+
28+
export const zBarMapped = zFooMapped.and(z.object({
29+
bar: z.string().optional(),
30+
id: z.literal('bar')
31+
}));
32+
33+
export const zBazMapped = zFooMapped.and(z.object({
34+
baz: z.string().optional(),
35+
id: z.literal('baz')
36+
}));
37+
38+
export const zQuxMapped = zFooMapped.and(z.object({
39+
qux: z.boolean().optional(),
40+
id: z.literal('QuxMapped')
41+
}));
42+
43+
export const zBarUnion = z.object({
44+
id: z.string().optional(),
45+
bar: z.string().optional()
46+
});
47+
48+
export const zBazUnion = z.object({
49+
id: z.string().optional(),
50+
baz: z.string().optional()
51+
});
52+
53+
export const zFooUnion = z.discriminatedUnion('id', [
54+
zBarUnion.extend({ id: z.literal('bar') }),
55+
zBazUnion.extend({ id: z.literal('baz') })
56+
]);
57+
58+
export const zQuxExtend = zFooUnion;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { z } from 'zod';
4+
5+
export const zQuux = z.enum(['Bar', 'Baz']);
6+
7+
export const zQux = z.object({
8+
id: z.string(),
9+
type: zQuux
10+
});
11+
12+
export const zBaz = zQux;
13+
14+
export const zBar = zQux;
15+
16+
export const zFoo = z.discriminatedUnion('type', [
17+
zBar.extend({ type: z.literal('Bar') }),
18+
zBaz.extend({ type: z.literal('Baz') })
19+
]);
20+
21+
export const zSpæcial = zQux;
22+
23+
export const zQuuz = z.discriminatedUnion('type', [
24+
zBar.extend({ type: z.literal('bar') }),
25+
zBaz.extend({ type: z.literal('baz') }),
26+
zSpæcial.extend({ type: z.literal('non-ascii') })
27+
]);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { z } from 'zod';
4+
5+
export const zQuux = z.enum(['Bar', 'Baz']);
6+
7+
export const zQux = z.object({
8+
id: z.string(),
9+
type: zQuux
10+
});
11+
12+
export const zBaz = zQux;
13+
14+
export const zBar = zQux;
15+
16+
export const zFoo = z.discriminatedUnion('type', [
17+
zBar.extend({ type: z.literal('Bar') }),
18+
zBaz.extend({ type: z.literal('Baz') })
19+
]);
20+
21+
export const zSpæcial = zQux;
22+
23+
export const zQuuz = z.discriminatedUnion('type', [
24+
zBar.extend({ type: z.literal('bar') }),
25+
zBaz.extend({ type: z.literal('baz') }),
26+
zSpæcial.extend({ type: z.literal('non-ascii') })
27+
]);

packages/openapi-ts-tests/zod/v3/__snapshots__/3.1.x/v4/default/zod.gen.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,9 @@ export const zModelSquare = z.object({
423423
/**
424424
* This is a model with one property with a 'one of' relationship where the options are not $ref
425425
*/
426-
export const zCompositionWithOneOfDiscriminator = z.union([
427-
z.object({
428-
kind: z.literal('circle')
429-
}).and(zModelCircle),
430-
z.object({
431-
kind: z.literal('square')
432-
}).and(zModelSquare)
426+
export const zCompositionWithOneOfDiscriminator = z.discriminatedUnion('kind', [
427+
zModelCircle.extend({ kind: z.literal('circle') }),
428+
zModelSquare.extend({ kind: z.literal('square') })
433429
]);
434430

435431
/**

0 commit comments

Comments
 (0)