Skip to content

Commit 85a5713

Browse files
authored
Merge branch 'master' into vue-infinite-query-custom-fetch
2 parents 7c4f518 + cd37aea commit 85a5713

4 files changed

Lines changed: 241 additions & 45 deletions

File tree

packages/orval/src/write-zod-specs.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ const createOutputOptions = (): Parameters<typeof writeZodSchemas>[4] =>
2929
strict: {
3030
body: true,
3131
},
32+
generate: {
33+
body: true,
34+
query: true,
35+
header: true,
36+
response: true,
37+
},
3238
coerce: {
3339
body: false,
3440
},
@@ -236,4 +242,81 @@ describe('write-zod-specs regressions', () => {
236242

237243
await fs.remove(root);
238244
});
245+
246+
it('honors response generate override in split zod output', async () => {
247+
const root = await fs.mkdtemp(path.join(tmpdir(), 'orval-zod-'));
248+
const schemasPath = path.join(root, 'schemas');
249+
250+
const context = {
251+
output: {
252+
override: {
253+
useDates: false,
254+
zod: {
255+
dateTimeOptions: {},
256+
timeOptions: {},
257+
},
258+
},
259+
},
260+
spec: {},
261+
target: '',
262+
workspace: root,
263+
} satisfies MinimalVerbsContext;
264+
265+
const verbOptions = {
266+
getPet: {
267+
operationName: 'getPet',
268+
originalOperation: {
269+
parameters: [],
270+
},
271+
override: {
272+
...createOutputOptions().override,
273+
zod: {
274+
...createOutputOptions().override.zod,
275+
generate: {
276+
param: true,
277+
body: true,
278+
query: true,
279+
header: true,
280+
response: false,
281+
},
282+
},
283+
},
284+
response: {
285+
types: {
286+
success: [
287+
{
288+
value: 'GetPetResponse',
289+
originalSchema: {
290+
type: 'object',
291+
properties: {
292+
id: {
293+
type: 'string',
294+
},
295+
},
296+
},
297+
},
298+
],
299+
errors: [],
300+
},
301+
},
302+
},
303+
} satisfies Parameters<typeof writeZodSchemasFromVerbs>[0];
304+
305+
await writeZodSchemasFromVerbs(
306+
verbOptions,
307+
schemasPath,
308+
'.ts',
309+
'',
310+
createOutputOptions(),
311+
context,
312+
);
313+
314+
if (await fs.pathExists(schemasPath)) {
315+
const directoryFiles = await fs.readdir(schemasPath);
316+
317+
expect(directoryFiles).not.toContain('GetPetResponse.ts');
318+
}
319+
320+
await fs.remove(root);
321+
});
239322
});

