Skip to content

Commit b6e654a

Browse files
committed
fix: Zod schemas use .and() instead of .merge()
1 parent f82d9ab commit b6e654a

File tree

9 files changed

+101
-28
lines changed

9 files changed

+101
-28
lines changed

.changeset/popular-cooks-chew.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+
fix: Zod schemas use .and() instead of .merge()

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,14 @@ describe(`OpenAPI ${version}`, () => {
696696
}),
697697
description: 'Zod schemas with circular reference 2',
698698
},
699+
{
700+
config: createConfig({
701+
input: 'zod-union-merge.json',
702+
output: 'zod-union-merge',
703+
plugins: ['zod'],
704+
}),
705+
description: "Zod schemas with merged unions (can't use .merge())",
706+
},
699707
];
700708

701709
it.each(scenarios)('$description', async ({ config }) => {

packages/openapi-ts-tests/test/__snapshots__/2.0.x/plugins/zod/default/zod.gen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,12 @@ export const zModelWithDuplicateImports = z.object({
190190
propC: zModelWithString.optional()
191191
});
192192

193-
export const zModelThatExtends = zModelWithString.merge(z.object({
193+
export const zModelThatExtends = zModelWithString.and(z.object({
194194
propExtendsA: z.string().optional(),
195195
propExtendsB: zModelWithString.optional()
196196
}));
197197

198-
export const zModelThatExtendsExtends = zModelWithString.merge(zModelThatExtends).merge(z.object({
198+
export const zModelThatExtendsExtends = zModelWithString.and(zModelThatExtends).and(z.object({
199199
propExtendsC: z.string().optional(),
200200
propExtendsD: zModelWithString.optional()
201201
}));

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,10 @@ export const zModelSquare = z.object({
275275
export const zCompositionWithOneOfDiscriminator = z.union([
276276
z.object({
277277
kind: z.literal('circle').optional()
278-
}).merge(zModelCircle),
278+
}).and(zModelCircle),
279279
z.object({
280280
kind: z.literal('square').optional()
281-
}).merge(zModelSquare)
281+
}).and(zModelSquare)
282282
]);
283283

284284
export const zCompositionWithAnyOf = z.object({
@@ -366,7 +366,7 @@ export const zCompositionWithAllOfAndNullable = z.object({
366366
propA: z.union([
367367
z.object({
368368
boolean: z.boolean().optional()
369-
}).merge(zModelWithEnum).merge(zModelWithArray).merge(zModelWithDictionary),
369+
}).and(zModelWithEnum).and(zModelWithArray).and(zModelWithDictionary),
370370
z.null()
371371
]).optional()
372372
});
@@ -388,7 +388,7 @@ export const zCompositionBaseModel = z.object({
388388
lastname: z.string().optional()
389389
});
390390

391-
export const zCompositionExtendedModel = zCompositionBaseModel.merge(z.object({
391+
export const zCompositionExtendedModel = zCompositionBaseModel.and(z.object({
392392
age: z.number(),
393393
firstName: z.string(),
394394
lastname: z.string()
@@ -427,12 +427,12 @@ export const zModelWithDuplicateImports = z.object({
427427
propC: zModelWithString.optional()
428428
});
429429

430-
export const zModelThatExtends = zModelWithString.merge(z.object({
430+
export const zModelThatExtends = zModelWithString.and(z.object({
431431
propExtendsA: z.string().optional(),
432432
propExtendsB: zModelWithString.optional()
433433
}));
434434

435-
export const zModelThatExtendsExtends = zModelWithString.merge(zModelThatExtends).merge(z.object({
435+
export const zModelThatExtendsExtends = zModelWithString.and(zModelThatExtends).and(z.object({
436436
propExtendsC: z.string().optional(),
437437
propExtendsD: zModelWithString.optional()
438438
}));
@@ -723,7 +723,7 @@ export const zDummyBResponse = z.void();
723723
export const zCallWithResponseResponse = zImport;
724724

725725
export const zCallWithDuplicateResponsesResponse = z.union([
726-
zModelWithBoolean.merge(zModelWithInteger),
726+
zModelWithBoolean.and(zModelWithInteger),
727727
zModelWithString
728728
]);
729729

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,10 @@ export const zModelSquare = z.object({
278278
export const zCompositionWithOneOfDiscriminator = z.union([
279279
z.object({
280280
kind: z.literal('circle').optional()
281-
}).merge(zModelCircle),
281+
}).and(zModelCircle),
282282
z.object({
283283
kind: z.literal('square').optional()
284-
}).merge(zModelSquare)
284+
}).and(zModelSquare)
285285
]);
286286

287287
export const zCompositionWithAnyOf = z.object({
@@ -361,7 +361,7 @@ export const zCompositionWithAllOfAndNullable = z.object({
361361
propA: z.union([
362362
z.object({
363363
boolean: z.boolean().optional()
364-
}).merge(zModelWithEnum).merge(zModelWithArray).merge(zModelWithDictionary),
364+
}).and(zModelWithEnum).and(zModelWithArray).and(zModelWithDictionary),
365365
z.null()
366366
]).optional()
367367
});
@@ -383,7 +383,7 @@ export const zCompositionBaseModel = z.object({
383383
lastname: z.string().optional()
384384
});
385385

386-
export const zCompositionExtendedModel = zCompositionBaseModel.merge(z.object({
386+
export const zCompositionExtendedModel = zCompositionBaseModel.and(z.object({
387387
age: z.number(),
388388
firstName: z.string(),
389389
lastname: z.string()
@@ -422,12 +422,12 @@ export const zModelWithDuplicateImports = z.object({
422422
propC: zModelWithString.optional()
423423
});
424424

425-
export const zModelThatExtends = zModelWithString.merge(z.object({
425+
export const zModelThatExtends = zModelWithString.and(z.object({
426426
propExtendsA: z.string().optional(),
427427
propExtendsB: zModelWithString.optional()
428428
}));
429429

430-
export const zModelThatExtendsExtends = zModelWithString.merge(zModelThatExtends).merge(z.object({
430+
export const zModelThatExtendsExtends = zModelWithString.and(zModelThatExtends).and(z.object({
431431
propExtendsC: z.string().optional(),
432432
propExtendsD: zModelWithString.optional()
433433
}));
@@ -714,7 +714,7 @@ export const zDummyBResponse = z.void();
714714
export const zCallWithResponseResponse = zImport;
715715

716716
export const zCallWithDuplicateResponsesResponse = z.union([
717-
zModelWithBoolean.merge(zModelWithInteger),
717+
zModelWithBoolean.and(zModelWithInteger),
718718
zModelWithString
719719
]);
720720

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 { z } from 'zod';
4+
5+
export const zBar = z.union([
6+
z.object({
7+
bar: z.string()
8+
}),
9+
z.object({
10+
baz: z.string()
11+
})
12+
]);
13+
14+
export const zFoo = zBar.and(z.object({
15+
foo: z.string()
16+
}));

packages/openapi-ts-tests/test/openapi-ts.config.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,7 @@ export default defineConfig(() => {
3636
// openapi: '3.1.0',
3737
// paths: {},
3838
// },
39-
path: path.resolve(
40-
__dirname,
41-
'spec',
42-
'3.0.x',
43-
'internal-name-conflict.json',
44-
),
39+
path: path.resolve(__dirname, 'spec', '3.1.x', 'zod-union-merge.json'),
4540
// path: 'http://localhost:4000/',
4641
// path: 'https://get.heyapi.dev/',
4742
// path: 'https://get.heyapi.dev/hey-api/backend?branch=main&version=1.0.0',
@@ -76,7 +71,7 @@ export default defineConfig(() => {
7671
// bundle: true,
7772
// bundleSource_EXPERIMENTAL: true,
7873
// exportFromIndex: true,
79-
name: '@hey-api/client-fetch',
74+
// name: '@hey-api/client-fetch',
8075
// strictBaseUrl: true,
8176
},
8277
{
@@ -88,7 +83,7 @@ export default defineConfig(() => {
8883
// auth: false,
8984
// client: false,
9085
// include...
91-
name: '@hey-api/sdk',
86+
// name: '@hey-api/sdk',
9287
// operationId: false,
9388
// serviceNameBuilder: '^Parameters',
9489
// throwOnError: true,
@@ -108,7 +103,7 @@ export default defineConfig(() => {
108103
// enumsCase: 'camelCase',
109104
// exportInlineEnums: true,
110105
// identifierCase: 'preserve',
111-
// name: '@hey-api/typescript',
106+
name: '@hey-api/typescript',
112107
// readOnlyWriteOnlyBehavior: 'off',
113108
// readableNameBuilder: 'Readable{{name}}',
114109
// writableNameBuilder: 'Writable{{name}}',
@@ -123,7 +118,7 @@ export default defineConfig(() => {
123118
},
124119
{
125120
// exportFromIndex: true,
126-
// name: 'zod',
121+
name: 'zod',
127122
},
128123
],
129124
// useOptions: false,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": {
4+
"title": "OpenAPI 3.1.0 zod union merge example",
5+
"version": "1"
6+
},
7+
"components": {
8+
"schemas": {
9+
"Foo": {
10+
"allOf": [
11+
{
12+
"$ref": "#/components/schemas/Bar"
13+
},
14+
{
15+
"properties": {
16+
"foo": {
17+
"type": "string"
18+
}
19+
},
20+
"required": ["foo"],
21+
"type": "object"
22+
}
23+
]
24+
},
25+
"Bar": {
26+
"oneOf": [
27+
{
28+
"properties": {
29+
"bar": {
30+
"type": "string"
31+
}
32+
},
33+
"required": ["bar"],
34+
"type": "object"
35+
},
36+
{
37+
"properties": {
38+
"baz": {
39+
"type": "string"
40+
}
41+
},
42+
"required": ["baz"],
43+
"type": "object"
44+
}
45+
]
46+
}
47+
}
48+
}
49+
}

packages/openapi-ts/src/plugins/zod/plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ interface Result {
2222
export const zodId = 'zod';
2323

2424
// frequently used identifiers
25+
const andIdentifier = compiler.identifier({ text: 'and' });
2526
const coerceIdentifier = compiler.identifier({ text: 'coerce' });
2627
const defaultIdentifier = compiler.identifier({ text: 'default' });
2728
const intersectionIdentifier = compiler.identifier({ text: 'intersection' });
2829
const lazyIdentifier = compiler.identifier({ text: 'lazy' });
2930
const lengthIdentifier = compiler.identifier({ text: 'length' });
3031
const literalIdentifier = compiler.identifier({ text: 'literal' });
3132
const maxIdentifier = compiler.identifier({ text: 'max' });
32-
const mergeIdentifier = compiler.identifier({ text: 'merge' });
3333
const minIdentifier = compiler.identifier({ text: 'min' });
3434
const objectIdentifier = compiler.identifier({ text: 'object' });
3535
const optionalIdentifier = compiler.identifier({ text: 'optional' });
@@ -954,7 +954,7 @@ const schemaToZodSchema = ({
954954
expression = compiler.callExpression({
955955
functionName: compiler.propertyAccessExpression({
956956
expression: expression!,
957-
name: mergeIdentifier,
957+
name: andIdentifier,
958958
}),
959959
parameters: [item],
960960
});

0 commit comments

Comments
 (0)