Skip to content

Commit 981fdf6

Browse files
authored
feat: generate zod.literal when schema has const value (orval-labs#2199)
1 parent 068f84a commit 981fdf6

File tree

4 files changed

+112
-6
lines changed

4 files changed

+112
-6
lines changed

packages/zod/src/index.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ const possibleSchemaTypes = [
6868
'array',
6969
];
7070

71-
const resolveZodType = (schema: SchemaObject) => {
71+
const resolveZodType = (schema: SchemaObject | SchemaObject31) => {
7272
const schemaTypeValue = schema.type;
7373
const type = Array.isArray(schemaTypeValue)
7474
? schemaTypeValue.find((t) => possibleSchemaTypes.includes(t))
@@ -128,7 +128,7 @@ type DateTimeOptions = {
128128
};
129129

130130
export const generateZodValidationSchemaDefinition = (
131-
schema: SchemaObject | undefined,
131+
schema: SchemaObject | SchemaObject31 | undefined,
132132
context: ContextSpecs,
133133
name: string,
134134
strict: boolean,
@@ -156,7 +156,7 @@ export const generateZodValidationSchemaDefinition = (
156156
const type = resolveZodType(schema);
157157
const required = rules?.required ?? false;
158158
const nullable =
159-
schema.nullable ??
159+
('nullable' in schema && schema.nullable) ||
160160
(Array.isArray(schema.type) && schema.type.includes('null'));
161161
const min = schema.minimum ?? schema.minLength ?? schema.minItems;
162162
const max = schema.maximum ?? schema.maxLength ?? schema.maxItems;
@@ -299,11 +299,19 @@ export const generateZodValidationSchemaDefinition = (
299299

300300
if (isZodV4) {
301301
if (!schema.format) {
302-
functions.push([type as string, undefined]);
302+
if ('const' in schema) {
303+
functions.push(['literal', `"${schema.const}"`]);
304+
} else {
305+
functions.push([type as string, undefined]);
306+
}
303307
break;
304308
}
305309
} else {
306-
functions.push([type as string, undefined]);
310+
if ('const' in schema) {
311+
functions.push(['literal', `"${schema.const}"`]);
312+
} else {
313+
functions.push([type as string, undefined]);
314+
}
307315
}
308316

309317
if (schema.format === 'date') {

packages/zod/src/zod.test.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
type ZodValidationSchemaDefinition,
99
} from '.';
1010

11-
import { ContextSpecs, GeneratorOptions } from '@orval/core';
11+
import { ContextSpecs } from '@orval/core';
1212

1313
const queryParams: ZodValidationSchemaDefinition = {
1414
functions: [
@@ -1453,3 +1453,95 @@ describe('generateZodWithEdgeCases', () => {
14531453
);
14541454
});
14551455
});
1456+
1457+
const schemaWithLiteralProperty = {
1458+
pathRoute: '/cats',
1459+
context: {
1460+
specKey: 'cat',
1461+
specs: {
1462+
cat: {
1463+
openapi: '3.0.0',
1464+
info: {
1465+
version: '1.0.0',
1466+
title: 'Cats',
1467+
},
1468+
paths: {
1469+
'/cats': {
1470+
post: {
1471+
operationId: 'xyz',
1472+
requestBody: {
1473+
required: true,
1474+
content: {
1475+
'application/json': {
1476+
schema: {
1477+
type: 'object',
1478+
properties: {
1479+
type: {
1480+
type: 'string',
1481+
const: 'WILD',
1482+
},
1483+
},
1484+
},
1485+
},
1486+
},
1487+
},
1488+
responses: {
1489+
'200': {},
1490+
},
1491+
},
1492+
},
1493+
},
1494+
},
1495+
},
1496+
output: {
1497+
override: {
1498+
zod: {
1499+
generateEachHttpStatus: false,
1500+
},
1501+
},
1502+
},
1503+
},
1504+
};
1505+
1506+
describe('generateZodWithLiteralProperty', () => {
1507+
it('correctly handles literal as a property name', async () => {
1508+
const result = await generateZod(
1509+
{
1510+
pathRoute: '/cats',
1511+
verb: 'post',
1512+
operationName: 'test',
1513+
override: {
1514+
zod: {
1515+
strict: {
1516+
param: false,
1517+
body: false,
1518+
response: false,
1519+
query: false,
1520+
header: false,
1521+
},
1522+
generate: {
1523+
param: false,
1524+
body: true,
1525+
response: true,
1526+
query: false,
1527+
header: false,
1528+
},
1529+
coerce: {
1530+
param: false,
1531+
body: false,
1532+
response: false,
1533+
query: false,
1534+
header: false,
1535+
},
1536+
},
1537+
},
1538+
},
1539+
schemaWithLiteralProperty,
1540+
{},
1541+
);
1542+
1543+
expect(result.implementation).toBe(
1544+
'export const testBody = zod.object({\n "type": zod.literal("WILD").optional()\n})\n\n',
1545+
);
1546+
});
1547+
});

tests/specifications/example-v3-1.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ components:
2828
title: Example tuple
2929
example_const:
3030
const: this_is_a_const
31+
example_string_const:
32+
type: string
33+
const: this_is_a_string_const
3134
example_enum:
3235
type: string
3336
enum:

tests/specifications/typed-arrays-tuples-v3-1.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ components:
5151
type: array
5252
example_const:
5353
const: this_is_a_const
54+
example_string_const:
55+
type: string
56+
const: this_is_a_string_const
5457
example_enum:
5558
type: string
5659
enum:

0 commit comments

Comments
 (0)