Skip to content

Commit e1711e5

Browse files
authoredJun 21, 2024
fix(zod): properly handling top level rules for array (#1475)
1 parent 68fd4fb commit e1711e5

File tree

2 files changed

+104
-55
lines changed

2 files changed

+104
-55
lines changed
 

‎packages/zod/src/index.ts

+95-52
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
import {
2-
ParameterObject,
3-
PathItemObject,
4-
ReferenceObject,
5-
RequestBodyObject,
6-
ResponseObject,
7-
SchemaObject,
8-
} from 'openapi3-ts/oas30';
9-
import {
10-
camel,
112
ClientBuilder,
123
ClientGeneratorsBuilder,
13-
escape,
4+
ContextSpecs,
145
GeneratorDependency,
6+
GeneratorMutator,
157
GeneratorOptions,
168
GeneratorVerbOptions,
17-
isString,
18-
resolveRef,
19-
ContextSpecs,
20-
isObject,
9+
ZodCoerceType,
10+
camel,
11+
escape,
12+
generateMutator,
13+
getNumberWord,
2114
isBoolean,
15+
isObject,
16+
isString,
2217
jsStringEscape,
23-
getNumberWord,
2418
pascal,
25-
ZodCoerceType,
26-
generateMutator,
27-
GeneratorMutator,
19+
resolveRef,
2820
} from '@orval/core';
2921
import uniq from 'lodash.uniq';
22+
import {
23+
ParameterObject,
24+
PathItemObject,
25+
ReferenceObject,
26+
RequestBodyObject,
27+
ResponseObject,
28+
SchemaObject,
29+
} from 'openapi3-ts/oas30';
3030

3131
const ZOD_DEPENDENCIES: GeneratorDependency[] = [
3232
{
@@ -76,12 +76,16 @@ export type ZodValidationSchemaDefinition = {
7676
consts: string[];
7777
};
7878

79+
const minAndMaxTypes = ['number', 'string', 'array'];
80+
7981
export const generateZodValidationSchemaDefinition = (
8082
schema: SchemaObject | undefined,
8183
context: ContextSpecs,
82-
_required: boolean | undefined,
8384
name: string,
8485
strict: boolean,
86+
rules?: {
87+
required?: boolean;
88+
},
8589
): ZodValidationSchemaDefinition => {
8690
if (!schema) return { functions: [], consts: [] };
8791

@@ -99,22 +103,13 @@ export const generateZodValidationSchemaDefinition = (
99103

100104
const functions: [string, any][] = [];
101105
const type = resolveZodType(schema.type);
102-
const required = schema.default !== undefined ? false : _required ?? false;
106+
const required =
107+
schema.default !== undefined ? false : rules?.required ?? false;
103108
const nullable =
104109
schema.nullable ??
105110
(Array.isArray(schema.type) && schema.type.includes('null'));
106-
const min =
107-
schema.minimum ??
108-
schema.exclusiveMinimum ??
109-
schema.minLength ??
110-
schema.minItems ??
111-
undefined;
112-
const max =
113-
schema.maximum ??
114-
schema.exclusiveMaximum ??
115-
schema.maxLength ??
116-
schema.maxItems ??
117-
undefined;
111+
const min = schema.minimum ?? schema.minLength ?? schema.minItems;
112+
const max = schema.maximum ?? schema.maxLength ?? schema.maxItems;
118113
const matches = schema.pattern ?? undefined;
119114

120115
switch (type) {
@@ -125,9 +120,11 @@ export const generateZodValidationSchemaDefinition = (
125120
generateZodValidationSchemaDefinition(
126121
items,
127122
context,
128-
true,
129123
camel(`${name}-item`),
130124
strict,
125+
{
126+
required: true,
127+
},
131128
),
132129
]);
133130
break;
@@ -193,9 +190,11 @@ export const generateZodValidationSchemaDefinition = (
193190
generateZodValidationSchemaDefinition(
194191
schema as SchemaObject,
195192
context,
196-
true,
197193
camel(name),
198194
strict,
195+
{
196+
required: true,
197+
},
199198
),
200199
),
201200
]);
@@ -210,9 +209,11 @@ export const generateZodValidationSchemaDefinition = (
210209
[key]: generateZodValidationSchemaDefinition(
211210
schema.properties?.[key] as any,
212211
context,
213-
schema.required?.includes(key),
214212
camel(`${name}-${key}`),
215213
strict,
214+
{
215+
required: schema.required?.includes(key),
216+
},
216217
),
217218
}))
218219
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
@@ -233,9 +234,11 @@ export const generateZodValidationSchemaDefinition = (
233234
? {}
234235
: (schema.additionalProperties as SchemaObject),
235236
context,
236-
true,
237237
name,
238238
strict,
239+
{
240+
required: true,
241+
},
239242
),
240243
]);
241244

@@ -248,18 +251,21 @@ export const generateZodValidationSchemaDefinition = (
248251
}
249252
}
250253

