@kubb/adapter-oas 5.13.1 → 5.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +455 -225
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +456 -226
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var compatibility = {
|
|
|
36
36
|
};
|
|
37
37
|
//#endregion
|
|
38
38
|
//#region package.json
|
|
39
|
-
var version = "5.
|
|
39
|
+
var version = "5.14.1";
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/constants.ts
|
|
42
42
|
/**
|
|
@@ -387,6 +387,198 @@ async function read(path) {
|
|
|
387
387
|
return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
|
|
388
388
|
}
|
|
389
389
|
//#endregion
|
|
390
|
+
//#region src/oas.ts
|
|
391
|
+
/**
|
|
392
|
+
* Returns `true` when a schema should be treated as nullable.
|
|
393
|
+
*
|
|
394
|
+
* Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
|
|
395
|
+
* `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
|
|
396
|
+
*/
|
|
397
|
+
function isNullable(schema) {
|
|
398
|
+
if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
|
|
399
|
+
const schemaType = schema?.type;
|
|
400
|
+
if (schemaType === "null") return true;
|
|
401
|
+
if (Array.isArray(schemaType)) return schemaType.includes("null");
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
|
|
406
|
+
*/
|
|
407
|
+
function pickExtensions(source) {
|
|
408
|
+
const entries = Object.entries(source).filter(([key]) => key.startsWith("x-"));
|
|
409
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
|
|
413
|
+
*/
|
|
414
|
+
function isReference(obj) {
|
|
415
|
+
return !!obj && typeof obj === "object" && "$ref" in obj;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
|
|
419
|
+
* excluding the Swagger 2 string form.
|
|
420
|
+
*/
|
|
421
|
+
function isDiscriminator(obj) {
|
|
422
|
+
const record = obj;
|
|
423
|
+
return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Returns `true` when a schema is a raw binary payload.
|
|
427
|
+
*/
|
|
428
|
+
function isBinary(schema) {
|
|
429
|
+
return schema.contentMediaType === "application/octet-stream" && schema.contentEncoding === void 0 && (schema.type === void 0 || schema.type === "string");
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Returns the binary schema for a non-JSON entry the OAS 3.1 upgrade emptied out, or `undefined`
|
|
433
|
+
* when the entry carries a schema of its own. The upgrade rewrites `format: 'binary'` into
|
|
434
|
+
* `contentMediaType` and drops it whenever it only repeats the media type key, for any binary
|
|
435
|
+
* media type (`application/octet-stream`, `application/pdf`, `image/png`, ...), not just
|
|
436
|
+
* `application/octet-stream`. Without the fallback such a body resolves to the empty schema type
|
|
437
|
+
* instead of a blob.
|
|
438
|
+
*/
|
|
439
|
+
function getBinaryFallbackSchema(mediaType, schema) {
|
|
440
|
+
if (!mediaType || isJsonMimeType(mediaType)) return void 0;
|
|
441
|
+
if (schema && Object.keys(schema).length > 0) return void 0;
|
|
442
|
+
return {
|
|
443
|
+
type: "string",
|
|
444
|
+
contentMediaType: "application/octet-stream"
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* MIME type fragments that mark a media type as JSON-like.
|
|
449
|
+
*
|
|
450
|
+
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
451
|
+
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
452
|
+
*/
|
|
453
|
+
const jsonMimeFragments = [
|
|
454
|
+
"application/json",
|
|
455
|
+
"application/x-json",
|
|
456
|
+
"text/json",
|
|
457
|
+
"text/x-json",
|
|
458
|
+
"+json"
|
|
459
|
+
];
|
|
460
|
+
/**
|
|
461
|
+
* Returns `true` when a media type string is JSON-like.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* isJsonMimeType('application/json') // true
|
|
466
|
+
* isJsonMimeType('application/vnd.api+json') // true
|
|
467
|
+
* isJsonMimeType('multipart/form-data') // false
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
function isJsonMimeType(mimeType) {
|
|
471
|
+
return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
|
|
472
|
+
}
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/refs.ts
|
|
475
|
+
/**
|
|
476
|
+
* Creates the `$ref` resolution service for one document. One pointer walk backs every method.
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* ```ts
|
|
480
|
+
* const refs = createRefs(document)
|
|
481
|
+
* refs.resolve<SchemaObject>('#/components/schemas/Pet')
|
|
482
|
+
* refs.resolve<SchemaObject>('#/components/schemas/Pet', { report: false })
|
|
483
|
+
* refs.resolveNode('#/components/schemas/Pet', parseSchema)
|
|
484
|
+
* refs.deref<ResponseObject>(operation.schema.responses?.['200'])
|
|
485
|
+
* refs.derefKeepingRef(parameter)
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
function createRefs(document) {
|
|
489
|
+
const pointerCache = /* @__PURE__ */ new Map();
|
|
490
|
+
const resolvedNodeCache = /* @__PURE__ */ new Map();
|
|
491
|
+
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
492
|
+
function walkPointer($ref) {
|
|
493
|
+
const trimmed = $ref.trim();
|
|
494
|
+
if (trimmed === "" || !trimmed.startsWith("#")) return {
|
|
495
|
+
applicable: false,
|
|
496
|
+
value: null
|
|
497
|
+
};
|
|
498
|
+
const pointer = trimmed.substring(1);
|
|
499
|
+
if (pointerCache.has(pointer)) return {
|
|
500
|
+
applicable: true,
|
|
501
|
+
value: pointerCache.get(pointer)
|
|
502
|
+
};
|
|
503
|
+
const current = pointer.split("/").filter(Boolean).map((token) => globalThis.decodeURIComponent(token).replaceAll("~1", "/").replaceAll("~0", "~")).reduce((obj, key) => obj?.[key], document);
|
|
504
|
+
if (current) pointerCache.set(pointer, current);
|
|
505
|
+
return {
|
|
506
|
+
applicable: true,
|
|
507
|
+
value: current ?? null
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Resolves a local `#/...` JSON pointer. Returns `null` for an empty or non-local ref.
|
|
512
|
+
* `report: true` (default) reports a `refNotFound` diagnostic into the active build (or throws
|
|
513
|
+
* outside one) when the pointer cannot be resolved. `report: false` resolves to `null` silently,
|
|
514
|
+
* for a speculative lookup where a missing ref is not an error.
|
|
515
|
+
*/
|
|
516
|
+
function resolve(refPath, options) {
|
|
517
|
+
const { applicable, value } = walkPointer(refPath);
|
|
518
|
+
if (!applicable || value || options?.report === false) return value;
|
|
519
|
+
const diagnostic = {
|
|
520
|
+
code: _kubb_kit.Diagnostics.code.refNotFound,
|
|
521
|
+
severity: "error",
|
|
522
|
+
message: `Could not find a definition for ${refPath}.`,
|
|
523
|
+
help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
|
|
524
|
+
location: {
|
|
525
|
+
kind: "schema",
|
|
526
|
+
pointer: refPath,
|
|
527
|
+
ref: refPath
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
if (!_kubb_kit.Diagnostics.report(diagnostic)) throw new _kubb_kit.Diagnostics.Error(diagnostic);
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Resolves a `$ref` to its parsed node via `parse`, guarding against cycles and memoizing per
|
|
535
|
+
* instance. Returns `null` when the ref is currently being resolved (a cycle) or cannot be
|
|
536
|
+
* resolved (e.g. a minimal document in a unit test).
|
|
537
|
+
*/
|
|
538
|
+
function resolveNode(refPath, parse) {
|
|
539
|
+
if (resolvingRefs.has(refPath)) return null;
|
|
540
|
+
if (!resolvedNodeCache.has(refPath)) {
|
|
541
|
+
let resolved = null;
|
|
542
|
+
try {
|
|
543
|
+
const referenced = resolve(refPath);
|
|
544
|
+
if (referenced) {
|
|
545
|
+
resolvingRefs.add(refPath);
|
|
546
|
+
resolved = parse({ schema: referenced });
|
|
547
|
+
resolvingRefs.delete(refPath);
|
|
548
|
+
}
|
|
549
|
+
} catch {}
|
|
550
|
+
resolvedNodeCache.set(refPath, resolved);
|
|
551
|
+
}
|
|
552
|
+
return resolvedNodeCache.get(refPath) ?? null;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Resolves a `$ref` value without mutating anything: when `value` holds a `$ref`, returns the
|
|
556
|
+
* resolved target. Returns `null` when the value is empty, cannot be resolved, or is still a
|
|
557
|
+
* `$ref` after resolving (e.g. a document with no component registry). A non-`$ref` value is
|
|
558
|
+
* returned as-is.
|
|
559
|
+
*/
|
|
560
|
+
function deref(value) {
|
|
561
|
+
if (!isReference(value)) return value ? value : null;
|
|
562
|
+
const resolved = resolve(value.$ref);
|
|
563
|
+
return resolved && !isReference(resolved) ? resolved : null;
|
|
564
|
+
}
|
|
565
|
+
/** Resolves a `$ref` object but keeps the `$ref` field on the result; other values pass through. */
|
|
566
|
+
function derefKeepingRef(value) {
|
|
567
|
+
if (isReference(value)) return {
|
|
568
|
+
...value,
|
|
569
|
+
...resolve(value.$ref),
|
|
570
|
+
$ref: value.$ref
|
|
571
|
+
};
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
resolve,
|
|
576
|
+
resolveNode,
|
|
577
|
+
deref,
|
|
578
|
+
derefKeepingRef
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
//#endregion
|
|
390
582
|
//#region src/load/source.ts
|
|
391
583
|
const urlRegExp = /^https?:\/+/i;
|
|
392
584
|
/**
|
|
@@ -507,6 +699,22 @@ function hasExternalRef(node) {
|
|
|
507
699
|
return Object.values(node).some(hasExternalRef);
|
|
508
700
|
}
|
|
509
701
|
/**
|
|
702
|
+
* Replaces each `$ref` into `#/paths/...` with a copy of its target, in place.
|
|
703
|
+
*
|
|
704
|
+
* The Swagger 2.0 upgrade moves `responses/200/schema` under `content`, so a ref to the old location
|
|
705
|
+
* would no longer resolve. A ref that points back into itself is kept, since inlining it never ends.
|
|
706
|
+
*/
|
|
707
|
+
function inlinePathRefs(node, resolve, visiting = []) {
|
|
708
|
+
if (!node || typeof node !== "object") return node;
|
|
709
|
+
const ref = node.$ref;
|
|
710
|
+
if (typeof ref === "string" && ref.startsWith("#/paths/") && !visiting.includes(ref)) {
|
|
711
|
+
const target = resolve(ref);
|
|
712
|
+
return target && typeof target === "object" ? inlinePathRefs(structuredClone(target), resolve, [...visiting, ref]) : node;
|
|
713
|
+
}
|
|
714
|
+
for (const [key, value] of Object.entries(node)) node[key] = inlinePathRefs(value, resolve, visiting);
|
|
715
|
+
return node;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
510
718
|
* Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
|
|
511
719
|
*
|
|
512
720
|
* External file schemas are hoisted into named `components.schemas` entries, so a property
|
|
@@ -564,6 +772,17 @@ async function parseFromConfig(source) {
|
|
|
564
772
|
await assertInputExists(source.path);
|
|
565
773
|
return bundleDocument(source.path);
|
|
566
774
|
})();
|
|
775
|
+
if (raw && typeof raw === "object") {
|
|
776
|
+
const document = raw;
|
|
777
|
+
for (const key of ["swagger", "openapi"]) {
|
|
778
|
+
const version = document[key];
|
|
779
|
+
if (typeof version === "number") document[key] = Number.isInteger(version) ? version.toFixed(1) : String(version);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (raw && typeof raw === "object" && "swagger" in raw) {
|
|
783
|
+
const refs = createRefs(raw);
|
|
784
|
+
inlinePathRefs(raw, (ref) => refs.resolve(ref, { report: false }));
|
|
785
|
+
}
|
|
567
786
|
try {
|
|
568
787
|
return (0, _scalar_openapi_upgrader.upgrade)(raw, "3.1");
|
|
569
788
|
} catch (error) {
|
|
@@ -660,90 +879,6 @@ async function validateDocumentInWorker(document) {
|
|
|
660
879
|
if (!message.valid) reportValidationErrors(message);
|
|
661
880
|
}
|
|
662
881
|
//#endregion
|
|
663
|
-
//#region src/oas.ts
|
|
664
|
-
/**
|
|
665
|
-
* Returns `true` when a schema should be treated as nullable.
|
|
666
|
-
*
|
|
667
|
-
* Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
|
|
668
|
-
* `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
|
|
669
|
-
*/
|
|
670
|
-
function isNullable(schema) {
|
|
671
|
-
if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
|
|
672
|
-
const schemaType = schema?.type;
|
|
673
|
-
if (schemaType === "null") return true;
|
|
674
|
-
if (Array.isArray(schemaType)) return schemaType.includes("null");
|
|
675
|
-
return false;
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
|
|
679
|
-
*/
|
|
680
|
-
function pickExtensions(source) {
|
|
681
|
-
const entries = Object.entries(source).filter(([key]) => key.startsWith("x-"));
|
|
682
|
-
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
683
|
-
}
|
|
684
|
-
/**
|
|
685
|
-
* Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
|
|
686
|
-
*/
|
|
687
|
-
function isReference(obj) {
|
|
688
|
-
return !!obj && typeof obj === "object" && "$ref" in obj;
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
|
|
692
|
-
* excluding the Swagger 2 string form.
|
|
693
|
-
*/
|
|
694
|
-
function isDiscriminator(obj) {
|
|
695
|
-
const record = obj;
|
|
696
|
-
return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Returns `true` when a schema is a raw binary payload.
|
|
700
|
-
*/
|
|
701
|
-
function isBinary(schema) {
|
|
702
|
-
return schema.contentMediaType === "application/octet-stream" && schema.contentEncoding === void 0 && (schema.type === void 0 || schema.type === "string");
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Returns the binary schema for a non-JSON entry the OAS 3.1 upgrade emptied out, or `undefined`
|
|
706
|
-
* when the entry carries a schema of its own. The upgrade rewrites `format: 'binary'` into
|
|
707
|
-
* `contentMediaType` and drops it whenever it only repeats the media type key, for any binary
|
|
708
|
-
* media type (`application/octet-stream`, `application/pdf`, `image/png`, ...), not just
|
|
709
|
-
* `application/octet-stream`. Without the fallback such a body resolves to the empty schema type
|
|
710
|
-
* instead of a blob.
|
|
711
|
-
*/
|
|
712
|
-
function getBinaryFallbackSchema(mediaType, schema) {
|
|
713
|
-
if (!mediaType || isJsonMimeType(mediaType)) return void 0;
|
|
714
|
-
if (schema && Object.keys(schema).length > 0) return void 0;
|
|
715
|
-
return {
|
|
716
|
-
type: "string",
|
|
717
|
-
contentMediaType: "application/octet-stream"
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* MIME type fragments that mark a media type as JSON-like.
|
|
722
|
-
*
|
|
723
|
-
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
724
|
-
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
725
|
-
*/
|
|
726
|
-
const jsonMimeFragments = [
|
|
727
|
-
"application/json",
|
|
728
|
-
"application/x-json",
|
|
729
|
-
"text/json",
|
|
730
|
-
"text/x-json",
|
|
731
|
-
"+json"
|
|
732
|
-
];
|
|
733
|
-
/**
|
|
734
|
-
* Returns `true` when a media type string is JSON-like.
|
|
735
|
-
*
|
|
736
|
-
* @example
|
|
737
|
-
* ```ts
|
|
738
|
-
* isJsonMimeType('application/json') // true
|
|
739
|
-
* isJsonMimeType('application/vnd.api+json') // true
|
|
740
|
-
* isJsonMimeType('multipart/form-data') // false
|
|
741
|
-
* ```
|
|
742
|
-
*/
|
|
743
|
-
function isJsonMimeType(mimeType) {
|
|
744
|
-
return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
|
|
745
|
-
}
|
|
746
|
-
//#endregion
|
|
747
882
|
//#region src/model/components.ts
|
|
748
883
|
/**
|
|
749
884
|
* Extracts the inline schema from a media-type `content` map, with the media type it sits under.
|
|
@@ -1172,6 +1307,19 @@ function getParameters({ operation, refs }) {
|
|
|
1172
1307
|
for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
|
|
1173
1308
|
return Array.from(paramMap.values());
|
|
1174
1309
|
}
|
|
1310
|
+
/** The `encoding` of a media type, reduced to the entries that set a field Kubb carries. */
|
|
1311
|
+
function getEncoding(encoding) {
|
|
1312
|
+
const entries = Object.entries(encoding ?? {}).flatMap(([property, { contentType, style, explode, allowReserved }]) => {
|
|
1313
|
+
const node = {
|
|
1314
|
+
...contentType !== void 0 ? { contentType } : {},
|
|
1315
|
+
...style !== void 0 ? { style } : {},
|
|
1316
|
+
...explode !== void 0 ? { explode } : {},
|
|
1317
|
+
...allowReserved !== void 0 ? { allowReserved } : {}
|
|
1318
|
+
};
|
|
1319
|
+
return Object.keys(node).length ? [[property, node]] : [];
|
|
1320
|
+
});
|
|
1321
|
+
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
1322
|
+
}
|
|
1175
1323
|
/** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
|
|
1176
1324
|
function getBodySchema({ content, contentType, refs }) {
|
|
1177
1325
|
const entry = contentType ? content?.[contentType] : void 0;
|
|
@@ -1279,10 +1427,12 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1279
1427
|
name: requestBodyName
|
|
1280
1428
|
});
|
|
1281
1429
|
scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}/schema`);
|
|
1430
|
+
const encoding = getEncoding(body?.content?.[ct]?.encoding);
|
|
1282
1431
|
return [_kubb_kit.ast.factory.createContent({
|
|
1283
1432
|
contentType: ct,
|
|
1284
1433
|
schema: _kubb_kit.ast.optionality(node, bodyRequired),
|
|
1285
|
-
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
1434
|
+
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
|
|
1435
|
+
...encoding && { encoding }
|
|
1286
1436
|
})];
|
|
1287
1437
|
});
|
|
1288
1438
|
const requestBody = content.length > 0 || body?.description ? {
|
|
@@ -1379,6 +1529,42 @@ function createNode({ schema, name, nullable, defaultValue }, extras) {
|
|
|
1379
1529
|
//#endregion
|
|
1380
1530
|
//#region src/emit/discriminator/preserve.ts
|
|
1381
1531
|
/**
|
|
1532
|
+
* Turns string mapping keys into the discriminator property's type when they parse cleanly.
|
|
1533
|
+
*/
|
|
1534
|
+
function typedKeys({ keys, propertyType }) {
|
|
1535
|
+
return keys.map((key) => {
|
|
1536
|
+
if (propertyType === "integer" || propertyType === "number") {
|
|
1537
|
+
const value = Number(key);
|
|
1538
|
+
return key.trim() !== "" && Number.isFinite(value) ? value : key;
|
|
1539
|
+
}
|
|
1540
|
+
if (propertyType === "boolean") {
|
|
1541
|
+
const lower = key.toLowerCase();
|
|
1542
|
+
return lower === "true" ? true : lower === "false" ? false : key;
|
|
1543
|
+
}
|
|
1544
|
+
return key;
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Reads the declared `type` of `propertyName` on a schema, following `$ref`s and `allOf` members.
|
|
1549
|
+
* A nullable array such as `['integer', 'null']` reads as its one non-null type.
|
|
1550
|
+
*/
|
|
1551
|
+
function propertyTypeOf({ schema, propertyName, refs, seen = /* @__PURE__ */ new Set() }) {
|
|
1552
|
+
const deref = (value) => value && isReference(value) ? refs.resolve(value.$ref, { report: false }) : value;
|
|
1553
|
+
if (schema && isReference(schema)) {
|
|
1554
|
+
if (seen.has(schema.$ref)) return void 0;
|
|
1555
|
+
seen.add(schema.$ref);
|
|
1556
|
+
}
|
|
1557
|
+
const resolved = deref(schema);
|
|
1558
|
+
const type = deref(resolved?.properties?.[propertyName])?.type ?? resolved?.allOf?.map((member) => propertyTypeOf({
|
|
1559
|
+
schema: member,
|
|
1560
|
+
propertyName,
|
|
1561
|
+
refs,
|
|
1562
|
+
seen
|
|
1563
|
+
})).find(Boolean);
|
|
1564
|
+
const nonNull = Array.isArray(type) ? type.filter((t) => t !== "null") : [];
|
|
1565
|
+
return nonNull.length === 1 ? nonNull[0] : type;
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1382
1568
|
* Creates a single-property object schema used as a discriminator literal.
|
|
1383
1569
|
*
|
|
1384
1570
|
* @example
|
|
@@ -1395,7 +1581,7 @@ function createDiscriminantNode({ propertyName, values }) {
|
|
|
1395
1581
|
name: propertyName,
|
|
1396
1582
|
schema: _kubb_kit.ast.factory.createSchema({
|
|
1397
1583
|
type: "enum",
|
|
1398
|
-
primitive:
|
|
1584
|
+
primitive: (0, _kubb_kit.enumPrimitive)(values),
|
|
1399
1585
|
enumValues: values
|
|
1400
1586
|
}),
|
|
1401
1587
|
required: true
|
|
@@ -1410,21 +1596,61 @@ function createDiscriminantNode({ propertyName, values }) {
|
|
|
1410
1596
|
*
|
|
1411
1597
|
* @example
|
|
1412
1598
|
* ```ts
|
|
1413
|
-
* findDiscriminators({ dog: '#/components/schemas/Dog', hound: '#/components/schemas/Dog' }, '#/components/schemas/Dog') // ['dog', 'hound']
|
|
1599
|
+
* findDiscriminators({ mapping: { dog: '#/components/schemas/Dog', hound: '#/components/schemas/Dog' }, ref: '#/components/schemas/Dog' }) // ['dog', 'hound']
|
|
1414
1600
|
* ```
|
|
1415
1601
|
*/
|
|
1416
|
-
function findDiscriminators(mapping, ref) {
|
|
1602
|
+
function findDiscriminators({ mapping, ref }) {
|
|
1417
1603
|
if (!mapping || !ref) return [];
|
|
1418
1604
|
return Object.entries(mapping).filter(([, value]) => value === ref).map(([key]) => key);
|
|
1419
1605
|
}
|
|
1420
1606
|
/**
|
|
1607
|
+
* Finds the discriminator a schema declares itself or through an inline `allOf` member.
|
|
1608
|
+
*/
|
|
1609
|
+
function getDiscriminator(schema) {
|
|
1610
|
+
return (isDiscriminator(schema) ? schema : schema.allOf?.find(isDiscriminator))?.discriminator;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Lists the mapping keys of a discriminator plus the keys of the nested discriminators its mapped
|
|
1614
|
+
* schemas declare on the same property, so a root `$type` accepts values from every level.
|
|
1615
|
+
*/
|
|
1616
|
+
function getMappingKeys({ discriminator: { propertyName, mapping }, refs, seen = /* @__PURE__ */ new Set() }) {
|
|
1617
|
+
return Object.entries(mapping ?? {}).flatMap(([key, target]) => {
|
|
1618
|
+
if (seen.has(target)) return [key];
|
|
1619
|
+
seen.add(target);
|
|
1620
|
+
const resolved = refs.resolve(target, { report: false });
|
|
1621
|
+
const nested = resolved && getDiscriminator(resolved);
|
|
1622
|
+
return nested?.propertyName === propertyName ? [key, ...getMappingKeys({
|
|
1623
|
+
discriminator: nested,
|
|
1624
|
+
refs,
|
|
1625
|
+
seen
|
|
1626
|
+
})] : [key];
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Drops the discriminator from an inline `allOf` member when a `$ref` sibling already declares one on
|
|
1631
|
+
* the same property. That mapping names the schema's own children, and keeping it pins the property
|
|
1632
|
+
* to those keys next to the parent's, which leaves `never`.
|
|
1633
|
+
*/
|
|
1634
|
+
function removeInheritedDiscriminator({ members, refs }) {
|
|
1635
|
+
const inherited = new Set(members.flatMap((member) => {
|
|
1636
|
+
const parent = isReference(member) ? refs.resolve(member.$ref, { report: false }) : null;
|
|
1637
|
+
return parent && getDiscriminator(parent)?.propertyName || [];
|
|
1638
|
+
}));
|
|
1639
|
+
return members.map((member) => {
|
|
1640
|
+
if (isReference(member) || !isDiscriminator(member) || member.oneOf || member.anyOf) return member;
|
|
1641
|
+
if (!inherited.has(member.discriminator.propertyName)) return member;
|
|
1642
|
+
const { discriminator: _discriminator, ...rest } = member;
|
|
1643
|
+
return rest;
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1421
1647
|
* Narrows each `oneOf`/`anyOf` member with its discriminant value, intersecting the member's own
|
|
1422
1648
|
* node with either the shared-properties slice carrying that value, or a synthetic discriminant
|
|
1423
1649
|
* literal. The referenced child schema's own definition is left untouched — narrowing happens only
|
|
1424
1650
|
* at this union usage site, which is what makes this mode "preserve" (as opposed to `propagate`,
|
|
1425
1651
|
* which additionally patches the child schema's own definition in a post-pass).
|
|
1426
1652
|
*/
|
|
1427
|
-
function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode, parse, name, refs }) {
|
|
1653
|
+
function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode, parentType, parse, name, refs }) {
|
|
1428
1654
|
function pickDiscriminatorPropertyNode(node, propertyName) {
|
|
1429
1655
|
const discriminatorProperty = _kubb_kit.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
|
|
1430
1656
|
if (!discriminatorProperty) return null;
|
|
@@ -1460,9 +1686,20 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
|
|
|
1460
1686
|
}
|
|
1461
1687
|
return unionMembers.map((s) => {
|
|
1462
1688
|
const ref = isReference(s) ? s.$ref : void 0;
|
|
1463
|
-
const mappedValues = findDiscriminators(
|
|
1689
|
+
const mappedValues = findDiscriminators({
|
|
1690
|
+
mapping: discriminator?.mapping,
|
|
1691
|
+
ref
|
|
1692
|
+
});
|
|
1464
1693
|
const implicitValue = mappedValues.length ? null : implicitDiscriminantValue(s);
|
|
1465
|
-
const
|
|
1694
|
+
const propertyType = propertyTypeOf({
|
|
1695
|
+
schema: s,
|
|
1696
|
+
propertyName: discriminator?.propertyName ?? "",
|
|
1697
|
+
refs
|
|
1698
|
+
}) ?? parentType;
|
|
1699
|
+
const discriminatorValues = typedKeys({
|
|
1700
|
+
keys: mappedValues.length ? mappedValues : implicitValue ? [implicitValue] : [],
|
|
1701
|
+
propertyType
|
|
1702
|
+
});
|
|
1466
1703
|
const memberNode = parse({
|
|
1467
1704
|
schema: s,
|
|
1468
1705
|
name
|
|
@@ -1494,21 +1731,34 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
|
|
|
1494
1731
|
members: allOfMembers.filter((item) => {
|
|
1495
1732
|
if (!isReference(item) || !name) return true;
|
|
1496
1733
|
const deref = refs.resolve(item.$ref);
|
|
1497
|
-
|
|
1498
|
-
const parentUnion = deref
|
|
1499
|
-
if (!parentUnion) return true;
|
|
1734
|
+
const discriminator = deref && getDiscriminator(deref);
|
|
1735
|
+
const parentUnion = deref?.oneOf ?? deref?.anyOf;
|
|
1736
|
+
if (!deref || !discriminator || isDiscriminator(deref) && !parentUnion) return true;
|
|
1500
1737
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1738
|
+
const values = findDiscriminators({
|
|
1739
|
+
mapping: discriminator.mapping,
|
|
1740
|
+
ref: childRef
|
|
1741
|
+
});
|
|
1742
|
+
if (values.length) {
|
|
1743
|
+
const { propertyName } = discriminator;
|
|
1744
|
+
const propertyType = propertyTypeOf({
|
|
1745
|
+
schema: deref,
|
|
1746
|
+
propertyName,
|
|
1747
|
+
refs
|
|
1748
|
+
}) ?? propertyTypeOf({
|
|
1749
|
+
schema: { $ref: childRef },
|
|
1750
|
+
propertyName,
|
|
1751
|
+
refs
|
|
1752
|
+
});
|
|
1753
|
+
discriminantValues.push({
|
|
1754
|
+
propertyName,
|
|
1755
|
+
values: typedKeys({
|
|
1756
|
+
keys: values,
|
|
1757
|
+
propertyType
|
|
1758
|
+
})
|
|
1508
1759
|
});
|
|
1509
|
-
return false;
|
|
1510
1760
|
}
|
|
1511
|
-
return
|
|
1761
|
+
return !(parentUnion && (values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
|
|
1512
1762
|
}),
|
|
1513
1763
|
discriminantValues
|
|
1514
1764
|
};
|
|
@@ -1635,10 +1885,9 @@ function resolveUnionMembers(members, siblingProperties) {
|
|
|
1635
1885
|
});
|
|
1636
1886
|
}
|
|
1637
1887
|
/**
|
|
1638
|
-
* Enriches any `oneOf` or `anyOf` branches on
|
|
1888
|
+
* Enriches a schema object, and any `oneOf` or `anyOf` branches on it, with properties from sibling schemas.
|
|
1639
1889
|
*/
|
|
1640
1890
|
function resolveUnionSchema(schema, siblingProperties) {
|
|
1641
|
-
if (!schema.oneOf && !schema.anyOf) return schema;
|
|
1642
1891
|
const next = { ...resolveUnionMembers([schema], siblingProperties)[0] ?? schema };
|
|
1643
1892
|
if (schema.oneOf) next.oneOf = resolveUnionMembers(schema.oneOf, siblingProperties);
|
|
1644
1893
|
if (schema.anyOf) next.anyOf = resolveUnionMembers(schema.anyOf, siblingProperties);
|
|
@@ -1662,6 +1911,17 @@ function convertRef({ schema, name, nullable, defaultValue, document, parse, ref
|
|
|
1662
1911
|
defaultValue
|
|
1663
1912
|
};
|
|
1664
1913
|
if (refPath && document.components && !refs.resolve(refPath, { report: false })) return createNode(ctx, { type: "unknown" });
|
|
1914
|
+
if (refPath && !refPath.startsWith("#/components/schemas/") && resolvedSchema) return _kubb_kit.ast.factory.createSchema({
|
|
1915
|
+
...resolvedSchema,
|
|
1916
|
+
title: schema.title ?? resolvedSchema.title,
|
|
1917
|
+
description: schema.description ?? resolvedSchema.description,
|
|
1918
|
+
deprecated: schema.deprecated ?? resolvedSchema.deprecated,
|
|
1919
|
+
readOnly: schema.readOnly ?? resolvedSchema.readOnly,
|
|
1920
|
+
writeOnly: schema.writeOnly ?? resolvedSchema.writeOnly,
|
|
1921
|
+
default: defaultValue ?? resolvedSchema.default,
|
|
1922
|
+
name: name ?? resolvedSchema.name,
|
|
1923
|
+
nullable: nullable ?? resolvedSchema.nullable
|
|
1924
|
+
});
|
|
1665
1925
|
const targetName = renames?.get(schema.$ref);
|
|
1666
1926
|
return createNode(ctx, {
|
|
1667
1927
|
type: "ref",
|
|
@@ -1693,9 +1953,41 @@ function convertNot({ schema, name, nullable, defaultValue, parse }) {
|
|
|
1693
1953
|
});
|
|
1694
1954
|
}
|
|
1695
1955
|
/**
|
|
1956
|
+
* Keywords that describe a schema or conditionally constrain it, but add no structure.
|
|
1957
|
+
*/
|
|
1958
|
+
const annotationKeys = /* @__PURE__ */ new Set([
|
|
1959
|
+
"description",
|
|
1960
|
+
"title",
|
|
1961
|
+
"example",
|
|
1962
|
+
"examples",
|
|
1963
|
+
"default",
|
|
1964
|
+
"deprecated",
|
|
1965
|
+
"readOnly",
|
|
1966
|
+
"writeOnly",
|
|
1967
|
+
"if",
|
|
1968
|
+
"then",
|
|
1969
|
+
"else"
|
|
1970
|
+
]);
|
|
1971
|
+
/**
|
|
1972
|
+
* Moves `allOf` members that hold only annotations or `if`/`then`/`else` onto the parent, so they do
|
|
1973
|
+
* not become `unknown`. The parent's own values win, and the conditional keywords are not enforced.
|
|
1974
|
+
*/
|
|
1975
|
+
function mergeAnnotations(schema) {
|
|
1976
|
+
const members = schema.allOf;
|
|
1977
|
+
const isAnnotation = (member) => !isReference(member) && Object.keys(member).every((key) => annotationKeys.has(key));
|
|
1978
|
+
const annotations = members.filter(isAnnotation);
|
|
1979
|
+
const rest = members.filter((member) => !isAnnotation(member));
|
|
1980
|
+
return annotations.length && rest.length ? {
|
|
1981
|
+
...Object.assign({}, ...annotations),
|
|
1982
|
+
...schema,
|
|
1983
|
+
allOf: rest
|
|
1984
|
+
} : schema;
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1696
1987
|
* Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
|
|
1697
1988
|
*/
|
|
1698
|
-
function convertAllOf({ schema, name, nullable, defaultValue, parse, refs }) {
|
|
1989
|
+
function convertAllOf({ schema: original, name, nullable, defaultValue, parse, refs }) {
|
|
1990
|
+
const schema = mergeAnnotations(original);
|
|
1699
1991
|
if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
|
|
1700
1992
|
const [memberSchema] = schema.allOf;
|
|
1701
1993
|
const memberNode = parse({
|
|
@@ -1721,7 +2013,10 @@ function convertAllOf({ schema, name, nullable, defaultValue, parse, refs }) {
|
|
|
1721
2013
|
});
|
|
1722
2014
|
}
|
|
1723
2015
|
const { members: discriminatedAllOf, discriminantValues } = extractDiscriminatedAllOfMembers({
|
|
1724
|
-
allOfMembers:
|
|
2016
|
+
allOfMembers: removeInheritedDiscriminator({
|
|
2017
|
+
members: schema.allOf,
|
|
2018
|
+
refs
|
|
2019
|
+
}),
|
|
1725
2020
|
name,
|
|
1726
2021
|
refs
|
|
1727
2022
|
});
|
|
@@ -1801,6 +2096,11 @@ function convertUnion(context) {
|
|
|
1801
2096
|
unionMembers,
|
|
1802
2097
|
discriminator,
|
|
1803
2098
|
sharedPropertiesNode,
|
|
2099
|
+
parentType: discriminator && propertyTypeOf({
|
|
2100
|
+
schema,
|
|
2101
|
+
propertyName: discriminator.propertyName,
|
|
2102
|
+
refs
|
|
2103
|
+
}),
|
|
1804
2104
|
parse,
|
|
1805
2105
|
name,
|
|
1806
2106
|
refs
|
|
@@ -2064,7 +2364,7 @@ function nameEnums(node, options) {
|
|
|
2064
2364
|
/**
|
|
2065
2365
|
* Converts an object-like schema into an `ObjectSchemaNode`.
|
|
2066
2366
|
*/
|
|
2067
|
-
function convertObject({ schema, name, nullable, defaultValue, options, parse }) {
|
|
2367
|
+
function convertObject({ schema, name, nullable, defaultValue, options, parse, refs }) {
|
|
2068
2368
|
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
2069
2369
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
2070
2370
|
const resolvedPropSchema = propSchema;
|
|
@@ -2081,7 +2381,7 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse })
|
|
|
2081
2381
|
name: propName,
|
|
2082
2382
|
schema: {
|
|
2083
2383
|
...schemaNode,
|
|
2084
|
-
nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
|
|
2384
|
+
nullable: schemaNode.type === "null" ? void 0 : propNullable || schemaNode.nullable || void 0
|
|
2085
2385
|
},
|
|
2086
2386
|
required
|
|
2087
2387
|
});
|
|
@@ -2121,7 +2421,17 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse })
|
|
|
2121
2421
|
});
|
|
2122
2422
|
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
2123
2423
|
const discPropName = schema.discriminator.propertyName;
|
|
2124
|
-
const values =
|
|
2424
|
+
const values = typedKeys({
|
|
2425
|
+
keys: getMappingKeys({
|
|
2426
|
+
discriminator: schema.discriminator,
|
|
2427
|
+
refs
|
|
2428
|
+
}),
|
|
2429
|
+
propertyType: propertyTypeOf({
|
|
2430
|
+
schema,
|
|
2431
|
+
propertyName: discPropName,
|
|
2432
|
+
refs
|
|
2433
|
+
})
|
|
2434
|
+
});
|
|
2125
2435
|
const enumName = name ? (0, _kubb_kit.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
|
|
2126
2436
|
return _kubb_kit.ast.applyMacros(objectNode, [(0, _kubb_kit.macroDiscriminatorEnum)({
|
|
2127
2437
|
propertyName: discPropName,
|
|
@@ -2179,8 +2489,9 @@ function convertArray({ schema, name, nullable, defaultValue, options, parse })
|
|
|
2179
2489
|
//#region src/emit/parseSchema.ts
|
|
2180
2490
|
/**
|
|
2181
2491
|
* Ordered schema rule table. Order is significant: composition keywords (`not`, `$ref`, `allOf`,
|
|
2182
|
-
* `oneOf`/`anyOf`) take precedence over `const
|
|
2183
|
-
*
|
|
2492
|
+
* `oneOf`/`anyOf`) take precedence over `const` (except a `$ref` with a sibling `const`, where the
|
|
2493
|
+
* `const` wins), which takes precedence over a genuine OAS 3.1 multi-type array (more than one
|
|
2494
|
+
* non-`null` type), which takes precedence over `format`/`type`.
|
|
2184
2495
|
* A multi-type array must split into its per-type members before `format` runs, otherwise
|
|
2185
2496
|
* `format` collapses the whole schema to one type (e.g. `type: ['null', 'integer', 'string'],
|
|
2186
2497
|
* format: 'int32'` would drop `string` and emit just `integer`); each split-off member still
|
|
@@ -2194,7 +2505,7 @@ const schemaRules = [
|
|
|
2194
2505
|
convert: convertNot
|
|
2195
2506
|
},
|
|
2196
2507
|
{
|
|
2197
|
-
match: ({ schema }) => isReference(schema),
|
|
2508
|
+
match: ({ schema }) => isReference(schema) && !("const" in schema && schema.const !== void 0),
|
|
2198
2509
|
convert: convertRef
|
|
2199
2510
|
},
|
|
2200
2511
|
{
|
|
@@ -2324,7 +2635,7 @@ function createSchemaParser(ctx) {
|
|
|
2324
2635
|
function collectInlineEnums(roots, topLevelNames) {
|
|
2325
2636
|
const promoted = /* @__PURE__ */ new Map();
|
|
2326
2637
|
for (const root of roots) {
|
|
2327
|
-
const isSchemaRoot = root.kind ===
|
|
2638
|
+
const isSchemaRoot = root.kind === _kubb_kit.ast.nodeKinds.schema;
|
|
2328
2639
|
for (const node of _kubb_kit.ast.collect(root, { schema: (schemaNode) => schemaNode })) {
|
|
2329
2640
|
if (node.type !== "enum" || !node.name) continue;
|
|
2330
2641
|
if (isSchemaRoot && node === root) continue;
|
|
@@ -2362,114 +2673,6 @@ function refPromotedEnums(node, promoted) {
|
|
|
2362
2673
|
} });
|
|
2363
2674
|
}
|
|
2364
2675
|
//#endregion
|
|
2365
|
-
//#region src/refs.ts
|
|
2366
|
-
/**
|
|
2367
|
-
* Creates the `$ref` resolution service for one document. One pointer walk backs every method.
|
|
2368
|
-
*
|
|
2369
|
-
* @example
|
|
2370
|
-
* ```ts
|
|
2371
|
-
* const refs = createRefs(document)
|
|
2372
|
-
* refs.resolve<SchemaObject>('#/components/schemas/Pet')
|
|
2373
|
-
* refs.resolve<SchemaObject>('#/components/schemas/Pet', { report: false })
|
|
2374
|
-
* refs.resolveNode('#/components/schemas/Pet', parseSchema)
|
|
2375
|
-
* refs.deref<ResponseObject>(operation.schema.responses?.['200'])
|
|
2376
|
-
* refs.derefKeepingRef(parameter)
|
|
2377
|
-
* ```
|
|
2378
|
-
*/
|
|
2379
|
-
function createRefs(document) {
|
|
2380
|
-
const pointerCache = /* @__PURE__ */ new Map();
|
|
2381
|
-
const resolvedNodeCache = /* @__PURE__ */ new Map();
|
|
2382
|
-
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
2383
|
-
function walkPointer($ref) {
|
|
2384
|
-
const trimmed = $ref.trim();
|
|
2385
|
-
if (trimmed === "" || !trimmed.startsWith("#")) return {
|
|
2386
|
-
applicable: false,
|
|
2387
|
-
value: null
|
|
2388
|
-
};
|
|
2389
|
-
const pointer = trimmed.substring(1);
|
|
2390
|
-
if (pointerCache.has(pointer)) return {
|
|
2391
|
-
applicable: true,
|
|
2392
|
-
value: pointerCache.get(pointer)
|
|
2393
|
-
};
|
|
2394
|
-
const current = pointer.split("/").filter(Boolean).map((token) => globalThis.decodeURIComponent(token).replaceAll("~1", "/").replaceAll("~0", "~")).reduce((obj, key) => obj?.[key], document);
|
|
2395
|
-
if (current) pointerCache.set(pointer, current);
|
|
2396
|
-
return {
|
|
2397
|
-
applicable: true,
|
|
2398
|
-
value: current ?? null
|
|
2399
|
-
};
|
|
2400
|
-
}
|
|
2401
|
-
/**
|
|
2402
|
-
* Resolves a local `#/...` JSON pointer. Returns `null` for an empty or non-local ref.
|
|
2403
|
-
* `report: true` (default) reports a `refNotFound` diagnostic into the active build (or throws
|
|
2404
|
-
* outside one) when the pointer cannot be resolved. `report: false` resolves to `null` silently,
|
|
2405
|
-
* for a speculative lookup where a missing ref is not an error.
|
|
2406
|
-
*/
|
|
2407
|
-
function resolve(refPath, options) {
|
|
2408
|
-
const { applicable, value } = walkPointer(refPath);
|
|
2409
|
-
if (!applicable || value || options?.report === false) return value;
|
|
2410
|
-
const diagnostic = {
|
|
2411
|
-
code: _kubb_kit.Diagnostics.code.refNotFound,
|
|
2412
|
-
severity: "error",
|
|
2413
|
-
message: `Could not find a definition for ${refPath}.`,
|
|
2414
|
-
help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
|
|
2415
|
-
location: {
|
|
2416
|
-
kind: "schema",
|
|
2417
|
-
pointer: refPath,
|
|
2418
|
-
ref: refPath
|
|
2419
|
-
}
|
|
2420
|
-
};
|
|
2421
|
-
if (!_kubb_kit.Diagnostics.report(diagnostic)) throw new _kubb_kit.Diagnostics.Error(diagnostic);
|
|
2422
|
-
return null;
|
|
2423
|
-
}
|
|
2424
|
-
/**
|
|
2425
|
-
* Resolves a `$ref` to its parsed node via `parse`, guarding against cycles and memoizing per
|
|
2426
|
-
* instance. Returns `null` when the ref is currently being resolved (a cycle) or cannot be
|
|
2427
|
-
* resolved (e.g. a minimal document in a unit test).
|
|
2428
|
-
*/
|
|
2429
|
-
function resolveNode(refPath, parse) {
|
|
2430
|
-
if (resolvingRefs.has(refPath)) return null;
|
|
2431
|
-
if (!resolvedNodeCache.has(refPath)) {
|
|
2432
|
-
let resolved = null;
|
|
2433
|
-
try {
|
|
2434
|
-
const referenced = resolve(refPath);
|
|
2435
|
-
if (referenced) {
|
|
2436
|
-
resolvingRefs.add(refPath);
|
|
2437
|
-
resolved = parse({ schema: referenced });
|
|
2438
|
-
resolvingRefs.delete(refPath);
|
|
2439
|
-
}
|
|
2440
|
-
} catch {}
|
|
2441
|
-
resolvedNodeCache.set(refPath, resolved);
|
|
2442
|
-
}
|
|
2443
|
-
return resolvedNodeCache.get(refPath) ?? null;
|
|
2444
|
-
}
|
|
2445
|
-
/**
|
|
2446
|
-
* Resolves a `$ref` value without mutating anything: when `value` holds a `$ref`, returns the
|
|
2447
|
-
* resolved target. Returns `null` when the value is empty, cannot be resolved, or is still a
|
|
2448
|
-
* `$ref` after resolving (e.g. a document with no component registry). A non-`$ref` value is
|
|
2449
|
-
* returned as-is.
|
|
2450
|
-
*/
|
|
2451
|
-
function deref(value) {
|
|
2452
|
-
if (!isReference(value)) return value ? value : null;
|
|
2453
|
-
const resolved = resolve(value.$ref);
|
|
2454
|
-
return resolved && !isReference(resolved) ? resolved : null;
|
|
2455
|
-
}
|
|
2456
|
-
/** Resolves a `$ref` object but keeps the `$ref` field on the result; other values pass through. */
|
|
2457
|
-
function derefKeepingRef(value) {
|
|
2458
|
-
if (isReference(value)) return {
|
|
2459
|
-
...value,
|
|
2460
|
-
...resolve(value.$ref),
|
|
2461
|
-
$ref: value.$ref
|
|
2462
|
-
};
|
|
2463
|
-
return value;
|
|
2464
|
-
}
|
|
2465
|
-
return {
|
|
2466
|
-
resolve,
|
|
2467
|
-
resolveNode,
|
|
2468
|
-
deref,
|
|
2469
|
-
derefKeepingRef
|
|
2470
|
-
};
|
|
2471
|
-
}
|
|
2472
|
-
//#endregion
|
|
2473
2676
|
//#region src/sourceLocator.ts
|
|
2474
2677
|
let cached;
|
|
2475
2678
|
const toRange = ([start, end]) => ({
|
|
@@ -2702,7 +2905,7 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2702
2905
|
};
|
|
2703
2906
|
parsedByName.set(name, node);
|
|
2704
2907
|
scanSchema(node);
|
|
2705
|
-
if (node.type === "ref" && node.name
|
|
2908
|
+
if (node.type === "ref" && node.name) refAliasMap.set(name, node);
|
|
2706
2909
|
if (_kubb_kit.ast.narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
|
|
2707
2910
|
if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2708
2911
|
}
|
|
@@ -2717,6 +2920,32 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2717
2920
|
}));
|
|
2718
2921
|
const promotedEnums = enums === "root" ? collectInlineEnums([...parsedByName.values(), ...operationNodes], new Set(Object.keys(schemas))) : null;
|
|
2719
2922
|
if (promotedEnums) for (const name of promotedEnums.keys()) enumNames.push(name);
|
|
2923
|
+
function isAliasCycle(name) {
|
|
2924
|
+
const seen = /* @__PURE__ */ new Set([name]);
|
|
2925
|
+
for (let next = refAliasMap.get(name)?.name; next; next = refAliasMap.get(next)?.name) {
|
|
2926
|
+
if (next === name) return true;
|
|
2927
|
+
if (seen.has(next)) return false;
|
|
2928
|
+
seen.add(next);
|
|
2929
|
+
}
|
|
2930
|
+
return false;
|
|
2931
|
+
}
|
|
2932
|
+
function createCycleNode(name, pointer) {
|
|
2933
|
+
_kubb_kit.Diagnostics.report({
|
|
2934
|
+
code: _kubb_kit.Diagnostics.code.aliasCycle,
|
|
2935
|
+
severity: "warning",
|
|
2936
|
+
message: `${name} only refers to other schemas that lead back to it, so it is resolved to unknown.`,
|
|
2937
|
+
help: "Give one of the schemas in the loop a real definition, or remove the `$ref` that closes it.",
|
|
2938
|
+
location: {
|
|
2939
|
+
kind: "schema",
|
|
2940
|
+
pointer: pointer ?? `#/components/schemas/${name}`
|
|
2941
|
+
}
|
|
2942
|
+
});
|
|
2943
|
+
return _kubb_kit.ast.factory.createSchema({
|
|
2944
|
+
type: "unknown",
|
|
2945
|
+
name,
|
|
2946
|
+
pointer
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2720
2949
|
const schemaNodes = promotedEnums ? [...promotedEnums.values()] : [];
|
|
2721
2950
|
for (const name of Object.keys(schemas)) {
|
|
2722
2951
|
const aliasTarget = refAliasMap.get(name)?.name;
|
|
@@ -2724,11 +2953,12 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2724
2953
|
const parsed = parsedByName.get(name);
|
|
2725
2954
|
const child = discriminatorChildMap?.get(name);
|
|
2726
2955
|
const base = child ? patchDiscriminatorNode(parsed, child) : parsed;
|
|
2727
|
-
const
|
|
2956
|
+
const resolved = aliased ? {
|
|
2728
2957
|
...aliased,
|
|
2729
2958
|
name,
|
|
2730
2959
|
pointer: parsed.pointer
|
|
2731
2960
|
} : base;
|
|
2961
|
+
const node = isAliasCycle(name) ? createCycleNode(name, parsed.pointer) : resolved;
|
|
2732
2962
|
schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
|
|
2733
2963
|
}
|
|
2734
2964
|
const operations = promotedEnums ? operationNodes.map((node) => refPromotedEnums(node, promotedEnums)) : operationNodes;
|