packages/orval/src/write-zod-specs.ts

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ interface WriteZodOutputOptions {
4141
strict: {
4242
body: boolean;
4343
};
44+
generate: {
45+
param: boolean;
46+
query: boolean;
47+
header: boolean;
48+
body: boolean;
49+
response: boolean;
50+
};
4451
coerce: {
4552
body: boolean | ZodCoerceType[];
4653
};
@@ -63,21 +70,28 @@ interface WriteZodVerbResponseType {
6370
originalSchema?: OpenApiSchemaObject;
6471
}
6572

66-
type WriteZodSchemasFromVerbsInput = Record<
67-
string,
68-
{
69-
operationName: string;
70-
originalOperation: {
71-
requestBody?: OpenApiRequestBodyObject | OpenApiReferenceObject;
72-
parameters?: (OpenApiParameterObject | OpenApiReferenceObject)[];
73+
interface WriteZodSchemasFromVerbsEntry {
74+
operationName: string;
75+
originalOperation: {
76+
requestBody?: OpenApiRequestBodyObject | OpenApiReferenceObject;
77+
parameters?: (OpenApiParameterObject | OpenApiReferenceObject)[];
78+
};
79+
response: {
80+
types: {
81+
success: WriteZodVerbResponseType[];
82+
errors: WriteZodVerbResponseType[];
7383
};
74-
response: {
75-
types: {
76-
success: WriteZodVerbResponseType[];
77-
errors: WriteZodVerbResponseType[];
78-
};
84+
};
85+
override?: {
86+
zod: {
87+
generate: WriteZodOutputOptions['override']['zod']['generate'];
7988
};
80-
}
89+
};
90+
}
91+
92+
type WriteZodSchemasFromVerbsInput = Record<
93+
string,
94+
WriteZodSchemasFromVerbsEntry
8195
>;
8296

8397
interface WriteZodSchemasFromVerbsContext {
@@ -365,6 +379,10 @@ export async function writeZodSchemasFromVerbs(
365379

366380
const generateVerbsSchemas = verbOptionsArray.flatMap((verbOption) => {
367381
const operation = verbOption.originalOperation;
382+
const shouldGenerate = {
383+
...output.override.zod.generate,
384+
...verbOption.override?.zod.generate,
385+
};
368386

369387
const requestBody = operation.requestBody;
370388
const requestBodyContent =
@@ -392,16 +410,17 @@ export async function writeZodSchemasFromVerbs(
392410
: [undefined, undefined];
393411
const bodySchema = bodyMedia?.schema as OpenApiSchemaObject | undefined;
394412

395-
const bodySchemas = bodySchema
396-
? [
397-
{
398-
name: `${pascal(verbOption.operationName)}Body`,
399-
schema: dereference(bodySchema, zodContext),
400-
bodyContentType,
401-
encoding: bodyMedia?.encoding,
402-
},
403-
]
404-
: [];
413+
const bodySchemas =
414+
shouldGenerate.body && bodySchema
415+
? [
416+
{
417+
name: `${pascal(verbOption.operationName)}Body`,
418+
schema: dereference(bodySchema, zodContext),
419+
bodyContentType,
420+
encoding: bodyMedia?.encoding,
421+
},
422+
]
423+
: [];
405424

406425
const parameters = operation.parameters;
407426

@@ -410,7 +429,7 @@ export async function writeZodSchemasFromVerbs(
410429
);
411430

412431
const queryParamsSchemas =
413-
queryParams && queryParams.length > 0
432+
shouldGenerate.query && queryParams && queryParams.length > 0
414433
? [
415434
{
416435
name: `${pascal(verbOption.operationName)}Params`,
@@ -438,7 +457,7 @@ export async function writeZodSchemasFromVerbs(
438457
);
439458

440459
const headerParamsSchemas =
441-
headerParams && headerParams.length > 0
460+
shouldGenerate.header && headerParams && headerParams.length > 0
442461
? [
443462
{
444463
name: `${pascal(verbOption.operationName)}Headers`,
@@ -461,25 +480,27 @@ export async function writeZodSchemasFromVerbs(
461480
]
462481
: [];
463482

464-
const responseSchemas = [
465-
...verbOption.response.types.success,
466-
...verbOption.response.types.errors,
467-
]
468-
.filter(
469-
(
470-
responseType,
471-
): responseType is typeof responseType & {
472-
originalSchema: OpenApiSchemaObject;
473-
} =>
474-
!!responseType.originalSchema &&
475-
!responseType.isRef &&
476-
isValidSchemaIdentifier(responseType.value) &&
477-
!isPrimitiveSchemaName(responseType.value),
478-
)
479-
.map((responseType) => ({
480-
name: responseType.value,
481-
schema: dereference(responseType.originalSchema, zodContext),
482-
}));
483+
const responseSchemas = shouldGenerate.response
484+
? [
485+
...verbOption.response.types.success,
486+
...verbOption.response.types.errors,
487+
]
488+
.filter(
489+
(
490+
responseType,
491+
): responseType is typeof responseType & {
492+
originalSchema: OpenApiSchemaObject;
493+
} =>
494+
!!responseType.originalSchema &&
495+
!responseType.isRef &&
496+
isValidSchemaIdentifier(responseType.value) &&
497+
!isPrimitiveSchemaName(responseType.value),
498+
)
499+
.map((responseType) => ({
500+
name: responseType.value,
501+
schema: dereference(responseType.originalSchema, zodContext),
502+
}))
503+
: [];
483504

484505
return dedupeSchemasByName([
485506
...bodySchemas,

packages/zod/src/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,18 @@ const parseBodyAndResponse = ({
14191419
};
14201420
};
14211421

1422+
const getSingleResponse = (
1423+
responses:
1424+
| Record<string, OpenApiResponseObject | OpenApiReferenceObject | undefined>
1425+
| undefined,
1426+
) => {
1427+
if (!responses) {
1428+
return;
1429+
}
1430+
1431+
return responses['200'] ?? responses['2XX'] ?? responses['2xx'];
1432+
};
1433+
14221434
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
14231435

14241436
export const parseParameters = ({
@@ -1639,7 +1651,7 @@ const generateZodRoute = async (
16391651
const responses = (
16401652
context.output.override.zod.generateEachHttpStatus
16411653
? Object.entries(spec[verb]?.responses ?? {})
1642-
: [['', spec[verb]?.responses?.[200]]]
1654+
: [['', getSingleResponse(spec[verb]?.responses)]]
16431655
) as [string, OpenApiResponseObject | OpenApiReferenceObject][];
16441656
const parsedResponses = responses.map(([code, response]) =>
16451657
parseBodyAndResponse({

packages/zod/src/zod.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,6 +3525,86 @@ describe('generatePartOfSchemaGenerateZod', () => {
35253525
);
35263526
});
35273527

3528+
it('falls back to 2XX response when 200 is not present', async () => {
3529+
const basePostOperation =
3530+
basicApiSchema.context.spec.paths?.['/cats']?.post ?? {};
3531+
3532+
const wildcardResponseApiSchema = {
3533+
...basicApiSchema,
3534+
context: {
3535+
...basicApiSchema.context,
3536+
spec: {
3537+
...basicApiSchema.context.spec,
3538+
paths: {
3539+
'/cats': {
3540+
post: {
3541+
...basePostOperation,
3542+
responses: {
3543+
'2XX': {
3544+
content: {
3545+
'application/json': {
3546+
schema: {
3547+
type: 'object',
3548+
properties: {
3549+
name: {
3550+
type: 'string',
3551+
},
3552+
},
3553+
},
3554+
},
3555+
},
3556+
},
3557+
},
3558+
},
3559+
},
3560+
},
3561+
},
3562+
},
3563+
} as typeof basicApiSchema;
3564+
3565+
const result = await generateZod(
3566+
{
3567+
pathRoute: '/cats',
3568+
verb: 'post',
3569+
operationName: 'test',
3570+
override: {
3571+
zod: {
3572+
strict: {
3573+
param: false,
3574+
body: false,
3575+
response: false,
3576+
query: false,
3577+
header: false,
3578+
},
3579+
generate: {
3580+
param: false,
3581+
body: false,
3582+
response: true,
3583+
query: false,
3584+
header: false,
3585+
},
3586+
coerce: {
3587+
param: false,
3588+
body: false,
3589+
response: false,
3590+
query: false,
3591+
header: false,
3592+
},
3593+
generateEachHttpStatus: false,
3594+
dateTimeOptions: {},
3595+
timeOptions: {},
3596+
},
3597+
},
3598+
} as unknown as Parameters<typeof generateZod>[0],
3599+
wildcardResponseApiSchema,
3600+
testOutput,
3601+
);
3602+
3603+
expect(result.implementation).toBe(
3604+
'export const TestResponse = zod.object({\n "name": zod.string().optional()\n})\n\n',
3605+
);
3606+
});
3607+
35283608
it('Only generate request body', async () => {
35293609
const result = await generateZod(
35303610
{

0 commit comments

Comments
 (0)