@cerios/openapi-to-zod 0.6.0 → 1.1.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
@@ -67,11 +67,24 @@ var ConfigurationError = class extends GeneratorError {
67
67
  // src/openapi-generator.ts
68
68
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
69
69
  import { dirname, normalize } from "path";
70
+ import { minimatch as minimatch2 } from "minimatch";
70
71
  import { parse } from "yaml";
71
72
 
72
73
  // src/utils/name-utils.ts
74
+ function sanitizeIdentifier(str) {
75
+ return str.replace(/[^a-zA-Z0-9._\-\s]+/g, "_");
76
+ }
73
77
  function toCamelCase(str, options) {
74
- let name = str.charAt(0).toLowerCase() + str.slice(1);
78
+ const sanitized = sanitizeIdentifier(str);
79
+ const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
80
+ let name;
81
+ if (words.length === 0) {
82
+ name = str.charAt(0).toLowerCase() + str.slice(1);
83
+ } else if (words.length === 1) {
84
+ name = words[0].charAt(0).toLowerCase() + words[0].slice(1);
85
+ } else {
86
+ name = words[0].charAt(0).toLowerCase() + words[0].slice(1) + words.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
87
+ }
75
88
  if (options == null ? void 0 : options.prefix) {
76
89
  const prefix = options.prefix.toLowerCase();
77
90
  name = prefix + name.charAt(0).toUpperCase() + name.slice(1);
@@ -84,12 +97,23 @@ function toCamelCase(str, options) {
84
97
  }
85
98
  function toPascalCase(str) {
86
99
  const stringValue = String(str);
87
- let result = stringValue.replace(/[^a-zA-Z0-9_]+/g, "_").split(/[-_]+/).filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
100
+ const isAlreadyValidCase = /^[a-zA-Z][a-zA-Z0-9]*$/.test(stringValue);
101
+ if (isAlreadyValidCase) {
102
+ return stringValue.charAt(0).toUpperCase() + stringValue.slice(1);
103
+ }
104
+ const sanitized = sanitizeIdentifier(stringValue);
105
+ const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
106
+ let result;
107
+ if (words.length === 0) {
108
+ result = "Value";
109
+ } else {
110
+ result = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
111
+ }
88
112
  if (/^\d/.test(result)) {
89
113
  result = `N${result}`;
90
114
  }
91
115
  if (!result || /^_+$/.test(result)) {
92
- result = "Value";
116
+ return "Value";
93
117
  }
94
118
  return result;
95
119
  }
@@ -101,9 +125,10 @@ function resolveRef(ref) {
101
125
  // src/generators/enum-generator.ts
102
126
  function generateEnum(name, values, options) {
103
127
  const schemaName = `${toCamelCase(name, options)}Schema`;
128
+ const typeName = toPascalCase(name);
104
129
  const enumValues = values.map((v) => `"${v}"`).join(", ");
105
130
  const schemaCode = `export const ${schemaName} = z.enum([${enumValues}]);`;
106
- const typeCode = `export type ${name} = z.infer<typeof ${schemaName}>;`;
131
+ const typeCode = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
107
132
  return { schemaCode, typeCode };
108
133
  }
109
134
 
@@ -203,6 +228,9 @@ var LRUCache = class {
203
228
  this.cache = /* @__PURE__ */ new Map();
204
229
  this.maxSize = maxSize;
205
230
  }
231
+ get capacity() {
232
+ return this.maxSize;
233
+ }
206
234
  get(key) {
207
235
  if (!this.cache.has(key)) return void 0;
208
236
  const value = this.cache.get(key);
@@ -233,6 +261,76 @@ var LRUCache = class {
233
261
  }
234
262
  };
235
263
 
264
+ // src/utils/pattern-utils.ts
265
+ function isRegexPattern(pattern) {
266
+ if (pattern.startsWith("^") || pattern.endsWith("$")) {
267
+ return true;
268
+ }
269
+ if (/\\[dDwWsS]/.test(pattern)) {
270
+ return true;
271
+ }
272
+ if (/\.\*|\.\+/.test(pattern)) {
273
+ return true;
274
+ }
275
+ if (/[[\]()]/.test(pattern)) {
276
+ return true;
277
+ }
278
+ if (/[^/][+?*]\{/.test(pattern)) {
279
+ return true;
280
+ }
281
+ return false;
282
+ }
283
+ function patternToRegex(pattern) {
284
+ if (pattern instanceof RegExp) {
285
+ return pattern;
286
+ }
287
+ if (isRegexPattern(pattern)) {
288
+ try {
289
+ return new RegExp(pattern);
290
+ } catch (error) {
291
+ console.warn(`\u26A0\uFE0F Invalid regex pattern "${pattern}": ${error instanceof Error ? error.message : String(error)}`);
292
+ return null;
293
+ }
294
+ }
295
+ return null;
296
+ }
297
+ function stripPrefix(input, pattern, ensureLeadingChar) {
298
+ if (!pattern) {
299
+ return input;
300
+ }
301
+ const regex = patternToRegex(pattern);
302
+ if (regex) {
303
+ const match = input.match(regex);
304
+ if (match && match.index === 0) {
305
+ const stripped = input.substring(match[0].length);
306
+ if (ensureLeadingChar) {
307
+ if (stripped === "") {
308
+ return ensureLeadingChar;
309
+ }
310
+ if (!stripped.startsWith(ensureLeadingChar)) {
311
+ return `${ensureLeadingChar}${stripped}`;
312
+ }
313
+ }
314
+ return stripped;
315
+ }
316
+ } else {
317
+ const stringPattern = pattern;
318
+ if (input.startsWith(stringPattern)) {
319
+ const stripped = input.substring(stringPattern.length);
320
+ if (ensureLeadingChar) {
321
+ if (stripped === "") {
322
+ return ensureLeadingChar;
323
+ }
324
+ if (!stripped.startsWith(ensureLeadingChar)) {
325
+ return `${ensureLeadingChar}${stripped}`;
326
+ }
327
+ }
328
+ return stripped;
329
+ }
330
+ }
331
+ return input;
332
+ }
333
+
236
334
  // src/validators/array-validator.ts
237
335
  function generateArrayValidation(schema, context) {
238
336
  var _a;
@@ -752,6 +850,11 @@ ${properties.join(",\n")}
752
850
 
753
851
  // src/validators/string-validator.ts
754
852
  var PATTERN_CACHE = new LRUCache(1e3);
853
+ function configurePatternCache(size) {
854
+ if (size > 0 && size !== PATTERN_CACHE.capacity) {
855
+ PATTERN_CACHE = new LRUCache(size);
856
+ }
857
+ }
755
858
  var FORMAT_MAP = {
756
859
  uuid: "z.uuid()",
757
860
  email: "z.email()",
@@ -1084,8 +1187,9 @@ var _PropertyGenerator = class _PropertyGenerator {
1084
1187
  }
1085
1188
  (_a = this.context.schemaDependencies.get(currentSchema)) == null ? void 0 : _a.add(refName);
1086
1189
  }
1087
- const schemaName = `${toCamelCase(resolvedRefName, this.context.namingOptions)}Schema`;
1088
- if (currentSchema && this.isCircularThroughAlias(currentSchema, refName)) {
1190
+ const strippedRefName = stripPrefix(resolvedRefName, this.context.stripSchemaPrefix);
1191
+ const schemaName = `${toCamelCase(strippedRefName, this.context.namingOptions)}Schema`;
1192
+ if (currentSchema && (refName === currentSchema || this.isCircularThroughAlias(currentSchema, refName))) {
1089
1193
  const lazySchema = `z.lazy((): z.ZodTypeAny => ${schemaName})`;
1090
1194
  return wrapNullable(lazySchema, nullable);
1091
1195
  }
@@ -1372,7 +1476,7 @@ var OpenApiGenerator = class {
1372
1476
  this.schemaUsageMap = /* @__PURE__ */ new Map();
1373
1477
  this.needsZodImport = true;
1374
1478
  this.filterStats = createFilterStatistics();
1375
- var _a, _b, _c;
1479
+ var _a, _b, _c, _d, _e;
1376
1480
  if (!options.input) {
1377
1481
  throw new ConfigurationError("Input path is required", { providedOptions: options });
1378
1482
  }
@@ -1385,11 +1489,18 @@ var OpenApiGenerator = class {
1385
1489
  schemaType: options.schemaType || "all",
1386
1490
  prefix: options.prefix,
1387
1491
  suffix: options.suffix,
1492
+ stripSchemaPrefix: options.stripSchemaPrefix,
1388
1493
  showStats: (_c = options.showStats) != null ? _c : true,
1389
1494
  request: options.request,
1390
1495
  response: options.response,
1391
- operationFilters: options.operationFilters
1496
+ operationFilters: options.operationFilters,
1497
+ ignoreHeaders: options.ignoreHeaders,
1498
+ cacheSize: (_d = options.cacheSize) != null ? _d : 1e3,
1499
+ batchSize: (_e = options.batchSize) != null ? _e : 10
1392
1500
  };
1501
+ if (this.options.cacheSize) {
1502
+ configurePatternCache(this.options.cacheSize);
1503
+ }
1393
1504
  try {
1394
1505
  const fs = __require("fs");
1395
1506
  if (!fs.existsSync(this.options.input)) {
@@ -1455,7 +1566,8 @@ var OpenApiGenerator = class {
1455
1566
  namingOptions: {
1456
1567
  prefix: this.options.prefix,
1457
1568
  suffix: this.options.suffix
1458
- }
1569
+ },
1570
+ stripSchemaPrefix: this.options.stripSchemaPrefix
1459
1571
  });
1460
1572
  }
1461
1573
  /**
@@ -1489,9 +1601,11 @@ var OpenApiGenerator = class {
1489
1601
  const typeCode = this.types.get(name);
1490
1602
  if (schemaCode) {
1491
1603
  output.push(schemaCode);
1492
- if (!schemaCode.includes(`export type ${name}`)) {
1493
- const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1494
- output.push(`export type ${name} = z.infer<typeof ${schemaName}>;`);
1604
+ const strippedName = stripPrefix(name, this.options.stripSchemaPrefix);
1605
+ const typeName = toPascalCase(strippedName);
1606
+ if (!schemaCode.includes(`export type ${typeName}`)) {
1607
+ const schemaName = `${toCamelCase(strippedName, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1608
+ output.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
1495
1609
  }
1496
1610
  output.push("");
1497
1611
  } else if (typeCode) {
@@ -1525,6 +1639,7 @@ var OpenApiGenerator = class {
1525
1639
  const normalizedOutput = normalize(this.options.output);
1526
1640
  this.ensureDirectoryExists(normalizedOutput);
1527
1641
  writeFileSync(normalizedOutput, output);
1642
+ console.log(` \u2713 Generated ${normalizedOutput}`);
1528
1643
  }
1529
1644
  /**
1530
1645
  * Resolve options for a specific context (request or response)
@@ -1806,7 +1921,8 @@ var OpenApiGenerator = class {
1806
1921
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1807
1922
  if (schema.enum) {
1808
1923
  const jsdoc2 = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1809
- const { schemaCode, typeCode } = generateEnum(name, schema.enum, {
1924
+ const strippedName2 = stripPrefix(name, this.options.stripSchemaPrefix);
1925
+ const { schemaCode, typeCode } = generateEnum(strippedName2, schema.enum, {
1810
1926
  prefix: this.options.prefix,
1811
1927
  suffix: this.options.suffix
1812
1928
  });
@@ -1815,7 +1931,8 @@ ${typeCode}`;
1815
1931
  this.schemas.set(name, enumSchemaCode);
1816
1932
  return;
1817
1933
  }
1818
- const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1934
+ const strippedName = stripPrefix(name, this.options.stripSchemaPrefix);
1935
+ const schemaName = `${toCamelCase(strippedName, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1819
1936
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1820
1937
  if (schema.allOf && schema.allOf.length === 1 && schema.allOf[0].$ref) {
1821
1938
  const refName = resolveRef(schema.allOf[0].$ref);
@@ -1831,7 +1948,8 @@ ${typeCode}`;
1831
1948
  namingOptions: {
1832
1949
  prefix: this.options.prefix,
1833
1950
  suffix: this.options.suffix
1834
- }
1951
+ },
1952
+ stripSchemaPrefix: this.options.stripSchemaPrefix
1835
1953
  });
1836
1954
  const isAlias = !!(schema.$ref && !schema.properties && !schema.allOf && !schema.oneOf && !schema.anyOf);
1837
1955
  const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
@@ -1935,6 +2053,24 @@ ${propsCode}
1935
2053
  }
1936
2054
  }
1937
2055
  }
2056
+ /**
2057
+ * Check if a header should be ignored based on filter patterns
2058
+ * @internal
2059
+ */
2060
+ shouldIgnoreHeader(headerName) {
2061
+ const ignorePatterns = this.options.ignoreHeaders;
2062
+ if (!ignorePatterns || ignorePatterns.length === 0) {
2063
+ return false;
2064
+ }
2065
+ if (ignorePatterns.includes("*")) {
2066
+ return true;
2067
+ }
2068
+ const headerLower = headerName.toLowerCase();
2069
+ return ignorePatterns.some((pattern) => {
2070
+ const patternLower = pattern.toLowerCase();
2071
+ return minimatch2(headerLower, patternLower);
2072
+ });
2073
+ }
1938
2074
  /**
1939
2075
  * Generate header parameter schemas for each operation
1940
2076
  * Header parameters are always string type (HTTP header semantics)
@@ -1957,7 +2093,7 @@ ${propsCode}
1957
2093
  continue;
1958
2094
  }
1959
2095
  const headerParams = operation.parameters.filter(
1960
- (param) => param && typeof param === "object" && param.in === "header"
2096
+ (param) => param && typeof param === "object" && param.in === "header" && !this.shouldIgnoreHeader(param.name)
1961
2097
  );
1962
2098
  if (headerParams.length === 0) {
1963
2099
  continue;
@@ -2015,7 +2151,8 @@ ${propsCode}
2015
2151
  generateQueryParamType(schema, param) {
2016
2152
  if (schema.$ref) {
2017
2153
  const refName = resolveRef(schema.$ref);
2018
- const schemaName = toCamelCase(refName, { prefix: this.options.prefix, suffix: this.options.suffix });
2154
+ const strippedRefName = stripPrefix(refName, this.options.stripSchemaPrefix);
2155
+ const schemaName = toCamelCase(strippedRefName, { prefix: this.options.prefix, suffix: this.options.suffix });
2019
2156
  return `${schemaName}Schema`;
2020
2157
  }
2021
2158
  if (schema.enum) {
@@ -2167,10 +2304,6 @@ export {
2167
2304
  OpenApiGenerator,
2168
2305
  SchemaGenerationError,
2169
2306
  SpecValidationError,
2170
- createFilterStatistics,
2171
- defineConfig,
2172
- formatFilterStatistics,
2173
- shouldIncludeOperation,
2174
- validateFilters
2307
+ defineConfig
2175
2308
  };
2176
2309
  //# sourceMappingURL=index.mjs.map