@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/index.mjs CHANGED
@@ -120,7 +120,7 @@ function generateEnum(name, values, options) {
120
120
  const enumCode = `export enum ${enumName} {
121
121
  ${enumEntries}
122
122
  }`;
123
- const schemaCode2 = `export const ${schemaName} = z.enum(${enumName});`;
123
+ const schemaCode2 = `export const ${schemaName} = z.nativeEnum(${enumName});`;
124
124
  const typeCode2 = `export type ${name} = z.infer<typeof ${schemaName}>;`;
125
125
  return { enumCode, schemaCode: schemaCode2, typeCode: typeCode2 };
126
126
  }
@@ -303,20 +303,20 @@ function generateArrayValidation(schema, context) {
303
303
  }
304
304
 
305
305
  // src/validators/composition-validator.ts
306
- function generateUnion(schemas, discriminator, isNullable2, context, options) {
306
+ function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
307
307
  if (discriminator) {
308
308
  let resolvedSchemas = schemas;
309
309
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
310
310
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
311
311
  }
312
- let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s));
312
+ let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
313
313
  if (options == null ? void 0 : options.passthrough) {
314
314
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
315
315
  }
316
316
  const union2 = `z.discriminatedUnion("${discriminator}", [${schemaStrings2.join(", ")}])`;
317
317
  return wrapNullable(union2, isNullable2);
318
318
  }
319
- let schemaStrings = schemas.map((s) => context.generatePropertySchema(s));
319
+ let schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema));
320
320
  if (options == null ? void 0 : options.passthrough) {
321
321
  schemaStrings = schemaStrings.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
322
322
  }
@@ -1149,7 +1149,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1149
1149
  {
1150
1150
  passthrough: needsPassthrough,
1151
1151
  discriminatorMapping: (_c = schema.discriminator) == null ? void 0 : _c.mapping
1152
- }
1152
+ },
1153
+ currentSchema
1153
1154
  );
1154
1155
  if (schema.unevaluatedProperties !== void 0) {
1155
1156
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1169,7 +1170,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1169
1170
  {
1170
1171
  passthrough: needsPassthrough,
1171
1172
  discriminatorMapping: (_e = schema.discriminator) == null ? void 0 : _e.mapping
1172
- }
1173
+ },
1174
+ currentSchema
1173
1175
  );
1174
1176
  if (schema.unevaluatedProperties !== void 0) {
1175
1177
  composition = this.applyUnevaluatedProperties(composition, schema);
@@ -1340,10 +1342,13 @@ var ZodSchemaGenerator = class {
1340
1342
  }
1341
1343
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1342
1344
  if (schema.enum) {
1343
- const typeMode = this.schemaTypeModeMap.get(name) || "inferred";
1344
- if (typeMode === "inferred") {
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 {
1345
1350
  const { enumCode } = generateEnum(name, schema.enum, {
1346
- enumType: this.options.enumType || "zod",
1351
+ enumType: "zod",
1347
1352
  prefix: this.options.prefix,
1348
1353
  suffix: this.options.suffix
1349
1354
  });
@@ -1351,14 +1356,13 @@ var ZodSchemaGenerator = class {
1351
1356
  this.enums.set(name, enumCode);
1352
1357
  this.needsZodImport = true;
1353
1358
  }
1354
- } else {
1355
- this.generateNativeEnum(name, schema);
1356
1359
  }
1357
1360
  }
1358
1361
  }
1359
1362
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1360
1363
  this.generateComponentSchema(name, schema);
1361
1364
  }
1365
+ this.generateQueryParameterSchemas();
1362
1366
  const orderedSchemaNames = this.topologicalSort();
