@cerios/openapi-to-zod 0.3.0 → 0.5.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.js CHANGED
@@ -25,10 +25,14 @@ __export(src_exports, {
25
25
  ConfigValidationError: () => ConfigValidationError,
26
26
  FileOperationError: () => FileOperationError,
27
27
  GeneratorError: () => GeneratorError,
28
+ OpenApiGenerator: () => OpenApiGenerator,
28
29
  SchemaGenerationError: () => SchemaGenerationError,
29
30
  SpecValidationError: () => SpecValidationError,
30
- ZodSchemaGenerator: () => ZodSchemaGenerator,
31
- defineConfig: () => defineConfig
31
+ createFilterStatistics: () => createFilterStatistics,
32
+ defineConfig: () => defineConfig,
33
+ formatFilterStatistics: () => formatFilterStatistics,
34
+ shouldIncludeOperation: () => shouldIncludeOperation,
35
+ validateFilters: () => validateFilters
32
36
  });
33
37
  module.exports = __toCommonJS(src_exports);
34
38
 
@@ -91,7 +95,7 @@ var ConfigurationError = class extends GeneratorError {
91
95
  }
92
96
  };
93
97
 
94
- // src/generator.ts
98
+ // src/openapi-generator.ts
95
99
  var import_node_fs = require("fs");
96
100
  var import_node_path = require("path");
97
101
  var import_yaml = require("yaml");
@@ -1279,8 +1283,142 @@ _PropertyGenerator.INCLUSION_RULES = {
1279
1283
  };
1280
1284
  var PropertyGenerator = _PropertyGenerator;
1281
1285
 
