Skip to content

Commit 7b53593

Browse files
authored
Merge pull request #3813 from hey-api/copilot/fix-typescript-errors-zod-schemas
fix(zod): fall back to `z.union()` when discriminated union members are `ZodIntersection`
2 parents 93d9698 + b88e8b5 commit 7b53593

20 files changed

Lines changed: 413 additions & 4 deletions

File tree

.changeset/mean-paths-tell.md

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+
**plugin(zod)**: fix: fallback `.discriminatedUnion` to `.union` if members contain intersection
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/mini';
4+
5+
export const zUserNotificationDeleteContentBase = z.object({
6+
name: z.string()
7+
});
8+
9+
export const zUserNotificationDeleteContentError = z.intersection(zUserNotificationDeleteContentBase, z.object({
10+
finishedAt: z.iso.datetime(),
11+
error: z.string()
12+
}));
13+
14+
export const zUserNotificationDeleteContentRunning = z.intersection(zUserNotificationDeleteContentBase, z.object({
15+
startedAt: z.iso.datetime()
16+
}));
17+
18+
export const zUserNotificationDeleteContentSuccess = z.intersection(zUserNotificationDeleteContentBase, z.object({
19+
finishedAt: z.iso.datetime()
20+
}));
21+
22+
export const zUserNotificationDeleteContent = z.union([
23+
z.intersection(z.object({
24+
status: z.literal('running')
25+
}), zUserNotificationDeleteContentRunning),
26+
z.intersection(z.object({
27+
status: z.literal('success')
28+
}), zUserNotificationDeleteContentSuccess),
29+
z.intersection(z.object({
30+
status: z.literal('error')
31+
}), zUserNotificationDeleteContentError)
32+
]);
33+
34+
/**
35+
* success
36+
*/
37+
export const zGetNotificationResponse = zUserNotificationDeleteContent;
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 { z } from 'zod/v3';
4+
5+
export const zUserNotificationDeleteContentBase = z.object({
6+
name: z.string()
7+
});
8+
9+
export const zUserNotificationDeleteContentError = zUserNotificationDeleteContentBase.and(z.object({
10+
finishedAt: z.string().datetime(),
11+
error: z.string()
12+
}));
13+
14+
export const zUserNotificationDeleteContentRunning = zUserNotificationDeleteContentBase.and(z.object({
15+
startedAt: z.string().datetime()
16+
}));
17+
18+
export const zUserNotificationDeleteContentSuccess = zUserNotificationDeleteContentBase.and(z.object({
19+
finishedAt: z.string().datetime()
20+
}));
21+
22+
export const zUserNotificationDeleteContent = z.union([
23+
z.object({
24+
status: z.literal('running')
25+
}).and(zUserNotificationDeleteContentRunning),
26+
z.object({
27+
status: z.literal('success')
28+
}).and(zUserNotificationDeleteContentSuccess),
29+
z.object({
30+
status: z.literal('error')
31+
}).and(zUserNotificationDeleteContentError)
32+
]);
33+
34+
/**
35+
* success
36+
*/
37+
export const zGetNotificationResponse = zUserNotificationDeleteContent;
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';
4+
5+
export const zUserNotificationDeleteContentBase = z.object({
6+
name: z.string()
7+
});
8+
9+
export const zUserNotificationDeleteContentError = zUserNotificationDeleteContentBase.and(z.object({
10+
finishedAt: z.iso.datetime(),
11+
error: z.string()
12+
}));
13+
14+
export const zUserNotificationDeleteContentRunning = zUserNotificationDeleteContentBase.and(z.object({
15+
startedAt: z.iso.datetime()
16+
}));
17+
18+
export const zUserNotificationDeleteContentSuccess = zUserNotificationDeleteContentBase.and(z.object({
19+
finishedAt: z.iso.datetime()
20+
}));
21+
22+
export const zUserNotificationDeleteContent = z.union([
23+
z.object({
24+
status: z.literal('running')
25+
}).and(zUserNotificationDeleteContentRunning),
26+
z.object({
27+
status: z.literal('success')
28+
}).and(zUserNotificationDeleteContentSuccess),
29+
z.object({
30+
status: z.literal('error')
31+
}).and(zUserNotificationDeleteContentError)
32+
]);
33+
34+
/**
35+
* success
36+
*/
37+
export const zGetNotificationResponse = zUserNotificationDeleteContent;
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/mini';
4+
5+
export const zUserNotificationDeleteContentBase = z.object({
6+
name: z.string()
7+
});
8+
9+
export const zUserNotificationDeleteContentError = z.intersection(zUserNotificationDeleteContentBase, z.object({
10+
finishedAt: z.iso.datetime(),
11+
error: z.string()
12+
}));
13+
14+
export const zUserNotificationDeleteContentRunning = z.intersection(zUserNotificationDeleteContentBase, z.object({
15+
startedAt: z.iso.datetime()
16+
}));
17+
18+
export const zUserNotificationDeleteContentSuccess = z.intersection(zUserNotificationDeleteContentBase, z.object({
19+
finishedAt: z.iso.datetime()
20+
}));
21+
22+
export const zUserNotificationDeleteContent = z.union([
23+
z.intersection(z.object({
24+
status: z.literal('running')
25+
}), zUserNotificationDeleteContentRunning),
26+
z.intersection(z.object({
27+
status: z.literal('success')
28+
}), zUserNotificationDeleteContentSuccess),
29+
z.intersection(z.object({
30+
status: z.literal('error')
31+
}), zUserNotificationDeleteContentError)
32+
]);
33+
34+
/**
35+
* success
36+
*/
37+
export const zGetNotificationResponse = zUserNotificationDeleteContent;
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 { z } from 'zod/v3';
4+
5+
export const zUserNotificationDeleteContentBase = z.object({
6+
name: z.string()
7+
});
8+
9+
export const zUserNotificationDeleteContentError = zUserNotificationDeleteContentBase.and(z.object({
10+
finishedAt: z.string().datetime(),
11+
error: z.string()
12+
}));
13+
14+
export const zUserNotificationDeleteContentRunning = zUserNotificationDeleteContentBase.and(z.object({
15+
startedAt: z.string().datetime()
16+
}));
17+
18+
export const zUserNotificationDeleteContentSuccess = zUserNotificationDeleteContentBase.and(z.object({
19+
finishedAt: z.string().datetime()
20+
}));
21+
22+
export const zUserNotificationDeleteContent = z.union([
23+
z.object({
24+
status: z.literal('running')
25+
}).and(zUserNotificationDeleteContentRunning),
26+
z.object({
27+
status: z.literal('success')
28+
}).and(zUserNotificationDeleteContentSuccess),
29+
z.object({
30+
status: z.literal('error')
31+
}).and(zUserNotificationDeleteContentError)
32+
]);
33+
34+
/**
35+
* success
36+
*/
37+
export const zGetNotificationResponse = zUserNotificationDeleteContent;
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';
4+
5+
export const zUserNotificationDeleteContentBase = z.object({
6+
name: z.string()
7+
});
8+
9+
export const zUserNotificationDeleteContentError = zUserNotificationDeleteContentBase.and(z.object({
10+
finishedAt: z.iso.datetime(),
11+
error: z.string()
12+
}));
13+
14+
export const zUserNotificationDeleteContentRunning = zUserNotificationDeleteContentBase.and(z.object({
15+
startedAt: z.iso.datetime()
16+
}));
17+
18+
export const zUserNotificationDeleteContentSuccess = zUserNotificationDeleteContentBase.and(z.object({
19+
finishedAt: z.iso.datetime()
20+
}));
21+
22+
export const zUserNotificationDeleteContent = z.union([
23+
z.object({
24+
status: z.literal('running')
25+
}).and(zUserNotificationDeleteContentRunning),
26+
z.object({
27+
status: z.literal('success')
28+
}).and(zUserNotificationDeleteContentSuccess),
29+
z.object({
30+
status: z.literal('error')
31+
}).and(zUserNotificationDeleteContentError)
32+
]);
33+
34+
/**
35+
* success
36+
*/
37+
export const zGetNotificationResponse = zUserNotificationDeleteContent;

packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ for (const zodVersion of zodVersions) {
3434
}),
3535
description: 'generates circular schemas',
3636
},
37+
{
38+
config: createConfig({
39+
input: 'discriminator-allof-member.yaml',
40+
output: 'discriminator-allof-member',
41+
}),
42+
description:
43+
'falls back to z.union() when discriminated union members have allOf (intersection)',
44+
},
3745
{
3846
config: createConfig({
3947
input: 'enum-null.json',

packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ for (const zodVersion of zodVersions) {
163163
}),
164164
description: 'generates discriminated union for oneOf with discriminator mapping',
165165
},
166+
{
167+
config: createConfig({
168+
input: 'discriminator-allof-member.yaml',
169+
output: 'discriminator-allof-member',
170+
}),
171+
description:
172+
'falls back to z.union() when discriminated union members have allOf (intersection)',
173+
},
166174
{
167175
config: createConfig({
168176
input: 'discriminator-any-of.yaml',

packages/openapi-ts/src/plugins/zod/mini/processor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ export function createProcessor(plugin: ZodPlugin['Instance']): ProcessorResult
5656
}) as ZodFinal;
5757

5858
if (shouldExport) {
59-
exportAst({ ...ctx, final, plugin });
59+
exportAst({
60+
...ctx,
61+
final,
62+
meta: result.meta.isIntersection ? { ...ctx.meta, isIntersection: true } : ctx.meta,
63+
plugin,
64+
});
6065
return;
6166
}
6267

0 commit comments

Comments
 (0)