Skip to content

Commit 2d80604

Browse files
fix: code
1 parent b917236 commit 2d80604

4 files changed

Lines changed: 213 additions & 45 deletions

File tree

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

Lines changed: 82 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,80 @@ 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+
body: true,
277+
query: true,
278+
header: true,
279+
response: false,
280+
},
281+
},
282+
},
283+
response: {
284+
types: {
285+
success: [
286+
{
287+
value: 'GetPetResponse',
288+
originalSchema: {
289+
type: 'object',
290+
properties: {
291+
id: {
292+
type: 'string',
293+
},
294+
},
295+
},
296+
},
297+
],
298+
errors: [],
299+
},
300+
},
301+
},
302+
} satisfies Parameters<typeof writeZodSchemasFromVerbs>[0];
303+
304+
await writeZodSchemasFromVerbs(
305+
verbOptions,
306+
schemasPath,
307+
'.ts',
308+
'',
309+
createOutputOptions(),
310+
context,
311+
);
312+
313+
if (await fs.pathExists(schemasPath)) {
314+
const directoryFiles = await fs.readdir(schemasPath);
315+
316+
expect(directoryFiles).not.toContain('GetPetResponse.ts');
317+
}
318+
319+
await fs.remove(root);
320+
});
239321
});

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

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'node:path';
33
import {
44
type ContextSpec,
55
conventionName,
6+
type GeneratorVerbOptions,
67
type NamingConvention,
78
type NormalizedOutputOptions,
89
type OpenApiParameterObject,
@@ -65,19 +66,10 @@ interface WriteZodVerbResponseType {
6566

6667
type WriteZodSchemasFromVerbsInput = Record<
6768
string,
68-
{
69-
operationName: string;
70-
originalOperation: {
71-
requestBody?: OpenApiRequestBodyObject | OpenApiReferenceObject;
72-
parameters?: (OpenApiParameterObject | OpenApiReferenceObject)[];
73-
};
74-
response: {
75-
types: {
76-
success: WriteZodVerbResponseType[];
77-
errors: WriteZodVerbResponseType[];
78-
};
79-
};
80-
}
69+
Pick<
70+
GeneratorVerbOptions,
71+
'operationName' | 'originalOperation' | 'response' | 'override'
72+
>
8173
>;
8274

8375
interface WriteZodSchemasFromVerbsContext {
@@ -365,6 +357,8 @@ export async function writeZodSchemasFromVerbs(
365357

366358
const generateVerbsSchemas = verbOptionsArray.flatMap((verbOption) => {
367359
const operation = verbOption.originalOperation;
360+
const shouldGenerate =
361+
verbOption.override?.zod.generate ?? output.override.zod.generate;
368362

369363
const requestBody = operation.requestBody;
370364
const requestBodyContent =
@@ -392,16 +386,17 @@ export async function writeZodSchemasFromVerbs(
392386
: [undefined, undefined];
393387
const bodySchema = bodyMedia?.schema as OpenApiSchemaObject | undefined;
394388

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-
: [];
389+
const bodySchemas =
390+
shouldGenerate.body && bodySchema
391+
? [
392+
{
393+
name: `${pascal(verbOption.operationName)}Body`,
394+
schema: dereference(bodySchema, zodContext),
395+
bodyContentType,
396+
encoding: bodyMedia?.encoding,
397+
},
398+
]
399+
: [];
405400

406401
const parameters = operation.parameters;
407402

@@ -410,7 +405,7 @@ export async function writeZodSchemasFromVerbs(
410405
);
411406

412407
const queryParamsSchemas =
413-
queryParams && queryParams.length > 0
408+
shouldGenerate.query && queryParams && queryParams.length > 0
414409
? [
415410
{
416411
name: `${pascal(verbOption.operationName)}Params`,
@@ -438,7 +433,7 @@ export async function writeZodSchemasFromVerbs(
438433
);
439434

440435
const headerParamsSchemas =
441-
headerParams && headerParams.length > 0
436+
shouldGenerate.header && headerParams && headerParams.length > 0
442437
? [
443438
{
444439
name: `${pascal(verbOption.operationName)}Headers`,
@@ -461,25 +456,27 @@ export async function writeZodSchemasFromVerbs(
461456
]
462457
: [];
463458

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-
}));
459+
const responseSchemas = shouldGenerate.response
460+
? [
461+
...verbOption.response.types.success,
462+
...verbOption.response.types.errors,
463+
]
464+
.filter(
465+
(
466+
responseType,
467+
): responseType is typeof responseType & {
468+
originalSchema: OpenApiSchemaObject;
469+
} =>
470+
!!responseType.originalSchema &&
471+
!responseType.isRef &&
472+
isValidSchemaIdentifier(responseType.value) &&
473+
!isPrimitiveSchemaName(responseType.value),
474+
)
475+
.map((responseType) => ({
476+
name: responseType.value,
477+
schema: dereference(responseType.originalSchema, zodContext),
478+
}))
479+
: [];
483480

484481
return dedupeSchemasByName([
485482
...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 undefined;
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: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,6 +3525,83 @@ describe('generatePartOfSchemaGenerateZod', () => {
35253525
);
35263526
});
35273527

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

0 commit comments

Comments
 (0)