@cerios/openapi-to-zod 0.1.2 → 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 CHANGED
@@ -127,7 +127,7 @@ function generateEnum(name, values, options) {
127
127
  const enumCode = `export enum ${enumName} {
128
128
  ${enumEntries}
129
129
  }`;
130
- const schemaCode2 = `export const ${schemaName} = z.enum(${enumName});`;
130
+ const schemaCode2 = `export const ${schemaName} = z.nativeEnum(${enumName});`;
131
131
  const typeCode2 = `export type ${name} = z.infer<typeof ${schemaName}>;`;
132
132
  return { enumCode, schemaCode: schemaCode2, typeCode: typeCode2 };
133
133
  }
@@ -310,20 +310,20 @@ function generateArrayValidation(schema, context) {
310
310
  }
311
311
 
312
312
  // src/validators/composition-validator.ts
313
- function generateUnion(schemas, discriminator, isNullable2, context, options) {
313
+ function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
314
314
  if (discriminator) {
315
315
  let resolvedSchemas = schemas;
316
316
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
317
317
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
318
318
  }
319
- let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s));
319
+ let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
320
320
  if (options == null ? void 0 : options.passthrough) {
321
321
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
322
322
  }
323
323
  const union2 = `z.discriminatedUnion("${discriminator}", [${schemaStrings2.join(", ")}])`;
324
324
  return wrapNullable(union2, isNullable2);
325
325
  }
326
- let schemaStrings = schemas.map((s) => context.generatePropertySchema(s));
326
+ let schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema));
327
327
  if (options == null ? void 0 : options.passthrough) {
328
328
  schemaStrings = schemaStrings.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
329
329
  }
@@ -1156,7 +1156,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1156
1156
  {
1157
1157
  passthrough: needsPassthrough,
1158
1158
  discriminatorMapping: (_c = schema.discriminator) == null ? void 0 : _c.mapping
1159
- }
1159
+ },
1160
+ currentSchema
1160
1161
  );
1161
1162
  if (schema.unevaluatedProperties !== void 0) {
1162
1163
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1176,7 +1177,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1176
1177
  {
1177
1178
  passthrough: needsPassthrough,
1178
1179
  discriminatorMapping: (_e = schema.discriminator) == null ? void 0 : _e.mapping
1179
- }
1180
+ },
1181
+ currentSchema
1180
1182
  );
1181
1183
  if (schema.unevaluatedProperties !== void 0) {
1182
1184
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1347,10 +1349,13 @@ var ZodSchemaGenerator = class {
1347
1349
  }
1348
1350
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1349
1351
  if (schema.enum) {
1350
- const typeMode = this.schemaTypeModeMap.get(name) || "inferred";
1351
- if (typeMode === "inferred") {
1352
+ const context = this.schemaUsageMap.get(name);
1353
+ const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1354
+ if (resolvedOptions.enumType === "typescript") {
1355
+ this.generateNativeEnum(name, schema);
1356
+ } else {
1352
1357
  const { enumCode } = generateEnum(name, schema.enum, {
1353
- enumType: this.options.enumType || "zod",
1358
+ enumType: "zod",
1354
1359
  prefix: this.options.prefix,
1355
1360
  suffix: this.options.suffix
1356
1361
  });
@@ -1358,14 +1363,13 @@ var ZodSchemaGenerator = class {
1358
1363
  this.enums.set(name, enumCode);
1359
1364
  this.needsZodImport = true;
1360
1365
  }
1361
- } else {
1362
- this.generateNativeEnum(name, schema);
1363
1366
  }
1364
1367
  }
1365
1368
  }
1366
1369
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1367
1370
  this.generateComponentSchema(name, schema);
1368
1371
  }
1372
+ this.generateQueryParameterSchemas();
1369
1373
  const orderedSchemaNames = this.topologicalSort();
