@cerios/openapi-to-zod 1.0.0 → 1.1.1

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,12 +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
+ import { minimatch as minimatch3 } from "minimatch";
71
71
  import { parse } from "yaml";
72
72
 
73
73
  // src/utils/name-utils.ts
74
+ function sanitizeIdentifier(str) {
75
+ return str.replace(/[^a-zA-Z0-9._\-\s]+/g, "_");
76
+ }
74
77
  function toCamelCase(str, options) {
75
- 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
+ }
76
88
  if (options == null ? void 0 : options.prefix) {
77
89
  const prefix = options.prefix.toLowerCase();
78
90
  name = prefix + name.charAt(0).toUpperCase() + name.slice(1);
@@ -85,12 +97,23 @@ function toCamelCase(str, options) {
85
97
  }
86
98
  function toPascalCase(str) {
87
99
  const stringValue = String(str);
88
- 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
+ }
89
112
  if (/^\d/.test(result)) {
90
113
  result = `N${result}`;
91
114
  }
92
115
  if (!result || /^_+$/.test(result)) {
93
- result = "Value";
116
+ return "Value";
94
117
  }
95
118
  return result;
96
119
  }
@@ -102,9 +125,28 @@ function resolveRef(ref) {
102
125
  // src/generators/enum-generator.ts
103
126
  function generateEnum(name, values, options) {
104
127
  const schemaName = `${toCamelCase(name, options)}Schema`;
105
- const enumValues = values.map((v) => `"${v}"`).join(", ");
106
- const schemaCode = `export const ${schemaName} = z.enum([${enumValues}]);`;
107
- const typeCode = `export type ${name} = z.infer<typeof ${schemaName}>;`;
128
+ const typeName = toPascalCase(name);
129
+ const allBooleans = values.every((v) => typeof v === "boolean");
130
+ if (allBooleans) {
131
+ const schemaCode2 = `export const ${schemaName} = z.boolean();`;
132
+ const typeCode2 = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
133
+ return { schemaCode: schemaCode2, typeCode: typeCode2 };
134
+ }
135
+ const allStrings = values.every((v) => typeof v === "string");
136
+ if (allStrings) {
137
+ const enumValues = values.map((v) => `"${v}"`).join(", ");
138
+ const schemaCode2 = `export const ${schemaName} = z.enum([${enumValues}]);`;
139
+ const typeCode2 = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
140
+ return { schemaCode: schemaCode2, typeCode: typeCode2 };
141
+ }
142
+ const literalValues = values.map((v) => {
143
+ if (typeof v === "string") {
144
+ return `z.literal("${v}")`;
145
+ }
146
+ return `z.literal(${v})`;
147
+ }).join(", ");
148
+ const schemaCode = `export const ${schemaName} = z.union([${literalValues}]);`;
149
+ const typeCode = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
108
150
  return { schemaCode, typeCode };
109
151
  }
110
152
 
@@ -113,7 +155,7 @@ function escapeDescription(str) {
113
155
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
114
156
  }
115
157
  function escapePattern(str) {
116
- return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
158
+ return str.replace(/\//g, "\\/");
117
159
  }
118
160
  function escapeJSDoc(str) {
119
161
  return str.replace(/\*\//g, "*\\/");
@@ -237,6 +279,64 @@ var LRUCache = class {
237
279
  }
238
280
  };
239
281
 
282
+ // src/utils/pattern-utils.ts
283
+ import { minimatch } from "minimatch";
284
+ function isValidGlobPattern(pattern) {
285
+ try {
286
+ new minimatch.Minimatch(pattern);
287
+ return true;
288
+ } catch {
289
+ return false;
290
+ }
291
+ }
292
+ function isGlobPattern(pattern) {
293
+ return /[*?[\]{}!]/.test(pattern);
294
+ }
295
+ function stripPrefix(input, pattern, ensureLeadingChar) {
296
+ if (!pattern) {
297
+ return input;
298
+ }
299
+ if (isGlobPattern(pattern) && !isValidGlobPattern(pattern)) {
300
+ console.warn(`\u26A0\uFE0F Invalid glob pattern "${pattern}": Pattern is malformed`);
301
+ return input;
302
+ }
303
+ if (isGlobPattern(pattern)) {
304
+ let longestMatch = -1;
305
+ for (let i = 1; i <= input.length; i++) {
306
+ const testPrefix = input.substring(0, i);
307
+ if (minimatch(testPrefix, pattern)) {
308
+ longestMatch = i;
309
+ }
310
+ }
311
+ if (longestMatch > 0) {
312
+ const stripped = input.substring(longestMatch);
313
+ if (ensureLeadingChar) {
314
+ if (stripped === "") {
315
+ return ensureLeadingChar;
316
+ }
317
+ if (!stripped.startsWith(ensureLeadingChar)) {
318
+ return `${ensureLeadingChar}${stripped}`;
319
+ }
320
+ }
321
+ return stripped === "" && !ensureLeadingChar ? input : stripped;
322
+ }
323
+ return input;
324
+ }
325
+ if (input.startsWith(pattern)) {
326
+ const stripped = input.substring(pattern.length);
327
+ if (ensureLeadingChar) {
328
+ if (stripped === "") {
329
+ return ensureLeadingChar;
330
+ }
331
+ if (!stripped.startsWith(ensureLeadingChar)) {
332
+ return `${ensureLeadingChar}${stripped}`;
333
+ }
334
+ }
335
+ return stripped;
336
+ }
337
+ return input;
338
+ }
339
+
240
340
  // src/validators/array-validator.ts
241
341
  function generateArrayValidation(schema, context) {
242
342
  var _a;
@@ -1093,8 +1193,9 @@ var _PropertyGenerator = class _PropertyGenerator {
1093
1193
  }
1094
1194
  (_a = this.context.schemaDependencies.get(currentSchema)) == null ? void 0 : _a.add(refName);
1095
1195
  }
1096
- const schemaName = `${toCamelCase(resolvedRefName, this.context.namingOptions)}Schema`;
1097
- if (currentSchema && this.isCircularThroughAlias(currentSchema, refName)) {
1196
+ const strippedRefName = stripPrefix(resolvedRefName, this.context.stripSchemaPrefix);
1197
+ const schemaName = `${toCamelCase(strippedRefName, this.context.namingOptions)}Schema`;
1198
+ if (currentSchema && (refName === currentSchema || this.isCircularThroughAlias(currentSchema, refName))) {
1098
1199
  const lazySchema = `z.lazy((): z.ZodTypeAny => ${schemaName})`;
1099
1200
  return wrapNullable(lazySchema, nullable);
1100
1201
  }
@@ -1106,9 +1207,25 @@ var _PropertyGenerator = class _PropertyGenerator {
1106
1207
  return wrapNullable(zodLiteral, nullable);
1107
1208
  }
1108
1209
  if (schema.enum) {
1109
- const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
1110
- const zodEnum = `z.enum([${enumValues}])`;
1111
- return wrapNullable(zodEnum, nullable);
1210
+ const allBooleans = schema.enum.every((v) => typeof v === "boolean");
1211
+ if (allBooleans) {
1212
+ const zodBoolean = "z.boolean()";
1213
+ return wrapNullable(zodBoolean, nullable);
1214
+ }
1215
+ const allStrings = schema.enum.every((v) => typeof v === "string");
1216
+ if (allStrings) {
1217
+ const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
1218
+ const zodEnum = `z.enum([${enumValues}])`;
1219
+ return wrapNullable(zodEnum, nullable);
1220
+ }
1221
+ const literalValues = schema.enum.map((v) => {
1222
+ if (typeof v === "string") {
1223
+ return `z.literal("${v}")`;
1224
+ }
1225
+ return `z.literal(${v})`;
1226
+ }).join(", ");
1227
+ const zodUnion = `z.union([${literalValues}])`;
1228
+ return wrapNullable(zodUnion, nullable);
1112
1229
  }
1113
1230
  if (schema.allOf) {
1114
1231
  let composition = generateAllOf(
@@ -1239,7 +1356,7 @@ _PropertyGenerator.INCLUSION_RULES = {
1239
1356
  var PropertyGenerator = _PropertyGenerator;
1240
1357
 
1241
1358
  // src/utils/operation-filters.ts
1242
- import { minimatch } from "minimatch";
1359
+ import { minimatch as minimatch2 } from "minimatch";
1243
1360
  function createFilterStatistics() {
1244
1361
  return {
1245
1362
  totalOperations: 0,
@@ -1258,7 +1375,7 @@ function matchesAnyPattern(value, patterns) {
1258
1375
  if (!value) {
1259
1376
  return false;
1260
1377
  }
1261
- return patterns.some((pattern) => minimatch(value, pattern));
1378
+ return patterns.some((pattern) => minimatch2(value, pattern));
1262
1379
  }
1263
1380
  function containsAny(arr, values) {
1264
1381
  if (!values || values.length === 0) {
@@ -1394,6 +1511,7 @@ var OpenApiGenerator = class {
1394
1511
  schemaType: options.schemaType || "all",
1395
1512
  prefix: options.prefix,
1396
1513
  suffix: options.suffix,
1514
+ stripSchemaPrefix: options.stripSchemaPrefix,
1397
1515
  showStats: (_c = options.showStats) != null ? _c : true,
1398
1516
  request: options.request,
1399
1517
  response: options.response,
@@ -1470,7 +1588,8 @@ var OpenApiGenerator = class {
1470
1588
  namingOptions: {
1471
1589
  prefix: this.options.prefix,
1472
1590
  suffix: this.options.suffix
1473
- }
1591
+ },
1592
+ stripSchemaPrefix: this.options.stripSchemaPrefix
1474
1593
  });
1475
1594
  }
1476
1595
  /**
@@ -1483,6 +1602,9 @@ var OpenApiGenerator = class {
1483
1602
  throw new SpecValidationError("No schemas found in OpenAPI spec", { filePath: this.options.input });
1484
1603
  }
1485
1604
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
1605
+ if (this.options.operationFilters && this.schemaUsageMap.size > 0 && !this.schemaUsageMap.has(name)) {
1606
+ continue;
1607
+ }
1486
1608
  this.generateComponentSchema(name, schema);
1487
1609
  }
1488
1610
  this.generateQueryParameterSchemas();
@@ -1504,9 +1626,11 @@ var OpenApiGenerator = class {
1504
1626
  const typeCode = this.types.get(name);
1505
1627
  if (schemaCode) {
1506
1628
  output.push(schemaCode);
1507
- if (!schemaCode.includes(`export type ${name}`)) {
1508
- const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1509
- output.push(`export type ${name} = z.infer<typeof ${schemaName}>;`);
1629
+ const strippedName = stripPrefix(name, this.options.stripSchemaPrefix);
1630
+ const typeName = toPascalCase(strippedName);
1631
+ if (!schemaCode.includes(`export type ${typeName}`)) {
1632
+ const schemaName = `${toCamelCase(strippedName, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1633
+ output.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
1510
1634
  }
1511
1635
  output.push("");
1512
1636
  } else if (typeCode) {
@@ -1822,7 +1946,8 @@ var OpenApiGenerator = class {
1822
1946
  const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
1823
1947
  if (schema.enum) {
1824
1948
  const jsdoc2 = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1825
- const { schemaCode, typeCode } = generateEnum(name, schema.enum, {
1949
+ const strippedName2 = stripPrefix(name, this.options.stripSchemaPrefix);
1950
+ const { schemaCode, typeCode } = generateEnum(strippedName2, schema.enum, {
1826
1951
  prefix: this.options.prefix,
1827
1952
  suffix: this.options.suffix
1828
1953
  });
@@ -1831,7 +1956,8 @@ ${typeCode}`;
1831
1956
  this.schemas.set(name, enumSchemaCode);
1832
1957
  return;
1833
1958
  }
1834
- const schemaName = `${toCamelCase(name, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1959
+ const strippedName = stripPrefix(name, this.options.stripSchemaPrefix);
1960
+ const schemaName = `${toCamelCase(strippedName, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
1835
1961
  const jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
1836
1962
  if (schema.allOf && schema.allOf.length === 1 && schema.allOf[0].$ref) {
1837
1963
  const refName = resolveRef(schema.allOf[0].$ref);
@@ -1847,7 +1973,8 @@ ${typeCode}`;
1847
1973
  namingOptions: {
1848
1974
  prefix: this.options.prefix,
1849
1975
  suffix: this.options.suffix
1850
- }
1976
+ },
1977
+ stripSchemaPrefix: this.options.stripSchemaPrefix
1851
1978
  });
1852
1979
  const isAlias = !!(schema.$ref && !schema.properties && !schema.allOf && !schema.oneOf && !schema.anyOf);
1853
1980
  const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
@@ -1966,7 +2093,7 @@ ${propsCode}
1966
2093
  const headerLower = headerName.toLowerCase();
1967
2094
  return ignorePatterns.some((pattern) => {
1968
2095
  const patternLower = pattern.toLowerCase();
1969
- return minimatch2(headerLower, patternLower);
2096
+ return minimatch3(headerLower, patternLower);
1970
2097
  });
1971
2098
  }
1972
2099
  /**
@@ -2049,12 +2176,27 @@ ${propsCode}
2049
2176
  generateQueryParamType(schema, param) {
2050
2177
  if (schema.$ref) {
2051
2178
  const refName = resolveRef(schema.$ref);
2052
- const schemaName = toCamelCase(refName, { prefix: this.options.prefix, suffix: this.options.suffix });
2179
+ const strippedRefName = stripPrefix(refName, this.options.stripSchemaPrefix);
2180
+ const schemaName = toCamelCase(strippedRefName, { prefix: this.options.prefix, suffix: this.options.suffix });
2053
2181
  return `${schemaName}Schema`;
2054
2182
  }
2055
2183
  if (schema.enum) {
2056
- const enumValues = schema.enum.map((v) => typeof v === "string" ? `"${v}"` : v).join(", ");
2057
- return `z.enum([${enumValues}])`;
2184
+ const allBooleans = schema.enum.every((v) => typeof v === "boolean");
2185
+ if (allBooleans) {
2186
+ return "z.boolean()";
2187
+ }
2188
+ const allStrings = schema.enum.every((v) => typeof v === "string");
2189
+ if (allStrings) {
2190
+ const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
2191
+ return `z.enum([${enumValues}])`;
2192
+ }
2193
+ const literalValues = schema.enum.map((v) => {
2194
+ if (typeof v === "string") {
2195
+ return `z.literal("${v}")`;
2196
+ }
2197
+ return `z.literal(${v})`;
2198
+ }).join(", ");
2199
+ return `z.union([${literalValues}])`;
2058
2200
  }
2059
2201
  const type = schema.type;
2060
2202
  if (type === "string") {