1363
1367
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1364
1368
  if (this.options.showStats === true) {
@@ -1369,12 +1373,8 @@ var ZodSchemaGenerator = class {
1369
1373
  output.push('import { z } from "zod";');
1370
1374
  output.push("");
1371
1375
  }
1372
- if (this.enums.size > 0 || this.nativeEnums.size > 0) {
1373
- output.push("// Enums");
1374
- for (const enumCode of this.enums.values()) {
1375
- output.push(enumCode);
1376
- output.push("");
1377
- }
1376
+ if (this.nativeEnums.size > 0) {
1377
+ output.push("// Native Enums");
1378
1378
  for (const enumCode of this.nativeEnums.values()) {
1379
1379
  output.push(enumCode);
1380
1380
  output.push("");
@@ -1382,9 +1382,13 @@ var ZodSchemaGenerator = class {
1382
1382
  }
1383
1383
  output.push("// Schemas and Types");
1384
1384
  for (const name of orderedSchemaNames) {
1385
+ const enumCode = this.enums.get(name);
1385
1386
  const schemaCode = this.schemas.get(name);
1386
1387
  const typeCode = this.types.get(name);
1387
- if (schemaCode) {
1388
+ if (enumCode) {
1389
+ output.push(enumCode);
1390
+ output.push("");
1391
+ } else if (schemaCode) {
1388
1392
  output.push(schemaCode);
1389
1393
  if (!schemaCode.includes(`export type ${name}`)) {
1390
1394
  const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
@@ -1722,10 +1726,19 @@ var ZodSchemaGenerator = class {
1722
1726
  const context = this.schemaUsageMap.get(name);
1723
1727
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1724
1728
  if (schema.enum) {
1725
- if (typeMode === "inferred") {
1726
- const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
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 {
1727
1740
  const { enumCode, schemaCode, typeCode } = generateEnum(name, schema.enum, {
1728
- enumType: resolvedOptions.enumType,
1741
+ enumType: "zod",
1729
1742
  prefix: this.options.prefix,
1730
1743
  suffix: this.options.suffix
1731
1744
  });
@@ -1784,6 +1797,133 @@ ${typeCode}`;
1784
1797
  this.schemas.set(name, zodSchemaCode);
1785
1798
  }
1786
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
+ }
1787
1927
  /**
1788
1928
  * Generate native TypeScript enum
1789
1929
  */
@@ -1794,8 +1934,8 @@ ${typeCode}`;
1794
1934
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1795
1935
  if (resolvedOptions.nativeEnumType === "enum") {
1796
1936
  const enumName = `${name}Enum`;
1797
- const members = schema.enum.map((value, index) => {
1798
- const key = typeof value === "string" ? this.toEnumKey(value) : `Value${index}`;
1937
+ const members = schema.enum.map((value) => {
1938
+ const key = typeof value === "string" ? this.toEnumKey(value) : `N${value}`;
1799
1939
  const val = typeof value === "string" ? `"${value}"` : value;
1800
1940
  return ` ${key} = ${val}`;
1801
1941
  }).join(",\n");
@@ -1938,7 +2078,11 @@ ${props.join("\n")}
1938
2078
  const visited = /* @__PURE__ */ new Set();
1939
2079
  const visiting = /* @__PURE__ */ new Set();
1940
2080
  const aliases = [];
2081
+ const circularDeps = /* @__PURE__ */ new Set();
1941
2082
  const codeCache = /* @__PURE__ */ new Map();
2083
+ for (const [name, code] of this.enums) {
2084
+ codeCache.set(name, code);
2085
+ }
1942
2086
  for (const [name, code] of this.schemas) {
1943
2087
  codeCache.set(name, code);
1944
2088
  }
@@ -1948,6 +2092,7 @@ ${props.join("\n")}
1948
2092
  const visit = (name) => {
1949
2093
  if (visited.has(name)) return;
1950
2094
  if (visiting.has(name)) {
2095
+ circularDeps.add(name);
1951
2096
  return;
1952
2097
  }
1953
2098
  visiting.add(name);
@@ -1962,19 +2107,27 @@ ${props.join("\n")}
1962
2107
  const deps = this.schemaDependencies.get(name);
1963
2108
  if (deps && deps.size > 0) {
1964
2109
  for (const dep of deps) {
1965
- if (this.schemas.has(dep) || this.types.has(dep)) {
2110
+ if (this.enums.has(dep) || this.schemas.has(dep) || this.types.has(dep)) {
1966
2111
  visit(dep);
1967
2112
  }
1968
2113
  }
1969
2114
  }
1970
2115
  visiting.delete(name);
1971
2116
  visited.add(name);
1972
- sorted.push(name);
2117
+ if (!circularDeps.has(name)) {
2118
+ sorted.push(name);
2119
+ }
1973
2120
  };
1974
- 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()]);
1975
2122
  for (const name of allNames) {
1976
2123
  visit(name);
1977
2124
  }
2125
+ for (const name of circularDeps) {
2126
+ if (!visited.has(name)) {
2127
+ sorted.push(name);
2128
+ visited.add(name);
2129
+ }
2130
+ }
1978
2131
  return [...sorted, ...aliases];
1979
2132
  }
1980
2133
  /**