1282
- // src/generator.ts
1283
- var ZodSchemaGenerator = class {
1286
+ // src/utils/operation-filters.ts
1287
+ var import_minimatch = require("minimatch");
1288
+ function createFilterStatistics() {
1289
+ return {
1290
+ totalOperations: 0,
1291
+ includedOperations: 0,
1292
+ filteredByTags: 0,
1293
+ filteredByPaths: 0,
1294
+ filteredByMethods: 0,
1295
+ filteredByOperationIds: 0,
1296
+ filteredByDeprecated: 0
1297
+ };
1298
+ }
1299
+ function matchesAnyPattern(value, patterns) {
1300
+ if (!patterns || patterns.length === 0) {
1301
+ return false;
1302
+ }
1303
+ if (!value) {
1304
+ return false;
1305
+ }
1306
+ return patterns.some((pattern) => (0, import_minimatch.minimatch)(value, pattern));
1307
+ }
1308
+ function containsAny(arr, values) {
1309
+ if (!values || values.length === 0) {
1310
+ return false;
1311
+ }
1312
+ if (!arr || arr.length === 0) {
1313
+ return false;
1314
+ }
1315
+ return values.some((value) => arr.includes(value));
1316
+ }
1317
+ function shouldIncludeOperation(operation, path, method, filters, stats) {
1318
+ if (!filters) {
1319
+ return true;
1320
+ }
1321
+ const methodLower = method.toLowerCase();
1322
+ const operationId = operation == null ? void 0 : operation.operationId;
1323
+ const tags = (operation == null ? void 0 : operation.tags) || [];
1324
+ const deprecated = (operation == null ? void 0 : operation.deprecated) === true;
1325
+ if (filters.includeTags && filters.includeTags.length > 0) {
1326
+ if (!containsAny(tags, filters.includeTags)) {
1327
+ if (stats) stats.filteredByTags++;
1328
+ return false;
1329
+ }
1330
+ }
1331
+ if (filters.includePaths && filters.includePaths.length > 0) {
1332
+ if (!matchesAnyPattern(path, filters.includePaths)) {
1333
+ if (stats) stats.filteredByPaths++;
1334
+ return false;
1335
+ }
1336
+ }
1337
+ if (filters.includeMethods && filters.includeMethods.length > 0) {
1338
+ const methodsLower = filters.includeMethods.map((m) => m.toLowerCase());
1339
+ if (!methodsLower.includes(methodLower)) {
1340
+ if (stats) stats.filteredByMethods++;
1341
+ return false;
1342
+ }
1343
+ }
1344
+ if (filters.includeOperationIds && filters.includeOperationIds.length > 0) {
1345
+ if (!matchesAnyPattern(operationId, filters.includeOperationIds)) {
1346
+ if (stats) stats.filteredByOperationIds++;
1347
+ return false;
1348
+ }
1349
+ }
1350
+ if (filters.excludeDeprecated === true && deprecated) {
1351
+ if (stats) stats.filteredByDeprecated++;
1352
+ return false;
1353
+ }
1354
+ if (filters.excludeTags && filters.excludeTags.length > 0) {
1355
+ if (containsAny(tags, filters.excludeTags)) {
1356
+ if (stats) stats.filteredByTags++;
1357
+ return false;
1358
+ }
1359
+ }
1360
+ if (filters.excludePaths && filters.excludePaths.length > 0) {
1361
+ if (matchesAnyPattern(path, filters.excludePaths)) {
1362
+ if (stats) stats.filteredByPaths++;
1363
+ return false;
1364
+ }
1365
+ }
1366
+ if (filters.excludeMethods && filters.excludeMethods.length > 0) {
1367
+ const methodsLower = filters.excludeMethods.map((m) => m.toLowerCase());
1368
+ if (methodsLower.includes(methodLower)) {
1369
+ if (stats) stats.filteredByMethods++;
1370
+ return false;
1371
+ }
1372
+ }
1373
+ if (filters.excludeOperationIds && filters.excludeOperationIds.length > 0) {
1374
+ if (matchesAnyPattern(operationId, filters.excludeOperationIds)) {
1375
+ if (stats) stats.filteredByOperationIds++;
1376
+ return false;
1377
+ }
1378
+ }
1379
+ return true;
1380
+ }
1381
+ function validateFilters(stats, filters) {
1382
+ if (!filters || stats.totalOperations === 0) {
1383
+ return;
1384
+ }
1385
+ if (stats.includedOperations === 0) {
1386
+ console.warn(
1387
+ `\u26A0\uFE0F Warning: All ${stats.totalOperations} operations were filtered out. Check your operationFilters configuration.`
1388
+ );
1389
+ const filterBreakdown = [];
1390
+ if (stats.filteredByTags > 0) filterBreakdown.push(`${stats.filteredByTags} by tags`);
1391
+ if (stats.filteredByPaths > 0) filterBreakdown.push(`${stats.filteredByPaths} by paths`);
1392
+ if (stats.filteredByMethods > 0) filterBreakdown.push(`${stats.filteredByMethods} by methods`);
1393
+ if (stats.filteredByOperationIds > 0) filterBreakdown.push(`${stats.filteredByOperationIds} by operationIds`);
1394
+ if (stats.filteredByDeprecated > 0) filterBreakdown.push(`${stats.filteredByDeprecated} by deprecated flag`);
1395
+ if (filterBreakdown.length > 0) {
1396
+ console.warn(` Filtered: ${filterBreakdown.join(", ")}`);
1397
+ }
1398
+ }
1399
+ }
1400
+ function formatFilterStatistics(stats) {
1401
+ if (stats.totalOperations === 0) {
1402
+ return "";
1403
+ }
1404
+ const lines = [];
1405
+ lines.push("Operation Filtering:");
1406
+ lines.push(` Total operations: ${stats.totalOperations}`);
1407
+ lines.push(` Included operations: ${stats.includedOperations}`);
1408
+ const filteredCount = stats.filteredByTags + stats.filteredByPaths + stats.filteredByMethods + stats.filteredByOperationIds + stats.filteredByDeprecated;
1409
+ if (filteredCount > 0) {
1410
+ lines.push(` Filtered operations: ${filteredCount}`);
1411
+ if (stats.filteredByTags > 0) lines.push(` - By tags: ${stats.filteredByTags}`);
1412
+ if (stats.filteredByPaths > 0) lines.push(` - By paths: ${stats.filteredByPaths}`);
1413
+ if (stats.filteredByMethods > 0) lines.push(` - By methods: ${stats.filteredByMethods}`);
1414
+ if (stats.filteredByOperationIds > 0) lines.push(` - By operationIds: ${stats.filteredByOperationIds}`);
1415
+ if (stats.filteredByDeprecated > 0) lines.push(` - By deprecated: ${stats.filteredByDeprecated}`);
1416
+ }
1417
+ return lines.join("\n");
1418
+ }
1419
+
1420
+ // src/openapi-generator.ts
1421
+ var OpenApiGenerator = class {
1284
1422
  constructor(options) {
1285
1423
  this.schemas = /* @__PURE__ */ new Map();
1286
1424
  this.types = /* @__PURE__ */ new Map();
@@ -1290,6 +1428,7 @@ var ZodSchemaGenerator = class {
1290
1428
  this.schemaUsageMap = /* @__PURE__ */ new Map();
1291
1429
  this.schemaTypeModeMap = /* @__PURE__ */ new Map();
1292
1430
  this.needsZodImport = false;
1431
+ this.filterStats = createFilterStatistics();
1293
1432
  var _a, _b, _c;
1294
1433
  if (!options.input) {
1295
1434
  throw new ConfigurationError("Input path is required", { providedOptions: options });
@@ -1307,7 +1446,8 @@ var ZodSchemaGenerator = class {
1307
1446
  showStats: (_c = options.showStats) != null ? _c : true,
1308
1447
  nativeEnumType: options.nativeEnumType || "union",
1309
1448
  request: options.request,
1310
- response: options.response
1449
+ response: options.response,
1450
+ operationFilters: options.operationFilters
1311
1451
  };
1312
1452
  try {
1313
1453
  const fs = require("fs");
@@ -1320,19 +1460,41 @@ var ZodSchemaGenerator = class {
1320
1460
  }
1321
1461
  }
1322
1462
  try {
1323
- const yamlContent = (0, import_node_fs.readFileSync)(this.options.input, "utf-8");
1324
- this.spec = (0, import_yaml.parse)(yamlContent);
1463
+ const content = (0, import_node_fs.readFileSync)(this.options.input, "utf-8");
1464
+ try {
1465
+ this.spec = (0, import_yaml.parse)(content);
1466
+ } catch (yamlError) {
1467
+ try {
1468
+ this.spec = JSON.parse(content);
1469
+ } catch {
1470
+ if (yamlError instanceof Error) {
1471
+ const errorMessage = [
1472
+ `Failed to parse OpenAPI specification from: ${this.options.input}`,
1473
+ "",
1474
+ `Error: ${yamlError.message}`,
1475
+ "",
1476
+ "Please ensure:",
1477
+ " - The file exists and is readable",
1478
+ " - The file contains valid YAML or JSON syntax",
1479
+ " - The file is a valid OpenAPI 3.x specification"
1480
+ ].join("\n");
1481
+ throw new SpecValidationError(errorMessage, {
1482
+ filePath: this.options.input,
1483
+ originalError: yamlError.message
1484
+ });
1485
+ }
1486
+ throw yamlError;
1487
+ }
1488
+ }
1325
1489
  } catch (error) {
1490
+ if (error instanceof SpecValidationError) {
1491
+ throw error;
1492
+ }
1326
1493
  if (error instanceof Error) {
1327
1494
  const errorMessage = [
1328
- `Failed to parse OpenAPI specification from: ${this.options.input}`,
1495
+ `Failed to read OpenAPI specification from: ${this.options.input}`,
1329
1496
  "",
1330
- `Error: ${error.message}`,
1331
- "",
1332
- "Please ensure:",
1333
- " - The file exists and is readable",
1334
- " - The file contains valid YAML syntax",
1335
- " - The file is a valid OpenAPI 3.x specification"
1497
+ `Error: ${error.message}`
1336
1498
  ].join("\n");
1337
1499
  throw new SpecValidationError(errorMessage, { filePath: this.options.input, originalError: error.message });
1338
1500
  }
@@ -1390,6 +1552,8 @@ var ZodSchemaGenerator = class {
1390
1552
  this.generateComponentSchema(name, schema);
1391
1553
  }
1392
1554
  this.generateQueryParameterSchemas();
1555
+ this.generateHeaderParameterSchemas();
1556
+ validateFilters(this.filterStats, this.options.operationFilters);
1393
1557
  const orderedSchemaNames = this.topologicalSort();
1394
1558
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1395
1559
  if (this.options.showStats === true) {
@@ -1483,9 +1647,16 @@ var ZodSchemaGenerator = class {
1483
1647
  const requestSchemas = /* @__PURE__ */ new Set();
1484
1648
  const responseSchemas = /* @__PURE__ */ new Set();
1485
1649
  if (this.spec.paths) {
1486
- for (const [, pathItem] of Object.entries(this.spec.paths)) {
1487
- for (const [, operation] of Object.entries(pathItem)) {
1650
+ for (const [path, pathItem] of Object.entries(this.spec.paths)) {
1651
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1652
+ for (const method of methods) {
1653
+ const operation = pathItem[method];
1488
1654
  if (typeof operation !== "object" || !operation) continue;
1655
+ this.filterStats.totalOperations++;
1656
+ if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters, this.filterStats)) {
1657
+ continue;
1658
+ }
1659
+ this.filterStats.includedOperations++;
1489
1660
  if ("requestBody" in operation && operation.requestBody && typeof operation.requestBody === "object" && "content" in operation.requestBody && operation.requestBody.content) {
1490
1661
  for (const mediaType of Object.values(operation.requestBody.content)) {
1491
1662
  if (mediaType && typeof mediaType === "object" && "schema" in mediaType && mediaType.schema) {
@@ -1835,12 +2006,15 @@ ${typeCode}`;
1835
2006
  if (!this.spec.paths) {
1836
2007
  return;
1837
2008
  }
1838
- for (const [_path, pathItem] of Object.entries(this.spec.paths)) {
2009
+ for (const [path, pathItem] of Object.entries(this.spec.paths)) {
1839
2010
  if (!pathItem || typeof pathItem !== "object") continue;
1840
2011
  const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1841
2012
  for (const method of methods) {
1842
2013
  const operation = pathItem[method];
1843
2014
  if (!operation) continue;
2015
+ if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2016
+ continue;
2017
+ }
1844
2018
  if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
1845
2019
  continue;
1846
2020
  }
@@ -1901,6 +2075,80 @@ ${propsCode}
1901
2075
  const jsdoc = `/**
1902
2076
  * Query parameters for ${operation.operationId}
1903
2077
  */
2078
+ `;
2079
+ const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
2080
+ this.schemas.set(schemaName, fullSchemaCode);
2081
+ this.needsZodImport = true;
2082
+ }
2083
+ }
2084
+ }
2085
+ /**
2086
+ * Generate header parameter schemas for each operation
2087
+ * Header parameters are always string type (HTTP header semantics)
2088
+ */
2089
+ generateHeaderParameterSchemas() {
2090
+ var _a;
2091
+ if (!this.spec.paths) {
2092
+ return;
2093
+ }
2094
+ for (const [path, pathItem] of Object.entries(this.spec.paths)) {
2095
+ if (!pathItem || typeof pathItem !== "object") continue;
2096
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
2097
+ for (const method of methods) {
2098
+ const operation = pathItem[method];
2099
+ if (!operation) continue;
2100
+ if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2101
+ continue;
2102
+ }
2103
+ if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2104
+ continue;
2105
+ }
2106
+ const headerParams = operation.parameters.filter(
2107
+ (param) => param && typeof param === "object" && param.in === "header"
2108
+ );
2109
+ if (headerParams.length === 0) {
2110
+ continue;
2111
+ }
2112
+ const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2113
+ const schemaName = `${pascalOperationId}HeaderParams`;
2114
+ if (!this.schemaDependencies.has(schemaName)) {
2115
+ this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
2116
+ }
2117
+ const properties = {};
2118
+ for (const param of headerParams) {
2119
+ const paramName = param.name;
2120
+ const paramSchema = param.schema;
2121
+ if (!paramSchema) continue;
2122
+ let zodType = "z.string()";
2123
+ if (param.description && this.requestOptions.includeDescriptions) {
2124
+ if (this.requestOptions.useDescribe) {
2125
+ zodType = `${zodType}.describe(${JSON.stringify(param.description)})`;
2126
+ }
2127
+ }
2128
+ zodType = `${zodType}.optional()`;
2129
+ properties[paramName] = zodType;
2130
+ if (paramSchema.$ref) {
2131
+ const refName = resolveRef(paramSchema.$ref);
2132
+ (_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
2133
+ }
2134
+ }
2135
+ const objectMode = this.requestOptions.mode;
2136
+ const zodMethod = objectMode === "strict" ? "strictObject" : objectMode === "loose" ? "looseObject" : "object";
2137
+ const propsCode = Object.entries(properties).map(([key, value]) => {
2138
+ const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
2139
+ const quotedKey = needsQuotes ? `"${key}"` : key;
2140
+ return ` ${quotedKey}: ${value}`;
2141
+ }).join(",\n");
2142
+ const schemaCode = `z.${zodMethod}({
2143
+ ${propsCode}
2144
+ })`;
2145
+ const operationName = pascalOperationId;
2146
+ const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2147
+ const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2148
+ const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
2149
+ const jsdoc = `/**
2150
+ * Header parameters for ${operation.operationId}
2151
+ */
1904
2152
  `;
1905
2153
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
1906
2154
  this.schemas.set(schemaName, fullSchemaCode);
@@ -2178,15 +2426,23 @@ ${props.join("\n")}
2178
2426
  stats.withConstraints++;
2179
2427
  }
2180
2428
  }
2181
- return [
2429
+ const output = [
2182
2430
  "// Generation Statistics:",
2183
2431
  `// Total schemas: ${stats.totalSchemas}`,
2184
2432
  `// Enums: ${stats.enums}`,
2185
2433
  `// Circular references: ${stats.withCircularRefs}`,
2186
2434
  `// Discriminated unions: ${stats.withDiscriminators}`,
2187
- `// With constraints: ${stats.withConstraints}`,
2188
- `// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`
2435
+ `// With constraints: ${stats.withConstraints}`
2189
2436
  ];
2437
+ if (this.options.operationFilters && this.filterStats.totalOperations > 0) {
2438
+ output.push("//");
2439
+ const filterStatsStr = formatFilterStatistics(this.filterStats);
2440
+ for (const line of filterStatsStr.split("\n")) {
2441
+ output.push(`// ${line}`);
2442
+ }
2443
+ }
2444
+ output.push(`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
2445
+ return output;
2190
2446
  }
2191
2447
  };
2192
2448
 
@@ -2201,9 +2457,13 @@ function defineConfig(config) {
2201
2457
  ConfigValidationError,
2202
2458
  FileOperationError,
2203
2459
  GeneratorError,
2460
+ OpenApiGenerator,
2204
2461
  SchemaGenerationError,
2205
2462
  SpecValidationError,
2206
- ZodSchemaGenerator,
2207
- defineConfig
2463
+ createFilterStatistics,
2464
+ defineConfig,
2465
+ formatFilterStatistics,
2466
+ shouldIncludeOperation,
2467
+ validateFilters
2208
2468
  });
2209
2469
  //# sourceMappingURL=index.js.map