251-
if (min !== undefined) {
252-
if (min === 1) {
253-
functions.push(['min', `${min}`]);
254-
} else {
255-
consts.push(`export const ${name}Min${constsCounterValue} = ${min};\n`);
256-
functions.push(['min', `${name}Min${constsCounterValue}`]);
254+
if (minAndMaxTypes.includes(type)) {
255+
if (min !== undefined) {
256+
if (min === 1) {
257+
functions.push(['min', `${min}`]);
258+
} else {
259+
consts.push(`export const ${name}Min${constsCounterValue} = ${min};\n`);
260+
functions.push(['min', `${name}Min${constsCounterValue}`]);
261+
}
262+
}
263+
if (max !== undefined) {
264+
consts.push(`export const ${name}Max${constsCounterValue} = ${max};\n`);
265+
functions.push(['max', `${name}Max${constsCounterValue}`]);
257266
}
258267
}
259-
if (max !== undefined) {
260-
consts.push(`export const ${name}Max${constsCounterValue} = ${max};\n`);
261-
functions.push(['max', `${name}Max${constsCounterValue}`]);
262-
}
268+
263269
if (matches) {
264270
const isStartWithSlash = matches.startsWith('/');
265271
const isEndWithSlash = matches.endsWith('/');
@@ -462,6 +468,10 @@ const parseBodyAndResponse = ({
462468
}): {
463469
input: ZodValidationSchemaDefinition;
464470
isArray: boolean;
471+
rules?: {
472+
min?: number;
473+
max?: number;
474+
};
465475
} => {
466476
if (!data) {
467477
return {
@@ -489,25 +499,42 @@ const parseBodyAndResponse = ({
489499

490500
// keep the same behaviour for array
491501
if (resolvedJsonSchema.items) {
502+
const min =
503+
resolvedJsonSchema.minimum ??
504+
resolvedJsonSchema.minLength ??
505+
resolvedJsonSchema.minItems;
506+
const max =
507+
resolvedJsonSchema.maximum ??
508+
resolvedJsonSchema.maxLength ??
509+
resolvedJsonSchema.maxItems;
510+
492511
return {
493512
input: generateZodValidationSchemaDefinition(
494513
resolvedJsonSchema.items as SchemaObject,
495514
context,
496-
true,
497515
name,
498516
strict,
517+
{
518+
required: true,
519+
},
499520
),
500521
isArray: true,
522+
rules: {
523+
...(typeof min !== 'undefined' ? { min } : {}),
524+
...(typeof max !== 'undefined' ? { max } : {}),
525+
},
501526
};
502527
}
503528

504529
return {
505530
input: generateZodValidationSchemaDefinition(
506531
resolvedJsonSchema,
507532
context,
508-
true,
509533
name,
510534
strict,
535+
{
536+
required: true,
537+
},
511538
),
512539
isArray: false,
513540
};
@@ -570,9 +597,11 @@ const parseParameters = ({
570597
const definition = generateZodValidationSchemaDefinition(
571598
schema,
572599
context,
573-
parameter.required,
574600
camel(`${operationName}-${parameter.in}-${parameter.name}`),
575601
mapStrict[parameter.in as 'path' | 'query' | 'header'] ?? false,
602+
{
603+
required: parameter.required,
604+
},
576605
);
577606

578607
if (parameter.in === 'header') {
@@ -772,7 +801,11 @@ const generateZodRoute = async (
772801
? [
773802
parsedBody.isArray
774803
? `export const ${operationName}BodyItem = ${inputBody.zod}
775-
export const ${operationName}Body = zod.array(${operationName}BodyItem)`
804+
export const ${operationName}Body = zod.array(${operationName}BodyItem)${
805+
parsedBody.rules?.min ? `.min(${parsedBody.rules?.min})` : ''
806+
}${
807+
parsedBody.rules?.max ? `.max(${parsedBody.rules?.max})` : ''
808+
}`
776809
: `export const ${operationName}Body = ${inputBody.zod}`,
777810
]
778811
: []),
@@ -786,8 +819,18 @@ export const ${operationName}Body = zod.array(${operationName}BodyItem)`
786819
...(inputResponse.zod
787820
? [
788821
parsedResponses[index].isArray
789-
? `export const ${operationResponse}Item = ${inputResponse.zod}
790-
export const ${operationResponse} = zod.array(${operationResponse}Item)`
822+
? `export const ${operationResponse}Item = ${
823+
inputResponse.zod
824+
}
825+
export const ${operationResponse} = zod.array(${operationResponse}Item)${
826+
parsedResponses[index].rules?.min
827+
? `.min(${parsedResponses[index].rules?.min})`
828+
: ''
829+
}${
830+
parsedResponses[index].rules?.max
831+
? `.max(${parsedResponses[index].rules?.max})`
832+
: ''
833+
}`
791834
: `export const ${operationResponse} = ${inputResponse.zod}`,
792835
]
793836
: []),

‎packages/zod/src/zod.test.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,11 @@ describe('generateZodValidationSchemaDefinition`', () => {
185185
},
186186
},
187187
} as ContextSpecs,
188-
true,
189188
'strict',
190189
true,
190+
{
191+
required: true,
192+
},
191193
);
192194

193195
expect(result).toEqual({
@@ -236,9 +238,11 @@ describe('generateZodValidationSchemaDefinition`', () => {
236238
},
237239
},
238240
} as ContextSpecs,
239-
true,
240241
'strict',
241242
true,
243+
{
244+
required: true,
245+
},
242246
);
243247

244248
expect(result).toEqual({
@@ -290,9 +294,11 @@ describe('generateZodValidationSchemaDefinition`', () => {
290294
},
291295
},
292296
} as ContextSpecs,
293-
true,
294297
'strict',
295298
true,
299+
{
300+
required: true,
301+
},
296302
);
297303

298304
expect(result).toEqual({

0 commit comments

Comments
 (0)
Please sign in to comment.