@cerios/openapi-to-zod 1.1.1 → 1.3.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/README.md +192 -10
- package/dist/cli.js +421 -49
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +428 -50
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +384 -46
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +384 -46
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.mts +72 -2
- package/dist/internal.d.ts +72 -2
- package/dist/internal.js +139 -1
- package/dist/internal.js.map +1 -1
- package/dist/internal.mjs +132 -1
- package/dist/internal.mjs.map +1 -1
- package/dist/{types-CI48CjiU.d.mts → types-B3GgqGzM.d.mts} +139 -1
- package/dist/{types-CI48CjiU.d.ts → types-B3GgqGzM.d.ts} +139 -1
- package/package.json +16 -4
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
|
|
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) {
|
|
@@ -384,12 +403,56 @@ function generateArrayValidation(schema, context) {
|
|
|
384
403
|
}
|
|
385
404
|
|
|
386
405
|
// src/validators/composition-validator.ts
|
|
406
|
+
function isDiscriminatorRequired(schemas, discriminator, context) {
|
|
407
|
+
const invalidSchemas = [];
|
|
408
|
+
for (const schema of schemas) {
|
|
409
|
+
const resolved = resolveSchema(schema, context);
|
|
410
|
+
const required = resolved.required || [];
|
|
411
|
+
if (!required.includes(discriminator)) {
|
|
412
|
+
const schemaName = schema.$ref ? schema.$ref.split("/").pop() || "inline" : "inline";
|
|
413
|
+
invalidSchemas.push(schemaName);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
valid: invalidSchemas.length === 0,
|
|
418
|
+
invalidSchemas
|
|
419
|
+
};
|
|
420
|
+
}
|
|
387
421
|
function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
|
|
422
|
+
if (schemas.length === 0) {
|
|
423
|
+
console.warn(
|
|
424
|
+
"[openapi-to-zod] Warning: Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
|
|
425
|
+
);
|
|
426
|
+
return wrapNullable(
|
|
427
|
+
'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
|
|
428
|
+
isNullable2
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (schemas.length === 1) {
|
|
432
|
+
let singleSchema = context.generatePropertySchema(schemas[0], currentSchema);
|
|
433
|
+
if ((options == null ? void 0 : options.passthrough) && !singleSchema.includes(".catchall(")) {
|
|
434
|
+
singleSchema = `${singleSchema}.catchall(z.unknown())`;
|
|
435
|
+
}
|
|
436
|
+
return wrapNullable(singleSchema, isNullable2);
|
|
437
|
+
}
|
|
388
438
|
if (discriminator) {
|
|
389
439
|
let resolvedSchemas = schemas;
|
|
390
440
|
if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
|
|
391
441
|
resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
|
|
392
442
|
}
|
|
443
|
+
const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
|
|
444
|
+
if (!discriminatorCheck.valid) {
|
|
445
|
+
console.warn(
|
|
446
|
+
`[openapi-to-zod] Warning: Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
|
|
447
|
+
);
|
|
448
|
+
let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
449
|
+
if (options == null ? void 0 : options.passthrough) {
|
|
450
|
+
schemaStrings3 = schemaStrings3.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
451
|
+
}
|
|
452
|
+
const fallbackDescription = `Discriminator "${discriminator}" is optional in some schemas (${discriminatorCheck.invalidSchemas.join(", ")}), using z.union() instead of z.discriminatedUnion()`;
|
|
453
|
+
const union3 = `z.union([${schemaStrings3.join(", ")}]).describe("${fallbackDescription}")`;
|
|
454
|
+
return wrapNullable(union3, isNullable2);
|
|
455
|
+
}
|
|
393
456
|
let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
394
457
|
if (options == null ? void 0 : options.passthrough) {
|
|
395
458
|
schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
@@ -404,25 +467,102 @@ function generateUnion(schemas, discriminator, isNullable2, context, options, cu
|
|
|
404
467
|
const union = `z.union([${schemaStrings.join(", ")}])`;
|
|
405
468
|
return wrapNullable(union, isNullable2);
|
|
406
469
|
}
|
|
470
|
+
function resolveSchema(schema, context) {
|
|
471
|
+
if (schema.$ref && context.resolveSchemaRef) {
|
|
472
|
+
const resolved = context.resolveSchemaRef(schema.$ref);
|
|
473
|
+
if (resolved) {
|
|
474
|
+
return resolved;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return schema;
|
|
478
|
+
}
|
|
479
|
+
function collectProperties(schema, context) {
|
|
480
|
+
const resolved = resolveSchema(schema, context);
|
|
481
|
+
const props = /* @__PURE__ */ new Map();
|
|
482
|
+
const sourceName = schema.$ref ? schema.$ref.split("/").pop() || "unknown" : "inline";
|
|
483
|
+
if (resolved.properties) {
|
|
484
|
+
for (const [key, value] of Object.entries(resolved.properties)) {
|
|
485
|
+
props.set(key, { schema: value, source: sourceName });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (resolved.allOf) {
|
|
489
|
+
for (const subSchema of resolved.allOf) {
|
|
490
|
+
const subProps = collectProperties(subSchema, context);
|
|
491
|
+
for (const [key, value] of subProps) {
|
|
492
|
+
if (!props.has(key)) {
|
|
493
|
+
props.set(key, value);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return props;
|
|
499
|
+
}
|
|
500
|
+
function schemasMatch(a, b) {
|
|
501
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
502
|
+
}
|
|
503
|
+
function detectConflictingProperties(schemas, context) {
|
|
504
|
+
const conflicts = [];
|
|
505
|
+
const propertyMap = /* @__PURE__ */ new Map();
|
|
506
|
+
for (const schema of schemas) {
|
|
507
|
+
const schemaProps = collectProperties(schema, context);
|
|
508
|
+
for (const [propName, propInfo] of schemaProps) {
|
|
509
|
+
const existing = propertyMap.get(propName);
|
|
510
|
+
if (existing) {
|
|
511
|
+
if (!schemasMatch(existing.schema, propInfo.schema)) {
|
|
512
|
+
conflicts.push(
|
|
513
|
+
`Property "${propName}" has conflicting definitions in ${existing.source} and ${propInfo.source}`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
} else {
|
|
517
|
+
propertyMap.set(propName, propInfo);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return conflicts;
|
|
522
|
+
}
|
|
407
523
|
function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
408
524
|
if (schemas.length === 1) {
|
|
409
525
|
const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false);
|
|
410
526
|
return wrapNullable(singleSchema, isNullable2);
|
|
411
527
|
}
|
|
528
|
+
const conflicts = detectConflictingProperties(schemas, context);
|
|
529
|
+
let conflictDescription = "";
|
|
530
|
+
if (conflicts.length > 0) {
|
|
531
|
+
for (const conflict of conflicts) {
|
|
532
|
+
console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
|
|
533
|
+
}
|
|
534
|
+
conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
|
|
535
|
+
}
|
|
412
536
|
const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
|
|
413
|
-
|
|
537
|
+
let result;
|
|
414
538
|
if (allObjects) {
|
|
415
|
-
let
|
|
539
|
+
let merged = context.generatePropertySchema(schemas[0], currentSchema, false);
|
|
540
|
+
for (let i = 1; i < schemas.length; i++) {
|
|
541
|
+
const schema = schemas[i];
|
|
542
|
+
if (schema.$ref) {
|
|
543
|
+
const refSchema = context.generatePropertySchema(schema, currentSchema, false);
|
|
544
|
+
merged = `${merged}.extend(${refSchema}.shape)`;
|
|
545
|
+
} else if (context.generateInlineObjectShape && (schema.properties || schema.type === "object")) {
|
|
546
|
+
const inlineShape = context.generateInlineObjectShape(schema, currentSchema);
|
|
547
|
+
merged = `${merged}.extend(${inlineShape})`;
|
|
548
|
+
} else {
|
|
549
|
+
const schemaString = context.generatePropertySchema(schema, currentSchema, false);
|
|
550
|
+
merged = `${merged}.extend(${schemaString}.shape)`;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
result = merged;
|
|
554
|
+
} else {
|
|
555
|
+
const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
|
|
556
|
+
let merged = schemaStrings[0];
|
|
416
557
|
for (let i = 1; i < schemaStrings.length; i++) {
|
|
417
|
-
|
|
558
|
+
merged = `${merged}.and(${schemaStrings[i]})`;
|
|
418
559
|
}
|
|
419
|
-
|
|
560
|
+
result = merged;
|
|
420
561
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
merged = `${merged}.and(${schemaStrings[i]})`;
|
|
562
|
+
if (conflictDescription) {
|
|
563
|
+
result = `${result}.describe("${conflictDescription}")`;
|
|
424
564
|
}
|
|
425
|
-
return wrapNullable(
|
|
565
|
+
return wrapNullable(result, isNullable2);
|
|
426
566
|
}
|
|
427
567
|
|
|
428
568
|
// src/validators/number-validator.ts
|
|
@@ -861,7 +1001,7 @@ function configurePatternCache(size) {
|
|
|
861
1001
|
PATTERN_CACHE = new LRUCache(size);
|
|
862
1002
|
}
|
|
863
1003
|
}
|
|
864
|
-
var
|
|
1004
|
+
var DEFAULT_FORMAT_MAP = {
|
|
865
1005
|
uuid: "z.uuid()",
|
|
866
1006
|
email: "z.email()",
|
|
867
1007
|
uri: "z.url()",
|
|
@@ -871,7 +1011,6 @@ var FORMAT_MAP = {
|
|
|
871
1011
|
byte: "z.base64()",
|
|
872
1012
|
binary: "z.string()",
|
|
873
1013
|
date: "z.iso.date()",
|
|
874
|
-
"date-time": "z.iso.datetime()",
|
|
875
1014
|
time: "z.iso.time()",
|
|
876
1015
|
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
1016
|
ipv4: "z.ipv4()",
|
|
@@ -890,6 +1029,30 @@ var FORMAT_MAP = {
|
|
|
890
1029
|
"json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
|
|
891
1030
|
"relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
|
|
892
1031
|
};
|
|
1032
|
+
var FORMAT_MAP = {
|
|
1033
|
+
...DEFAULT_FORMAT_MAP,
|
|
1034
|
+
"date-time": "z.iso.datetime()"
|
|
1035
|
+
};
|
|
1036
|
+
function configureDateTimeFormat(pattern) {
|
|
1037
|
+
if (!pattern) {
|
|
1038
|
+
FORMAT_MAP["date-time"] = "z.iso.datetime()";
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
1042
|
+
if (patternStr === "") {
|
|
1043
|
+
FORMAT_MAP["date-time"] = "z.iso.datetime()";
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
try {
|
|
1047
|
+
new RegExp(patternStr);
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
throw new Error(
|
|
1050
|
+
`Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
const escapedPattern = escapePattern(patternStr);
|
|
1054
|
+
FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
|
|
1055
|
+
}
|
|
893
1056
|
function generateStringValidation(schema, useDescribe) {
|
|
894
1057
|
let validation = FORMAT_MAP[schema.format || ""] || "z.string()";
|
|
895
1058
|
if (schema.minLength !== void 0) {
|
|
@@ -1075,6 +1238,15 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1075
1238
|
}
|
|
1076
1239
|
return mappedSchemas;
|
|
1077
1240
|
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Resolve a $ref string to the actual schema
|
|
1243
|
+
*/
|
|
1244
|
+
resolveSchemaRef(ref) {
|
|
1245
|
+
var _a, _b;
|
|
1246
|
+
const schemaName = ref.split("/").pop();
|
|
1247
|
+
if (!schemaName) return void 0;
|
|
1248
|
+
return (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
|
|
1249
|
+
}
|
|
1078
1250
|
/**
|
|
1079
1251
|
* Resolve a schema name through any aliases to get the actual schema name
|
|
1080
1252
|
* If the schema is an alias (allOf with single $ref), return the target name
|
|
@@ -1152,7 +1324,7 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1152
1324
|
let schemaWithCatchall = baseSchema;
|
|
1153
1325
|
if (baseSchema.includes(".union([") || baseSchema.includes(".discriminatedUnion(")) {
|
|
1154
1326
|
schemaWithCatchall = baseSchema;
|
|
1155
|
-
} else if (baseSchema.includes(".
|
|
1327
|
+
} else if (baseSchema.includes(".extend(")) {
|
|
1156
1328
|
schemaWithCatchall = `${baseSchema}.catchall(z.unknown())`;
|
|
1157
1329
|
}
|
|
1158
1330
|
if (schema.unevaluatedProperties === false) {
|
|
@@ -1179,7 +1351,12 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1179
1351
|
if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
|
|
1180
1352
|
schema = this.filterNestedProperties(schema);
|
|
1181
1353
|
}
|
|
1182
|
-
const
|
|
1354
|
+
const isSchemaRef = !!schema.$ref;
|
|
1355
|
+
const isEnum = !!schema.enum;
|
|
1356
|
+
const isConst = schema.const !== void 0;
|
|
1357
|
+
const shouldApplyDefaultNullable = !isTopLevel && !isSchemaRef && !isEnum && !isConst;
|
|
1358
|
+
const effectiveDefaultNullable = shouldApplyDefaultNullable ? this.context.defaultNullable : false;
|
|
1359
|
+
const nullable = isNullable(schema, effectiveDefaultNullable);
|
|
1183
1360
|
if (hasMultipleTypes(schema)) {
|
|
1184
1361
|
const union = this.generateMultiTypeUnion(schema, currentSchema);
|
|
1185
1362
|
return wrapNullable(union, nullable);
|
|
@@ -1231,7 +1408,11 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1231
1408
|
let composition = generateAllOf(
|
|
1232
1409
|
schema.allOf,
|
|
1233
1410
|
nullable,
|
|
1234
|
-
{
|
|
1411
|
+
{
|
|
1412
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1413
|
+
generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
|
|
1414
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1415
|
+
},
|
|
1235
1416
|
currentSchema
|
|
1236
1417
|
);
|
|
1237
1418
|
if (schema.unevaluatedProperties !== void 0) {
|
|
@@ -1247,7 +1428,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1247
1428
|
nullable,
|
|
1248
1429
|
{
|
|
1249
1430
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1250
|
-
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
|
|
1431
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
1432
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1251
1433
|
},
|
|
1252
1434
|
{
|
|
1253
1435
|
passthrough: needsPassthrough,
|
|
@@ -1268,7 +1450,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1268
1450
|
nullable,
|
|
1269
1451
|
{
|
|
1270
1452
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1271
|
-
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
|
|
1453
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
1454
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1272
1455
|
},
|
|
1273
1456
|
{
|
|
1274
1457
|
passthrough: needsPassthrough,
|
|
@@ -1331,7 +1514,17 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1331
1514
|
);
|
|
1332
1515
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1333
1516
|
} else {
|
|
1334
|
-
|
|
1517
|
+
switch (this.context.emptyObjectBehavior) {
|
|
1518
|
+
case "strict":
|
|
1519
|
+
validation = "z.strictObject({})";
|
|
1520
|
+
break;
|
|
1521
|
+
case "loose":
|
|
1522
|
+
validation = "z.looseObject({})";
|
|
1523
|
+
break;
|
|
1524
|
+
default:
|
|
1525
|
+
validation = "z.record(z.string(), z.unknown())";
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1335
1528
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1336
1529
|
}
|
|
1337
1530
|
break;
|
|
@@ -1346,6 +1539,44 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1346
1539
|
}
|
|
1347
1540
|
return result;
|
|
1348
1541
|
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Generate inline object shape for use with .extend()
|
|
1544
|
+
* Returns just the shape object literal: { prop1: z.string(), prop2: z.number() }
|
|
1545
|
+
*
|
|
1546
|
+
* This method is specifically for allOf compositions where we need to pass
|
|
1547
|
+
* the shape directly to .extend() instead of using z.object({...}).shape.
|
|
1548
|
+
* This avoids the .nullable().shape bug when inline objects have nullable: true.
|
|
1549
|
+
*
|
|
1550
|
+
* According to Zod docs (https://zod.dev/api?id=extend):
|
|
1551
|
+
* - .extend() accepts an object of shape definitions
|
|
1552
|
+
* - e.g., baseSchema.extend({ prop: z.string() })
|
|
1553
|
+
*/
|
|
1554
|
+
generateInlineObjectShape(schema, currentSchema) {
|
|
1555
|
+
const required = new Set(schema.required || []);
|
|
1556
|
+
const properties = [];
|
|
1557
|
+
if (schema.properties) {
|
|
1558
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
1559
|
+
if (!this.shouldIncludeProperty(propSchema)) {
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
const isRequired = required.has(propName);
|
|
1563
|
+
const zodSchema = this.generatePropertySchema(propSchema, currentSchema);
|
|
1564
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
1565
|
+
const quotedPropName = validIdentifier.test(propName) ? propName : `"${propName}"`;
|
|
1566
|
+
let propertyDef = `${quotedPropName}: ${zodSchema}`;
|
|
1567
|
+
if (!isRequired) {
|
|
1568
|
+
propertyDef += ".optional()";
|
|
1569
|
+
}
|
|
1570
|
+
properties.push(propertyDef);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (properties.length === 0) {
|
|
1574
|
+
return "{}";
|
|
1575
|
+
}
|
|
1576
|
+
return `{
|
|
1577
|
+
${properties.map((p) => ` ${p}`).join(",\n")}
|
|
1578
|
+
}`;
|
|
1579
|
+
}
|
|
1349
1580
|
};
|
|
1350
1581
|
// Performance optimization: Lookup table for faster inclusion checks
|
|
1351
1582
|
_PropertyGenerator.INCLUSION_RULES = {
|
|
@@ -1489,6 +1720,59 @@ function formatFilterStatistics(stats) {
|
|
|
1489
1720
|
return lines.join("\n");
|
|
1490
1721
|
}
|
|
1491
1722
|
|
|
1723
|
+
// src/utils/ref-resolver.ts
|
|
1724
|
+
function resolveRef2(obj, spec, maxDepth = 10) {
|
|
1725
|
+
var _a, _b, _c, _d;
|
|
1726
|
+
if (!obj || typeof obj !== "object" || maxDepth <= 0) return obj;
|
|
1727
|
+
if (!obj.$ref) return obj;
|
|
1728
|
+
const ref = obj.$ref;
|
|
1729
|
+
let resolved = null;
|
|
1730
|
+
const paramMatch = ref.match(/^#\/components\/parameters\/(.+)$/);
|
|
1731
|
+
const requestBodyMatch = ref.match(/^#\/components\/requestBodies\/(.+)$/);
|
|
1732
|
+
const responseMatch = ref.match(/^#\/components\/responses\/(.+)$/);
|
|
1733
|
+
const schemaMatch = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
1734
|
+
if (paramMatch && ((_a = spec.components) == null ? void 0 : _a.parameters)) {
|
|
1735
|
+
const name = paramMatch[1];
|
|
1736
|
+
resolved = spec.components.parameters[name];
|
|
1737
|
+
} else if (requestBodyMatch && ((_b = spec.components) == null ? void 0 : _b.requestBodies)) {
|
|
1738
|
+
const name = requestBodyMatch[1];
|
|
1739
|
+
resolved = spec.components.requestBodies[name];
|
|
1740
|
+
} else if (responseMatch && ((_c = spec.components) == null ? void 0 : _c.responses)) {
|
|
1741
|
+
const name = responseMatch[1];
|
|
1742
|
+
resolved = spec.components.responses[name];
|
|
1743
|
+
} else if (schemaMatch && ((_d = spec.components) == null ? void 0 : _d.schemas)) {
|
|
1744
|
+
const name = schemaMatch[1];
|
|
1745
|
+
resolved = spec.components.schemas[name];
|
|
1746
|
+
}
|
|
1747
|
+
if (resolved) {
|
|
1748
|
+
if (resolved.$ref) {
|
|
1749
|
+
return resolveRef2(resolved, spec, maxDepth - 1);
|
|
1750
|
+
}
|
|
1751
|
+
return resolved;
|
|
1752
|
+
}
|
|
1753
|
+
return obj;
|
|
1754
|
+
}
|
|
1755
|
+
function resolveParameterRef(param, spec) {
|
|
1756
|
+
return resolveRef2(param, spec);
|
|
1757
|
+
}
|
|
1758
|
+
function mergeParameters(pathParams, operationParams, spec) {
|
|
1759
|
+
const resolvedPathParams = (pathParams || []).map((p) => resolveParameterRef(p, spec));
|
|
1760
|
+
const resolvedOperationParams = (operationParams || []).map((p) => resolveParameterRef(p, spec));
|
|
1761
|
+
const merged = [...resolvedPathParams];
|
|
1762
|
+
for (const opParam of resolvedOperationParams) {
|
|
1763
|
+
if (!opParam || typeof opParam !== "object") continue;
|
|
1764
|
+
const existingIndex = merged.findIndex(
|
|
1765
|
+
(p) => p && typeof p === "object" && p.name === opParam.name && p.in === opParam.in
|
|
1766
|
+
);
|
|
1767
|
+
if (existingIndex >= 0) {
|
|
1768
|
+
merged[existingIndex] = opParam;
|
|
1769
|
+
} else {
|
|
1770
|
+
merged.push(opParam);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
return merged;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1492
1776
|
// src/openapi-generator.ts
|
|
1493
1777
|
var OpenApiGenerator = class {
|
|
1494
1778
|
constructor(options) {
|
|
@@ -1498,7 +1782,7 @@ var OpenApiGenerator = class {
|
|
|
1498
1782
|
this.schemaUsageMap = /* @__PURE__ */ new Map();
|
|
1499
1783
|
this.needsZodImport = true;
|
|
1500
1784
|
this.filterStats = createFilterStatistics();
|
|
1501
|
-
var _a, _b, _c, _d, _e;
|
|
1785
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1502
1786
|
if (!options.input) {
|
|
1503
1787
|
throw new ConfigurationError("Input path is required", { providedOptions: options });
|
|
1504
1788
|
}
|
|
@@ -1508,21 +1792,28 @@ var OpenApiGenerator = class {
|
|
|
1508
1792
|
output: options.output,
|
|
1509
1793
|
includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
|
|
1510
1794
|
useDescribe: (_b = options.useDescribe) != null ? _b : false,
|
|
1795
|
+
defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
|
|
1796
|
+
emptyObjectBehavior: (_d = options.emptyObjectBehavior) != null ? _d : "loose",
|
|
1511
1797
|
schemaType: options.schemaType || "all",
|
|
1512
1798
|
prefix: options.prefix,
|
|
1513
1799
|
suffix: options.suffix,
|
|
1514
1800
|
stripSchemaPrefix: options.stripSchemaPrefix,
|
|
1515
|
-
|
|
1801
|
+
stripPathPrefix: options.stripPathPrefix,
|
|
1802
|
+
showStats: (_e = options.showStats) != null ? _e : true,
|
|
1516
1803
|
request: options.request,
|
|
1517
1804
|
response: options.response,
|
|
1518
1805
|
operationFilters: options.operationFilters,
|
|
1519
1806
|
ignoreHeaders: options.ignoreHeaders,
|
|
1520
|
-
cacheSize: (
|
|
1521
|
-
batchSize: (
|
|
1807
|
+
cacheSize: (_f = options.cacheSize) != null ? _f : 1e3,
|
|
1808
|
+
batchSize: (_g = options.batchSize) != null ? _g : 10,
|
|
1809
|
+
customDateTimeFormatRegex: options.customDateTimeFormatRegex
|
|
1522
1810
|
};
|
|
1523
1811
|
if (this.options.cacheSize) {
|
|
1524
1812
|
configurePatternCache(this.options.cacheSize);
|
|
1525
1813
|
}
|
|
1814
|
+
if (this.options.customDateTimeFormatRegex) {
|
|
1815
|
+
configureDateTimeFormat(this.options.customDateTimeFormatRegex);
|
|
1816
|
+
}
|
|
1526
1817
|
try {
|
|
1527
1818
|
const fs = __require("fs");
|
|
1528
1819
|
if (!fs.existsSync(this.options.input)) {
|
|
@@ -1585,6 +1876,8 @@ var OpenApiGenerator = class {
|
|
|
1585
1876
|
mode: this.requestOptions.mode,
|
|
1586
1877
|
includeDescriptions: this.requestOptions.includeDescriptions,
|
|
1587
1878
|
useDescribe: this.requestOptions.useDescribe,
|
|
1879
|
+
defaultNullable: (_h = this.options.defaultNullable) != null ? _h : false,
|
|
1880
|
+
emptyObjectBehavior: (_i = this.options.emptyObjectBehavior) != null ? _i : "loose",
|
|
1588
1881
|
namingOptions: {
|
|
1589
1882
|
prefix: this.options.prefix,
|
|
1590
1883
|
suffix: this.options.suffix
|
|
@@ -1938,7 +2231,7 @@ var OpenApiGenerator = class {
|
|
|
1938
2231
|
* Generate schema for a component
|
|
1939
2232
|
*/
|
|
1940
2233
|
generateComponentSchema(name, schema) {
|
|
1941
|
-
var _a, _b;
|
|
2234
|
+
var _a, _b, _c, _d;
|
|
1942
2235
|
if (!this.schemaDependencies.has(name)) {
|
|
1943
2236
|
this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
|
|
1944
2237
|
}
|
|
@@ -1970,14 +2263,15 @@ ${typeCode}`;
|
|
|
1970
2263
|
mode: resolvedOptions.mode,
|
|
1971
2264
|
includeDescriptions: resolvedOptions.includeDescriptions,
|
|
1972
2265
|
useDescribe: resolvedOptions.useDescribe,
|
|
2266
|
+
defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
|
|
2267
|
+
emptyObjectBehavior: (_c = this.options.emptyObjectBehavior) != null ? _c : "loose",
|
|
1973
2268
|
namingOptions: {
|
|
1974
2269
|
prefix: this.options.prefix,
|
|
1975
2270
|
suffix: this.options.suffix
|
|
1976
2271
|
},
|
|
1977
2272
|
stripSchemaPrefix: this.options.stripSchemaPrefix
|
|
1978
2273
|
});
|
|
1979
|
-
const
|
|
1980
|
-
const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
|
|
2274
|
+
const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
|
|
1981
2275
|
const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
|
|
1982
2276
|
if (zodSchema.includes("z.discriminatedUnion(")) {
|
|
1983
2277
|
const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
|
|
@@ -1987,7 +2281,7 @@ ${typeCode}`;
|
|
|
1987
2281
|
const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
|
|
1988
2282
|
if (depMatch) {
|
|
1989
2283
|
const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
|
|
1990
|
-
(
|
|
2284
|
+
(_d = this.schemaDependencies.get(name)) == null ? void 0 : _d.add(depName);
|
|
1991
2285
|
}
|
|
1992
2286
|
}
|
|
1993
2287
|
}
|
|
@@ -2011,16 +2305,20 @@ ${typeCode}`;
|
|
|
2011
2305
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
2012
2306
|
continue;
|
|
2013
2307
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
}
|
|
2017
|
-
const queryParams = operation.parameters.filter(
|
|
2308
|
+
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
2309
|
+
const queryParams = allParams.filter(
|
|
2018
2310
|
(param) => param && typeof param === "object" && param.in === "query"
|
|
2019
2311
|
);
|
|
2020
2312
|
if (queryParams.length === 0) {
|
|
2021
2313
|
continue;
|
|
2022
2314
|
}
|
|
2023
|
-
|
|
2315
|
+
let pascalOperationId;
|
|
2316
|
+
if (operation.operationId) {
|
|
2317
|
+
pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
|
|
2318
|
+
} else {
|
|
2319
|
+
const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
|
|
2320
|
+
pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
|
|
2321
|
+
}
|
|
2024
2322
|
const schemaName = `${pascalOperationId}QueryParams`;
|
|
2025
2323
|
if (!this.schemaDependencies.has(schemaName)) {
|
|
2026
2324
|
this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
|
|
@@ -2068,8 +2366,9 @@ ${propsCode}
|
|
|
2068
2366
|
const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
|
|
2069
2367
|
const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
|
|
2070
2368
|
const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
|
|
2369
|
+
const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
|
|
2071
2370
|
const jsdoc = `/**
|
|
2072
|
-
* Query parameters for ${
|
|
2371
|
+
* Query parameters for ${jsdocOperationName}
|
|
2073
2372
|
*/
|
|
2074
2373
|
`;
|
|
2075
2374
|
const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
|
|
@@ -2078,6 +2377,35 @@ ${propsCode}
|
|
|
2078
2377
|
}
|
|
2079
2378
|
}
|
|
2080
2379
|
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Generate a PascalCase method name from HTTP method and path
|
|
2382
|
+
* Used as fallback when operationId is not available
|
|
2383
|
+
* @internal
|
|
2384
|
+
*/
|
|
2385
|
+
generateMethodNameFromPath(method, path) {
|
|
2386
|
+
const segments = path.split("/").filter(Boolean).map((segment) => {
|
|
2387
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
2388
|
+
const paramName = segment.slice(1, -1);
|
|
2389
|
+
return `By${this.capitalizeSegment(paramName)}`;
|
|
2390
|
+
}
|
|
2391
|
+
return this.capitalizeSegment(segment);
|
|
2392
|
+
}).join("");
|
|
2393
|
+
const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
|
2394
|
+
return `${capitalizedMethod}${segments}`;
|
|
2395
|
+
}
|
|
2396
|
+
/**
|
|
2397
|
+
* Capitalizes a path segment, handling special characters like dashes, underscores, and dots
|
|
2398
|
+
* @internal
|
|
2399
|
+
*/
|
|
2400
|
+
capitalizeSegment(str) {
|
|
2401
|
+
if (str.includes("-") || str.includes("_") || str.includes(".")) {
|
|
2402
|
+
return str.split(/[-_.]/).map((part) => {
|
|
2403
|
+
if (!part) return "";
|
|
2404
|
+
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
2405
|
+
}).join("");
|
|
2406
|
+
}
|
|
2407
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2408
|
+
}
|
|
2081
2409
|
/**
|
|
2082
2410
|
* Check if a header should be ignored based on filter patterns
|
|
2083
2411
|
* @internal
|
|
@@ -2114,16 +2442,20 @@ ${propsCode}
|
|
|
2114
2442
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
2115
2443
|
continue;
|
|
2116
2444
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
}
|
|
2120
|
-
const headerParams = operation.parameters.filter(
|
|
2445
|
+
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
2446
|
+
const headerParams = allParams.filter(
|
|
2121
2447
|
(param) => param && typeof param === "object" && param.in === "header" && !this.shouldIgnoreHeader(param.name)
|
|
2122
2448
|
);
|
|
2123
2449
|
if (headerParams.length === 0) {
|
|
2124
2450
|
continue;
|
|
2125
2451
|
}
|
|
2126
|
-
|
|
2452
|
+
let pascalOperationId;
|
|
2453
|
+
if (operation.operationId) {
|
|
2454
|
+
pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
|
|
2455
|
+
} else {
|
|
2456
|
+
const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
|
|
2457
|
+
pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
|
|
2458
|
+
}
|
|
2127
2459
|
const schemaName = `${pascalOperationId}HeaderParams`;
|
|
2128
2460
|
if (!this.schemaDependencies.has(schemaName)) {
|
|
2129
2461
|
this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
|
|
@@ -2160,8 +2492,9 @@ ${propsCode}
|
|
|
2160
2492
|
const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
|
|
2161
2493
|
const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
|
|
2162
2494
|
const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
|
|
2495
|
+
const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
|
|
2163
2496
|
const jsdoc = `/**
|
|
2164
|
-
* Header parameters for ${
|
|
2497
|
+
* Header parameters for ${jsdocOperationName}
|
|
2165
2498
|
*/
|
|
2166
2499
|
`;
|
|
2167
2500
|
const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
|
|
@@ -2200,13 +2533,23 @@ ${propsCode}
|
|
|
2200
2533
|
}
|
|
2201
2534
|
const type = schema.type;
|
|
2202
2535
|
if (type === "string") {
|
|
2536
|
+
const formatMap = {
|
|
2537
|
+
email: "z.email()",
|
|
2538
|
+
uri: "z.url()",
|
|
2539
|
+
url: "z.url()",
|
|
2540
|
+
uuid: "z.uuid()"
|
|
2541
|
+
};
|
|
2542
|
+
if (schema.format && formatMap[schema.format]) {
|
|
2543
|
+
let zodType2 = formatMap[schema.format];
|
|
2544
|
+
if (schema.minLength !== void 0) zodType2 = `${zodType2}.min(${schema.minLength})`;
|
|
2545
|
+
if (schema.maxLength !== void 0) zodType2 = `${zodType2}.max(${schema.maxLength})`;
|
|
2546
|
+
if (schema.pattern) zodType2 = `${zodType2}.regex(/${schema.pattern}/)`;
|
|
2547
|
+
return zodType2;
|
|
2548
|
+
}
|
|
2203
2549
|
let zodType = "z.string()";
|
|
2204
2550
|
if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
|
|
2205
2551
|
if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
|
|
2206
2552
|
if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
|
|
2207
|
-
if (schema.format === "email") zodType = `${zodType}.email()`;
|
|
2208
|
-
if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
|
|
2209
|
-
if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
|
|
2210
2553
|
return zodType;
|
|
2211
2554
|
}
|
|
2212
2555
|
if (type === "number" || type === "integer") {
|
|
@@ -2231,11 +2574,6 @@ ${propsCode}
|
|
|
2231
2574
|
}
|
|
2232
2575
|
return "z.unknown()";
|
|
2233
2576
|
}
|
|
2234
|
-
// REMOVED: generateNativeEnum method - no longer needed as we only generate Zod schemas
|
|
2235
|
-
// REMOVED: toEnumKey method - was only used by generateNativeEnum
|
|
2236
|
-
// REMOVED: addConstraintsToJSDoc method - was only used for native TypeScript types
|
|
2237
|
-
// REMOVED: generateNativeTypeDefinition method - was only used for native TypeScript types
|
|
2238
|
-
// REMOVED: generateObjectType method - was only used for native TypeScript types
|
|
2239
2577
|
/**
|
|
2240
2578
|
* Topological sort for schema dependencies
|
|
2241
2579
|
* Returns schemas in the order they should be declared
|