1370
1374
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1371
1375
  if (this.options.showStats === true) {
@@ -1376,12 +1380,8 @@ var ZodSchemaGenerator = class {
1376
1380
  output.push('import { z } from "zod";');
1377
1381
  output.push("");
1378
1382
  }
1379
- if (this.enums.size > 0 || this.nativeEnums.size > 0) {
1380
- output.push("// Enums");
1381
- for (const enumCode of this.enums.values()) {
1382
- output.push(enumCode);
1383
- output.push("");
1384
- }
1383
+ if (this.nativeEnums.size > 0) {
1384
+ output.push("// Native Enums");
1385
1385
  for (const enumCode of this.nativeEnums.values()) {
1386
1386
  output.push(enumCode);
1387
1387
  output.push("");
@@ -1389,9 +1389,13 @@ var ZodSchemaGenerator = class {
1389
1389
  }
1390
1390
  output.push("// Schemas and Types");
1391
1391
  for (const name of orderedSchemaNames) {
1392
+ const enumCode = this.enums.get(name);
1392
1393
  const schemaCode = this.schemas.get(name);
1393
1394
  const typeCode = this.types.get(name);
1394
- if (schemaCode) {
1395
+ if (enumCode) {
1396
+ output.push(enumCode);
1397
+ output.push("");
1398
+ } else if (schemaCode) {
1395
1399
  output.push(schemaCode);
1396
1400
  if (!schemaCode.includes(`export type ${name}`)) {
1397
1401
  const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
@@ -1729,10 +1733,19 @@ var ZodSchemaGenerator = class {
1729
1733
  const context = this.schemaUsageMap.get(name);
1730
1734
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1731
1735
  if (schema.enum) {
1732
- if (typeMode === "inferred") {
1733
- const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1736
+ const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1737
+ if (resolvedOptions.enumType === "typescript") {
1738
+ const { schemaCode, typeCode } = generateEnum(name, schema.enum, {
1739
+ enumType: "typescript",
1740
+ prefix: this.options.prefix,
1741
+ suffix: this.options.suffix
1742
+ });
1743
+ const enumSchemaCode = `${jsdoc}${schemaCode}
1744
+ ${typeCode}`;
1745
+ this.schemas.set(name, enumSchemaCode);
1746
+ } else {
1734
1747
  const { enumCode, schemaCode, typeCode } = generateEnum(name, schema.enum, {
1735
- enumType: resolvedOptions.enumType,
1748
+ enumType: "zod",
1736
1749
  prefix: this.options.prefix,
1737
1750
  suffix: this.options.suffix
1738
1751
  });
@@ -1791,6 +1804,133 @@ ${typeCode}`;
1791
1804
  this.schemas.set(name, zodSchemaCode);
1792
1805
  }
1793
1806
  }
1807
+ /**
1808
+ * Generate query parameter schemas for each operation
1809
+ */
1810
+ generateQueryParameterSchemas() {
1811
+ var _a;
1812
+ if (!this.spec.paths) {
1813
+ return;
1814
+ }
1815
+ for (const [_path, pathItem] of Object.entries(this.spec.paths)) {
1816
+ if (!pathItem || typeof pathItem !== "object") continue;
1817
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1818
+ for (const method of methods) {
1819
+ const operation = pathItem[method];
1820
+ if (!operation) continue;
1821
+ if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
1822
+ continue;
1823
+ }
1824
+ const queryParams = operation.parameters.filter(
1825
+ (param) => param && typeof param === "object" && param.in === "query"
1826
+ );
1827
+ if (queryParams.length === 0) {
1828
+ continue;
1829
+ }
1830
+ const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
1831
+ const schemaName = `${pascalOperationId}QueryParams`;
1832
+ if (!this.schemaDependencies.has(schemaName)) {
1833
+ this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
1834
+ }
1835
+ const properties = {};
1836
+ const required = [];
1837
+ for (const param of queryParams) {
1838
+ const paramName = param.name;
1839
+ const isRequired = param.required === true;
1840
+ const paramSchema = param.schema;
1841
+ if (!paramSchema) continue;
1842
+ let zodType = this.generateQueryParamType(paramSchema, param);
1843
+ if (paramSchema.type === "array" && paramSchema.items) {
1844
+ const itemType = this.generateQueryParamType(paramSchema.items, param);
1845
+ zodType = `z.array(${itemType})`;
1846
+ }
1847
+ if (param.description && this.requestOptions.includeDescriptions) {
1848
+ if (this.requestOptions.useDescribe) {
1849
+ zodType = `${zodType}.describe(${JSON.stringify(param.description)})`;
1850
+ }
1851
+ }
1852
+ if (!isRequired) {
1853
+ zodType = `${zodType}.optional()`;
1854
+ }
1855
+ properties[paramName] = zodType;
1856
+ if (isRequired) {
1857
+ required.push(paramName);
1858
+ }
1859
+ if (paramSchema.$ref) {
1860
+ const refName = resolveRef(paramSchema.$ref);
1861
+ (_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
1862
+ }
1863
+ }
1864
+ const objectMode = this.requestOptions.mode;
1865
+ const zodMethod = objectMode === "strict" ? "strictObject" : objectMode === "loose" ? "looseObject" : "object";
1866
+ const propsCode = Object.entries(properties).map(([key, value]) => {
1867
+ const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
1868
+ const quotedKey = needsQuotes ? `"${key}"` : key;
1869
+ return ` ${quotedKey}: ${value}`;
1870
+ }).join(",\n");
1871
+ const schemaCode = `z.${zodMethod}({
1872
+ ${propsCode}
1873
+ })`;
1874
+ const operationName = pascalOperationId;
1875
+ const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
1876
+ const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
1877
+ const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
1878
+ const jsdoc = `/**
1879
+ * Query parameters for ${operation.operationId}
1880
+ */
1881
+ `;
1882
+ const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
1883
+ this.schemas.set(schemaName, fullSchemaCode);
1884
+ this.needsZodImport = true;
1885
+ }
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Generate Zod type for a query parameter schema
1890
+ */
1891
+ generateQueryParamType(schema, param) {
1892
+ if (schema.$ref) {
1893
+ const refName = resolveRef(schema.$ref);
1894
+ const schemaName = toCamelCase(refName, { prefix: this.options.prefix, suffix: this.options.suffix });
1895
+ return `${schemaName}Schema`;
1896
+ }
1897
+ if (schema.enum) {
1898
+ const enumValues = schema.enum.map((v) => typeof v === "string" ? `"${v}"` : v).join(", ");
1899
+ return `z.enum([${enumValues}])`;
1900
+ }
1901
+ const type = schema.type;
1902
+ if (type === "string") {
1903
+ let zodType = "z.string()";
1904
+ if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
1905
+ if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
1906
+ if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
1907
+ if (schema.format === "email") zodType = `${zodType}.email()`;
1908
+ if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
1909
+ if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
1910
+ return zodType;
1911
+ }
1912
+ if (type === "number" || type === "integer") {
1913
+ let zodType = type === "integer" ? "z.number().int()" : "z.number()";
1914
+ if (schema.minimum !== void 0) {
1915
+ zodType = schema.exclusiveMinimum ? `${zodType}.gt(${schema.minimum})` : `${zodType}.gte(${schema.minimum})`;
1916
+ }
1917
+ if (schema.maximum !== void 0) {
1918
+ zodType = schema.exclusiveMaximum ? `${zodType}.lt(${schema.maximum})` : `${zodType}.lte(${schema.maximum})`;
1919
+ }
1920
+ return zodType;
1921
+ }
1922
+ if (type === "boolean") {
1923
+ return "z.boolean()";
1924
+ }
1925
+ if (type === "array" && schema.items) {
1926
+ const itemType = this.generateQueryParamType(schema.items, param);
1927
+ let arrayType = `z.array(${itemType})`;
1928
+ if (schema.minItems !== void 0) arrayType = `${arrayType}.min(${schema.minItems})`;
1929
+ if (schema.maxItems !== void 0) arrayType = `${arrayType}.max(${schema.maxItems})`;
1930
+ return arrayType;
1931
+ }
1932
+ return "z.unknown()";
1933
+ }
1794
1934
  /**
1795
1935
  * Generate native TypeScript enum
1796
1936
  */
@@ -1801,8 +1941,8 @@ ${typeCode}`;
1801
1941
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1802
1942
  if (resolvedOptions.nativeEnumType === "enum") {
1803
1943
  const enumName = `${name}Enum`;
1804
- const members = schema.enum.map((value, index) => {
1805
- const key = typeof value === "string" ? this.toEnumKey(value) : `Value${index}`;
1944
+ const members = schema.enum.map((value) => {
1945
+ const key = typeof value === "string" ? this.toEnumKey(value) : `N${value}`;
1806
1946
  const val = typeof value === "string" ? `"${value}"` : value;
1807
1947
  return ` ${key} = ${val}`;
1808
1948
  }).join(",\n");
@@ -1945,7 +2085,11 @@ ${props.join("\n")}
1945
2085
  const visited = /* @__PURE__ */ new Set();
1946
2086
  const visiting = /* @__PURE__ */ new Set();
1947
2087
  const aliases = [];
2088
+ const circularDeps = /* @__PURE__ */ new Set();
1948
2089
  const codeCache = /* @__PURE__ */ new Map();
2090
+ for (const [name, code] of this.enums) {
2091
+ codeCache.set(name, code);
2092
+ }
1949
2093
  for (const [name, code] of this.schemas) {
1950
2094
  codeCache.set(name, code);
1951
2095
  }
@@ -1955,6 +2099,7 @@ ${props.join("\n")}
1955
2099
  const visit = (name) => {
1956
2100
  if (visited.has(name)) return;
1957
2101
  if (visiting.has(name)) {
2102
+ circularDeps.add(name);
1958
2103
  return;
1959
2104
  }
1960
2105
  visiting.add(name);
@@ -1969,19 +2114,27 @@ ${props.join("\n")}
1969
2114
  const deps = this.schemaDependencies.get(name);
1970
2115
  if (deps && deps.size > 0) {
1971
2116
  for (const dep of deps) {
1972
- if (this.schemas.has(dep) || this.types.has(dep)) {
2117
+ if (this.enums.has(dep) || this.schemas.has(dep) || this.types.has(dep)) {
1973
2118
  visit(dep);
1974
2119
  }
1975
2120
  }
1976
2121
  }
1977
2122
  visiting.delete(name);
1978
2123
  visited.add(name);
1979
- sorted.push(name);
2124
+ if (!circularDeps.has(name)) {
2125
+ sorted.push(name);
2126
+ }
1980
2127
  };
1981
- const allNames = /* @__PURE__ */ new Set([...this.schemas.keys(), ...this.types.keys()]);
2128
+ const allNames = /* @__PURE__ */ new Set([...this.enums.keys(), ...this.schemas.keys(), ...this.types.keys()]);
1982
2129
  for (const name of allNames) {
1983
2130
  visit(name);
1984
2131
  }
2132
+ for (const name of circularDeps) {
2133
+ if (!visited.has(name)) {
2134
+ sorted.push(name);
2135
+ visited.add(name);
2136
+ }
2137
+ }
1985
2138
  return [...sorted, ...aliases];
1986
2139
  }
1987
2140
  /**
@@ -2016,8 +2169,7 @@ ${props.join("\n")}
2016
2169
 
2017
2170
  // src/batch-executor.ts
2018
2171
  async function processSpec(spec, index, total) {
2019
- const specName = spec.name || spec.input;
2020
- console.log(`Processing [${index + 1}/${total}] ${specName}...`);
2172
+ console.log(`Processing [${index + 1}/${total}] ${spec.input}...`);
2021
2173
  try {
2022
2174
  const generator = new ZodSchemaGenerator(spec);
2023
2175
  generator.generate();
@@ -2076,8 +2228,7 @@ ${"=".repeat(50)}`);
2076
2228
  console.log("\nFailed specs:");
2077
2229
  for (const result of summary.results) {
2078
2230
  if (!result.success) {
2079
- const specName = result.spec.name || result.spec.input;
2080
- console.error(` \u2717 ${specName}`);
2231
+ console.error(` \u2717 ${result.spec.input}`);
2081
2232
  console.error(` Error: ${result.error}`);
2082
2233
  }
2083
2234
  }
@@ -2117,12 +2268,6 @@ function getBatchExitCode(summary) {
2117
2268
  return summary.failed > 0 ? 1 : 0;
2118
2269
  }
2119
2270
 
2120
- // src/types.ts
2121
- var FilePath = {
2122
- from: (path) => path,
2123
- is: (value) => typeof value === "string" && value.length > 0
2124
- };
2125
-
2126
2271
  // src/utils/config-loader.ts
2127
2272
  var import_cosmiconfig = require("cosmiconfig");
2128
2273
  var import_zod = require("zod");
@@ -2359,8 +2504,8 @@ async function executeSingleSpecMode(options) {
2359
2504
  });
2360
2505
  }
2361
2506
  const generatorOptions = {
2362
- input: FilePath.from(options.input),
2363
- output: FilePath.from(options.output),
2507
+ input: options.input,
2508
+ output: options.output,
2364
2509
  mode: options.mode,
2365
2510
  includeDescriptions: options.descriptions,
2366
2511
  enumType: options.enumType,