@cerios/openapi-to-zod 1.1.1 → 1.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
@@ -163,14 +163,17 @@ function escapeJSDoc(str) {
163
163
  function wrapNullable(validation, isNullable2) {
164
164
  return isNullable2 ? `${validation}.nullable()` : validation;
165
165
  }
166
- function isNullable(schema) {
166
+ function isNullable(schema, defaultNullable = false) {
167
167
  if (schema.nullable === true) {
168
168
  return true;
169
169
  }
170
+ if (schema.nullable === false) {
171
+ return false;
172
+ }
170
173
  if (Array.isArray(schema.type)) {
171
174
  return schema.type.includes("null");
172
175
  }
173
- return false;
176
+ return defaultNullable;
174
177
  }
175
178
  function getPrimaryType(schema) {
176
179
  if (Array.isArray(schema.type)) {
@@ -336,6 +339,22 @@ function stripPrefix(input, pattern, ensureLeadingChar) {
336
339
  }
337
340
  return input;
338
341
  }
342
+ function stripPathPrefix(path, pattern) {
343
+ if (!pattern) {
344
+ return path;
345
+ }
346
+ if (!isGlobPattern(pattern)) {
347
+ let normalizedPattern = pattern.trim();
348
+ if (!normalizedPattern.startsWith("/")) {
349
+ normalizedPattern = `/${normalizedPattern}`;
350
+ }
351
+ if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
352
+ normalizedPattern = normalizedPattern.slice(0, -1);
353
+ }
354
+ return stripPrefix(path, normalizedPattern, "/");
355
+ }
356
+ return stripPrefix(path, pattern, "/");
357
+ }
339
358
 
340
359
  // src/validators/array-validator.ts
341
360
  function generateArrayValidation(schema, context) {
@@ -861,7 +880,7 @@ function configurePatternCache(size) {
861
880
  PATTERN_CACHE = new LRUCache(size);
862
881
  }
863
882
  }
864
- var FORMAT_MAP = {
883
+ var DEFAULT_FORMAT_MAP = {
865
884
  uuid: "z.uuid()",
866
885
  email: "z.email()",
867
886
  uri: "z.url()",
@@ -871,7 +890,6 @@ var FORMAT_MAP = {
871
890
  byte: "z.base64()",
872
891
  binary: "z.string()",
873
892
  date: "z.iso.date()",
874
- "date-time": "z.iso.datetime()",
875
893
  time: "z.iso.time()",
876
894
  duration: 'z.string().refine((val) => /^P(?:(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?|\\d+W)$/.test(val) && !/^PT?$/.test(val), { message: "Must be a valid ISO 8601 duration" })',
877
895
  ipv4: "z.ipv4()",
@@ -890,6 +908,30 @@ var FORMAT_MAP = {
890
908
  "json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
891
909
  "relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
892
910
  };
911
+ var FORMAT_MAP = {
912
+ ...DEFAULT_FORMAT_MAP,
913
+ "date-time": "z.iso.datetime()"
914
+ };
915
+ function configureDateTimeFormat(pattern) {
916
+ if (!pattern) {
917
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
918
+ return;
919
+ }
920
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
921
+ if (patternStr === "") {
922
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
923
+ return;
924
+ }
925
+ try {
926
+ new RegExp(patternStr);
927
+ } catch (error) {
928
+ throw new Error(
929
+ `Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
930
+ );
931
+ }
932
+ const escapedPattern = escapePattern(patternStr);
933
+ FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
934
+ }
893
935
  function generateStringValidation(schema, useDescribe) {
894
936
  let validation = FORMAT_MAP[schema.format || ""] || "z.string()";
895
937
  if (schema.minLength !== void 0) {
@@ -1179,7 +1221,8 @@ var _PropertyGenerator = class _PropertyGenerator {
1179
1221
  if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
1180
1222
  schema = this.filterNestedProperties(schema);
1181
1223
  }
1182
- const nullable = isNullable(schema);
1224
+ const effectiveDefaultNullable = isTopLevel ? false : this.context.defaultNullable;
1225
+ const nullable = isNullable(schema, effectiveDefaultNullable);
1183
1226
  if (hasMultipleTypes(schema)) {
1184
1227
  const union = this.generateMultiTypeUnion(schema, currentSchema);
1185
1228
  return wrapNullable(union, nullable);
@@ -1498,7 +1541,7 @@ var OpenApiGenerator = class {
1498
1541
  this.schemaUsageMap = /* @__PURE__ */ new Map();
1499
1542
  this.needsZodImport = true;
1500
1543
  this.filterStats = createFilterStatistics();
1501
- var _a, _b, _c, _d, _e;
1544
+ var _a, _b, _c, _d, _e, _f, _g;
1502
1545
  if (!options.input) {
1503
1546
  throw new ConfigurationError("Input path is required", { providedOptions: options });
1504
1547
  }
@@ -1508,21 +1551,27 @@ var OpenApiGenerator = class {
1508
1551
  output: options.output,
1509
1552
  includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
1510
1553
  useDescribe: (_b = options.useDescribe) != null ? _b : false,
1554
+ defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
1511
1555
  schemaType: options.schemaType || "all",
1512
1556
  prefix: options.prefix,
1513
1557
  suffix: options.suffix,
1514
1558
  stripSchemaPrefix: options.stripSchemaPrefix,
1515
- showStats: (_c = options.showStats) != null ? _c : true,
1559
+ stripPathPrefix: options.stripPathPrefix,
1560
+ showStats: (_d = options.showStats) != null ? _d : true,
1516
1561
  request: options.request,
1517
1562
  response: options.response,
1518
1563
  operationFilters: options.operationFilters,
1519
1564
  ignoreHeaders: options.ignoreHeaders,
1520
- cacheSize: (_d = options.cacheSize) != null ? _d : 1e3,
1521
- batchSize: (_e = options.batchSize) != null ? _e : 10
1565
+ cacheSize: (_e = options.cacheSize) != null ? _e : 1e3,
1566
+ batchSize: (_f = options.batchSize) != null ? _f : 10,
1567
+ customDateTimeFormatRegex: options.customDateTimeFormatRegex
1522
1568
  };
1523
1569
  if (this.options.cacheSize) {
1524
1570
  configurePatternCache(this.options.cacheSize);
1525
1571
  }
1572
+ if (this.options.customDateTimeFormatRegex) {
1573
+ configureDateTimeFormat(this.options.customDateTimeFormatRegex);
1574
+ }
1526
1575
  try {
1527
1576
  const fs = __require("fs");
1528
1577
  if (!fs.existsSync(this.options.input)) {
@@ -1585,6 +1634,7 @@ var OpenApiGenerator = class {
1585
1634
  mode: this.requestOptions.mode,
1586
1635
  includeDescriptions: this.requestOptions.includeDescriptions,
1587
1636
  useDescribe: this.requestOptions.useDescribe,
1637
+ defaultNullable: (_g = this.options.defaultNullable) != null ? _g : false,
1588
1638
  namingOptions: {
1589
1639
  prefix: this.options.prefix,
1590
1640
  suffix: this.options.suffix
@@ -1938,7 +1988,7 @@ var OpenApiGenerator = class {
1938
1988
  * Generate schema for a component
1939
1989
  */
1940
1990
  generateComponentSchema(name, schema) {
1941
- var _a, _b;
1991
+ var _a, _b, _c;
1942
1992
  if (!this.schemaDependencies.has(name)) {
1943
1993
  this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
1944
1994
  }
@@ -1970,14 +2020,14 @@ ${typeCode}`;
1970
2020
  mode: resolvedOptions.mode,
1971
2021
  includeDescriptions: resolvedOptions.includeDescriptions,
1972
2022
  useDescribe: resolvedOptions.useDescribe,
2023
+ defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
1973
2024
  namingOptions: {
1974
2025
  prefix: this.options.prefix,
1975
2026
  suffix: this.options.suffix
1976
2027
  },
1977
2028
  stripSchemaPrefix: this.options.stripSchemaPrefix
1978
2029
  });
1979
- const isAlias = !!(schema.$ref && !schema.properties && !schema.allOf && !schema.oneOf && !schema.anyOf);
1980
- const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
2030
+ const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
1981
2031
  const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
1982
2032
  if (zodSchema.includes("z.discriminatedUnion(")) {
1983
2033
  const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
@@ -1987,7 +2037,7 @@ ${typeCode}`;
1987
2037
  const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
1988
2038
  if (depMatch) {
1989
2039
  const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
1990
- (_b = this.schemaDependencies.get(name)) == null ? void 0 : _b.add(depName);
2040
+ (_c = this.schemaDependencies.get(name)) == null ? void 0 : _c.add(depName);
1991
2041
  }
1992
2042
  }
1993
2043
  }
@@ -2011,7 +2061,7 @@ ${typeCode}`;
2011
2061
  if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2012
2062
  continue;
2013
2063
  }
2014
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2064
+ if (!operation.parameters || !Array.isArray(operation.parameters)) {
2015
2065
  continue;
2016
2066
  }
2017
2067
  const queryParams = operation.parameters.filter(
@@ -2020,7 +2070,13 @@ ${typeCode}`;
2020
2070
  if (queryParams.length === 0) {
2021
2071
  continue;
2022
2072
  }
2023
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2073
+ let pascalOperationId;
2074
+ if (operation.operationId) {
2075
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2076
+ } else {
2077
+ const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
2078
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
2079
+ }
2024
2080
  const schemaName = `${pascalOperationId}QueryParams`;
2025
2081
  if (!this.schemaDependencies.has(schemaName)) {
2026
2082
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -2068,8 +2124,9 @@ ${propsCode}
2068
2124
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2069
2125
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2070
2126
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
2127
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
2071
2128
  const jsdoc = `/**
2072
- * Query parameters for ${operation.operationId}
2129
+ * Query parameters for ${jsdocOperationName}
2073
2130
  */
2074
2131
  `;
2075
2132
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -2078,6 +2135,35 @@ ${propsCode}
2078
2135
  }
2079
2136
  }
2080
2137
  }
2138
+ /**
2139
+ * Generate a PascalCase method name from HTTP method and path
2140
+ * Used as fallback when operationId is not available
2141
+ * @internal
2142
+ */
2143
+ generateMethodNameFromPath(method, path) {
2144
+ const segments = path.split("/").filter(Boolean).map((segment) => {
2145
+ if (segment.startsWith("{") && segment.endsWith("}")) {
2146
+ const paramName = segment.slice(1, -1);
2147
+ return `By${this.capitalizeSegment(paramName)}`;
2148
+ }
2149
+ return this.capitalizeSegment(segment);
2150
+ }).join("");
2151
+ const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
2152
+ return `${capitalizedMethod}${segments}`;
2153
+ }
2154
+ /**
2155
+ * Capitalizes a path segment, handling special characters like dashes, underscores, and dots
2156
+ * @internal
2157
+ */
2158
+ capitalizeSegment(str) {
2159
+ if (str.includes("-") || str.includes("_") || str.includes(".")) {
2160
+ return str.split(/[-_.]/).map((part) => {
2161
+ if (!part) return "";
2162
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
2163
+ }).join("");
2164
+ }
2165
+ return str.charAt(0).toUpperCase() + str.slice(1);
2166
+ }
2081
2167
  /**
2082
2168
  * Check if a header should be ignored based on filter patterns
2083
2169
  * @internal
@@ -2114,7 +2200,7 @@ ${propsCode}
2114
2200
  if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
2115
2201
  continue;
2116
2202
  }
2117
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
2203
+ if (!operation.parameters || !Array.isArray(operation.parameters)) {
2118
2204
  continue;
2119
2205
  }
2120
2206
  const headerParams = operation.parameters.filter(
@@ -2123,7 +2209,13 @@ ${propsCode}
2123
2209
  if (headerParams.length === 0) {
2124
2210
  continue;
2125
2211
  }
2126
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2212
+ let pascalOperationId;
2213
+ if (operation.operationId) {
2214
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
2215
+ } else {
2216
+ const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
2217
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
2218
+ }
2127
2219
  const schemaName = `${pascalOperationId}HeaderParams`;
2128
2220
  if (!this.schemaDependencies.has(schemaName)) {
2129
2221
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -2160,8 +2252,9 @@ ${propsCode}
2160
2252
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
2161
2253
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
2162
2254
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
2255
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
2163
2256
  const jsdoc = `/**
2164
- * Header parameters for ${operation.operationId}
2257
+ * Header parameters for ${jsdocOperationName}
2165
2258
  */
2166
2259
  `;
2167
2260
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;