@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.mjs CHANGED
@@ -64,7 +64,7 @@ var ConfigurationError = class extends GeneratorError {
64
64
  }
65
65
  };
66
66
 
67
- // src/generator.ts
67
+ // src/openapi-generator.ts
68
68
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
69
69
  import { dirname, normalize } from "path";
70
70
  import { parse } from "yaml";
@@ -1252,8 +1252,142 @@ _PropertyGenerator.INCLUSION_RULES = {
1252
1252
  };
1253
1253
  var PropertyGenerator = _PropertyGenerator;
1254
1254
 
1255
- // src/generator.ts
1256
- var ZodSchemaGenerator = class {
1255
+ // src/utils/operation-filters.ts
1256
+ import { minimatch } from "minimatch";
1257
+ function createFilterStatistics() {
1258
+ return {
1259
+ totalOperations: 0,
1260
+ includedOperations: 0,
1261
+ filteredByTags: 0,
1262
+ filteredByPaths: 0,
1263
+ filteredByMethods: 0,
1264
+ filteredByOperationIds: 0,
1265
+ filteredByDeprecated: 0
1266
+ };
1267
+ }
1268
+ function matchesAnyPattern(value, patterns) {
1269
+ if (!patterns || patterns.length === 0) {
1270
+ return false;
1271
+ }
1272
+ if (!value) {
1273
+ return false;
1274
+ }
1275
+ return patterns.some((pattern) => minimatch(value, pattern));
1276
+ }
1277
+ function containsAny(arr, values) {
1278
+ if (!values || values.length === 0) {
1279
+ return false;
1280
+ }
1281
+ if (!arr || arr.length === 0) {
1282
+ return false;
1283
+ }
1284
+ return values.some((value) => arr.includes(value));
1285
+ }
1286
+ function shouldIncludeOperation(operation, path, method, filters, stats) {
1287
+ if (!filters) {
1288
+ return true;
1289
+ }
1290
+ const methodLower = method.toLowerCase();
1291
+ const operationId = operation == null ? void 0 : operation.operationId;
1292
+ const tags = (operation == null ? void 0 : operation.tags) || [];
1293
+ const deprecated = (operation == null ? void 0 : operation.deprecated) === true;
1294
+ if (filters.includeTags && filters.includeTags.length > 0) {
1295
+ if (!containsAny(tags, filters.includeTags)) {
1296
+ if (stats) stats.filteredByTags++;
1297
+ return false;
1298
+ }
1299
+ }
1300
+ if (filters.includePaths && filters.includePaths.length > 0) {
1301
+ if (!matchesAnyPattern(path, filters.includePaths)) {
1302
+ if (stats) stats.filteredByPaths++;
1303
+ return false;
1304
+ }
1305
+ }
1306
+ if (filters.includeMethods && filters.includeMethods.length > 0) {
1307
+ const methodsLower = filters.includeMethods.map((m) => m.toLowerCase());
1308
+ if (!methodsLower.includes(methodLower)) {
1309
+ if (stats) stats.filteredByMethods++;
1310
+ return false;
1311
+ }
1312
+ }
1313
+ if (filters.includeOperationIds && filters.includeOperationIds.length > 0) {
1314
+ if (!matchesAnyPattern(operationId, filters.includeOperationIds)) {
1315
+ if (stats) stats.filteredByOperationIds++;
1316
+ return false;
1317
+ }
1318
+ }
1319
+ if (filters.excludeDeprecated === true && deprecated) {
1320
+ if (stats) stats.filteredByDeprecated++;
1321
+ return false;
1322
+ }
1323
+ if (filters.excludeTags && filters.excludeTags.length > 0) {
1324
+ if (containsAny(tags, filters.excludeTags)) {
1325
+ if (stats) stats.filteredByTags++;
1326
+ return false;
1327
+ }
1328
+ }
1329
+ if (filters.excludePaths && filters.excludePaths.length > 0) {
1330
+ if (matchesAnyPattern(path, filters.excludePaths)) {
1331
+ if (stats) stats.filteredByPaths++;
1332
+ return false;
1333
+ }
1334
+ }
1335
+ if (filters.excludeMethods && filters.excludeMethods.length > 0) {
1336
+ const methodsLower = filters.excludeMethods.map((m) => m.toLowerCase());
1337
+ if (methodsLower.includes(methodLower)) {
1338
+ if (stats) stats.filteredByMethods++;
1339
+ return false;
1340
+ }
1341
+ }
1342
+ if (filters.excludeOperationIds && filters.excludeOperationIds.length > 0) {
1343
+ if (matchesAnyPattern(operationId, filters.excludeOperationIds)) {
1344
+ if (stats) stats.filteredByOperationIds++;
1345
+ return false;
1346
+ }
1347
+ }
1348
+ return true;
1349
+ }
1350
+ function validateFilters(stats, filters) {
1351
+ if (!filters || stats.totalOperations === 0) {
1352
+ return;
1353
+ }
1354
+ if (stats.includedOperations === 0) {
1355
+ console.warn(
1356
+ `\u26A0\uFE0F Warning: All ${stats.totalOperations} operations were filtered out. Check your operationFilters configuration.`
1357
+ );
1358
+ const filterBreakdown = [];
1359
+ if (stats.filteredByTags > 0) filterBreakdown.push(`${stats.filteredByTags} by tags`);
1360
+ if (stats.filteredByPaths > 0) filterBreakdown.push(`${stats.filteredByPaths} by paths`);
1361
+ if (stats.filteredByMethods > 0) filterBreakdown.push(`${stats.filteredByMethods} by methods`);
1362
+ if (stats.filteredByOperationIds > 0) filterBreakdown.push(`${stats.filteredByOperationIds} by operationIds`);
1363
+ if (stats.filteredByDeprecated > 0) filterBreakdown.push(`${stats.filteredByDeprecated} by deprecated flag`);
1364
+ if (filterBreakdown.length > 0) {
1365
+ console.warn(` Filtered: ${filterBreakdown.join(", ")}`);
1366
+ }
1367
+ }
1368
+ }
1369
+ function formatFilterStatistics(stats) {
1370
+ if (stats.totalOperations === 0) {
1371
+ return "";
1372
+ }
1373
+ const lines = [];
1374
+ lines.push("Operation Filtering:");
1375
+ lines.push(` Total operations: ${stats.totalOperations}`);
1376
+ lines.push(` Included operations: ${stats.includedOperations}`);
1377
+ const filteredCount = stats.filteredByTags + stats.filteredByPaths + stats.filteredByMethods + stats.filteredByOperationIds + stats.filteredByDeprecated;
1378
+ if (filteredCount > 0) {
1379
+ lines.push(` Filtered operations: ${filteredCount}`);
1380
+ if (stats.filteredByTags > 0) lines.push(` - By tags: ${stats.filteredByTags}`);
1381
+ if (stats.filteredByPaths > 0) lines.push(` - By paths: ${stats.filteredByPaths}`);
1382
+ if (stats.filteredByMethods > 0) lines.push(` - By methods: ${stats.filteredByMethods}`);
1383
+ if (stats.filteredByOperationIds > 0) lines.push(` - By operationIds: ${stats.filteredByOperationIds}`);
1384
+ if (stats.filteredByDeprecated > 0) lines.push(` - By deprecated: ${stats.filteredByDeprecated}`);
1385
+ }
1386
+ return lines.join("\n");
1387
+ }
1388
+
1389
+ // src/openapi-generator.ts
1390
+ var OpenApiGenerator = class {
1257
1391
  constructor(options) {
1258
1392
  this.schemas = /* @__PURE__ */ new Map();
1259
1393
  this.types = /* @__PURE__ */ new Map();
@@ -1263,6 +1397,7 @@ var ZodSchemaGenerator = class {
1263
1397
  this.schemaUsageMap = /* @__PURE__ */ new Map();
1264
1398
  this.schemaTypeModeMap = /* @__PURE__ */ new Map();
1265
1399
  this.needsZodImport = false;
1400
+ this.filterStats = createFilterStatistics();
1266
1401
  var _a, _b, _c;
1267
1402
  if (!options.input) {
1268
1403
  throw new ConfigurationError("Input path is required", { providedOptions: options });
@@ -1280,7 +1415,8 @@ var ZodSchemaGenerator = class {
1280
1415
  showStats: (_c = options.showStats) != null ? _c : true,
1281
1416
  nativeEnumType: options.nativeEnumType || "union",
1282
1417
  request: options.request,
1283
- response: options.response
1418
+ response: options.response,
1419
+ operationFilters: options.operationFilters
1284
1420
  };
1285
1421
  try {
1286
1422
  const fs = __require("fs");
@@ -1293,19 +1429,41 @@ var ZodSchemaGenerator = class {
1293
1429
  }
1294
1430
  }
1295
1431
  try {
1296
- const yamlContent = readFileSync(this.options.input, "utf-8");
1297
- this.spec = parse(yamlContent);
1432
+ const content = readFileSync(this.options.input, "utf-8");
1433
+ try {
1434
+ this.spec = parse(content);
1435
+ } catch (yamlError) {
1436
+ try {
1437
+ this.spec = JSON.parse(content);
1438
+ } catch {
1439
+ if (yamlError instanceof Error) {
1440
+ const errorMessage = [
1441
+ `Failed to parse OpenAPI specification from: ${this.options.input}`,
1442
+ "",
1443
+ `Error: ${yamlError.message}`,
1444
+ "",
1445
+ "Please ensure:",
1446
+ " - The file exists and is readable",
1447
+ " - The file contains valid YAML or JSON syntax",
1448
+ " - The file is a valid OpenAPI 3.x specification"
1449
+ ].join("\n");
1450
+ throw new SpecValidationError(errorMessage, {
1451
+ filePath: this.options.input,
1452
+ originalError: yamlError.message
1453
+ });
1454
+ }
1455
+ throw yamlError;
1456
+ }
1457
+ }
1298
1458
  } catch (error) {
1459
+ if (error instanceof SpecValidationError) {
1460
+ throw error;
1461
+ }
1299
1462
  if (error instanceof Error) {
1300
1463
  const errorMessage = [
1301
- `Failed to parse OpenAPI specification from: ${this.options.input}`,
1464
+ `Failed to read OpenAPI specification from: ${this.options.input}`,
1302
1465
  "",
1303
- `Error: ${error.message}`,
1304
- "",
1305
- "Please ensure:",
1306
- " - The file exists and is readable",
1307
- " - The file contains valid YAML syntax",
1308
- " - The file is a valid OpenAPI 3.x specification"
1466
+ `Error: ${error.message}`
1309
1467
  ].join("\n");
1310
1468
  throw new SpecValidationError(errorMessage, { filePath: this.options.input, originalError: error.message });
1311
1469
  }
@@ -1363,6 +1521,8 @@ var ZodSchemaGenerator = class {
1363
1521
  this.generateComponentSchema(name, schema);
1364
1522
  }
1365
1523
  this.generateQueryParameterSchemas();
1524
+ this.generateHeaderParameterSchemas();
1525
+ validateFilters(this.filterStats, this.options.operationFilters);
1366
1526
  const orderedSchemaNames = this.topologicalSort();
1367
1527
  const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
1368
1528
  if (this.options.showStats === true) {
@@ -1456,9 +1616,16 @@ var ZodSchemaGenerator = class {
1456
1616
  const requestSchemas = /* @__PURE__ */ new Set();
1457
1617
  const responseSchemas = /* @__PURE__ */ new Set();
1458
1618
  if (this.spec.paths) {
1459
- for (const [, pathItem] of Object.entries(this.spec.paths)) {
1460
- for (const [, operation] of Object.entries(pathItem)) {
1619
+ for (const [path, pathItem] of Object.entries(this.spec.paths)) {
1620
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1621
+ for (const method of methods) {
1622
+ const operation = pathItem[method];
1461
1623
  if (typeof operation !== "object" || !operation) continue;
1624
+ this.filterStats.totalOperations++;
1625
+ if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters, this.filterStats)) {
1626
+ continue;
1627
+ }
1628
+ this.filterStats.includedOperations++;
1462
1629
  if ("requestBody" in operation && operation.requestBody && typeof operation.requestBody === "object" && "content" in operation.requestBody && operation.requestBody.content) {
1463
1630
  for (const mediaType of Object.values(operation.requestBody.content)) {
1464
1631
  if (mediaType && typeof mediaType === "object" && "schema" in mediaType && mediaType.schema) {
@@ -1808,12 +1975,15 @@ ${typeCode}`;
1808
1975
  if (!this.spec.paths) {
1809
1976
  return;
1810
1977
  }
1811
- for (const [_path, pathItem] of Object.entries(this.spec.paths)) {
1978
+ for (const [path, pathItem] of Object.entries(this.spec.paths)) {
1812
1979
  if (!pathItem || typeof pathItem !== "object") continue;
1813
1980
  const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
1814
1981
  for (const method of methods) {
1815
1982
  const operation = pathItem[method];
1816
1983
  if (!operation) continue;
1984
+ if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
1985
+ continue;
1986
+ }
1817
1987
  if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
1818
1988
  continue;
1819
1989
  }
@@ -1874,6 +2044,80 @@ ${propsCode}
1874
2044
  const jsdoc = `/**
1875
2045
  * Query parameters for ${operation.operationId}
1876
2046
  */
2047
+ `;
2048
+ const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
2049
+ this.schemas.set(schemaName, fullSchemaCode);
2050
+ this.needsZodImport = true;
2051
+ }
2052
+ }
2053
+ }
2054
+ /**
2055
+ * Generate header parameter schemas for each operation
2056
+ * Header parameters are always string type (HTTP header semantics)
2057
+ */
2058
+ generateHeaderParameterSchemas() {
2059
+ var _a;
2060
+ if (!this.spec.paths) {
2061
+ return;
2062
+ }
2063
+ for (const [path, pathItem] of Object.entries(this.spec.paths)) {
2064
+ if (!pathItem || typeof pathItem !== "object") continue;
2065
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
2066
+ for (const method of methods) {
2067
+ const operation = pathItem[method];
2068
+ if (!operation) continue;
2069
+ if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2070
+ continue;
2071
+ }
2072
+ if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2073
+ continue;
2074
+ }
2075
+ const headerParams = operation.parameters.filter(
2076
+ (param) => param && typeof param === "object" && param.in === "header"
2077
+ );
2078
+ if (headerParams.length === 0) {
2079
+ continue;
2080
+ }
2081
+ const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2082
+ const schemaName = `${pascalOperationId}HeaderParams`;
2083
+ if (!this.schemaDependencies.has(schemaName)) {
2084
+ this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
2085
+ }
2086
+ const properties = {};
2087
+ for (const param of headerParams) {
2088
+ const paramName = param.name;
2089
+ const paramSchema = param.schema;
2090
+ if (!paramSchema) continue;
2091
+ let zodType = "z.string()";
2092
+ if (param.description && this.requestOptions.includeDescriptions) {
2093
+ if (this.requestOptions.useDescribe) {
2094
+ zodType = `${zodType}.describe(${JSON.stringify(param.description)})`;
2095
+ }
2096
+ }
2097
+ zodType = `${zodType}.optional()`;
2098
+ properties[paramName] = zodType;
2099
+ if (paramSchema.$ref) {
2100
+ const refName = resolveRef(paramSchema.$ref);
2101
+ (_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
2102
+ }
2103
+ }
2104
+ const objectMode = this.requestOptions.mode;
2105
+ const zodMethod = objectMode === "strict" ? "strictObject" : objectMode === "loose" ? "looseObject" : "object";
2106
+ const propsCode = Object.entries(properties).map(([key, value]) => {
2107
+ const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key);
2108
+ const quotedKey = needsQuotes ? `"${key}"` : key;
2109
+ return ` ${quotedKey}: ${value}`;
2110
+ }).join(",\n");
2111
+ const schemaCode = `z.${zodMethod}({
2112
+ ${propsCode}
2113
+ })`;
2114
+ const operationName = pascalOperationId;
2115
+ const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2116
+ const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2117
+ const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
2118
+ const jsdoc = `/**
2119
+ * Header parameters for ${operation.operationId}
2120
+ */
1877
2121
  `;
1878
2122
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
1879
2123
  this.schemas.set(schemaName, fullSchemaCode);
@@ -2151,15 +2395,23 @@ ${props.join("\n")}
2151
2395
  stats.withConstraints++;
2152
2396
  }
2153
2397
  }
2154
- return [
2398
+ const output = [
2155
2399
  "// Generation Statistics:",
2156
2400
  `// Total schemas: ${stats.totalSchemas}`,
2157
2401
  `// Enums: ${stats.enums}`,
2158
2402
  `// Circular references: ${stats.withCircularRefs}`,
2159
2403
  `// Discriminated unions: ${stats.withDiscriminators}`,
2160
- `// With constraints: ${stats.withConstraints}`,
2161
- `// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`
2404
+ `// With constraints: ${stats.withConstraints}`
2162
2405
  ];
2406
+ if (this.options.operationFilters && this.filterStats.totalOperations > 0) {
2407
+ output.push("//");
2408
+ const filterStatsStr = formatFilterStatistics(this.filterStats);
2409
+ for (const line of filterStatsStr.split("\n")) {
2410
+ output.push(`// ${line}`);
2411
+ }
2412
+ }
2413
+ output.push(`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
2414
+ return output;
2163
2415
  }
2164
2416
  };
2165
2417
 
@@ -2173,9 +2425,13 @@ export {
2173
2425
  ConfigValidationError,
2174
2426
  FileOperationError,
2175
2427
  GeneratorError,
2428
+ OpenApiGenerator,
2176
2429
  SchemaGenerationError,
2177
2430
  SpecValidationError,
2178
- ZodSchemaGenerator,
2179
- defineConfig
2431
+ createFilterStatistics,
2432
+ defineConfig,
2433
+ formatFilterStatistics,
2434
+ shouldIncludeOperation,
2435
+ validateFilters
2180
2436
  };
2181
2437
  //# sourceMappingURL=index.mjs.map