@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.js
CHANGED
|
@@ -190,14 +190,17 @@ function escapeJSDoc(str) {
|
|
|
190
190
|
function wrapNullable(validation, isNullable2) {
|
|
191
191
|
return isNullable2 ? `${validation}.nullable()` : validation;
|
|
192
192
|
}
|
|
193
|
-
function isNullable(schema) {
|
|
193
|
+
function isNullable(schema, defaultNullable = false) {
|
|
194
194
|
if (schema.nullable === true) {
|
|
195
195
|
return true;
|
|
196
196
|
}
|
|
197
|
+
if (schema.nullable === false) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
197
200
|
if (Array.isArray(schema.type)) {
|
|
198
201
|
return schema.type.includes("null");
|
|
199
202
|
}
|
|
200
|
-
return
|
|
203
|
+
return defaultNullable;
|
|
201
204
|
}
|
|
202
205
|
function getPrimaryType(schema) {
|
|
203
206
|
if (Array.isArray(schema.type)) {
|
|
@@ -363,6 +366,22 @@ function stripPrefix(input, pattern, ensureLeadingChar) {
|
|
|
363
366
|
}
|
|
364
367
|
return input;
|
|
365
368
|
}
|
|
369
|
+
function stripPathPrefix(path, pattern) {
|
|
370
|
+
if (!pattern) {
|
|
371
|
+
return path;
|
|
372
|
+
}
|
|
373
|
+
if (!isGlobPattern(pattern)) {
|
|
374
|
+
let normalizedPattern = pattern.trim();
|
|
375
|
+
if (!normalizedPattern.startsWith("/")) {
|
|
376
|
+
normalizedPattern = `/${normalizedPattern}`;
|
|
377
|
+
}
|
|
378
|
+
if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
|
|
379
|
+
normalizedPattern = normalizedPattern.slice(0, -1);
|
|
380
|
+
}
|
|
381
|
+
return stripPrefix(path, normalizedPattern, "/");
|
|
382
|
+
}
|
|
383
|
+
return stripPrefix(path, pattern, "/");
|
|
384
|
+
}
|
|
366
385
|
|
|
367
386
|
// src/validators/array-validator.ts
|
|
368
387
|
function generateArrayValidation(schema, context) {
|
|
@@ -411,12 +430,56 @@ function generateArrayValidation(schema, context) {
|
|
|
411
430
|
}
|
|
412
431
|
|
|
413
432
|
// src/validators/composition-validator.ts
|
|
433
|
+
function isDiscriminatorRequired(schemas, discriminator, context) {
|
|
434
|
+
const invalidSchemas = [];
|
|
435
|
+
for (const schema of schemas) {
|
|
436
|
+
const resolved = resolveSchema(schema, context);
|
|
437
|
+
const required = resolved.required || [];
|
|
438
|
+
if (!required.includes(discriminator)) {
|
|
439
|
+
const schemaName = schema.$ref ? schema.$ref.split("/").pop() || "inline" : "inline";
|
|
440
|
+
invalidSchemas.push(schemaName);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
valid: invalidSchemas.length === 0,
|
|
445
|
+
invalidSchemas
|
|
446
|
+
};
|
|
447
|
+
}
|
|
414
448
|
function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
|
|
449
|
+
if (schemas.length === 0) {
|
|
450
|
+
console.warn(
|
|
451
|
+
"[openapi-to-zod] Warning: Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
|
|
452
|
+
);
|
|
453
|
+
return wrapNullable(
|
|
454
|
+
'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
|
|
455
|
+
isNullable2
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (schemas.length === 1) {
|
|
459
|
+
let singleSchema = context.generatePropertySchema(schemas[0], currentSchema);
|
|
460
|
+
if ((options == null ? void 0 : options.passthrough) && !singleSchema.includes(".catchall(")) {
|
|
461
|
+
singleSchema = `${singleSchema}.catchall(z.unknown())`;
|
|
462
|
+
}
|
|
463
|
+
return wrapNullable(singleSchema, isNullable2);
|
|
464
|
+
}
|
|
415
465
|
if (discriminator) {
|
|
416
466
|
let resolvedSchemas = schemas;
|
|
417
467
|
if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
|
|
418
468
|
resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
|
|
419
469
|
}
|
|
470
|
+
const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
|
|
471
|
+
if (!discriminatorCheck.valid) {
|
|
472
|
+
console.warn(
|
|
473
|
+
`[openapi-to-zod] Warning: Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
|
|
474
|
+
);
|
|
475
|
+
let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
476
|
+
if (options == null ? void 0 : options.passthrough) {
|
|
477
|
+
schemaStrings3 = schemaStrings3.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
478
|
+
}
|
|
479
|
+
const fallbackDescription = `Discriminator "${discriminator}" is optional in some schemas (${discriminatorCheck.invalidSchemas.join(", ")}), using z.union() instead of z.discriminatedUnion()`;
|
|
480
|
+
const union3 = `z.union([${schemaStrings3.join(", ")}]).describe("${fallbackDescription}")`;
|
|
481
|
+
return wrapNullable(union3, isNullable2);
|
|
482
|
+
}
|
|
420
483
|
let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
421
484
|
if (options == null ? void 0 : options.passthrough) {
|
|
422
485
|
schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
@@ -431,25 +494,102 @@ function generateUnion(schemas, discriminator, isNullable2, context, options, cu
|
|
|
431
494
|
const union = `z.union([${schemaStrings.join(", ")}])`;
|
|
432
495
|
return wrapNullable(union, isNullable2);
|
|
433
496
|
}
|
|
497
|
+
function resolveSchema(schema, context) {
|
|
498
|
+
if (schema.$ref && context.resolveSchemaRef) {
|
|
499
|
+
const resolved = context.resolveSchemaRef(schema.$ref);
|
|
500
|
+
if (resolved) {
|
|
501
|
+
return resolved;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return schema;
|
|
505
|
+
}
|
|
506
|
+
function collectProperties(schema, context) {
|
|
507
|
+
const resolved = resolveSchema(schema, context);
|
|
508
|
+
const props = /* @__PURE__ */ new Map();
|
|
509
|
+
const sourceName = schema.$ref ? schema.$ref.split("/").pop() || "unknown" : "inline";
|
|
510
|
+
if (resolved.properties) {
|
|
511
|
+
for (const [key, value] of Object.entries(resolved.properties)) {
|
|
512
|
+
props.set(key, { schema: value, source: sourceName });
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (resolved.allOf) {
|
|
516
|
+
for (const subSchema of resolved.allOf) {
|
|
517
|
+
const subProps = collectProperties(subSchema, context);
|
|
518
|
+
for (const [key, value] of subProps) {
|
|
519
|
+
if (!props.has(key)) {
|
|
520
|
+
props.set(key, value);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return props;
|
|
526
|
+
}
|
|
527
|
+
function schemasMatch(a, b) {
|
|
528
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
529
|
+
}
|
|
530
|
+
function detectConflictingProperties(schemas, context) {
|
|
531
|
+
const conflicts = [];
|
|
532
|
+
const propertyMap = /* @__PURE__ */ new Map();
|
|
533
|
+
for (const schema of schemas) {
|
|
534
|
+
const schemaProps = collectProperties(schema, context);
|
|
535
|
+
for (const [propName, propInfo] of schemaProps) {
|
|
536
|
+
const existing = propertyMap.get(propName);
|
|
537
|
+
if (existing) {
|
|
538
|
+
if (!schemasMatch(existing.schema, propInfo.schema)) {
|
|
539
|
+
conflicts.push(
|
|
540
|
+
`Property "${propName}" has conflicting definitions in ${existing.source} and ${propInfo.source}`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
propertyMap.set(propName, propInfo);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return conflicts;
|
|
549
|
+
}
|
|
434
550
|
function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
435
551
|
if (schemas.length === 1) {
|
|
436
552
|
const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false);
|
|
437
553
|
return wrapNullable(singleSchema, isNullable2);
|
|
438
554
|
}
|
|
555
|
+
const conflicts = detectConflictingProperties(schemas, context);
|
|
556
|
+
let conflictDescription = "";
|
|
557
|
+
if (conflicts.length > 0) {
|
|
558
|
+
for (const conflict of conflicts) {
|
|
559
|
+
console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
|
|
560
|
+
}
|
|
561
|
+
conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
|
|
562
|
+
}
|
|
439
563
|
const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
|
|
440
|
-
|
|
564
|
+
let result;
|
|
441
565
|
if (allObjects) {
|
|
442
|
-
let
|
|
566
|
+
let merged = context.generatePropertySchema(schemas[0], currentSchema, false);
|
|
567
|
+
for (let i = 1; i < schemas.length; i++) {
|
|
568
|
+
const schema = schemas[i];
|
|
569
|
+
if (schema.$ref) {
|
|
570
|
+
const refSchema = context.generatePropertySchema(schema, currentSchema, false);
|
|
571
|
+
merged = `${merged}.extend(${refSchema}.shape)`;
|
|
572
|
+
} else if (context.generateInlineObjectShape && (schema.properties || schema.type === "object")) {
|
|
573
|
+
const inlineShape = context.generateInlineObjectShape(schema, currentSchema);
|
|
574
|
+
merged = `${merged}.extend(${inlineShape})`;
|
|
575
|
+
} else {
|
|
576
|
+
const schemaString = context.generatePropertySchema(schema, currentSchema, false);
|
|
577
|
+
merged = `${merged}.extend(${schemaString}.shape)`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
result = merged;
|
|
581
|
+
} else {
|
|
582
|
+
const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
|
|
583
|
+
let merged = schemaStrings[0];
|
|
443
584
|
for (let i = 1; i < schemaStrings.length; i++) {
|
|
444
|
-
|
|
585
|
+
merged = `${merged}.and(${schemaStrings[i]})`;
|
|
445
586
|
}
|
|
446
|
-
|
|
587
|
+
result = merged;
|
|
447
588
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
merged = `${merged}.and(${schemaStrings[i]})`;
|
|
589
|
+
if (conflictDescription) {
|
|
590
|
+
result = `${result}.describe("${conflictDescription}")`;
|
|
451
591
|
}
|
|
452
|
-
return wrapNullable(
|
|
592
|
+
return wrapNullable(result, isNullable2);
|
|
453
593
|
}
|
|
454
594
|
|
|
455
595
|
// src/validators/number-validator.ts
|
|
@@ -888,7 +1028,7 @@ function configurePatternCache(size) {
|
|
|
888
1028
|
PATTERN_CACHE = new LRUCache(size);
|
|
889
1029
|
}
|
|
890
1030
|
}
|
|
891
|
-
var
|
|
1031
|
+
var DEFAULT_FORMAT_MAP = {
|
|
892
1032
|
uuid: "z.uuid()",
|
|
893
1033
|
email: "z.email()",
|
|
894
1034
|
uri: "z.url()",
|
|
@@ -898,7 +1038,6 @@ var FORMAT_MAP = {
|
|
|
898
1038
|
byte: "z.base64()",
|
|
899
1039
|
binary: "z.string()",
|
|
900
1040
|
date: "z.iso.date()",
|
|
901
|
-
"date-time": "z.iso.datetime()",
|
|
902
1041
|
time: "z.iso.time()",
|
|
903
1042
|
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" })',
|
|
904
1043
|
ipv4: "z.ipv4()",
|
|
@@ -917,6 +1056,30 @@ var FORMAT_MAP = {
|
|
|
917
1056
|
"json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
|
|
918
1057
|
"relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
|
|
919
1058
|
};
|
|
1059
|
+
var FORMAT_MAP = {
|
|
1060
|
+
...DEFAULT_FORMAT_MAP,
|
|
1061
|
+
"date-time": "z.iso.datetime()"
|
|
1062
|
+
};
|
|
1063
|
+
function configureDateTimeFormat(pattern) {
|
|
1064
|
+
if (!pattern) {
|
|
1065
|
+
FORMAT_MAP["date-time"] = "z.iso.datetime()";
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
1069
|
+
if (patternStr === "") {
|
|
1070
|
+
FORMAT_MAP["date-time"] = "z.iso.datetime()";
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
new RegExp(patternStr);
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
throw new Error(
|
|
1077
|
+
`Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
const escapedPattern = escapePattern(patternStr);
|
|
1081
|
+
FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
|
|
1082
|
+
}
|
|
920
1083
|
function generateStringValidation(schema, useDescribe) {
|
|
921
1084
|
let validation = FORMAT_MAP[schema.format || ""] || "z.string()";
|
|
922
1085
|
if (schema.minLength !== void 0) {
|
|
@@ -1102,6 +1265,15 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1102
1265
|
}
|
|
1103
1266
|
return mappedSchemas;
|
|
1104
1267
|
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Resolve a $ref string to the actual schema
|
|
1270
|
+
*/
|
|
1271
|
+
resolveSchemaRef(ref) {
|
|
1272
|
+
var _a, _b;
|
|
1273
|
+
const schemaName = ref.split("/").pop();
|
|
1274
|
+
if (!schemaName) return void 0;
|
|
1275
|
+
return (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
|
|
1276
|
+
}
|
|
1105
1277
|
/**
|
|
1106
1278
|
* Resolve a schema name through any aliases to get the actual schema name
|
|
1107
1279
|
* If the schema is an alias (allOf with single $ref), return the target name
|
|
@@ -1179,7 +1351,7 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1179
1351
|
let schemaWithCatchall = baseSchema;
|
|
1180
1352
|
if (baseSchema.includes(".union([") || baseSchema.includes(".discriminatedUnion(")) {
|
|
1181
1353
|
schemaWithCatchall = baseSchema;
|
|
1182
|
-
} else if (baseSchema.includes(".
|
|
1354
|
+
} else if (baseSchema.includes(".extend(")) {
|
|
1183
1355
|
schemaWithCatchall = `${baseSchema}.catchall(z.unknown())`;
|
|
1184
1356
|
}
|
|
1185
1357
|
if (schema.unevaluatedProperties === false) {
|
|
@@ -1206,7 +1378,12 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1206
1378
|
if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
|
|
1207
1379
|
schema = this.filterNestedProperties(schema);
|
|
1208
1380
|
}
|
|
1209
|
-
const
|
|
1381
|
+
const isSchemaRef = !!schema.$ref;
|
|
1382
|
+
const isEnum = !!schema.enum;
|
|
1383
|
+
const isConst = schema.const !== void 0;
|
|
1384
|
+
const shouldApplyDefaultNullable = !isTopLevel && !isSchemaRef && !isEnum && !isConst;
|
|
1385
|
+
const effectiveDefaultNullable = shouldApplyDefaultNullable ? this.context.defaultNullable : false;
|
|
1386
|
+
const nullable = isNullable(schema, effectiveDefaultNullable);
|
|
1210
1387
|
if (hasMultipleTypes(schema)) {
|
|
1211
1388
|
const union = this.generateMultiTypeUnion(schema, currentSchema);
|
|
1212
1389
|
return wrapNullable(union, nullable);
|
|
@@ -1258,7 +1435,11 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1258
1435
|
let composition = generateAllOf(
|
|
1259
1436
|
schema.allOf,
|
|
1260
1437
|
nullable,
|
|
1261
|
-
{
|
|
1438
|
+
{
|
|
1439
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1440
|
+
generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
|
|
1441
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1442
|
+
},
|
|
1262
1443
|
currentSchema
|
|
1263
1444
|
);
|
|
1264
1445
|
if (schema.unevaluatedProperties !== void 0) {
|
|
@@ -1274,7 +1455,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1274
1455
|
nullable,
|
|
1275
1456
|
{
|
|
1276
1457
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1277
|
-
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
|
|
1458
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
1459
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1278
1460
|
},
|
|
1279
1461
|
{
|
|
1280
1462
|
passthrough: needsPassthrough,
|
|
@@ -1295,7 +1477,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1295
1477
|
nullable,
|
|
1296
1478
|
{
|
|
1297
1479
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1298
|
-
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
|
|
1480
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
1481
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1299
1482
|
},
|
|
1300
1483
|
{
|
|
1301
1484
|
passthrough: needsPassthrough,
|
|
@@ -1358,7 +1541,17 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1358
1541
|
);
|
|
1359
1542
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1360
1543
|
} else {
|
|
1361
|
-
|
|
1544
|
+
switch (this.context.emptyObjectBehavior) {
|
|
1545
|
+
case "strict":
|
|
1546
|
+
validation = "z.strictObject({})";
|
|
1547
|
+
break;
|
|
1548
|
+
case "loose":
|
|
1549
|
+
validation = "z.looseObject({})";
|
|
1550
|
+
break;
|
|
1551
|
+
default:
|
|
1552
|
+
validation = "z.record(z.string(), z.unknown())";
|
|
1553
|
+
break;
|
|
1554
|
+
}
|
|
1362
1555
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1363
1556
|
}
|
|
1364
1557
|
break;
|
|
@@ -1373,6 +1566,44 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1373
1566
|
}
|
|
1374
1567
|
return result;
|
|
1375
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Generate inline object shape for use with .extend()
|
|
1571
|
+
* Returns just the shape object literal: { prop1: z.string(), prop2: z.number() }
|
|
1572
|
+
*
|
|
1573
|
+
* This method is specifically for allOf compositions where we need to pass
|
|
1574
|
+
* the shape directly to .extend() instead of using z.object({...}).shape.
|
|
1575
|
+
* This avoids the .nullable().shape bug when inline objects have nullable: true.
|
|
1576
|
+
*
|
|
1577
|
+
* According to Zod docs (https://zod.dev/api?id=extend):
|
|
1578
|
+
* - .extend() accepts an object of shape definitions
|
|
1579
|
+
* - e.g., baseSchema.extend({ prop: z.string() })
|
|
1580
|
+
*/
|
|
1581
|
+
generateInlineObjectShape(schema, currentSchema) {
|
|
1582
|
+
const required = new Set(schema.required || []);
|
|
1583
|
+
const properties = [];
|
|
1584
|
+
if (schema.properties) {
|
|
1585
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
1586
|
+
if (!this.shouldIncludeProperty(propSchema)) {
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
const isRequired = required.has(propName);
|
|
1590
|
+
const zodSchema = this.generatePropertySchema(propSchema, currentSchema);
|
|
1591
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
1592
|
+
const quotedPropName = validIdentifier.test(propName) ? propName : `"${propName}"`;
|
|
1593
|
+
let propertyDef = `${quotedPropName}: ${zodSchema}`;
|
|
1594
|
+
if (!isRequired) {
|
|
1595
|
+
propertyDef += ".optional()";
|
|
1596
|
+
}
|
|
1597
|
+
properties.push(propertyDef);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (properties.length === 0) {
|
|
1601
|
+
return "{}";
|
|
1602
|
+
}
|
|
1603
|
+
return `{
|
|
1604
|
+
${properties.map((p) => ` ${p}`).join(",\n")}
|
|
1605
|
+
}`;
|
|
1606
|
+
}
|
|
1376
1607
|
};
|
|
1377
1608
|
// Performance optimization: Lookup table for faster inclusion checks
|
|
1378
1609
|
_PropertyGenerator.INCLUSION_RULES = {
|
|
@@ -1516,6 +1747,59 @@ function formatFilterStatistics(stats) {
|
|
|
1516
1747
|
return lines.join("\n");
|
|
1517
1748
|
}
|
|
1518
1749
|
|
|
1750
|
+
// src/utils/ref-resolver.ts
|
|
1751
|
+
function resolveRef2(obj, spec, maxDepth = 10) {
|
|
1752
|
+
var _a, _b, _c, _d;
|
|
1753
|
+
if (!obj || typeof obj !== "object" || maxDepth <= 0) return obj;
|
|
1754
|
+
if (!obj.$ref) return obj;
|
|
1755
|
+
const ref = obj.$ref;
|
|
1756
|
+
let resolved = null;
|
|
1757
|
+
const paramMatch = ref.match(/^#\/components\/parameters\/(.+)$/);
|
|
1758
|
+
const requestBodyMatch = ref.match(/^#\/components\/requestBodies\/(.+)$/);
|
|
1759
|
+
const responseMatch = ref.match(/^#\/components\/responses\/(.+)$/);
|
|
1760
|
+
const schemaMatch = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
1761
|
+
if (paramMatch && ((_a = spec.components) == null ? void 0 : _a.parameters)) {
|
|
1762
|
+
const name = paramMatch[1];
|
|
1763
|
+
resolved = spec.components.parameters[name];
|
|
1764
|
+
} else if (requestBodyMatch && ((_b = spec.components) == null ? void 0 : _b.requestBodies)) {
|
|
1765
|
+
const name = requestBodyMatch[1];
|
|
1766
|
+
resolved = spec.components.requestBodies[name];
|
|
1767
|
+
} else if (responseMatch && ((_c = spec.components) == null ? void 0 : _c.responses)) {
|
|
1768
|
+
const name = responseMatch[1];
|
|
1769
|
+
resolved = spec.components.responses[name];
|
|
1770
|
+
} else if (schemaMatch && ((_d = spec.components) == null ? void 0 : _d.schemas)) {
|
|
1771
|
+
const name = schemaMatch[1];
|
|
1772
|
+
resolved = spec.components.schemas[name];
|
|
1773
|
+
}
|
|
1774
|
+
if (resolved) {
|
|
1775
|
+
if (resolved.$ref) {
|
|
1776
|
+
return resolveRef2(resolved, spec, maxDepth - 1);
|
|
1777
|
+
}
|
|
1778
|
+
return resolved;
|
|
1779
|
+
}
|
|
1780
|
+
return obj;
|
|
1781
|
+
}
|
|
1782
|
+
function resolveParameterRef(param, spec) {
|
|
1783
|
+
return resolveRef2(param, spec);
|
|
1784
|
+
}
|
|
1785
|
+
function mergeParameters(pathParams, operationParams, spec) {
|
|
1786
|
+
const resolvedPathParams = (pathParams || []).map((p) => resolveParameterRef(p, spec));
|
|
1787
|
+
const resolvedOperationParams = (operationParams || []).map((p) => resolveParameterRef(p, spec));
|
|
1788
|
+
const merged = [...resolvedPathParams];
|
|
1789
|
+
for (const opParam of resolvedOperationParams) {
|
|
1790
|
+
if (!opParam || typeof opParam !== "object") continue;
|
|
1791
|
+
const existingIndex = merged.findIndex(
|
|
1792
|
+
(p) => p && typeof p === "object" && p.name === opParam.name && p.in === opParam.in
|
|
1793
|
+
);
|
|
1794
|
+
if (existingIndex >= 0) {
|
|
1795
|
+
merged[existingIndex] = opParam;
|
|
1796
|
+
} else {
|
|
1797
|
+
merged.push(opParam);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return merged;
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1519
1803
|
// src/openapi-generator.ts
|
|
1520
1804
|
var OpenApiGenerator = class {
|
|
1521
1805
|
constructor(options) {
|
|
@@ -1525,7 +1809,7 @@ var OpenApiGenerator = class {
|
|
|
1525
1809
|
this.schemaUsageMap = /* @__PURE__ */ new Map();
|
|
1526
1810
|
this.needsZodImport = true;
|
|
1527
1811
|
this.filterStats = createFilterStatistics();
|
|
1528
|
-
var _a, _b, _c, _d, _e;
|
|
1812
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1529
1813
|
if (!options.input) {
|
|
1530
1814
|
throw new ConfigurationError("Input path is required", { providedOptions: options });
|
|
1531
1815
|
}
|
|
@@ -1535,21 +1819,28 @@ var OpenApiGenerator = class {
|
|
|
1535
1819
|
output: options.output,
|
|
1536
1820
|
includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
|
|
1537
1821
|
useDescribe: (_b = options.useDescribe) != null ? _b : false,
|
|
1822
|
+
defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
|
|
1823
|
+
emptyObjectBehavior: (_d = options.emptyObjectBehavior) != null ? _d : "loose",
|
|
1538
1824
|
schemaType: options.schemaType || "all",
|
|
1539
1825
|
prefix: options.prefix,
|
|
1540
1826
|
suffix: options.suffix,
|
|
1541
1827
|
stripSchemaPrefix: options.stripSchemaPrefix,
|
|
1542
|
-
|
|
1828
|
+
stripPathPrefix: options.stripPathPrefix,
|
|
1829
|
+
showStats: (_e = options.showStats) != null ? _e : true,
|
|
1543
1830
|
request: options.request,
|
|
1544
1831
|
response: options.response,
|
|
1545
1832
|
operationFilters: options.operationFilters,
|
|
1546
1833
|
ignoreHeaders: options.ignoreHeaders,
|
|
1547
|
-
cacheSize: (
|
|
1548
|
-
batchSize: (
|
|
1834
|
+
cacheSize: (_f = options.cacheSize) != null ? _f : 1e3,
|
|
1835
|
+
batchSize: (_g = options.batchSize) != null ? _g : 10,
|
|
1836
|
+
customDateTimeFormatRegex: options.customDateTimeFormatRegex
|
|
1549
1837
|
};
|
|
1550
1838
|
if (this.options.cacheSize) {
|
|
1551
1839
|
configurePatternCache(this.options.cacheSize);
|
|
1552
1840
|
}
|
|
1841
|
+
if (this.options.customDateTimeFormatRegex) {
|
|
1842
|
+
configureDateTimeFormat(this.options.customDateTimeFormatRegex);
|
|
1843
|
+
}
|
|
1553
1844
|
try {
|
|
1554
1845
|
const fs = require("fs");
|
|
1555
1846
|
if (!fs.existsSync(this.options.input)) {
|
|
@@ -1612,6 +1903,8 @@ var OpenApiGenerator = class {
|
|
|
1612
1903
|
mode: this.requestOptions.mode,
|
|
1613
1904
|
includeDescriptions: this.requestOptions.includeDescriptions,
|
|
1614
1905
|
useDescribe: this.requestOptions.useDescribe,
|
|
1906
|
+
defaultNullable: (_h = this.options.defaultNullable) != null ? _h : false,
|
|
1907
|
+
emptyObjectBehavior: (_i = this.options.emptyObjectBehavior) != null ? _i : "loose",
|
|
1615
1908
|
namingOptions: {
|
|
1616
1909
|
prefix: this.options.prefix,
|
|
1617
1910
|
suffix: this.options.suffix
|
|
@@ -1965,7 +2258,7 @@ var OpenApiGenerator = class {
|
|
|
1965
2258
|
* Generate schema for a component
|
|
1966
2259
|
*/
|
|
1967
2260
|
generateComponentSchema(name, schema) {
|
|
1968
|
-
var _a, _b;
|
|
2261
|
+
var _a, _b, _c, _d;
|
|
1969
2262
|
if (!this.schemaDependencies.has(name)) {
|
|
1970
2263
|
this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
|
|
1971
2264
|
}
|
|
@@ -1997,14 +2290,15 @@ ${typeCode}`;
|
|
|
1997
2290
|
mode: resolvedOptions.mode,
|
|
1998
2291
|
includeDescriptions: resolvedOptions.includeDescriptions,
|
|
1999
2292
|
useDescribe: resolvedOptions.useDescribe,
|
|
2293
|
+
defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
|
|
2294
|
+
emptyObjectBehavior: (_c = this.options.emptyObjectBehavior) != null ? _c : "loose",
|
|
2000
2295
|
namingOptions: {
|
|
2001
2296
|
prefix: this.options.prefix,
|
|
2002
2297
|
suffix: this.options.suffix
|
|
2003
2298
|
},
|
|
2004
2299
|
stripSchemaPrefix: this.options.stripSchemaPrefix
|
|
2005
2300
|
});
|
|
2006
|
-
const
|
|
2007
|
-
const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
|
|
2301
|
+
const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
|
|
2008
2302
|
const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
|
|
2009
2303
|
if (zodSchema.includes("z.discriminatedUnion(")) {
|
|
2010
2304
|
const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
|
|
@@ -2014,7 +2308,7 @@ ${typeCode}`;
|
|
|
2014
2308
|
const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
|
|
2015
2309
|
if (depMatch) {
|
|
2016
2310
|
const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
|
|
2017
|
-
(
|
|
2311
|
+
(_d = this.schemaDependencies.get(name)) == null ? void 0 : _d.add(depName);
|
|
2018
2312
|
}
|
|
2019
2313
|
}
|
|
2020
2314
|
}
|
|
@@ -2038,16 +2332,20 @@ ${typeCode}`;
|
|
|
2038
2332
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
2039
2333
|
continue;
|
|
2040
2334
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
}
|
|
2044
|
-
const queryParams = operation.parameters.filter(
|
|
2335
|
+
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
2336
|
+
const queryParams = allParams.filter(
|
|
2045
2337
|
(param) => param && typeof param === "object" && param.in === "query"
|
|
2046
2338
|
);
|
|
2047
2339
|
if (queryParams.length === 0) {
|
|
2048
2340
|
continue;
|
|
2049
2341
|
}
|
|
2050
|
-
|
|
2342
|
+
let pascalOperationId;
|
|
2343
|
+
if (operation.operationId) {
|
|
2344
|
+
pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
|
|
2345
|
+
} else {
|
|
2346
|
+
const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
|
|
2347
|
+
pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
|
|
2348
|
+
}
|
|
2051
2349
|
const schemaName = `${pascalOperationId}QueryParams`;
|
|
2052
2350
|
if (!this.schemaDependencies.has(schemaName)) {
|
|
2053
2351
|
this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
|
|
@@ -2095,8 +2393,9 @@ ${propsCode}
|
|
|
2095
2393
|
const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
|
|
2096
2394
|
const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
|
|
2097
2395
|
const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
|
|
2396
|
+
const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
|
|
2098
2397
|
const jsdoc = `/**
|
|
2099
|
-
* Query parameters for ${
|
|
2398
|
+
* Query parameters for ${jsdocOperationName}
|
|
2100
2399
|
*/
|
|
2101
2400
|
`;
|
|
2102
2401
|
const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
|
|
@@ -2105,6 +2404,35 @@ ${propsCode}
|
|
|
2105
2404
|
}
|
|
2106
2405
|
}
|
|
2107
2406
|
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Generate a PascalCase method name from HTTP method and path
|
|
2409
|
+
* Used as fallback when operationId is not available
|
|
2410
|
+
* @internal
|
|
2411
|
+
*/
|
|
2412
|
+
generateMethodNameFromPath(method, path) {
|
|
2413
|
+
const segments = path.split("/").filter(Boolean).map((segment) => {
|
|
2414
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
2415
|
+
const paramName = segment.slice(1, -1);
|
|
2416
|
+
return `By${this.capitalizeSegment(paramName)}`;
|
|
2417
|
+
}
|
|
2418
|
+
return this.capitalizeSegment(segment);
|
|
2419
|
+
}).join("");
|
|
2420
|
+
const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
|
2421
|
+
return `${capitalizedMethod}${segments}`;
|
|
2422
|
+
}
|
|
2423
|
+
/**
|
|
2424
|
+
* Capitalizes a path segment, handling special characters like dashes, underscores, and dots
|
|
2425
|
+
* @internal
|
|
2426
|
+
*/
|
|
2427
|
+
capitalizeSegment(str) {
|
|
2428
|
+
if (str.includes("-") || str.includes("_") || str.includes(".")) {
|
|
2429
|
+
return str.split(/[-_.]/).map((part) => {
|
|
2430
|
+
if (!part) return "";
|
|
2431
|
+
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
2432
|
+
}).join("");
|
|
2433
|
+
}
|
|
2434
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2435
|
+
}
|
|
2108
2436
|
/**
|
|
2109
2437
|
* Check if a header should be ignored based on filter patterns
|
|
2110
2438
|
* @internal
|
|
@@ -2141,16 +2469,20 @@ ${propsCode}
|
|
|
2141
2469
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
2142
2470
|
continue;
|
|
2143
2471
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
}
|
|
2147
|
-
const headerParams = operation.parameters.filter(
|
|
2472
|
+
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
2473
|
+
const headerParams = allParams.filter(
|
|
2148
2474
|
(param) => param && typeof param === "object" && param.in === "header" && !this.shouldIgnoreHeader(param.name)
|
|
2149
2475
|
);
|
|
2150
2476
|
if (headerParams.length === 0) {
|
|
2151
2477
|
continue;
|
|
2152
2478
|
}
|
|
2153
|
-
|
|
2479
|
+
let pascalOperationId;
|
|
2480
|
+
if (operation.operationId) {
|
|
2481
|
+
pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
|
|
2482
|
+
} else {
|
|
2483
|
+
const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
|
|
2484
|
+
pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
|
|
2485
|
+
}
|
|
2154
2486
|
const schemaName = `${pascalOperationId}HeaderParams`;
|
|
2155
2487
|
if (!this.schemaDependencies.has(schemaName)) {
|
|
2156
2488
|
this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
|
|
@@ -2187,8 +2519,9 @@ ${propsCode}
|
|
|
2187
2519
|
const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
|
|
2188
2520
|
const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
|
|
2189
2521
|
const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
|
|
2522
|
+
const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
|
|
2190
2523
|
const jsdoc = `/**
|
|
2191
|
-
* Header parameters for ${
|
|
2524
|
+
* Header parameters for ${jsdocOperationName}
|
|
2192
2525
|
*/
|
|
2193
2526
|
`;
|
|
2194
2527
|
const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
|
|
@@ -2227,13 +2560,23 @@ ${propsCode}
|
|
|
2227
2560
|
}
|
|
2228
2561
|
const type = schema.type;
|
|
2229
2562
|
if (type === "string") {
|
|
2563
|
+
const formatMap = {
|
|
2564
|
+
email: "z.email()",
|
|
2565
|
+
uri: "z.url()",
|
|
2566
|
+
url: "z.url()",
|
|
2567
|
+
uuid: "z.uuid()"
|
|
2568
|
+
};
|
|
2569
|
+
if (schema.format && formatMap[schema.format]) {
|
|
2570
|
+
let zodType2 = formatMap[schema.format];
|
|
2571
|
+
if (schema.minLength !== void 0) zodType2 = `${zodType2}.min(${schema.minLength})`;
|
|
2572
|
+
if (schema.maxLength !== void 0) zodType2 = `${zodType2}.max(${schema.maxLength})`;
|
|
2573
|
+
if (schema.pattern) zodType2 = `${zodType2}.regex(/${schema.pattern}/)`;
|
|
2574
|
+
return zodType2;
|
|
2575
|
+
}
|
|
2230
2576
|
let zodType = "z.string()";
|
|
2231
2577
|
if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
|
|
2232
2578
|
if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
|
|
2233
2579
|
if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
|
|
2234
|
-
if (schema.format === "email") zodType = `${zodType}.email()`;
|
|
2235
|
-
if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
|
|
2236
|
-
if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
|
|
2237
2580
|
return zodType;
|
|
2238
2581
|
}
|
|
2239
2582
|
if (type === "number" || type === "integer") {
|
|
@@ -2258,11 +2601,6 @@ ${propsCode}
|
|
|
2258
2601
|
}
|
|
2259
2602
|
return "z.unknown()";
|
|
2260
2603
|
}
|
|
2261
|
-
// REMOVED: generateNativeEnum method - no longer needed as we only generate Zod schemas
|
|
2262
|
-
// REMOVED: toEnumKey method - was only used by generateNativeEnum
|
|
2263
|
-
// REMOVED: addConstraintsToJSDoc method - was only used for native TypeScript types
|
|
2264
|
-
// REMOVED: generateNativeTypeDefinition method - was only used for native TypeScript types
|
|
2265
|
-
// REMOVED: generateObjectType method - was only used for native TypeScript types
|
|
2266
2604
|
/**
|
|
2267
2605
|
* Topological sort for schema dependencies
|
|
2268
2606
|
* Returns schemas in the order they should be declared
|