@cerios/openapi-to-zod 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +194 -38
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +195 -47
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +190 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -17
package/dist/index.mjs
CHANGED
|
@@ -65,7 +65,8 @@ var ConfigurationError = class extends GeneratorError {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// src/generator.ts
|
|
68
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
68
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
69
|
+
import { dirname } from "path";
|
|
69
70
|
import { parse } from "yaml";
|
|
70
71
|
|
|
71
72
|
// src/utils/name-utils.ts
|
|
@@ -119,7 +120,7 @@ function generateEnum(name, values, options) {
|
|
|
119
120
|
const enumCode = `export enum ${enumName} {
|
|
120
121
|
${enumEntries}
|
|
121
122
|
}`;
|
|
122
|
-
const schemaCode2 = `export const ${schemaName} = z.
|
|
123
|
+
const schemaCode2 = `export const ${schemaName} = z.nativeEnum(${enumName});`;
|
|
123
124
|
const typeCode2 = `export type ${name} = z.infer<typeof ${schemaName}>;`;
|
|
124
125
|
return { enumCode, schemaCode: schemaCode2, typeCode: typeCode2 };
|
|
125
126
|
}
|
|
@@ -302,20 +303,20 @@ function generateArrayValidation(schema, context) {
|
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
// src/validators/composition-validator.ts
|
|
305
|
-
function generateUnion(schemas, discriminator, isNullable2, context, options) {
|
|
306
|
+
function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
|
|
306
307
|
if (discriminator) {
|
|
307
308
|
let resolvedSchemas = schemas;
|
|
308
309
|
if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
|
|
309
310
|
resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
|
|
310
311
|
}
|
|
311
|
-
let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s));
|
|
312
|
+
let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
312
313
|
if (options == null ? void 0 : options.passthrough) {
|
|
313
314
|
schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
314
315
|
}
|
|
315
316
|
const union2 = `z.discriminatedUnion("${discriminator}", [${schemaStrings2.join(", ")}])`;
|
|
316
317
|
return wrapNullable(union2, isNullable2);
|
|
317
318
|
}
|
|
318
|
-
let schemaStrings = schemas.map((s) => context.generatePropertySchema(s));
|
|
319
|
+
let schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
319
320
|
if (options == null ? void 0 : options.passthrough) {
|
|
320
321
|
schemaStrings = schemaStrings.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
321
322
|
}
|
|
@@ -1148,7 +1149,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1148
1149
|
{
|
|
1149
1150
|
passthrough: needsPassthrough,
|
|
1150
1151
|
discriminatorMapping: (_c = schema.discriminator) == null ? void 0 : _c.mapping
|
|
1151
|
-
}
|
|
1152
|
+
},
|
|
1153
|
+
currentSchema
|
|
1152
1154
|
);
|
|
1153
1155
|
if (schema.unevaluatedProperties !== void 0) {
|
|
1154
1156
|
composition = this.applyUnevaluatedProperties(composition, schema);
|
|
@@ -1168,7 +1170,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1168
1170
|
{
|
|
1169
1171
|
passthrough: needsPassthrough,
|
|
1170
1172
|
discriminatorMapping: (_e = schema.discriminator) == null ? void 0 : _e.mapping
|
|
1171
|
-
}
|
|
1173
|
+
},
|
|
1174
|
+
currentSchema
|
|
1172
1175
|
);
|
|
1173
1176
|
if (schema.unevaluatedProperties !== void 0) {
|
|
1174
1177
|
composition = this.applyUnevaluatedProperties(composition, schema);
|
|
@@ -1339,10 +1342,13 @@ var ZodSchemaGenerator = class {
|
|
|
1339
1342
|
}
|
|
1340
1343
|
for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
|
|
1341
1344
|
if (schema.enum) {
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1345
|
+
const context = this.schemaUsageMap.get(name);
|
|
1346
|
+
const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
|
|
1347
|
+
if (resolvedOptions.enumType === "typescript") {
|
|
1348
|
+
this.generateNativeEnum(name, schema);
|
|
1349
|
+
} else {
|
|
1344
1350
|
const { enumCode } = generateEnum(name, schema.enum, {
|
|
1345
|
-
enumType:
|
|
1351
|
+
enumType: "zod",
|
|
1346
1352
|
prefix: this.options.prefix,
|
|
1347
1353
|
suffix: this.options.suffix
|
|
1348
1354
|
});
|
|
@@ -1350,14 +1356,13 @@ var ZodSchemaGenerator = class {
|
|
|
1350
1356
|
this.enums.set(name, enumCode);
|
|
1351
1357
|
this.needsZodImport = true;
|
|
1352
1358
|
}
|
|
1353
|
-
} else {
|
|
1354
|
-
this.generateNativeEnum(name, schema);
|
|
1355
1359
|
}
|
|
1356
1360
|
}
|
|
1357
1361
|
}
|
|
1358
1362
|
for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
|
|
1359
1363
|
this.generateComponentSchema(name, schema);
|
|
1360
1364
|
}
|
|
1365
|
+
this.generateQueryParameterSchemas();
|
|
1361
1366
|
const orderedSchemaNames = this.topologicalSort();
|
|
1362
1367
|
const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
|
|
1363
1368
|
if (this.options.showStats === true) {
|
|
@@ -1368,12 +1373,8 @@ var ZodSchemaGenerator = class {
|
|
|
1368
1373
|
output.push('import { z } from "zod";');
|
|
1369
1374
|
output.push("");
|
|
1370
1375
|
}
|
|
1371
|
-
if (this.
|
|
1372
|
-
output.push("// Enums");
|
|
1373
|
-
for (const enumCode of this.enums.values()) {
|
|
1374
|
-
output.push(enumCode);
|
|
1375
|
-
output.push("");
|
|
1376
|
-
}
|
|
1376
|
+
if (this.nativeEnums.size > 0) {
|
|
1377
|
+
output.push("// Native Enums");
|
|
1377
1378
|
for (const enumCode of this.nativeEnums.values()) {
|
|
1378
1379
|
output.push(enumCode);
|
|
1379
1380
|
output.push("");
|
|
@@ -1381,9 +1382,13 @@ var ZodSchemaGenerator = class {
|
|
|
1381
1382
|
}
|
|
1382
1383
|
output.push("// Schemas and Types");
|
|
1383
1384
|
for (const name of orderedSchemaNames) {
|
|
1385
|
+
const enumCode = this.enums.get(name);
|
|
1384
1386
|
const schemaCode = this.schemas.get(name);
|
|
1385
1387
|
const typeCode = this.types.get(name);
|
|
1386
|
-
if (
|
|
1388
|
+
if (enumCode) {
|
|
1389
|
+
output.push(enumCode);
|
|
1390
|
+
output.push("");
|
|
1391
|
+
} else if (schemaCode) {
|
|
1387
1392
|
output.push(schemaCode);
|
|
1388
1393
|
if (!schemaCode.includes(`export type ${name}`)) {
|
|
1389
1394
|
const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
|
|
@@ -1397,6 +1402,15 @@ var ZodSchemaGenerator = class {
|
|
|
1397
1402
|
}
|
|
1398
1403
|
return output.join("\n");
|
|
1399
1404
|
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Ensure directory exists for a file path
|
|
1407
|
+
*/
|
|
1408
|
+
ensureDirectoryExists(filePath) {
|
|
1409
|
+
const dir = dirname(filePath);
|
|
1410
|
+
if (!existsSync(dir)) {
|
|
1411
|
+
mkdirSync(dir, { recursive: true });
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1400
1414
|
/**
|
|
1401
1415
|
* Generate the complete output file
|
|
1402
1416
|
*/
|
|
@@ -1408,6 +1422,7 @@ var ZodSchemaGenerator = class {
|
|
|
1408
1422
|
);
|
|
1409
1423
|
}
|
|
1410
1424
|
const output = this.generateString();
|
|
1425
|
+
this.ensureDirectoryExists(this.options.output);
|
|
1411
1426
|
writeFileSync(this.options.output, output);
|
|
1412
1427
|
}
|
|
1413
1428
|
/**
|
|
@@ -1711,10 +1726,19 @@ var ZodSchemaGenerator = class {
|
|
|
1711
1726
|
const context = this.schemaUsageMap.get(name);
|
|
1712
1727
|
const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
|
|
1713
1728
|
if (schema.enum) {
|
|
1714
|
-
|
|
1715
|
-
|
|
1729
|
+
const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
|
|
1730
|
+
if (resolvedOptions.enumType === "typescript") {
|
|
1731
|
+
const { schemaCode, typeCode } = generateEnum(name, schema.enum, {
|
|
1732
|
+
enumType: "typescript",
|
|
1733
|
+
prefix: this.options.prefix,
|
|
1734
|
+
suffix: this.options.suffix
|
|
1735
|
+
});
|
|
1736
|
+
const enumSchemaCode = `${jsdoc}${schemaCode}
|
|
1737
|
+
${typeCode}`;
|
|
1738
|
+
this.schemas.set(name, enumSchemaCode);
|
|
1739
|
+
} else {
|
|
1716
1740
|
const { enumCode, schemaCode, typeCode } = generateEnum(name, schema.enum, {
|
|
1717
|
-
enumType:
|
|
1741
|
+
enumType: "zod",
|
|
1718
1742
|
prefix: this.options.prefix,
|
|
1719
1743
|
suffix: this.options.suffix
|
|
1720
1744
|
});
|
|
@@ -1773,6 +1797,133 @@ ${typeCode}`;
|
|
|
1773
1797
|
this.schemas.set(name, zodSchemaCode);
|
|
1774
1798
|
}
|
|
1775
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Generate query parameter schemas for each operation
|
|
1802
|
+
*/
|
|
1803
|
+
generateQueryParameterSchemas() {
|
|
1804
|
+
var _a;
|
|
1805
|
+
if (!this.spec.paths) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
for (const [_path, pathItem] of Object.entries(this.spec.paths)) {
|
|
1809
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
1810
|
+
const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
1811
|
+
for (const method of methods) {
|
|
1812
|
+
const operation = pathItem[method];
|
|
1813
|
+
if (!operation) continue;
|
|
1814
|
+
if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
const queryParams = operation.parameters.filter(
|
|
1818
|
+
(param) => param && typeof param === "object" && param.in === "query"
|
|
1819
|
+
);
|
|
1820
|
+
if (queryParams.length === 0) {
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
|
|
1824
|
+
const schemaName = `${pascalOperationId}QueryParams`;
|
|
1825
|
+
if (!this.schemaDependencies.has(schemaName)) {
|
|
1826
|
+
this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
|
|
1827
|
+
}
|
|
1828
|
+
const properties = {};
|
|
1829
|
+
const required = [];
|
|
1830
|
+
for (const param of queryParams) {
|
|
1831
|
+
const paramName = param.name;
|
|
1832
|
+
const isRequired = param.required === true;
|
|
1833
|
+
const paramSchema = param.schema;
|
|
1834
|
+
if (!paramSchema) continue;
|
|
1835
|
+
let zodType = this.generateQueryParamType(paramSchema, param);
|
|
1836
|
+
if (paramSchema.type === "array" && paramSchema.items) {
|
|
1837
|
+
const itemType = this.generateQueryParamType(paramSchema.items, param);
|
|
1838
|
+
zodType = `z.array(${itemType})`;
|
|
1839
|
+
}
|
|
1840
|
+
if (param.description && this.requestOptions.includeDescriptions) {
|
|
1841
|
+
if (this.requestOptions.useDescribe) {
|
|
1842
|
+
zodType = `${zodType}.describe(${JSON.stringify(param.description)})`;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
if (!isRequired) {
|
|
1846
|
+
zodType = `${zodType}.optional()`;
|
|
1847
|
+
}
|
|
1848
|
+
properties[paramName] = zodType;
|
|
1849
|
+
if (isRequired) {
|
|
1850
|
+
required.push(paramName);
|
|
1851
|
+
}
|
|
1852
|
+
if (paramSchema.$ref) {
|
|
1853
|
+
const refName = resolveRef(paramSchema.$ref);
|
|
1854
|
+
(_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
const objectMode = this.requestOptions.mode;
|
|
1858
|
+
const zodMethod = objectMode === "strict" ? "strictObject" : objectMode === "loose" ? "looseObject" : "object";
|
|
1859
|
+
const propsCode = Object.entries(properties).map(([key, value]) => {
|
|
1860
|
+
const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
|
|
1861
|
+
const quotedKey = needsQuotes ? `"${key}"` : key;
|
|
1862
|
+
return ` ${quotedKey}: ${value}`;
|
|
1863
|
+
}).join(",\n");
|
|
1864
|
+
const schemaCode = `z.${zodMethod}({
|
|
1865
|
+
${propsCode}
|
|
1866
|
+
})`;
|
|
1867
|
+
const operationName = pascalOperationId;
|
|
1868
|
+
const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
|
|
1869
|
+
const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
|
|
1870
|
+
const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
|
|
1871
|
+
const jsdoc = `/**
|
|
1872
|
+
* Query parameters for ${operation.operationId}
|
|
1873
|
+
*/
|
|
1874
|
+
`;
|
|
1875
|
+
const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
|
|
1876
|
+
this.schemas.set(schemaName, fullSchemaCode);
|
|
1877
|
+
this.needsZodImport = true;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Generate Zod type for a query parameter schema
|
|
1883
|
+
*/
|
|
1884
|
+
generateQueryParamType(schema, param) {
|
|
1885
|
+
if (schema.$ref) {
|
|
1886
|
+
const refName = resolveRef(schema.$ref);
|
|
1887
|
+
const schemaName = toCamelCase(refName, { prefix: this.options.prefix, suffix: this.options.suffix });
|
|
1888
|
+
return `${schemaName}Schema`;
|
|
1889
|
+
}
|
|
1890
|
+
if (schema.enum) {
|
|
1891
|
+
const enumValues = schema.enum.map((v) => typeof v === "string" ? `"${v}"` : v).join(", ");
|
|
1892
|
+
return `z.enum([${enumValues}])`;
|
|
1893
|
+
}
|
|
1894
|
+
const type = schema.type;
|
|
1895
|
+
if (type === "string") {
|
|
1896
|
+
let zodType = "z.string()";
|
|
1897
|
+
if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
|
|
1898
|
+
if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
|
|
1899
|
+
if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
|
|
1900
|
+
if (schema.format === "email") zodType = `${zodType}.email()`;
|
|
1901
|
+
if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
|
|
1902
|
+
if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
|
|
1903
|
+
return zodType;
|
|
1904
|
+
}
|
|
1905
|
+
if (type === "number" || type === "integer") {
|
|
1906
|
+
let zodType = type === "integer" ? "z.number().int()" : "z.number()";
|
|
1907
|
+
if (schema.minimum !== void 0) {
|
|
1908
|
+
zodType = schema.exclusiveMinimum ? `${zodType}.gt(${schema.minimum})` : `${zodType}.gte(${schema.minimum})`;
|
|
1909
|
+
}
|
|
1910
|
+
if (schema.maximum !== void 0) {
|
|
1911
|
+
zodType = schema.exclusiveMaximum ? `${zodType}.lt(${schema.maximum})` : `${zodType}.lte(${schema.maximum})`;
|
|
1912
|
+
}
|
|
1913
|
+
return zodType;
|
|
1914
|
+
}
|
|
1915
|
+
if (type === "boolean") {
|
|
1916
|
+
return "z.boolean()";
|
|
1917
|
+
}
|
|
1918
|
+
if (type === "array" && schema.items) {
|
|
1919
|
+
const itemType = this.generateQueryParamType(schema.items, param);
|
|
1920
|
+
let arrayType = `z.array(${itemType})`;
|
|
1921
|
+
if (schema.minItems !== void 0) arrayType = `${arrayType}.min(${schema.minItems})`;
|
|
1922
|
+
if (schema.maxItems !== void 0) arrayType = `${arrayType}.max(${schema.maxItems})`;
|
|
1923
|
+
return arrayType;
|
|
1924
|
+
}
|
|
1925
|
+
return "z.unknown()";
|
|
1926
|
+
}
|
|
1776
1927
|
/**
|
|
1777
1928
|
* Generate native TypeScript enum
|
|
1778
1929
|
*/
|
|
@@ -1783,8 +1934,8 @@ ${typeCode}`;
|
|
|
1783
1934
|
const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
|
|
1784
1935
|
if (resolvedOptions.nativeEnumType === "enum") {
|
|
1785
1936
|
const enumName = `${name}Enum`;
|
|
1786
|
-
const members = schema.enum.map((value
|
|
1787
|
-
const key = typeof value === "string" ? this.toEnumKey(value) : `
|
|
1937
|
+
const members = schema.enum.map((value) => {
|
|
1938
|
+
const key = typeof value === "string" ? this.toEnumKey(value) : `N${value}`;
|
|
1788
1939
|
const val = typeof value === "string" ? `"${value}"` : value;
|
|
1789
1940
|
return ` ${key} = ${val}`;
|
|
1790
1941
|
}).join(",\n");
|
|
@@ -1927,7 +2078,11 @@ ${props.join("\n")}
|
|
|
1927
2078
|
const visited = /* @__PURE__ */ new Set();
|
|
1928
2079
|
const visiting = /* @__PURE__ */ new Set();
|
|
1929
2080
|
const aliases = [];
|
|
2081
|
+
const circularDeps = /* @__PURE__ */ new Set();
|
|
1930
2082
|
const codeCache = /* @__PURE__ */ new Map();
|
|
2083
|
+
for (const [name, code] of this.enums) {
|
|
2084
|
+
codeCache.set(name, code);
|
|
2085
|
+
}
|
|
1931
2086
|
for (const [name, code] of this.schemas) {
|
|
1932
2087
|
codeCache.set(name, code);
|
|
1933
2088
|
}
|
|
@@ -1937,6 +2092,7 @@ ${props.join("\n")}
|
|
|
1937
2092
|
const visit = (name) => {
|
|
1938
2093
|
if (visited.has(name)) return;
|
|
1939
2094
|
if (visiting.has(name)) {
|
|
2095
|
+
circularDeps.add(name);
|
|
1940
2096
|
return;
|
|
1941
2097
|
}
|
|
1942
2098
|
visiting.add(name);
|
|
@@ -1951,19 +2107,27 @@ ${props.join("\n")}
|
|
|
1951
2107
|
const deps = this.schemaDependencies.get(name);
|
|
1952
2108
|
if (deps && deps.size > 0) {
|
|
1953
2109
|
for (const dep of deps) {
|
|
1954
|
-
if (this.schemas.has(dep) || this.types.has(dep)) {
|
|
2110
|
+
if (this.enums.has(dep) || this.schemas.has(dep) || this.types.has(dep)) {
|
|
1955
2111
|
visit(dep);
|
|
1956
2112
|
}
|
|
1957
2113
|
}
|
|
1958
2114
|
}
|
|
1959
2115
|
visiting.delete(name);
|
|
1960
2116
|
visited.add(name);
|
|
1961
|
-
|
|
2117
|
+
if (!circularDeps.has(name)) {
|
|
2118
|
+
sorted.push(name);
|
|
2119
|
+
}
|
|
1962
2120
|
};
|
|
1963
|
-
const allNames = /* @__PURE__ */ new Set([...this.schemas.keys(), ...this.types.keys()]);
|
|
2121
|
+
const allNames = /* @__PURE__ */ new Set([...this.enums.keys(), ...this.schemas.keys(), ...this.types.keys()]);
|
|
1964
2122
|
for (const name of allNames) {
|
|
1965
2123
|
visit(name);
|
|
1966
2124
|
}
|
|
2125
|
+
for (const name of circularDeps) {
|
|
2126
|
+
if (!visited.has(name)) {
|
|
2127
|
+
sorted.push(name);
|
|
2128
|
+
visited.add(name);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
1967
2131
|
return [...sorted, ...aliases];
|
|
1968
2132
|
}
|
|
1969
2133
|
/**
|