@cerios/openapi-to-zod 1.3.2 → 1.4.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/cli.js +32 -35
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +34 -37
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +32 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32 -35
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.mts +159 -52
- package/dist/internal.d.ts +159 -52
- package/dist/internal.js +1508 -242
- package/dist/internal.js.map +1 -1
- package/dist/internal.mjs +1506 -240
- package/dist/internal.mjs.map +1 -1
- package/package.json +1 -1
package/dist/internal.js
CHANGED
|
@@ -32,8 +32,9 @@ var internal_exports = {};
|
|
|
32
32
|
__export(internal_exports, {
|
|
33
33
|
LRUCache: () => LRUCache,
|
|
34
34
|
OperationFiltersSchema: () => OperationFiltersSchema,
|
|
35
|
+
PropertyGenerator: () => PropertyGenerator,
|
|
35
36
|
RequestResponseOptionsSchema: () => RequestResponseOptionsSchema,
|
|
36
|
-
|
|
37
|
+
buildDateTimeValidation: () => buildDateTimeValidation,
|
|
37
38
|
createFilterStatistics: () => createFilterStatistics,
|
|
38
39
|
createTypeScriptLoader: () => createTypeScriptLoader,
|
|
39
40
|
escapeJSDoc: () => escapeJSDoc,
|
|
@@ -43,9 +44,8 @@ __export(internal_exports, {
|
|
|
43
44
|
getBatchExitCode: () => getBatchExitCode,
|
|
44
45
|
getResponseParseMethod: () => getResponseParseMethod,
|
|
45
46
|
mergeParameters: () => mergeParameters,
|
|
46
|
-
resetFormatMap: () => resetFormatMap,
|
|
47
47
|
resolveParameterRef: () => resolveParameterRef,
|
|
48
|
-
resolveRef: () =>
|
|
48
|
+
resolveRef: () => resolveRef2,
|
|
49
49
|
resolveRequestBodyRef: () => resolveRequestBodyRef,
|
|
50
50
|
resolveResponseRef: () => resolveResponseRef,
|
|
51
51
|
shouldIncludeOperation: () => shouldIncludeOperation,
|
|
@@ -187,6 +187,1502 @@ function getBatchExitCode(summary) {
|
|
|
187
187
|
return summary.failed > 0 ? 1 : 0;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// src/utils/lru-cache.ts
|
|
191
|
+
var LRUCache = class {
|
|
192
|
+
constructor(maxSize) {
|
|
193
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
194
|
+
this.maxSize = maxSize;
|
|
195
|
+
}
|
|
196
|
+
get capacity() {
|
|
197
|
+
return this.maxSize;
|
|
198
|
+
}
|
|
199
|
+
get(key) {
|
|
200
|
+
if (!this.cache.has(key)) return void 0;
|
|
201
|
+
const value = this.cache.get(key);
|
|
202
|
+
if (value === void 0) return void 0;
|
|
203
|
+
this.cache.delete(key);
|
|
204
|
+
this.cache.set(key, value);
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
set(key, value) {
|
|
208
|
+
if (this.cache.has(key)) {
|
|
209
|
+
this.cache.delete(key);
|
|
210
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
211
|
+
const firstKey = this.cache.keys().next().value;
|
|
212
|
+
if (firstKey !== void 0) {
|
|
213
|
+
this.cache.delete(firstKey);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.cache.set(key, value);
|
|
217
|
+
}
|
|
218
|
+
has(key) {
|
|
219
|
+
return this.cache.has(key);
|
|
220
|
+
}
|
|
221
|
+
clear() {
|
|
222
|
+
this.cache.clear();
|
|
223
|
+
}
|
|
224
|
+
size() {
|
|
225
|
+
return this.cache.size;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/utils/name-utils.ts
|
|
230
|
+
function sanitizeIdentifier(str) {
|
|
231
|
+
return str.replace(/[^a-zA-Z0-9._\-\s]+/g, "_");
|
|
232
|
+
}
|
|
233
|
+
function toCamelCase(str, options) {
|
|
234
|
+
const sanitized = sanitizeIdentifier(str);
|
|
235
|
+
const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
|
|
236
|
+
let name;
|
|
237
|
+
if (words.length === 0) {
|
|
238
|
+
name = str.charAt(0).toLowerCase() + str.slice(1);
|
|
239
|
+
} else if (words.length === 1) {
|
|
240
|
+
name = words[0].charAt(0).toLowerCase() + words[0].slice(1);
|
|
241
|
+
} else {
|
|
242
|
+
name = words[0].charAt(0).toLowerCase() + words[0].slice(1) + words.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
243
|
+
}
|
|
244
|
+
if (options == null ? void 0 : options.prefix) {
|
|
245
|
+
const prefix = options.prefix.charAt(0).toLowerCase() + options.prefix.slice(1);
|
|
246
|
+
name = prefix + name.charAt(0).toUpperCase() + name.slice(1);
|
|
247
|
+
}
|
|
248
|
+
if (options == null ? void 0 : options.suffix) {
|
|
249
|
+
const suffix = options.suffix.charAt(0).toUpperCase() + options.suffix.slice(1);
|
|
250
|
+
name = name + suffix;
|
|
251
|
+
}
|
|
252
|
+
return name;
|
|
253
|
+
}
|
|
254
|
+
function toPascalCase(str) {
|
|
255
|
+
const stringValue = String(str);
|
|
256
|
+
const isAlreadyValidCase = /^[a-zA-Z][a-zA-Z0-9]*$/.test(stringValue);
|
|
257
|
+
if (isAlreadyValidCase) {
|
|
258
|
+
return stringValue.charAt(0).toUpperCase() + stringValue.slice(1);
|
|
259
|
+
}
|
|
260
|
+
const sanitized = sanitizeIdentifier(stringValue);
|
|
261
|
+
const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
|
|
262
|
+
let result;
|
|
263
|
+
if (words.length === 0) {
|
|
264
|
+
result = "Value";
|
|
265
|
+
} else {
|
|
266
|
+
result = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
267
|
+
}
|
|
268
|
+
if (/^\d/.test(result)) {
|
|
269
|
+
result = `N${result}`;
|
|
270
|
+
}
|
|
271
|
+
if (!result || /^_+$/.test(result)) {
|
|
272
|
+
return "Value";
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
function resolveRef(ref) {
|
|
277
|
+
const parts = ref.split("/");
|
|
278
|
+
return parts[parts.length - 1];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/utils/pattern-utils.ts
|
|
282
|
+
var import_minimatch = require("minimatch");
|
|
283
|
+
function isValidGlobPattern(pattern) {
|
|
284
|
+
try {
|
|
285
|
+
new import_minimatch.minimatch.Minimatch(pattern);
|
|
286
|
+
return true;
|
|
287
|
+
} catch {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function isGlobPattern(pattern) {
|
|
292
|
+
return /[*?[\]{}!]/.test(pattern);
|
|
293
|
+
}
|
|
294
|
+
function stripPrefix(input, pattern, ensureLeadingChar) {
|
|
295
|
+
if (!pattern) {
|
|
296
|
+
return input;
|
|
297
|
+
}
|
|
298
|
+
if (isGlobPattern(pattern) && !isValidGlobPattern(pattern)) {
|
|
299
|
+
console.warn(`\u26A0\uFE0F Invalid glob pattern "${pattern}": Pattern is malformed`);
|
|
300
|
+
return input;
|
|
301
|
+
}
|
|
302
|
+
if (isGlobPattern(pattern)) {
|
|
303
|
+
let longestMatch = -1;
|
|
304
|
+
for (let i = 1; i <= input.length; i++) {
|
|
305
|
+
const testPrefix = input.substring(0, i);
|
|
306
|
+
if ((0, import_minimatch.minimatch)(testPrefix, pattern)) {
|
|
307
|
+
longestMatch = i;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (longestMatch > 0) {
|
|
311
|
+
const stripped = input.substring(longestMatch);
|
|
312
|
+
if (ensureLeadingChar) {
|
|
313
|
+
if (stripped === "") {
|
|
314
|
+
return ensureLeadingChar;
|
|
315
|
+
}
|
|
316
|
+
if (!stripped.startsWith(ensureLeadingChar)) {
|
|
317
|
+
return `${ensureLeadingChar}${stripped}`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return stripped === "" && !ensureLeadingChar ? input : stripped;
|
|
321
|
+
}
|
|
322
|
+
return input;
|
|
323
|
+
}
|
|
324
|
+
if (input.startsWith(pattern)) {
|
|
325
|
+
const stripped = input.substring(pattern.length);
|
|
326
|
+
if (ensureLeadingChar) {
|
|
327
|
+
if (stripped === "") {
|
|
328
|
+
return ensureLeadingChar;
|
|
329
|
+
}
|
|
330
|
+
if (!stripped.startsWith(ensureLeadingChar)) {
|
|
331
|
+
return `${ensureLeadingChar}${stripped}`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return stripped;
|
|
335
|
+
}
|
|
336
|
+
return input;
|
|
337
|
+
}
|
|
338
|
+
function stripPathPrefix(path, pattern) {
|
|
339
|
+
if (!pattern) {
|
|
340
|
+
return path;
|
|
341
|
+
}
|
|
342
|
+
if (!isGlobPattern(pattern)) {
|
|
343
|
+
let normalizedPattern = pattern.trim();
|
|
344
|
+
if (!normalizedPattern.startsWith("/")) {
|
|
345
|
+
normalizedPattern = `/${normalizedPattern}`;
|
|
346
|
+
}
|
|
347
|
+
if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
|
|
348
|
+
normalizedPattern = normalizedPattern.slice(0, -1);
|
|
349
|
+
}
|
|
350
|
+
return stripPrefix(path, normalizedPattern, "/");
|
|
351
|
+
}
|
|
352
|
+
return stripPrefix(path, pattern, "/");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/utils/string-utils.ts
|
|
356
|
+
function escapeDescription(str) {
|
|
357
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
358
|
+
}
|
|
359
|
+
function escapePattern(str) {
|
|
360
|
+
return str.replace(/(?<!\\)\//g, "\\/");
|
|
361
|
+
}
|
|
362
|
+
function escapeJSDoc(str) {
|
|
363
|
+
return str.replace(/\*\//g, "*\\/");
|
|
364
|
+
}
|
|
365
|
+
function wrapNullable(validation, isNullable2) {
|
|
366
|
+
return isNullable2 ? `${validation}.nullable()` : validation;
|
|
367
|
+
}
|
|
368
|
+
function isNullable(schema, defaultNullable = false) {
|
|
369
|
+
if (schema.nullable === true) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (schema.nullable === false) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
if (Array.isArray(schema.type)) {
|
|
376
|
+
return schema.type.includes("null");
|
|
377
|
+
}
|
|
378
|
+
return defaultNullable;
|
|
379
|
+
}
|
|
380
|
+
function getPrimaryType(schema) {
|
|
381
|
+
if (Array.isArray(schema.type)) {
|
|
382
|
+
const nonNullType = schema.type.find((t) => t !== "null");
|
|
383
|
+
return nonNullType;
|
|
384
|
+
}
|
|
385
|
+
return schema.type;
|
|
386
|
+
}
|
|
387
|
+
function hasMultipleTypes(schema) {
|
|
388
|
+
if (Array.isArray(schema.type)) {
|
|
389
|
+
const nonNullTypes = schema.type.filter((t) => t !== "null");
|
|
390
|
+
return nonNullTypes.length > 1;
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
function addDescription(validation, description, useDescribe) {
|
|
395
|
+
if (!description || !useDescribe) return validation;
|
|
396
|
+
const escapedDesc = escapeDescription(description);
|
|
397
|
+
return `${validation}.describe("${escapedDesc}")`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/validators/array-validator.ts
|
|
401
|
+
function generateArrayValidation(schema, context) {
|
|
402
|
+
var _a;
|
|
403
|
+
let validation;
|
|
404
|
+
if (schema.prefixItems && schema.prefixItems.length > 0) {
|
|
405
|
+
const tupleItems = schema.prefixItems.map((item) => context.generatePropertySchema(item, context.currentSchema));
|
|
406
|
+
validation = `z.tuple([${tupleItems.join(", ")}])`;
|
|
407
|
+
if (schema.items) {
|
|
408
|
+
const restSchema = context.generatePropertySchema(schema.items, context.currentSchema);
|
|
409
|
+
validation += `.rest(${restSchema})`;
|
|
410
|
+
} else if (schema.unevaluatedItems && typeof schema.unevaluatedItems === "object") {
|
|
411
|
+
const restSchema = context.generatePropertySchema(schema.unevaluatedItems, context.currentSchema);
|
|
412
|
+
validation += `.rest(${restSchema})`;
|
|
413
|
+
}
|
|
414
|
+
} else if (schema.items) {
|
|
415
|
+
const itemSchema = context.generatePropertySchema(schema.items, context.currentSchema);
|
|
416
|
+
validation = `z.array(${itemSchema})`;
|
|
417
|
+
if (schema.minItems !== void 0) {
|
|
418
|
+
validation += `.min(${schema.minItems})`;
|
|
419
|
+
}
|
|
420
|
+
if (schema.maxItems !== void 0) {
|
|
421
|
+
validation += `.max(${schema.maxItems})`;
|
|
422
|
+
}
|
|
423
|
+
if (schema.uniqueItems === true) {
|
|
424
|
+
validation += `.refine((items) => new Set(items).size === items.length, { message: "Array items must be unique" })`;
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
validation = "z.array(z.unknown())";
|
|
428
|
+
}
|
|
429
|
+
if (schema.contains) {
|
|
430
|
+
const containsSchema = context.generatePropertySchema(schema.contains, context.currentSchema);
|
|
431
|
+
const minCount = (_a = schema.minContains) != null ? _a : 1;
|
|
432
|
+
const maxCount = schema.maxContains;
|
|
433
|
+
if (maxCount !== void 0) {
|
|
434
|
+
validation += `.refine((arr) => { const matches = arr.filter(item => ${containsSchema}.safeParse(item).success); return matches.length >= ${minCount} && matches.length <= ${maxCount}; }, { message: "Array must contain between ${minCount} and ${maxCount} items matching the schema" })`;
|
|
435
|
+
} else {
|
|
436
|
+
validation += `.refine((arr) => arr.filter(item => ${containsSchema}.safeParse(item).success).length >= ${minCount}, { message: "Array must contain at least ${minCount} item(s) matching the schema" })`;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (schema.unevaluatedItems === false && schema.prefixItems && schema.prefixItems.length > 0 && !schema.items) {
|
|
440
|
+
const prefixCount = schema.prefixItems.length;
|
|
441
|
+
validation += `.refine((arr) => arr.length <= ${prefixCount}, { message: "No unevaluated items allowed beyond prefix items" })`;
|
|
442
|
+
}
|
|
443
|
+
return addDescription(validation, schema.description, context.useDescribe);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/validators/composition-validator.ts
|
|
447
|
+
function isDiscriminatorRequired(schemas, discriminator, context) {
|
|
448
|
+
const invalidSchemas = [];
|
|
449
|
+
for (const schema of schemas) {
|
|
450
|
+
const resolved = resolveSchema(schema, context);
|
|
451
|
+
const required = resolved.required || [];
|
|
452
|
+
if (!required.includes(discriminator)) {
|
|
453
|
+
const schemaName = schema.$ref ? schema.$ref.split("/").pop() || "inline" : "inline";
|
|
454
|
+
invalidSchemas.push(schemaName);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
valid: invalidSchemas.length === 0,
|
|
459
|
+
invalidSchemas
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
|
|
463
|
+
if (schemas.length === 0) {
|
|
464
|
+
console.warn(
|
|
465
|
+
"[openapi-to-zod] Warning: Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
|
|
466
|
+
);
|
|
467
|
+
return wrapNullable(
|
|
468
|
+
'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
|
|
469
|
+
isNullable2
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
if (schemas.length === 1) {
|
|
473
|
+
let singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false, true);
|
|
474
|
+
if ((options == null ? void 0 : options.passthrough) && !singleSchema.includes(".catchall(")) {
|
|
475
|
+
singleSchema = `${singleSchema}.catchall(z.unknown())`;
|
|
476
|
+
}
|
|
477
|
+
return wrapNullable(singleSchema, isNullable2);
|
|
478
|
+
}
|
|
479
|
+
if (discriminator) {
|
|
480
|
+
let resolvedSchemas = schemas;
|
|
481
|
+
if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
|
|
482
|
+
resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
|
|
483
|
+
}
|
|
484
|
+
const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
|
|
485
|
+
if (!discriminatorCheck.valid) {
|
|
486
|
+
console.warn(
|
|
487
|
+
`[openapi-to-zod] Warning: Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
|
|
488
|
+
);
|
|
489
|
+
let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema, false, true));
|
|
490
|
+
if (options == null ? void 0 : options.passthrough) {
|
|
491
|
+
schemaStrings3 = schemaStrings3.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
492
|
+
}
|
|
493
|
+
const fallbackDescription = `Discriminator "${discriminator}" is optional in some schemas (${discriminatorCheck.invalidSchemas.join(", ")}), using z.union() instead of z.discriminatedUnion()`;
|
|
494
|
+
const union3 = `z.union([${schemaStrings3.join(", ")}]).describe("${fallbackDescription}")`;
|
|
495
|
+
return wrapNullable(union3, isNullable2);
|
|
496
|
+
}
|
|
497
|
+
let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema, false, true));
|
|
498
|
+
if (options == null ? void 0 : options.passthrough) {
|
|
499
|
+
schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
500
|
+
}
|
|
501
|
+
const union2 = `z.discriminatedUnion("${discriminator}", [${schemaStrings2.join(", ")}])`;
|
|
502
|
+
return wrapNullable(union2, isNullable2);
|
|
503
|
+
}
|
|
504
|
+
let schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false, true));
|
|
505
|
+
if (options == null ? void 0 : options.passthrough) {
|
|
506
|
+
schemaStrings = schemaStrings.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
507
|
+
}
|
|
508
|
+
const union = `z.union([${schemaStrings.join(", ")}])`;
|
|
509
|
+
return wrapNullable(union, isNullable2);
|
|
510
|
+
}
|
|
511
|
+
function resolveSchema(schema, context) {
|
|
512
|
+
if (schema.$ref && context.resolveSchemaRef) {
|
|
513
|
+
const resolved = context.resolveSchemaRef(schema.$ref);
|
|
514
|
+
if (resolved) {
|
|
515
|
+
return resolved;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return schema;
|
|
519
|
+
}
|
|
520
|
+
function collectProperties(schema, context) {
|
|
521
|
+
const resolved = resolveSchema(schema, context);
|
|
522
|
+
const props = /* @__PURE__ */ new Map();
|
|
523
|
+
const sourceName = schema.$ref ? schema.$ref.split("/").pop() || "unknown" : "inline";
|
|
524
|
+
if (resolved.properties) {
|
|
525
|
+
for (const [key, value] of Object.entries(resolved.properties)) {
|
|
526
|
+
props.set(key, { schema: value, source: sourceName });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (resolved.allOf) {
|
|
530
|
+
for (const subSchema of resolved.allOf) {
|
|
531
|
+
const subProps = collectProperties(subSchema, context);
|
|
532
|
+
for (const [key, value] of subProps) {
|
|
533
|
+
if (!props.has(key)) {
|
|
534
|
+
props.set(key, value);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return props;
|
|
540
|
+
}
|
|
541
|
+
function schemasMatch(a, b) {
|
|
542
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
543
|
+
}
|
|
544
|
+
function detectConflictingProperties(schemas, context) {
|
|
545
|
+
const conflicts = [];
|
|
546
|
+
const propertyMap = /* @__PURE__ */ new Map();
|
|
547
|
+
for (const schema of schemas) {
|
|
548
|
+
const schemaProps = collectProperties(schema, context);
|
|
549
|
+
for (const [propName, propInfo] of schemaProps) {
|
|
550
|
+
const existing = propertyMap.get(propName);
|
|
551
|
+
if (existing) {
|
|
552
|
+
if (!schemasMatch(existing.schema, propInfo.schema)) {
|
|
553
|
+
conflicts.push(
|
|
554
|
+
`Property "${propName}" has conflicting definitions in ${existing.source} and ${propInfo.source}`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
propertyMap.set(propName, propInfo);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return conflicts;
|
|
563
|
+
}
|
|
564
|
+
function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
565
|
+
if (schemas.length === 1) {
|
|
566
|
+
const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false, true);
|
|
567
|
+
return wrapNullable(singleSchema, isNullable2);
|
|
568
|
+
}
|
|
569
|
+
const conflicts = detectConflictingProperties(schemas, context);
|
|
570
|
+
let conflictDescription = "";
|
|
571
|
+
if (conflicts.length > 0) {
|
|
572
|
+
for (const conflict of conflicts) {
|
|
573
|
+
console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
|
|
574
|
+
}
|
|
575
|
+
conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
|
|
576
|
+
}
|
|
577
|
+
const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
|
|
578
|
+
let result;
|
|
579
|
+
if (allObjects) {
|
|
580
|
+
let merged = context.generatePropertySchema(schemas[0], currentSchema, false, true);
|
|
581
|
+
for (let i = 1; i < schemas.length; i++) {
|
|
582
|
+
const schema = schemas[i];
|
|
583
|
+
if (schema.$ref) {
|
|
584
|
+
const refSchema = context.generatePropertySchema(schema, currentSchema, false, true);
|
|
585
|
+
merged = `${merged}.extend(${refSchema}.shape)`;
|
|
586
|
+
} else if (context.generateInlineObjectShape && (schema.properties || schema.type === "object")) {
|
|
587
|
+
const inlineShape = context.generateInlineObjectShape(schema, currentSchema);
|
|
588
|
+
merged = `${merged}.extend(${inlineShape})`;
|
|
589
|
+
} else {
|
|
590
|
+
const schemaString = context.generatePropertySchema(schema, currentSchema, false, true);
|
|
591
|
+
merged = `${merged}.extend(${schemaString}.shape)`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
result = merged;
|
|
595
|
+
} else {
|
|
596
|
+
const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false, true));
|
|
597
|
+
let merged = schemaStrings[0];
|
|
598
|
+
for (let i = 1; i < schemaStrings.length; i++) {
|
|
599
|
+
merged = `${merged}.and(${schemaStrings[i]})`;
|
|
600
|
+
}
|
|
601
|
+
result = merged;
|
|
602
|
+
}
|
|
603
|
+
if (conflictDescription) {
|
|
604
|
+
result = `${result}.describe("${conflictDescription}")`;
|
|
605
|
+
}
|
|
606
|
+
return wrapNullable(result, isNullable2);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/validators/number-validator.ts
|
|
610
|
+
function generateNumberValidation(schema, isInt, useDescribe) {
|
|
611
|
+
let validation = isInt ? "z.number().int()" : "z.number()";
|
|
612
|
+
if (schema.minimum !== void 0) {
|
|
613
|
+
const isExclusive = schema.exclusiveMinimum === true;
|
|
614
|
+
validation += isExclusive ? `.gt(${schema.minimum})` : `.gte(${schema.minimum})`;
|
|
615
|
+
} else if (typeof schema.exclusiveMinimum === "number") {
|
|
616
|
+
validation += `.gt(${schema.exclusiveMinimum})`;
|
|
617
|
+
}
|
|
618
|
+
if (schema.maximum !== void 0) {
|
|
619
|
+
const isExclusive = schema.exclusiveMaximum === true;
|
|
620
|
+
validation += isExclusive ? `.lt(${schema.maximum})` : `.lte(${schema.maximum})`;
|
|
621
|
+
} else if (typeof schema.exclusiveMaximum === "number") {
|
|
622
|
+
validation += `.lt(${schema.exclusiveMaximum})`;
|
|
623
|
+
}
|
|
624
|
+
if (schema.multipleOf !== void 0) {
|
|
625
|
+
validation += `.multipleOf(${schema.multipleOf})`;
|
|
626
|
+
}
|
|
627
|
+
return addDescription(validation, schema.description, useDescribe);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/generators/jsdoc-generator.ts
|
|
631
|
+
function generateJSDoc(schema, name, options = { includeDescriptions: true }) {
|
|
632
|
+
if (!schema || typeof schema !== "object") {
|
|
633
|
+
return "";
|
|
634
|
+
}
|
|
635
|
+
if (!options.includeDescriptions) {
|
|
636
|
+
if (schema.deprecated) {
|
|
637
|
+
return "/** @deprecated */\n";
|
|
638
|
+
}
|
|
639
|
+
return "";
|
|
640
|
+
}
|
|
641
|
+
if (!schema.description && !schema.title && !schema.deprecated && !schema.examples && schema.example === void 0) {
|
|
642
|
+
return "";
|
|
643
|
+
}
|
|
644
|
+
const parts = [];
|
|
645
|
+
if (schema.title && typeof schema.title === "string" && (!name || schema.title !== name)) {
|
|
646
|
+
const sanitizedTitle = escapeJSDoc(schema.title).replace(/@/g, "\\@");
|
|
647
|
+
parts.push(sanitizedTitle);
|
|
648
|
+
}
|
|
649
|
+
if (schema.description && typeof schema.description === "string") {
|
|
650
|
+
const sanitizedDesc = escapeJSDoc(schema.description).replace(/@/g, "\\@").replace(/\*\//g, "*\\/");
|
|
651
|
+
parts.push(sanitizedDesc);
|
|
652
|
+
}
|
|
653
|
+
if (schema.examples && Array.isArray(schema.examples) && schema.examples.length > 0) {
|
|
654
|
+
try {
|
|
655
|
+
const examplesStr = schema.examples.map((ex) => JSON.stringify(ex)).join(", ");
|
|
656
|
+
parts.push(`@example ${examplesStr}`);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
console.warn("Warning: Could not serialize schema examples", error);
|
|
659
|
+
}
|
|
660
|
+
} else if (schema.example !== void 0) {
|
|
661
|
+
try {
|
|
662
|
+
parts.push(`@example ${JSON.stringify(schema.example)}`);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
console.warn("Warning: Could not serialize schema example", error);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (schema.deprecated) {
|
|
668
|
+
parts.push("@deprecated");
|
|
669
|
+
}
|
|
670
|
+
if (parts.length === 0) {
|
|
671
|
+
return "";
|
|
672
|
+
}
|
|
673
|
+
const fullComment = parts.join(" ");
|
|
674
|
+
return `/** ${fullComment} */
|
|
675
|
+
`;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/validators/conditional-validator.ts
|
|
679
|
+
function generatePropertyAccess(propName) {
|
|
680
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
681
|
+
return validIdentifier.test(propName) ? `obj.${propName}` : `obj["${propName}"]`;
|
|
682
|
+
}
|
|
683
|
+
function generateDependencies(schema, generatePropertySchema, currentSchema) {
|
|
684
|
+
if (!schema.dependencies) {
|
|
685
|
+
return "";
|
|
686
|
+
}
|
|
687
|
+
let result = "";
|
|
688
|
+
for (const [prop, dependency] of Object.entries(schema.dependencies)) {
|
|
689
|
+
if (Array.isArray(dependency)) {
|
|
690
|
+
if (dependency.length === 0) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const propAccess = generatePropertyAccess(prop);
|
|
694
|
+
const checkLogic = dependency.map((p) => {
|
|
695
|
+
const pAccess = generatePropertyAccess(p);
|
|
696
|
+
return `if (${pAccess} === undefined) missing.push('${p}');`;
|
|
697
|
+
}).join("\n ");
|
|
698
|
+
result += `.superRefine((obj, ctx) => {
|
|
699
|
+
if (${propAccess} === undefined) return;
|
|
700
|
+
const missing: string[] = [];
|
|
701
|
+
${checkLogic}
|
|
702
|
+
if (missing.length > 0) {
|
|
703
|
+
ctx.addIssue({
|
|
704
|
+
code: "custom",
|
|
705
|
+
message: \`When '${prop}' is present, the following properties are required: \${missing.join(', ')}\`,
|
|
706
|
+
path: []
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
})`;
|
|
710
|
+
} else if (generatePropertySchema) {
|
|
711
|
+
const depSchema = { ...dependency, type: dependency.type || "object" };
|
|
712
|
+
const depSchemaValidation = generatePropertySchema(depSchema, currentSchema);
|
|
713
|
+
const propAccess = generatePropertyAccess(prop);
|
|
714
|
+
result += `.superRefine((obj, ctx) => {
|
|
715
|
+
if (${propAccess} === undefined) return;
|
|
716
|
+
const validation = ${depSchemaValidation}.safeParse(obj);
|
|
717
|
+
if (!validation.success) {
|
|
718
|
+
const errors = validation.error.issues.map(i => \` - \${i.path.join('.')}: \${i.message}\`).join('\\n');
|
|
719
|
+
ctx.addIssue({
|
|
720
|
+
code: "custom",
|
|
721
|
+
message: \`When '${prop}' is present, object must satisfy additional constraints:\\n\${errors}\`,
|
|
722
|
+
path: []
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
})`;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return result;
|
|
729
|
+
}
|
|
730
|
+
function generateConditionalCheck(schema) {
|
|
731
|
+
const conditions = [];
|
|
732
|
+
if (schema.properties) {
|
|
733
|
+
for (const [prop, propSchema] of Object.entries(schema.properties)) {
|
|
734
|
+
const propAccess = generatePropertyAccess(prop);
|
|
735
|
+
if (propSchema.type) {
|
|
736
|
+
conditions.push(`typeof ${propAccess} === "${propSchema.type}"`);
|
|
737
|
+
}
|
|
738
|
+
if (propSchema.const !== void 0) {
|
|
739
|
+
const value = typeof propSchema.const === "string" ? `"${propSchema.const}"` : propSchema.const;
|
|
740
|
+
conditions.push(`${propAccess} === ${value}`);
|
|
741
|
+
}
|
|
742
|
+
if (propSchema.minimum !== void 0) {
|
|
743
|
+
conditions.push(`${propAccess} >= ${propSchema.minimum}`);
|
|
744
|
+
}
|
|
745
|
+
if (propSchema.maximum !== void 0) {
|
|
746
|
+
conditions.push(`${propAccess} <= ${propSchema.maximum}`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (schema.required) {
|
|
751
|
+
for (const prop of schema.required) {
|
|
752
|
+
conditions.push(`${generatePropertyAccess(prop)} !== undefined`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return conditions.length > 0 ? conditions.join(" && ") : "true";
|
|
756
|
+
}
|
|
757
|
+
function generateConditionalValidation(schema) {
|
|
758
|
+
const checks = [];
|
|
759
|
+
if (schema.required) {
|
|
760
|
+
for (const prop of schema.required) {
|
|
761
|
+
checks.push(`${generatePropertyAccess(prop)} !== undefined`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (schema.properties) {
|
|
765
|
+
for (const [prop, propSchema] of Object.entries(schema.properties)) {
|
|
766
|
+
const propAccess = generatePropertyAccess(prop);
|
|
767
|
+
if (propSchema.minimum !== void 0) {
|
|
768
|
+
checks.push(`${propAccess} === undefined || ${propAccess} >= ${propSchema.minimum}`);
|
|
769
|
+
}
|
|
770
|
+
if (propSchema.maximum !== void 0) {
|
|
771
|
+
checks.push(`${propAccess} === undefined || ${propAccess} <= ${propSchema.maximum}`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return checks.length > 0 ? checks.join(" && ") : "true";
|
|
776
|
+
}
|
|
777
|
+
function generateIfThenElse(schema) {
|
|
778
|
+
if (!schema.if || !schema.then && !schema.else) {
|
|
779
|
+
return "";
|
|
780
|
+
}
|
|
781
|
+
const ifCondition = generateConditionalCheck(schema.if);
|
|
782
|
+
if (schema.then && schema.else) {
|
|
783
|
+
const thenValidation = generateConditionalValidation(schema.then);
|
|
784
|
+
const elseValidation2 = generateConditionalValidation(schema.else);
|
|
785
|
+
const thenRequiredProps = schema.then.required || [];
|
|
786
|
+
const elseRequiredProps2 = schema.else.required || [];
|
|
787
|
+
return `.superRefine((obj, ctx) => {
|
|
788
|
+
const ifConditionMet = ${ifCondition};
|
|
789
|
+
if (ifConditionMet) {
|
|
790
|
+
// Then branch
|
|
791
|
+
const thenValid = ${thenValidation};
|
|
792
|
+
if (!thenValid) {
|
|
793
|
+
${thenRequiredProps.length > 0 ? `
|
|
794
|
+
const missingThenProps = ${JSON.stringify(thenRequiredProps)}.filter(p => obj[p] === undefined);
|
|
795
|
+
const message = missingThenProps.length > 0
|
|
796
|
+
? \`When condition is met, required properties are missing: \${missingThenProps.join(', ')}\`
|
|
797
|
+
: "When condition is met, validation constraints failed";
|
|
798
|
+
` : `
|
|
799
|
+
const message = "When condition is met, validation constraints failed";
|
|
800
|
+
`}
|
|
801
|
+
ctx.addIssue({
|
|
802
|
+
code: "custom",
|
|
803
|
+
message: message,
|
|
804
|
+
path: []
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
// Else branch
|
|
809
|
+
const elseValid = ${elseValidation2};
|
|
810
|
+
if (!elseValid) {
|
|
811
|
+
${elseRequiredProps2.length > 0 ? `
|
|
812
|
+
const missingElseProps = ${JSON.stringify(elseRequiredProps2)}.filter(p => obj[p] === undefined);
|
|
813
|
+
const message = missingElseProps.length > 0
|
|
814
|
+
? \`When condition is not met, required properties are missing: \${missingElseProps.join(', ')}\`
|
|
815
|
+
: "When condition is not met, validation constraints failed";
|
|
816
|
+
` : `
|
|
817
|
+
const message = "When condition is not met, validation constraints failed";
|
|
818
|
+
`}
|
|
819
|
+
ctx.addIssue({
|
|
820
|
+
code: "custom",
|
|
821
|
+
message: message,
|
|
822
|
+
path: []
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
})`;
|
|
827
|
+
}
|
|
828
|
+
if (schema.then) {
|
|
829
|
+
const thenValidation = generateConditionalValidation(schema.then);
|
|
830
|
+
const thenRequiredProps = schema.then.required || [];
|
|
831
|
+
return `.superRefine((obj, ctx) => {
|
|
832
|
+
const ifConditionMet = ${ifCondition};
|
|
833
|
+
if (ifConditionMet) {
|
|
834
|
+
const thenValid = ${thenValidation};
|
|
835
|
+
if (!thenValid) {
|
|
836
|
+
${thenRequiredProps.length > 0 ? `
|
|
837
|
+
const missingProps = ${JSON.stringify(thenRequiredProps)}.filter(p => obj[p] === undefined);
|
|
838
|
+
const message = missingProps.length > 0
|
|
839
|
+
? \`When condition is met, required properties are missing: \${missingProps.join(', ')}\`
|
|
840
|
+
: "When condition is met, validation constraints failed";
|
|
841
|
+
` : `
|
|
842
|
+
const message = "When condition is met, validation constraints failed";
|
|
843
|
+
`}
|
|
844
|
+
ctx.addIssue({
|
|
845
|
+
code: "custom",
|
|
846
|
+
message: message,
|
|
847
|
+
path: []
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
})`;
|
|
852
|
+
}
|
|
853
|
+
if (!schema.else) return "";
|
|
854
|
+
const elseValidation = generateConditionalValidation(schema.else);
|
|
855
|
+
const elseRequiredProps = schema.else.required || [];
|
|
856
|
+
return `.superRefine((obj, ctx) => {
|
|
857
|
+
const ifConditionMet = ${ifCondition};
|
|
858
|
+
if (!ifConditionMet) {
|
|
859
|
+
const elseValid = ${elseValidation};
|
|
860
|
+
if (!elseValid) {
|
|
861
|
+
${elseRequiredProps.length > 0 ? `
|
|
862
|
+
const missingProps = ${JSON.stringify(elseRequiredProps)}.filter(p => obj[p] === undefined);
|
|
863
|
+
const message = missingProps.length > 0
|
|
864
|
+
? \`When condition is not met, required properties are missing: \${missingProps.join(', ')}\`
|
|
865
|
+
: "When condition is not met, validation constraints failed";
|
|
866
|
+
` : `
|
|
867
|
+
const message = "When condition is not met, validation constraints failed";
|
|
868
|
+
`}
|
|
869
|
+
ctx.addIssue({
|
|
870
|
+
code: "custom",
|
|
871
|
+
message: message,
|
|
872
|
+
path: []
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
})`;
|
|
877
|
+
}
|
|
878
|
+
function generateDependentRequired(schema) {
|
|
879
|
+
if (!schema.dependentRequired) {
|
|
880
|
+
return "";
|
|
881
|
+
}
|
|
882
|
+
let result = "";
|
|
883
|
+
for (const [prop, requiredProps] of Object.entries(schema.dependentRequired)) {
|
|
884
|
+
if (requiredProps.length === 0) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const propAccess = generatePropertyAccess(prop);
|
|
888
|
+
const checkLogic = requiredProps.map((rp) => {
|
|
889
|
+
const rpAccess = generatePropertyAccess(rp);
|
|
890
|
+
return `if (${rpAccess} === undefined) missing.push('${rp}');`;
|
|
891
|
+
}).join("\n ");
|
|
892
|
+
result += `.superRefine((obj, ctx) => {
|
|
893
|
+
if (${propAccess} === undefined) return;
|
|
894
|
+
const missing: string[] = [];
|
|
895
|
+
${checkLogic}
|
|
896
|
+
if (missing.length > 0) {
|
|
897
|
+
ctx.addIssue({
|
|
898
|
+
code: "custom",
|
|
899
|
+
message: \`When '${prop}' is present, the following properties are required: \${missing.join(', ')}\`,
|
|
900
|
+
path: []
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
})`;
|
|
904
|
+
}
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/validators/object-validator.ts
|
|
909
|
+
function needsQuoting(propName) {
|
|
910
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
911
|
+
return !validIdentifier.test(propName);
|
|
912
|
+
}
|
|
913
|
+
function generatePropertyAccess2(propName) {
|
|
914
|
+
return needsQuoting(propName) ? `obj["${propName}"]` : `obj.${propName}`;
|
|
915
|
+
}
|
|
916
|
+
function generateObjectSchema(schema, context, currentSchema) {
|
|
917
|
+
const required = new Set(schema.required || []);
|
|
918
|
+
const properties = [];
|
|
919
|
+
if (schema.properties) {
|
|
920
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
921
|
+
if (!context.shouldIncludeProperty(propSchema)) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
const isRequired = required.has(propName);
|
|
925
|
+
const zodSchema = context.generatePropertySchema(propSchema, currentSchema);
|
|
926
|
+
const quotedPropName = needsQuoting(propName) ? `"${propName}"` : propName;
|
|
927
|
+
let propertyDef = ` ${quotedPropName}: ${zodSchema}`;
|
|
928
|
+
if (!isRequired) {
|
|
929
|
+
propertyDef += ".optional()";
|
|
930
|
+
}
|
|
931
|
+
const jsdoc = generateJSDoc(propSchema, propName, { includeDescriptions: context.includeDescriptions });
|
|
932
|
+
if (jsdoc) {
|
|
933
|
+
properties.push(`${jsdoc.trimEnd()}
|
|
934
|
+
${propertyDef}`);
|
|
935
|
+
} else {
|
|
936
|
+
properties.push(propertyDef);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
let objectMethod;
|
|
941
|
+
if (schema.additionalProperties === false) {
|
|
942
|
+
objectMethod = "z.strictObject";
|
|
943
|
+
} else {
|
|
944
|
+
switch (context.mode) {
|
|
945
|
+
case "strict":
|
|
946
|
+
objectMethod = "z.strictObject";
|
|
947
|
+
break;
|
|
948
|
+
case "loose":
|
|
949
|
+
objectMethod = "z.looseObject";
|
|
950
|
+
break;
|
|
951
|
+
default:
|
|
952
|
+
objectMethod = "z.object";
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
let objectDef = `${objectMethod}({
|
|
956
|
+
${properties.join(",\n")}
|
|
957
|
+
})`;
|
|
958
|
+
if (schema.additionalProperties !== void 0) {
|
|
959
|
+
if (typeof schema.additionalProperties === "object") {
|
|
960
|
+
const additionalSchema = context.generatePropertySchema(schema.additionalProperties, currentSchema);
|
|
961
|
+
objectDef += `.catchall(${additionalSchema})`;
|
|
962
|
+
} else if (schema.additionalProperties === true) {
|
|
963
|
+
objectDef += ".catchall(z.unknown())";
|
|
964
|
+
}
|
|
965
|
+
} else if (schema.patternProperties) {
|
|
966
|
+
objectDef += ".catchall(z.unknown())";
|
|
967
|
+
}
|
|
968
|
+
if (schema.minProperties !== void 0 || schema.maxProperties !== void 0) {
|
|
969
|
+
const conditions = [];
|
|
970
|
+
if (schema.minProperties !== void 0) {
|
|
971
|
+
conditions.push(`Object.keys(obj).length >= ${schema.minProperties}`);
|
|
972
|
+
}
|
|
973
|
+
if (schema.maxProperties !== void 0) {
|
|
974
|
+
conditions.push(`Object.keys(obj).length <= ${schema.maxProperties}`);
|
|
975
|
+
}
|
|
976
|
+
const condition = conditions.join(" && ");
|
|
977
|
+
let message = "Object ";
|
|
978
|
+
if (schema.minProperties !== void 0 && schema.maxProperties !== void 0) {
|
|
979
|
+
message += `must have between ${schema.minProperties} and ${schema.maxProperties} properties`;
|
|
980
|
+
} else if (schema.minProperties !== void 0) {
|
|
981
|
+
message += `must have at least ${schema.minProperties} ${schema.minProperties === 1 ? "property" : "properties"}`;
|
|
982
|
+
} else {
|
|
983
|
+
message += `must have at most ${schema.maxProperties} ${schema.maxProperties === 1 ? "property" : "properties"}`;
|
|
984
|
+
}
|
|
985
|
+
objectDef += `.refine((obj) => ${condition}, { message: "${message}" })`;
|
|
986
|
+
}
|
|
987
|
+
const definedProps = new Set(Object.keys(schema.properties || {}));
|
|
988
|
+
const undefinedRequired = (schema.required || []).filter((prop) => !definedProps.has(prop));
|
|
989
|
+
if (undefinedRequired.length > 0) {
|
|
990
|
+
if (!objectDef.includes(".catchall(")) {
|
|
991
|
+
objectDef += ".catchall(z.unknown())";
|
|
992
|
+
}
|
|
993
|
+
const requiredChecks = undefinedRequired.map((prop) => `${generatePropertyAccess2(prop)} !== undefined`).join(" && ");
|
|
994
|
+
const propList = undefinedRequired.join(", ");
|
|
995
|
+
objectDef += `.refine((obj) => ${requiredChecks}, { message: "Missing required fields: ${propList}" })`;
|
|
996
|
+
}
|
|
997
|
+
if (schema.patternProperties) {
|
|
998
|
+
const definedProps2 = Object.keys(schema.properties || {});
|
|
999
|
+
const definedPropsSet = `new Set(${JSON.stringify(definedProps2)})`;
|
|
1000
|
+
const patterns = Object.entries(schema.patternProperties);
|
|
1001
|
+
const patternSchemas = patterns.map(([pattern, patternSchema]) => ({
|
|
1002
|
+
pattern,
|
|
1003
|
+
escapedPattern: pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'"),
|
|
1004
|
+
zodSchema: context.generatePropertySchema(patternSchema, currentSchema)
|
|
1005
|
+
}));
|
|
1006
|
+
objectDef += `.superRefine((obj, ctx) => {
|
|
1007
|
+
const definedPropsSet = ${definedPropsSet};
|
|
1008
|
+
const patterns = ${JSON.stringify(patternSchemas.map((p) => ({ pattern: p.escapedPattern })))};
|
|
1009
|
+
const schemas = [${patternSchemas.map((p) => p.zodSchema).join(", ")}];
|
|
1010
|
+
const regexps = patterns.map(p => new RegExp(p.pattern));
|
|
1011
|
+
|
|
1012
|
+
// Check all object keys
|
|
1013
|
+
for (const key of Object.keys(obj)) {
|
|
1014
|
+
// Skip properties that are explicitly defined
|
|
1015
|
+
if (definedPropsSet.has(key)) continue;
|
|
1016
|
+
|
|
1017
|
+
// Find first matching pattern (first-match-wins priority)
|
|
1018
|
+
for (let i = 0; i < regexps.length; i++) {
|
|
1019
|
+
if (regexps[i].test(key)) {
|
|
1020
|
+
const validation = schemas[i].safeParse(obj[key]);
|
|
1021
|
+
if (!validation.success) {
|
|
1022
|
+
// Add detailed error messages with property name and pattern
|
|
1023
|
+
for (const issue of validation.error.issues) {
|
|
1024
|
+
ctx.addIssue({
|
|
1025
|
+
...issue,
|
|
1026
|
+
path: [key, ...issue.path],
|
|
1027
|
+
message: \`Property '\${key}' (pattern '\${patterns[i].pattern}'): \${issue.message}\`
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
break; // First match wins, stop checking other patterns
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
})`;
|
|
1036
|
+
}
|
|
1037
|
+
if (schema.propertyNames) {
|
|
1038
|
+
const hasPattern = schema.propertyNames.pattern !== void 0;
|
|
1039
|
+
const hasMinLength = schema.propertyNames.minLength !== void 0;
|
|
1040
|
+
const hasMaxLength = schema.propertyNames.maxLength !== void 0;
|
|
1041
|
+
if (hasPattern || hasMinLength || hasMaxLength) {
|
|
1042
|
+
const escapedPattern = hasPattern && schema.propertyNames.pattern ? schema.propertyNames.pattern.replace(/\\/g, "\\\\").replace(/'/g, "\\'") : null;
|
|
1043
|
+
const minLen = schema.propertyNames.minLength;
|
|
1044
|
+
const maxLen = schema.propertyNames.maxLength;
|
|
1045
|
+
objectDef += `.superRefine((obj, ctx) => {
|
|
1046
|
+
${escapedPattern ? `const pattern = /${escapedPattern}/;` : ""}
|
|
1047
|
+
|
|
1048
|
+
for (const key of Object.keys(obj)) {
|
|
1049
|
+
const failures: string[] = [];
|
|
1050
|
+
|
|
1051
|
+
${hasPattern ? `
|
|
1052
|
+
if (!pattern.test(key)) {
|
|
1053
|
+
failures.push("must match pattern '${schema.propertyNames.pattern}'");
|
|
1054
|
+
}
|
|
1055
|
+
` : ""}
|
|
1056
|
+
|
|
1057
|
+
${hasMinLength ? `
|
|
1058
|
+
if (key.length < ${minLen}) {
|
|
1059
|
+
failures.push("must be at least ${minLen} characters");
|
|
1060
|
+
}
|
|
1061
|
+
` : ""}
|
|
1062
|
+
|
|
1063
|
+
${hasMaxLength ? `
|
|
1064
|
+
if (key.length > ${maxLen}) {
|
|
1065
|
+
failures.push("must be at most ${maxLen} characters");
|
|
1066
|
+
}
|
|
1067
|
+
` : ""}
|
|
1068
|
+
|
|
1069
|
+
if (failures.length > 0) {
|
|
1070
|
+
ctx.addIssue({
|
|
1071
|
+
code: "custom",
|
|
1072
|
+
message: \`Property name '\${key}' \${failures.join(", ")}\`,
|
|
1073
|
+
path: [key]
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
})`;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
objectDef += generateDependencies(schema, context.generatePropertySchema, currentSchema);
|
|
1081
|
+
objectDef += generateDependentRequired(schema);
|
|
1082
|
+
objectDef += generateIfThenElse(schema);
|
|
1083
|
+
return objectDef;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/validators/string-validator.ts
|
|
1087
|
+
var DEFAULT_FORMAT_MAP = {
|
|
1088
|
+
uuid: "z.uuid()",
|
|
1089
|
+
email: "z.email()",
|
|
1090
|
+
uri: "z.url()",
|
|
1091
|
+
url: "z.url()",
|
|
1092
|
+
"uri-reference": 'z.string().refine((val) => !/\\s/.test(val), { message: "Must be a valid URI reference" })',
|
|
1093
|
+
hostname: 'z.string().refine((val) => /^(?=.{1,253}$)(?:(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)*(?!-)[A-Za-z0-9-]{1,63}(?<!-)$/.test(val), { message: "Must be a valid hostname" })',
|
|
1094
|
+
byte: "z.base64()",
|
|
1095
|
+
binary: "z.string()",
|
|
1096
|
+
date: "z.iso.date()",
|
|
1097
|
+
time: "z.iso.time()",
|
|
1098
|
+
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" })',
|
|
1099
|
+
ipv4: "z.ipv4()",
|
|
1100
|
+
ipv6: "z.ipv6()",
|
|
1101
|
+
emoji: "z.emoji()",
|
|
1102
|
+
base64: "z.base64()",
|
|
1103
|
+
base64url: "z.base64url()",
|
|
1104
|
+
nanoid: "z.nanoid()",
|
|
1105
|
+
cuid: "z.cuid()",
|
|
1106
|
+
cuid2: "z.cuid2()",
|
|
1107
|
+
ulid: "z.ulid()",
|
|
1108
|
+
cidr: "z.cidrv4()",
|
|
1109
|
+
// Default to v4
|
|
1110
|
+
cidrv4: "z.cidrv4()",
|
|
1111
|
+
cidrv6: "z.cidrv6()",
|
|
1112
|
+
"json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
|
|
1113
|
+
"relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
|
|
1114
|
+
};
|
|
1115
|
+
function buildDateTimeValidation(pattern) {
|
|
1116
|
+
if (!pattern) {
|
|
1117
|
+
return "z.iso.datetime()";
|
|
1118
|
+
}
|
|
1119
|
+
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
1120
|
+
if (patternStr === "") {
|
|
1121
|
+
return "z.iso.datetime()";
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
new RegExp(patternStr);
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
throw new Error(
|
|
1127
|
+
`Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
const escapedPattern = escapePattern(patternStr);
|
|
1131
|
+
return `z.string().regex(/${escapedPattern}/)`;
|
|
1132
|
+
}
|
|
1133
|
+
function generateStringValidation(schema, useDescribe, context) {
|
|
1134
|
+
let validation;
|
|
1135
|
+
const format = schema.format || "";
|
|
1136
|
+
if (format === "date-time") {
|
|
1137
|
+
validation = context.dateTimeValidation;
|
|
1138
|
+
} else {
|
|
1139
|
+
validation = DEFAULT_FORMAT_MAP[format] || "z.string()";
|
|
1140
|
+
}
|
|
1141
|
+
if (schema.minLength !== void 0) {
|
|
1142
|
+
validation += `.min(${schema.minLength})`;
|
|
1143
|
+
}
|
|
1144
|
+
if (schema.maxLength !== void 0) {
|
|
1145
|
+
validation += `.max(${schema.maxLength})`;
|
|
1146
|
+
}
|
|
1147
|
+
if (schema.pattern) {
|
|
1148
|
+
let escapedPattern = context.patternCache.get(schema.pattern);
|
|
1149
|
+
if (escapedPattern === void 0) {
|
|
1150
|
+
escapedPattern = escapePattern(schema.pattern);
|
|
1151
|
+
context.patternCache.set(schema.pattern, escapedPattern);
|
|
1152
|
+
}
|
|
1153
|
+
validation += `.regex(/${escapedPattern}/)`;
|
|
1154
|
+
}
|
|
1155
|
+
if (schema.contentEncoding && !schema.format) {
|
|
1156
|
+
switch (schema.contentEncoding) {
|
|
1157
|
+
case "base64":
|
|
1158
|
+
validation = "z.base64()";
|
|
1159
|
+
break;
|
|
1160
|
+
case "base64url":
|
|
1161
|
+
validation = "z.base64url()";
|
|
1162
|
+
break;
|
|
1163
|
+
case "quoted-printable":
|
|
1164
|
+
validation = 'z.string().refine((val) => /^[\\x20-\\x7E\\r\\n=]*$/.test(val), { message: "Must be valid quoted-printable encoding" })';
|
|
1165
|
+
break;
|
|
1166
|
+
case "7bit":
|
|
1167
|
+
case "8bit":
|
|
1168
|
+
case "binary":
|
|
1169
|
+
validation = "z.string()";
|
|
1170
|
+
break;
|
|
1171
|
+
default:
|
|
1172
|
+
validation = `z.string().describe("Content encoding: ${schema.contentEncoding}")`;
|
|
1173
|
+
}
|
|
1174
|
+
if (schema.minLength !== void 0) {
|
|
1175
|
+
validation += `.min(${schema.minLength})`;
|
|
1176
|
+
}
|
|
1177
|
+
if (schema.maxLength !== void 0) {
|
|
1178
|
+
validation += `.max(${schema.maxLength})`;
|
|
1179
|
+
}
|
|
1180
|
+
if (schema.pattern) {
|
|
1181
|
+
let escapedPattern = context.patternCache.get(schema.pattern);
|
|
1182
|
+
if (escapedPattern === void 0) {
|
|
1183
|
+
escapedPattern = escapePattern(schema.pattern);
|
|
1184
|
+
context.patternCache.set(schema.pattern, escapedPattern);
|
|
1185
|
+
}
|
|
1186
|
+
validation += `.regex(/${escapedPattern}/)`;
|
|
1187
|
+
}
|
|
1188
|
+
} else if (schema.contentMediaType) {
|
|
1189
|
+
const mediaType = schema.contentMediaType;
|
|
1190
|
+
if (mediaType === "application/json") {
|
|
1191
|
+
validation += `.refine((val) => { try { JSON.parse(val); return true; } catch { return false; } }, { message: "Must be valid JSON" })`;
|
|
1192
|
+
} else if (mediaType === "application/xml" || mediaType === "text/xml") {
|
|
1193
|
+
validation += `.refine((val) => { try { if (typeof DOMParser !== "undefined") { const parser = new DOMParser(); const doc = parser.parseFromString(val, "text/xml"); return !doc.querySelector("parsererror"); } return /^\\s*<[^>]+>/.test(val); } catch { return false; } }, { message: "Must be valid XML" })`;
|
|
1194
|
+
} else if (mediaType === "application/yaml" || mediaType === "application/x-yaml" || mediaType === "text/yaml") {
|
|
1195
|
+
validation += `.refine((val) => { try { return val.trim().length > 0 && !/^[[{]/.test(val.trim()); } catch { return false; } }, { message: "Must be valid YAML" })`;
|
|
1196
|
+
} else if (mediaType === "text/html") {
|
|
1197
|
+
validation += `.refine((val) => /<[^>]+>/.test(val), { message: "Must contain HTML tags" })`;
|
|
1198
|
+
} else if (mediaType === "text/plain") {
|
|
1199
|
+
validation += `.refine(() => true, { message: "Plain text content" })`;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return addDescription(validation, schema.description, useDescribe);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/generators/property-generator.ts
|
|
1206
|
+
var _PropertyGenerator = class _PropertyGenerator {
|
|
1207
|
+
constructor(context) {
|
|
1208
|
+
// Performance optimization: Memoize filtered property results
|
|
1209
|
+
this.filteredPropsCache = /* @__PURE__ */ new Map();
|
|
1210
|
+
// Performance optimization: LRU cache for generated schemas
|
|
1211
|
+
this.schemaCache = new LRUCache(500);
|
|
1212
|
+
this.context = context;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Check if a property should be included based on schemaType and readOnly/writeOnly flags
|
|
1216
|
+
*/
|
|
1217
|
+
shouldIncludeProperty(schema) {
|
|
1218
|
+
const rule = _PropertyGenerator.INCLUSION_RULES[this.context.schemaType];
|
|
1219
|
+
return rule(schema);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Recursively filter any schema type (helper for composition schemas)
|
|
1223
|
+
*/
|
|
1224
|
+
filterSchemaRecursive(schema) {
|
|
1225
|
+
if (schema.$ref) {
|
|
1226
|
+
return schema;
|
|
1227
|
+
}
|
|
1228
|
+
if (schema.properties) {
|
|
1229
|
+
return this.filterNestedProperties(schema);
|
|
1230
|
+
}
|
|
1231
|
+
if (schema.type === "array" && schema.items && typeof schema.items === "object" && schema.items.properties) {
|
|
1232
|
+
return {
|
|
1233
|
+
...schema,
|
|
1234
|
+
items: this.filterNestedProperties(schema.items)
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
return schema;
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Recursively filter properties in nested objects based on readOnly/writeOnly
|
|
1241
|
+
* Performance optimized with memoization
|
|
1242
|
+
*/
|
|
1243
|
+
filterNestedProperties(schema) {
|
|
1244
|
+
var _a, _b;
|
|
1245
|
+
const propKeys = schema.properties ? Object.keys(schema.properties).sort().join(",") : "";
|
|
1246
|
+
const cacheKey = `${this.context.schemaType}:${schema.type || "unknown"}:${propKeys}:${((_a = schema.required) == null ? void 0 : _a.join(",")) || ""}`;
|
|
1247
|
+
const cached = this.filteredPropsCache.get(cacheKey);
|
|
1248
|
+
if (cached) {
|
|
1249
|
+
return cached;
|
|
1250
|
+
}
|
|
1251
|
+
if (!schema.properties) {
|
|
1252
|
+
return schema;
|
|
1253
|
+
}
|
|
1254
|
+
const filteredProperties = {};
|
|
1255
|
+
const filteredRequired = [];
|
|
1256
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
1257
|
+
if (!this.shouldIncludeProperty(propSchema)) {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
let filteredPropSchema = propSchema;
|
|
1261
|
+
if (propSchema.type === "object" && propSchema.properties) {
|
|
1262
|
+
filteredPropSchema = this.filterNestedProperties(propSchema);
|
|
1263
|
+
} else if (propSchema.type === "array" && propSchema.items && typeof propSchema.items === "object" && propSchema.items.properties) {
|
|
1264
|
+
filteredPropSchema = {
|
|
1265
|
+
...propSchema,
|
|
1266
|
+
items: this.filterNestedProperties(propSchema.items)
|
|
1267
|
+
};
|
|
1268
|
+
} else if (propSchema.allOf || propSchema.oneOf || propSchema.anyOf) {
|
|
1269
|
+
if (propSchema.allOf) {
|
|
1270
|
+
filteredPropSchema = {
|
|
1271
|
+
...propSchema,
|
|
1272
|
+
allOf: propSchema.allOf.map((s) => this.filterSchemaRecursive(s))
|
|
1273
|
+
};
|
|
1274
|
+
} else if (propSchema.oneOf) {
|
|
1275
|
+
filteredPropSchema = {
|
|
1276
|
+
...propSchema,
|
|
1277
|
+
oneOf: propSchema.oneOf.map((s) => this.filterSchemaRecursive(s))
|
|
1278
|
+
};
|
|
1279
|
+
} else if (propSchema.anyOf) {
|
|
1280
|
+
filteredPropSchema = {
|
|
1281
|
+
...propSchema,
|
|
1282
|
+
anyOf: propSchema.anyOf.map((s) => this.filterSchemaRecursive(s))
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
filteredProperties[propName] = filteredPropSchema;
|
|
1287
|
+
if ((_b = schema.required) == null ? void 0 : _b.includes(propName)) {
|
|
1288
|
+
filteredRequired.push(propName);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const result = {
|
|
1292
|
+
...schema,
|
|
1293
|
+
properties: filteredProperties,
|
|
1294
|
+
required: filteredRequired.length > 0 ? filteredRequired : void 0
|
|
1295
|
+
};
|
|
1296
|
+
this.filteredPropsCache.set(cacheKey, result);
|
|
1297
|
+
return result;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Resolve discriminator mapping to actual schema references
|
|
1301
|
+
*/
|
|
1302
|
+
resolveDiscriminatorMapping(mapping, schemas) {
|
|
1303
|
+
const mappedSchemas = [];
|
|
1304
|
+
for (const [_, schemaRef] of Object.entries(mapping)) {
|
|
1305
|
+
const matchingSchema = schemas.find((s) => {
|
|
1306
|
+
if (s.$ref) {
|
|
1307
|
+
return s.$ref === schemaRef || s.$ref.endsWith(schemaRef);
|
|
1308
|
+
}
|
|
1309
|
+
return false;
|
|
1310
|
+
});
|
|
1311
|
+
if (matchingSchema) {
|
|
1312
|
+
mappedSchemas.push(matchingSchema);
|
|
1313
|
+
} else {
|
|
1314
|
+
mappedSchemas.push({ $ref: schemaRef });
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
for (const schema of schemas) {
|
|
1318
|
+
if (!mappedSchemas.includes(schema)) {
|
|
1319
|
+
mappedSchemas.push(schema);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return mappedSchemas;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Resolve a $ref string to the actual schema
|
|
1326
|
+
*/
|
|
1327
|
+
resolveSchemaRef(ref) {
|
|
1328
|
+
var _a, _b;
|
|
1329
|
+
const schemaName = ref.split("/").pop();
|
|
1330
|
+
if (!schemaName) return void 0;
|
|
1331
|
+
return (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Resolve a schema name through any aliases to get the actual schema name
|
|
1335
|
+
* If the schema is an alias (allOf with single $ref), return the target name
|
|
1336
|
+
*/
|
|
1337
|
+
resolveSchemaAlias(schemaName) {
|
|
1338
|
+
var _a, _b;
|
|
1339
|
+
const schema = (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
|
|
1340
|
+
if (!schema) return schemaName;
|
|
1341
|
+
if (schema.allOf && schema.allOf.length === 1 && schema.allOf[0].$ref && !schema.properties && !schema.oneOf && !schema.anyOf) {
|
|
1342
|
+
const targetName = resolveRef(schema.allOf[0].$ref);
|
|
1343
|
+
return this.resolveSchemaAlias(targetName);
|
|
1344
|
+
}
|
|
1345
|
+
return schemaName;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Check if this is a circular dependency through aliases
|
|
1349
|
+
*/
|
|
1350
|
+
isCircularThroughAlias(fromSchema, toSchema) {
|
|
1351
|
+
var _a, _b;
|
|
1352
|
+
const toSchemaSpec = (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[toSchema];
|
|
1353
|
+
if (!toSchemaSpec) return false;
|
|
1354
|
+
if (toSchemaSpec.allOf && toSchemaSpec.allOf.length === 1 && toSchemaSpec.allOf[0].$ref) {
|
|
1355
|
+
const aliasTarget = resolveRef(toSchemaSpec.allOf[0].$ref);
|
|
1356
|
+
return aliasTarget === fromSchema;
|
|
1357
|
+
}
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Generate union for multiple types (OpenAPI 3.1)
|
|
1362
|
+
*/
|
|
1363
|
+
generateMultiTypeUnion(schema, currentSchema) {
|
|
1364
|
+
if (!Array.isArray(schema.type)) {
|
|
1365
|
+
return "z.unknown()";
|
|
1366
|
+
}
|
|
1367
|
+
const nonNullTypes = schema.type.filter((t) => t !== "null");
|
|
1368
|
+
const schemas = nonNullTypes.map((type) => {
|
|
1369
|
+
const typeSchema = { ...schema, type };
|
|
1370
|
+
return this.generatePropertySchema(typeSchema, currentSchema);
|
|
1371
|
+
});
|
|
1372
|
+
return `z.union([${schemas.join(", ")}])`;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Apply unevaluatedProperties validation to a schema
|
|
1376
|
+
*/
|
|
1377
|
+
applyUnevaluatedProperties(baseSchema, schema) {
|
|
1378
|
+
const evaluatedProps = /* @__PURE__ */ new Set();
|
|
1379
|
+
if (schema.properties) {
|
|
1380
|
+
for (const propName of Object.keys(schema.properties)) {
|
|
1381
|
+
evaluatedProps.add(propName);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
const collectPropsFromComposition = (schemas) => {
|
|
1385
|
+
var _a, _b;
|
|
1386
|
+
if (!schemas) return;
|
|
1387
|
+
for (const subSchema of schemas) {
|
|
1388
|
+
if (subSchema.properties) {
|
|
1389
|
+
for (const propName of Object.keys(subSchema.properties)) {
|
|
1390
|
+
evaluatedProps.add(propName);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (subSchema.$ref) {
|
|
1394
|
+
const refSchema = (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[subSchema.$ref.split("/").pop() || ""];
|
|
1395
|
+
if (refSchema == null ? void 0 : refSchema.properties) {
|
|
1396
|
+
for (const propName of Object.keys(refSchema.properties)) {
|
|
1397
|
+
evaluatedProps.add(propName);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
collectPropsFromComposition(schema.allOf);
|
|
1404
|
+
collectPropsFromComposition(schema.oneOf);
|
|
1405
|
+
collectPropsFromComposition(schema.anyOf);
|
|
1406
|
+
const evaluatedPropsSet = `new Set(${JSON.stringify([...evaluatedProps])})`;
|
|
1407
|
+
let schemaWithCatchall = baseSchema;
|
|
1408
|
+
if (baseSchema.includes(".union([") || baseSchema.includes(".discriminatedUnion(")) {
|
|
1409
|
+
schemaWithCatchall = baseSchema;
|
|
1410
|
+
} else if (baseSchema.includes(".extend(")) {
|
|
1411
|
+
schemaWithCatchall = `${baseSchema}.catchall(z.unknown())`;
|
|
1412
|
+
}
|
|
1413
|
+
if (schema.unevaluatedProperties === false) {
|
|
1414
|
+
return `${schemaWithCatchall}.refine((obj) => Object.keys(obj).every(key => ${evaluatedPropsSet}.has(key)), { message: "No unevaluated properties allowed" })`;
|
|
1415
|
+
} else if (typeof schema.unevaluatedProperties === "object") {
|
|
1416
|
+
const unevalSchema = this.generatePropertySchema(schema.unevaluatedProperties);
|
|
1417
|
+
return `${schemaWithCatchall}.refine((obj) => Object.keys(obj).filter(key => !${evaluatedPropsSet}.has(key)).every(key => ${unevalSchema}.safeParse(obj[key]).success), { message: "Unevaluated properties must match the schema" })`;
|
|
1418
|
+
}
|
|
1419
|
+
return baseSchema;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Generate Zod schema for a property
|
|
1423
|
+
* @param schema - The OpenAPI schema to generate
|
|
1424
|
+
* @param currentSchema - The name of the current schema being processed (for circular ref detection)
|
|
1425
|
+
* @param isTopLevel - Whether this is a top-level schema definition
|
|
1426
|
+
* @param suppressDefaultNullable - When true, don't apply defaultNullable (used when outer schema has explicit nullable: false)
|
|
1427
|
+
*/
|
|
1428
|
+
generatePropertySchema(schema, currentSchema, isTopLevel = false, suppressDefaultNullable = false) {
|
|
1429
|
+
var _a, _b, _c, _d, _e;
|
|
1430
|
+
const isCacheable = !schema.$ref && !schema.allOf && !schema.oneOf && !schema.anyOf && !currentSchema;
|
|
1431
|
+
if (isCacheable) {
|
|
1432
|
+
const cacheKey = JSON.stringify({
|
|
1433
|
+
schema,
|
|
1434
|
+
type: this.context.schemaType,
|
|
1435
|
+
mode: this.context.mode,
|
|
1436
|
+
suppressDefaultNullable
|
|
1437
|
+
});
|
|
1438
|
+
const cached = this.schemaCache.get(cacheKey);
|
|
1439
|
+
if (cached) {
|
|
1440
|
+
return cached;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
|
|
1444
|
+
schema = this.filterNestedProperties(schema);
|
|
1445
|
+
}
|
|
1446
|
+
const isEnum = !!schema.enum;
|
|
1447
|
+
const isConst = schema.const !== void 0;
|
|
1448
|
+
const shouldApplyDefaultNullable = !isTopLevel && !isEnum && !isConst && !suppressDefaultNullable;
|
|
1449
|
+
const effectiveDefaultNullable = shouldApplyDefaultNullable ? this.context.defaultNullable : false;
|
|
1450
|
+
const nullable = isNullable(schema, effectiveDefaultNullable);
|
|
1451
|
+
if (hasMultipleTypes(schema)) {
|
|
1452
|
+
const union = this.generateMultiTypeUnion(schema, currentSchema);
|
|
1453
|
+
return wrapNullable(union, nullable);
|
|
1454
|
+
}
|
|
1455
|
+
if (schema.$ref) {
|
|
1456
|
+
const refName = resolveRef(schema.$ref);
|
|
1457
|
+
const resolvedRefName = this.resolveSchemaAlias(refName);
|
|
1458
|
+
if (currentSchema && refName !== currentSchema && !isTopLevel) {
|
|
1459
|
+
if (!this.context.schemaDependencies.has(currentSchema)) {
|
|
1460
|
+
this.context.schemaDependencies.set(currentSchema, /* @__PURE__ */ new Set());
|
|
1461
|
+
}
|
|
1462
|
+
(_a = this.context.schemaDependencies.get(currentSchema)) == null ? void 0 : _a.add(refName);
|
|
1463
|
+
}
|
|
1464
|
+
const strippedRefName = stripPrefix(resolvedRefName, this.context.stripSchemaPrefix);
|
|
1465
|
+
const schemaName = `${toCamelCase(strippedRefName, this.context.namingOptions)}Schema`;
|
|
1466
|
+
if (currentSchema && (refName === currentSchema || this.isCircularThroughAlias(currentSchema, refName))) {
|
|
1467
|
+
const lazySchema = `z.lazy((): z.ZodTypeAny => ${schemaName})`;
|
|
1468
|
+
return wrapNullable(lazySchema, nullable);
|
|
1469
|
+
}
|
|
1470
|
+
return wrapNullable(schemaName, nullable);
|
|
1471
|
+
}
|
|
1472
|
+
if (schema.const !== void 0) {
|
|
1473
|
+
const literalValue = typeof schema.const === "string" ? `"${schema.const}"` : schema.const;
|
|
1474
|
+
const zodLiteral = `z.literal(${literalValue})`;
|
|
1475
|
+
return wrapNullable(zodLiteral, nullable);
|
|
1476
|
+
}
|
|
1477
|
+
if (schema.enum) {
|
|
1478
|
+
const allBooleans = schema.enum.every((v) => typeof v === "boolean");
|
|
1479
|
+
if (allBooleans) {
|
|
1480
|
+
const zodBoolean = "z.boolean()";
|
|
1481
|
+
return wrapNullable(zodBoolean, nullable);
|
|
1482
|
+
}
|
|
1483
|
+
const allStrings = schema.enum.every((v) => typeof v === "string");
|
|
1484
|
+
if (allStrings) {
|
|
1485
|
+
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
|
1486
|
+
const zodEnum = `z.enum([${enumValues}])`;
|
|
1487
|
+
return wrapNullable(zodEnum, nullable);
|
|
1488
|
+
}
|
|
1489
|
+
const literalValues = schema.enum.map((v) => {
|
|
1490
|
+
if (typeof v === "string") {
|
|
1491
|
+
return `z.literal("${v}")`;
|
|
1492
|
+
}
|
|
1493
|
+
return `z.literal(${v})`;
|
|
1494
|
+
}).join(", ");
|
|
1495
|
+
const zodUnion = `z.union([${literalValues}])`;
|
|
1496
|
+
return wrapNullable(zodUnion, nullable);
|
|
1497
|
+
}
|
|
1498
|
+
if (schema.allOf) {
|
|
1499
|
+
const compositionNullable = isNullable(schema, false);
|
|
1500
|
+
let composition = generateAllOf(
|
|
1501
|
+
schema.allOf,
|
|
1502
|
+
compositionNullable,
|
|
1503
|
+
{
|
|
1504
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1505
|
+
generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
|
|
1506
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1507
|
+
},
|
|
1508
|
+
currentSchema
|
|
1509
|
+
);
|
|
1510
|
+
if (schema.unevaluatedProperties !== void 0) {
|
|
1511
|
+
composition = this.applyUnevaluatedProperties(composition, schema);
|
|
1512
|
+
}
|
|
1513
|
+
return composition;
|
|
1514
|
+
}
|
|
1515
|
+
if (schema.oneOf) {
|
|
1516
|
+
const compositionNullable = isNullable(schema, false);
|
|
1517
|
+
const needsPassthrough = schema.unevaluatedProperties !== void 0;
|
|
1518
|
+
let composition = generateUnion(
|
|
1519
|
+
schema.oneOf,
|
|
1520
|
+
(_b = schema.discriminator) == null ? void 0 : _b.propertyName,
|
|
1521
|
+
compositionNullable,
|
|
1522
|
+
{
|
|
1523
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1524
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
1525
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
passthrough: needsPassthrough,
|
|
1529
|
+
discriminatorMapping: (_c = schema.discriminator) == null ? void 0 : _c.mapping
|
|
1530
|
+
},
|
|
1531
|
+
currentSchema
|
|
1532
|
+
);
|
|
1533
|
+
if (schema.unevaluatedProperties !== void 0) {
|
|
1534
|
+
composition = this.applyUnevaluatedProperties(composition, schema);
|
|
1535
|
+
}
|
|
1536
|
+
return composition;
|
|
1537
|
+
}
|
|
1538
|
+
if (schema.anyOf) {
|
|
1539
|
+
const compositionNullable = isNullable(schema, false);
|
|
1540
|
+
const needsPassthrough = schema.unevaluatedProperties !== void 0;
|
|
1541
|
+
let composition = generateUnion(
|
|
1542
|
+
schema.anyOf,
|
|
1543
|
+
(_d = schema.discriminator) == null ? void 0 : _d.propertyName,
|
|
1544
|
+
compositionNullable,
|
|
1545
|
+
{
|
|
1546
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1547
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
1548
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
passthrough: needsPassthrough,
|
|
1552
|
+
discriminatorMapping: (_e = schema.discriminator) == null ? void 0 : _e.mapping
|
|
1553
|
+
},
|
|
1554
|
+
currentSchema
|
|
1555
|
+
);
|
|
1556
|
+
if (schema.unevaluatedProperties !== void 0) {
|
|
1557
|
+
composition = this.applyUnevaluatedProperties(composition, schema);
|
|
1558
|
+
}
|
|
1559
|
+
return composition;
|
|
1560
|
+
}
|
|
1561
|
+
if (schema.not) {
|
|
1562
|
+
const notSchema = this.generatePropertySchema(schema.not, currentSchema);
|
|
1563
|
+
let baseValidation;
|
|
1564
|
+
if (schema.type || schema.properties || schema.items) {
|
|
1565
|
+
const { not: _, ...baseSchema } = schema;
|
|
1566
|
+
baseValidation = this.generatePropertySchema(baseSchema, currentSchema);
|
|
1567
|
+
} else {
|
|
1568
|
+
baseValidation = "z.unknown()";
|
|
1569
|
+
}
|
|
1570
|
+
const refined = `${baseValidation}.refine((val) => !${notSchema}.safeParse(val).success, { message: "Value must not match the excluded schema" })`;
|
|
1571
|
+
return wrapNullable(refined, nullable);
|
|
1572
|
+
}
|
|
1573
|
+
let validation = "";
|
|
1574
|
+
const primaryType = getPrimaryType(schema);
|
|
1575
|
+
switch (primaryType) {
|
|
1576
|
+
case "string":
|
|
1577
|
+
validation = generateStringValidation(schema, this.context.useDescribe, {
|
|
1578
|
+
dateTimeValidation: this.context.dateTimeValidation,
|
|
1579
|
+
patternCache: this.context.patternCache
|
|
1580
|
+
});
|
|
1581
|
+
break;
|
|
1582
|
+
case "number":
|
|
1583
|
+
validation = generateNumberValidation(schema, false, this.context.useDescribe);
|
|
1584
|
+
break;
|
|
1585
|
+
case "integer":
|
|
1586
|
+
validation = generateNumberValidation(schema, true, this.context.useDescribe);
|
|
1587
|
+
break;
|
|
1588
|
+
case "boolean":
|
|
1589
|
+
validation = "z.boolean()";
|
|
1590
|
+
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1591
|
+
break;
|
|
1592
|
+
case "array":
|
|
1593
|
+
validation = generateArrayValidation(schema, {
|
|
1594
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1595
|
+
useDescribe: this.context.useDescribe,
|
|
1596
|
+
currentSchema
|
|
1597
|
+
});
|
|
1598
|
+
break;
|
|
1599
|
+
case "object":
|
|
1600
|
+
if (schema.properties || schema.required || schema.minProperties !== void 0 || schema.maxProperties !== void 0 || schema.patternProperties || schema.propertyNames) {
|
|
1601
|
+
validation = generateObjectSchema(
|
|
1602
|
+
schema,
|
|
1603
|
+
{
|
|
1604
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
1605
|
+
shouldIncludeProperty: this.shouldIncludeProperty.bind(this),
|
|
1606
|
+
mode: this.context.mode,
|
|
1607
|
+
includeDescriptions: this.context.includeDescriptions,
|
|
1608
|
+
useDescribe: this.context.useDescribe
|
|
1609
|
+
},
|
|
1610
|
+
currentSchema
|
|
1611
|
+
);
|
|
1612
|
+
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1613
|
+
} else {
|
|
1614
|
+
switch (this.context.emptyObjectBehavior) {
|
|
1615
|
+
case "strict":
|
|
1616
|
+
validation = "z.strictObject({})";
|
|
1617
|
+
break;
|
|
1618
|
+
case "loose":
|
|
1619
|
+
validation = "z.looseObject({})";
|
|
1620
|
+
break;
|
|
1621
|
+
default:
|
|
1622
|
+
validation = "z.record(z.string(), z.unknown())";
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1626
|
+
}
|
|
1627
|
+
break;
|
|
1628
|
+
default:
|
|
1629
|
+
validation = "z.unknown()";
|
|
1630
|
+
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1631
|
+
}
|
|
1632
|
+
const result = wrapNullable(validation, nullable);
|
|
1633
|
+
if (isCacheable) {
|
|
1634
|
+
const cacheKey = JSON.stringify({ schema, type: this.context.schemaType, mode: this.context.mode });
|
|
1635
|
+
this.schemaCache.set(cacheKey, result);
|
|
1636
|
+
}
|
|
1637
|
+
return result;
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Generate inline object shape for use with .extend()
|
|
1641
|
+
* Returns just the shape object literal: { prop1: z.string(), prop2: z.number() }
|
|
1642
|
+
*
|
|
1643
|
+
* This method is specifically for allOf compositions where we need to pass
|
|
1644
|
+
* the shape directly to .extend() instead of using z.object({...}).shape.
|
|
1645
|
+
* This avoids the .nullable().shape bug when inline objects have nullable: true.
|
|
1646
|
+
*
|
|
1647
|
+
* According to Zod docs (https://zod.dev/api?id=extend):
|
|
1648
|
+
* - .extend() accepts an object of shape definitions
|
|
1649
|
+
* - e.g., baseSchema.extend({ prop: z.string() })
|
|
1650
|
+
*/
|
|
1651
|
+
generateInlineObjectShape(schema, currentSchema) {
|
|
1652
|
+
const required = new Set(schema.required || []);
|
|
1653
|
+
const properties = [];
|
|
1654
|
+
if (schema.properties) {
|
|
1655
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
1656
|
+
if (!this.shouldIncludeProperty(propSchema)) {
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
const isRequired = required.has(propName);
|
|
1660
|
+
const zodSchema = this.generatePropertySchema(propSchema, currentSchema);
|
|
1661
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
1662
|
+
const quotedPropName = validIdentifier.test(propName) ? propName : `"${propName}"`;
|
|
1663
|
+
let propertyDef = `${quotedPropName}: ${zodSchema}`;
|
|
1664
|
+
if (!isRequired) {
|
|
1665
|
+
propertyDef += ".optional()";
|
|
1666
|
+
}
|
|
1667
|
+
properties.push(propertyDef);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
if (properties.length === 0) {
|
|
1671
|
+
return "{}";
|
|
1672
|
+
}
|
|
1673
|
+
return `{
|
|
1674
|
+
${properties.map((p) => ` ${p}`).join(",\n")}
|
|
1675
|
+
}`;
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
// Performance optimization: Lookup table for faster inclusion checks
|
|
1679
|
+
_PropertyGenerator.INCLUSION_RULES = {
|
|
1680
|
+
request: (schema) => !schema.readOnly,
|
|
1681
|
+
response: (schema) => !schema.writeOnly,
|
|
1682
|
+
all: () => true
|
|
1683
|
+
};
|
|
1684
|
+
var PropertyGenerator = _PropertyGenerator;
|
|
1685
|
+
|
|
190
1686
|
// src/utils/config-schemas.ts
|
|
191
1687
|
var import_zod = require("zod");
|
|
192
1688
|
var RequestResponseOptionsSchema = import_zod.z.strictObject({
|
|
@@ -273,95 +1769,8 @@ function getResponseParseMethod(contentType, fallback = "text") {
|
|
|
273
1769
|
return { method: fallback, isUnknown: true };
|
|
274
1770
|
}
|
|
275
1771
|
|
|
276
|
-
// src/utils/lru-cache.ts
|
|
277
|
-
var LRUCache = class {
|
|
278
|
-
constructor(maxSize) {
|
|
279
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
280
|
-
this.maxSize = maxSize;
|
|
281
|
-
}
|
|
282
|
-
get capacity() {
|
|
283
|
-
return this.maxSize;
|
|
284
|
-
}
|
|
285
|
-
get(key) {
|
|
286
|
-
if (!this.cache.has(key)) return void 0;
|
|
287
|
-
const value = this.cache.get(key);
|
|
288
|
-
if (value === void 0) return void 0;
|
|
289
|
-
this.cache.delete(key);
|
|
290
|
-
this.cache.set(key, value);
|
|
291
|
-
return value;
|
|
292
|
-
}
|
|
293
|
-
set(key, value) {
|
|
294
|
-
if (this.cache.has(key)) {
|
|
295
|
-
this.cache.delete(key);
|
|
296
|
-
} else if (this.cache.size >= this.maxSize) {
|
|
297
|
-
const firstKey = this.cache.keys().next().value;
|
|
298
|
-
if (firstKey !== void 0) {
|
|
299
|
-
this.cache.delete(firstKey);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
this.cache.set(key, value);
|
|
303
|
-
}
|
|
304
|
-
has(key) {
|
|
305
|
-
return this.cache.has(key);
|
|
306
|
-
}
|
|
307
|
-
clear() {
|
|
308
|
-
this.cache.clear();
|
|
309
|
-
}
|
|
310
|
-
size() {
|
|
311
|
-
return this.cache.size;
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
// src/utils/name-utils.ts
|
|
316
|
-
function sanitizeIdentifier(str) {
|
|
317
|
-
return str.replace(/[^a-zA-Z0-9._\-\s]+/g, "_");
|
|
318
|
-
}
|
|
319
|
-
function toCamelCase(str, options) {
|
|
320
|
-
const sanitized = sanitizeIdentifier(str);
|
|
321
|
-
const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
|
|
322
|
-
let name;
|
|
323
|
-
if (words.length === 0) {
|
|
324
|
-
name = str.charAt(0).toLowerCase() + str.slice(1);
|
|
325
|
-
} else if (words.length === 1) {
|
|
326
|
-
name = words[0].charAt(0).toLowerCase() + words[0].slice(1);
|
|
327
|
-
} else {
|
|
328
|
-
name = words[0].charAt(0).toLowerCase() + words[0].slice(1) + words.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
329
|
-
}
|
|
330
|
-
if (options == null ? void 0 : options.prefix) {
|
|
331
|
-
const prefix = options.prefix.charAt(0).toLowerCase() + options.prefix.slice(1);
|
|
332
|
-
name = prefix + name.charAt(0).toUpperCase() + name.slice(1);
|
|
333
|
-
}
|
|
334
|
-
if (options == null ? void 0 : options.suffix) {
|
|
335
|
-
const suffix = options.suffix.charAt(0).toUpperCase() + options.suffix.slice(1);
|
|
336
|
-
name = name + suffix;
|
|
337
|
-
}
|
|
338
|
-
return name;
|
|
339
|
-
}
|
|
340
|
-
function toPascalCase(str) {
|
|
341
|
-
const stringValue = String(str);
|
|
342
|
-
const isAlreadyValidCase = /^[a-zA-Z][a-zA-Z0-9]*$/.test(stringValue);
|
|
343
|
-
if (isAlreadyValidCase) {
|
|
344
|
-
return stringValue.charAt(0).toUpperCase() + stringValue.slice(1);
|
|
345
|
-
}
|
|
346
|
-
const sanitized = sanitizeIdentifier(stringValue);
|
|
347
|
-
const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
|
|
348
|
-
let result;
|
|
349
|
-
if (words.length === 0) {
|
|
350
|
-
result = "Value";
|
|
351
|
-
} else {
|
|
352
|
-
result = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
353
|
-
}
|
|
354
|
-
if (/^\d/.test(result)) {
|
|
355
|
-
result = `N${result}`;
|
|
356
|
-
}
|
|
357
|
-
if (!result || /^_+$/.test(result)) {
|
|
358
|
-
return "Value";
|
|
359
|
-
}
|
|
360
|
-
return result;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
1772
|
// src/utils/operation-filters.ts
|
|
364
|
-
var
|
|
1773
|
+
var import_minimatch2 = require("minimatch");
|
|
365
1774
|
function createFilterStatistics() {
|
|
366
1775
|
return {
|
|
367
1776
|
totalOperations: 0,
|
|
@@ -380,7 +1789,7 @@ function matchesAnyPattern(value, patterns) {
|
|
|
380
1789
|
if (!value) {
|
|
381
1790
|
return false;
|
|
382
1791
|
}
|
|
383
|
-
return patterns.some((pattern) => (0,
|
|
1792
|
+
return patterns.some((pattern) => (0, import_minimatch2.minimatch)(value, pattern));
|
|
384
1793
|
}
|
|
385
1794
|
function containsAny(arr, values) {
|
|
386
1795
|
if (!values || values.length === 0) {
|
|
@@ -494,82 +1903,8 @@ function formatFilterStatistics(stats) {
|
|
|
494
1903
|
return lines.join("\n");
|
|
495
1904
|
}
|
|
496
1905
|
|
|
497
|
-
// src/utils/pattern-utils.ts
|
|
498
|
-
var import_minimatch2 = require("minimatch");
|
|
499
|
-
function isValidGlobPattern(pattern) {
|
|
500
|
-
try {
|
|
501
|
-
new import_minimatch2.minimatch.Minimatch(pattern);
|
|
502
|
-
return true;
|
|
503
|
-
} catch {
|
|
504
|
-
return false;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
function isGlobPattern(pattern) {
|
|
508
|
-
return /[*?[\]{}!]/.test(pattern);
|
|
509
|
-
}
|
|
510
|
-
function stripPrefix(input, pattern, ensureLeadingChar) {
|
|
511
|
-
if (!pattern) {
|
|
512
|
-
return input;
|
|
513
|
-
}
|
|
514
|
-
if (isGlobPattern(pattern) && !isValidGlobPattern(pattern)) {
|
|
515
|
-
console.warn(`\u26A0\uFE0F Invalid glob pattern "${pattern}": Pattern is malformed`);
|
|
516
|
-
return input;
|
|
517
|
-
}
|
|
518
|
-
if (isGlobPattern(pattern)) {
|
|
519
|
-
let longestMatch = -1;
|
|
520
|
-
for (let i = 1; i <= input.length; i++) {
|
|
521
|
-
const testPrefix = input.substring(0, i);
|
|
522
|
-
if ((0, import_minimatch2.minimatch)(testPrefix, pattern)) {
|
|
523
|
-
longestMatch = i;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (longestMatch > 0) {
|
|
527
|
-
const stripped = input.substring(longestMatch);
|
|
528
|
-
if (ensureLeadingChar) {
|
|
529
|
-
if (stripped === "") {
|
|
530
|
-
return ensureLeadingChar;
|
|
531
|
-
}
|
|
532
|
-
if (!stripped.startsWith(ensureLeadingChar)) {
|
|
533
|
-
return `${ensureLeadingChar}${stripped}`;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return stripped === "" && !ensureLeadingChar ? input : stripped;
|
|
537
|
-
}
|
|
538
|
-
return input;
|
|
539
|
-
}
|
|
540
|
-
if (input.startsWith(pattern)) {
|
|
541
|
-
const stripped = input.substring(pattern.length);
|
|
542
|
-
if (ensureLeadingChar) {
|
|
543
|
-
if (stripped === "") {
|
|
544
|
-
return ensureLeadingChar;
|
|
545
|
-
}
|
|
546
|
-
if (!stripped.startsWith(ensureLeadingChar)) {
|
|
547
|
-
return `${ensureLeadingChar}${stripped}`;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return stripped;
|
|
551
|
-
}
|
|
552
|
-
return input;
|
|
553
|
-
}
|
|
554
|
-
function stripPathPrefix(path, pattern) {
|
|
555
|
-
if (!pattern) {
|
|
556
|
-
return path;
|
|
557
|
-
}
|
|
558
|
-
if (!isGlobPattern(pattern)) {
|
|
559
|
-
let normalizedPattern = pattern.trim();
|
|
560
|
-
if (!normalizedPattern.startsWith("/")) {
|
|
561
|
-
normalizedPattern = `/${normalizedPattern}`;
|
|
562
|
-
}
|
|
563
|
-
if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
|
|
564
|
-
normalizedPattern = normalizedPattern.slice(0, -1);
|
|
565
|
-
}
|
|
566
|
-
return stripPrefix(path, normalizedPattern, "/");
|
|
567
|
-
}
|
|
568
|
-
return stripPrefix(path, pattern, "/");
|
|
569
|
-
}
|
|
570
|
-
|
|
571
1906
|
// src/utils/ref-resolver.ts
|
|
572
|
-
function
|
|
1907
|
+
function resolveRef2(obj, spec, maxDepth = 10) {
|
|
573
1908
|
var _a, _b, _c, _d;
|
|
574
1909
|
if (!obj || typeof obj !== "object" || maxDepth <= 0) return obj;
|
|
575
1910
|
if (!obj.$ref) return obj;
|
|
@@ -594,20 +1929,20 @@ function resolveRef(obj, spec, maxDepth = 10) {
|
|
|
594
1929
|
}
|
|
595
1930
|
if (resolved) {
|
|
596
1931
|
if (resolved.$ref) {
|
|
597
|
-
return
|
|
1932
|
+
return resolveRef2(resolved, spec, maxDepth - 1);
|
|
598
1933
|
}
|
|
599
1934
|
return resolved;
|
|
600
1935
|
}
|
|
601
1936
|
return obj;
|
|
602
1937
|
}
|
|
603
1938
|
function resolveParameterRef(param, spec) {
|
|
604
|
-
return
|
|
1939
|
+
return resolveRef2(param, spec);
|
|
605
1940
|
}
|
|
606
1941
|
function resolveRequestBodyRef(requestBody, spec) {
|
|
607
|
-
return
|
|
1942
|
+
return resolveRef2(requestBody, spec);
|
|
608
1943
|
}
|
|
609
1944
|
function resolveResponseRef(response, spec) {
|
|
610
|
-
return
|
|
1945
|
+
return resolveRef2(response, spec);
|
|
611
1946
|
}
|
|
612
1947
|
function mergeParameters(pathParams, operationParams, spec) {
|
|
613
1948
|
const resolvedPathParams = (pathParams || []).map((p) => resolveParameterRef(p, spec));
|
|
@@ -627,14 +1962,6 @@ function mergeParameters(pathParams, operationParams, spec) {
|
|
|
627
1962
|
return merged;
|
|
628
1963
|
}
|
|
629
1964
|
|
|
630
|
-
// src/utils/string-utils.ts
|
|
631
|
-
function escapePattern(str) {
|
|
632
|
-
return str.replace(/\//g, "\\/");
|
|
633
|
-
}
|
|
634
|
-
function escapeJSDoc(str) {
|
|
635
|
-
return str.replace(/\*\//g, "*\\/");
|
|
636
|
-
}
|
|
637
|
-
|
|
638
1965
|
// src/utils/typescript-loader.ts
|
|
639
1966
|
function createTypeScriptLoader() {
|
|
640
1967
|
return async (filepath) => {
|
|
@@ -668,73 +1995,13 @@ function createTypeScriptLoader() {
|
|
|
668
1995
|
}
|
|
669
1996
|
};
|
|
670
1997
|
}
|
|
671
|
-
|
|
672
|
-
// src/validators/string-validator.ts
|
|
673
|
-
var PATTERN_CACHE = new LRUCache(1e3);
|
|
674
|
-
var DEFAULT_FORMAT_MAP = {
|
|
675
|
-
uuid: "z.uuid()",
|
|
676
|
-
email: "z.email()",
|
|
677
|
-
uri: "z.url()",
|
|
678
|
-
url: "z.url()",
|
|
679
|
-
"uri-reference": 'z.string().refine((val) => !/\\s/.test(val), { message: "Must be a valid URI reference" })',
|
|
680
|
-
hostname: 'z.string().refine((val) => /^(?=.{1,253}$)(?:(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)*(?!-)[A-Za-z0-9-]{1,63}(?<!-)$/.test(val), { message: "Must be a valid hostname" })',
|
|
681
|
-
byte: "z.base64()",
|
|
682
|
-
binary: "z.string()",
|
|
683
|
-
date: "z.iso.date()",
|
|
684
|
-
time: "z.iso.time()",
|
|
685
|
-
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" })',
|
|
686
|
-
ipv4: "z.ipv4()",
|
|
687
|
-
ipv6: "z.ipv6()",
|
|
688
|
-
emoji: "z.emoji()",
|
|
689
|
-
base64: "z.base64()",
|
|
690
|
-
base64url: "z.base64url()",
|
|
691
|
-
nanoid: "z.nanoid()",
|
|
692
|
-
cuid: "z.cuid()",
|
|
693
|
-
cuid2: "z.cuid2()",
|
|
694
|
-
ulid: "z.ulid()",
|
|
695
|
-
cidr: "z.cidrv4()",
|
|
696
|
-
// Default to v4
|
|
697
|
-
cidrv4: "z.cidrv4()",
|
|
698
|
-
cidrv6: "z.cidrv6()",
|
|
699
|
-
"json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
|
|
700
|
-
"relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
|
|
701
|
-
};
|
|
702
|
-
var FORMAT_MAP = {
|
|
703
|
-
...DEFAULT_FORMAT_MAP,
|
|
704
|
-
"date-time": "z.iso.datetime()"
|
|
705
|
-
};
|
|
706
|
-
function configureDateTimeFormat(pattern) {
|
|
707
|
-
if (!pattern) {
|
|
708
|
-
FORMAT_MAP["date-time"] = "z.iso.datetime()";
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
712
|
-
if (patternStr === "") {
|
|
713
|
-
FORMAT_MAP["date-time"] = "z.iso.datetime()";
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
try {
|
|
717
|
-
new RegExp(patternStr);
|
|
718
|
-
} catch (error) {
|
|
719
|
-
throw new Error(
|
|
720
|
-
`Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
const escapedPattern = escapePattern(patternStr);
|
|
724
|
-
FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
|
|
725
|
-
}
|
|
726
|
-
function resetFormatMap() {
|
|
727
|
-
FORMAT_MAP = {
|
|
728
|
-
...DEFAULT_FORMAT_MAP,
|
|
729
|
-
"date-time": "z.iso.datetime()"
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
1998
|
// Annotate the CommonJS export names for ESM import in node:
|
|
733
1999
|
0 && (module.exports = {
|
|
734
2000
|
LRUCache,
|
|
735
2001
|
OperationFiltersSchema,
|
|
2002
|
+
PropertyGenerator,
|
|
736
2003
|
RequestResponseOptionsSchema,
|
|
737
|
-
|
|
2004
|
+
buildDateTimeValidation,
|
|
738
2005
|
createFilterStatistics,
|
|
739
2006
|
createTypeScriptLoader,
|
|
740
2007
|
escapeJSDoc,
|
|
@@ -744,7 +2011,6 @@ function resetFormatMap() {
|
|
|
744
2011
|
getBatchExitCode,
|
|
745
2012
|
getResponseParseMethod,
|
|
746
2013
|
mergeParameters,
|
|
747
|
-
resetFormatMap,
|
|
748
2014
|
resolveParameterRef,
|
|
749
2015
|
resolveRef,
|
|
750
2016
|
resolveRequestBodyRef,
|