@speclynx/apidom-reference 5.2.2 → 5.2.5
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/CHANGELOG.md +20 -0
- package/README.md +30 -11
- package/dist/apidom-reference.browser.js +1121 -516
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +28 -28
- package/src/bundle/strategies/arazzo-1/visitor.cjs +105 -22
- package/src/bundle/strategies/arazzo-1/visitor.mjs +107 -24
- package/src/bundle/strategies/openapi-3-1/visitor.cjs +197 -47
- package/src/bundle/strategies/openapi-3-1/visitor.mjs +200 -50
- package/src/bundle/util.cjs +57 -2
- package/src/bundle/util.mjs +52 -0
- package/src/dereference/strategies/arazzo-1/index.cjs +9 -7
- package/src/dereference/strategies/arazzo-1/index.mjs +3 -1
- package/src/dereference/strategies/arazzo-1/selectors/$anchor.cjs +6 -0
- package/src/dereference/strategies/arazzo-1/selectors/$anchor.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/selectors/uri.cjs +6 -0
- package/src/dereference/strategies/arazzo-1/selectors/uri.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/util.cjs +6 -0
- package/src/dereference/strategies/arazzo-1/util.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/visitor.cjs +67 -9
- package/src/dereference/strategies/arazzo-1/visitor.mjs +71 -13
- package/src/dereference/strategies/openapi-3-1/index.cjs +2 -1
- package/src/dereference/strategies/openapi-3-1/index.mjs +3 -1
- package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +16 -4
- package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +14 -3
- package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.cjs +29 -0
- package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.mjs +22 -0
- package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +54 -27
- package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +51 -27
- package/src/dereference/strategies/openapi-3-1/util.cjs +75 -14
- package/src/dereference/strategies/openapi-3-1/util.mjs +68 -14
- package/src/dereference/strategies/openapi-3-1/visitor.cjs +85 -9
- package/src/dereference/strategies/openapi-3-1/visitor.mjs +89 -13
- package/src/util/url.cjs +35 -2
- package/src/util/url.mjs +32 -0
- package/types/apidom-reference.d.ts +12 -0
- package/types/bundle/strategies/arazzo-1/index.d.ts +1 -1
- package/types/bundle/strategies/arazzo-1/visitor.d.ts +51 -6
- package/types/bundle/strategies/openapi-3-1/index.d.ts +1 -1
- package/types/bundle/strategies/openapi-3-1/visitor.d.ts +59 -3
- package/types/bundle/util.d.ts +39 -0
- package/types/dereference/strategies/arazzo-1/selectors/$anchor.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/selectors/uri.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/util.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/visitor.d.ts +17 -1
- package/types/dereference/strategies/openapi-3-1/selectors/$anchor.d.ts +8 -0
- package/types/dereference/strategies/openapi-3-1/selectors/json-pointer.d.ts +9 -0
- package/types/dereference/strategies/openapi-3-1/selectors/uri.d.ts +37 -4
- package/types/dereference/strategies/openapi-3-1/util.d.ts +53 -2
- package/types/dereference/strategies/openapi-3-1/visitor.d.ts +17 -1
- package/types/util/url.d.ts +10 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { propEq, none } from 'ramda';
|
|
2
|
-
import { isElement, isObjectElement, isStringElement, ObjectElement, AnnotationElement, cloneDeep, cloneShallow } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { isElement, isObjectElement, isStringElement, isParseResultElement, ObjectElement, AnnotationElement, cloneDeep, cloneShallow } from '@speclynx/apidom-datamodel';
|
|
3
3
|
import { toValue, toYAML, fixedFields } from '@speclynx/apidom-core';
|
|
4
4
|
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
5
5
|
import { traverseAsync } from '@speclynx/apidom-traverse';
|
|
@@ -16,8 +16,8 @@ import Reference from "../../../Reference.mjs";
|
|
|
16
16
|
import File from "../../../File.mjs";
|
|
17
17
|
import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "../../../dereference/strategies/openapi-3-1/selectors/$anchor.mjs";
|
|
18
18
|
import { evaluate as uriEvaluate } from "../../../dereference/strategies/openapi-3-1/selectors/uri.mjs";
|
|
19
|
-
import { resolveSchema$refField, resolveSchema$idField, maybeRefractToSchemaElement } from "../../../dereference/strategies/openapi-3-1/util.mjs";
|
|
20
|
-
import {
|
|
19
|
+
import { resolveSchema$refField, resolveSchema$idField, resolveSchemaBaseURI, maybeRefractToSchemaElement } from "../../../dereference/strategies/openapi-3-1/util.mjs";
|
|
20
|
+
import { rebaseSchema$ref, relocateSchema$ref, sanitizeComponentName, toPascalCase, uniqueName as resolveUniqueName } from "../../util.mjs";
|
|
21
21
|
/**
|
|
22
22
|
* Maps the `referenced-element` meta value (set during refraction) to the
|
|
23
23
|
* Components Object field where the referenced element should be hoisted.
|
|
@@ -59,6 +59,12 @@ const componentFieldElementByField = {
|
|
|
59
59
|
[cf.pathItems.name]: ComponentsPathItemsElement
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* A bundled element awaiting placement into the entry document's Components
|
|
64
|
+
* Object field.
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
|
|
62
68
|
/**
|
|
63
69
|
* @public
|
|
64
70
|
*/
|
|
@@ -116,18 +122,50 @@ class OpenAPI3_1BundleVisitor {
|
|
|
116
122
|
* generic object referenced once as a Parameter and once as a Response).
|
|
117
123
|
*/
|
|
118
124
|
refractCache;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Index of `$id`-bearing schemas per document, shared across the entry document
|
|
128
|
+
* and every external document visitor of a single bundle run so it never
|
|
129
|
+
* outlives mutations of the documents (e.g. the final placement of components).
|
|
130
|
+
*/
|
|
131
|
+
schema$idIndex;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Bundled fragments waiting to be placed into the entry document's
|
|
135
|
+
* Components Object, shared across the entry document and every external
|
|
136
|
+
* document visitor. Placement is deferred until the entry document traversal
|
|
137
|
+
* completes: the traversal walks live content, so placing a fragment mid-way
|
|
138
|
+
* would make it descend into the fragment and re-bundle its `$ref`s against
|
|
139
|
+
* the entry document's base URI instead of the fragment's own.
|
|
140
|
+
*/
|
|
141
|
+
placements;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The base URI the traversed content will have once placed into the entry
|
|
145
|
+
* document, when placement changes it: content hoisted out of a non-schema
|
|
146
|
+
* document (a Response, Parameter, Path Item, ...) trades that document's
|
|
147
|
+
* base URI for the entry document's. Unset for the entry document itself and
|
|
148
|
+
* for embedded schema resources, whose `$id` carries their base along.
|
|
149
|
+
*/
|
|
150
|
+
relocationBaseURI;
|
|
119
151
|
constructor({
|
|
120
152
|
reference,
|
|
121
153
|
options,
|
|
122
154
|
assignments = new Map(),
|
|
123
155
|
reservedNames = new Map(),
|
|
124
|
-
refractCache = new WeakMap()
|
|
156
|
+
refractCache = new WeakMap(),
|
|
157
|
+
schema$idIndex = new WeakMap(),
|
|
158
|
+
placements = [],
|
|
159
|
+
relocationBaseURI
|
|
125
160
|
}) {
|
|
126
161
|
this.reference = reference;
|
|
127
162
|
this.options = options;
|
|
128
163
|
this.assignments = assignments;
|
|
129
164
|
this.reservedNames = reservedNames;
|
|
130
165
|
this.refractCache = refractCache;
|
|
166
|
+
this.schema$idIndex = schema$idIndex;
|
|
167
|
+
this.placements = placements;
|
|
168
|
+
this.relocationBaseURI = relocationBaseURI;
|
|
131
169
|
}
|
|
132
170
|
getRefracted(source, type) {
|
|
133
171
|
return this.refractCache.get(source)?.get(type);
|
|
@@ -185,6 +223,22 @@ class OpenAPI3_1BundleVisitor {
|
|
|
185
223
|
return ref.startsWith('#') ? ref : url.getHash(ref);
|
|
186
224
|
}
|
|
187
225
|
|
|
226
|
+
/**
|
|
227
|
+
* The base URI the given Schema Object's `$ref` will be resolved against once
|
|
228
|
+
* the content is placed into the entry document, or `undefined` when the
|
|
229
|
+
* `$ref` as written resolves to `$refBaseURI` from there as well: the content
|
|
230
|
+
* isn't relocated, an absolute `$id` on the schema's ancestor chain pins its
|
|
231
|
+
* base, or the hoisted document sits next to the entry document.
|
|
232
|
+
*/
|
|
233
|
+
relocatedSchemaBaseURI(path, $refBaseURI) {
|
|
234
|
+
if (this.relocationBaseURI === undefined) return undefined;
|
|
235
|
+
const relocatedBaseURI = resolveSchemaBaseURI(this.relocationBaseURI, path);
|
|
236
|
+
if (resolveSchema$refField(relocatedBaseURI, path.node) === $refBaseURI) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
return relocatedBaseURI;
|
|
240
|
+
}
|
|
241
|
+
|
|
188
242
|
/**
|
|
189
243
|
* Handles an error according to the `bundle.continueOnError` option.
|
|
190
244
|
*
|
|
@@ -303,13 +357,16 @@ class OpenAPI3_1BundleVisitor {
|
|
|
303
357
|
|
|
304
358
|
/**
|
|
305
359
|
* Computes a collision-free component name within the target field. A name is
|
|
306
|
-
* taken if it's already
|
|
307
|
-
* is still being bundled (reserved before recursion).
|
|
360
|
+
* taken if it's already present in the entry document's components or reserved
|
|
361
|
+
* by a component that is still being bundled (reserved before recursion).
|
|
308
362
|
*/
|
|
309
363
|
uniqueName(candidate, field) {
|
|
310
|
-
const
|
|
364
|
+
const {
|
|
365
|
+
components
|
|
366
|
+
} = this.entryResult;
|
|
367
|
+
const fieldElement = isObjectElement(components) ? components.get(field) : undefined;
|
|
311
368
|
const reserved = this.reservedNames.get(field) ?? new Set();
|
|
312
|
-
return resolveUniqueName(candidate, name => fieldElement.hasKey(name) || reserved.has(name));
|
|
369
|
+
return resolveUniqueName(candidate, name => isObjectElement(fieldElement) && fieldElement.hasKey(name) || reserved.has(name));
|
|
313
370
|
}
|
|
314
371
|
|
|
315
372
|
/**
|
|
@@ -333,6 +390,28 @@ class OpenAPI3_1BundleVisitor {
|
|
|
333
390
|
// append (never prepend) so the result element stays ahead of annotations
|
|
334
391
|
this.entryParseResult.content.push(annotation);
|
|
335
392
|
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The entry document traversal is rooted at its Parse Result (nested
|
|
396
|
+
* traversals are rooted at fragments), so leaving it marks the moment the
|
|
397
|
+
* whole entry document has been visited and the collected fragments can be
|
|
398
|
+
* placed into its Components Object without the traversal descending into them.
|
|
399
|
+
* Guarded on the node so that a traversal rooted at any other Parse Result
|
|
400
|
+
* never flushes the shared placements early.
|
|
401
|
+
*/
|
|
402
|
+
ParseResultElement = {
|
|
403
|
+
leave: path => {
|
|
404
|
+
if (path.node !== this.entryParseResult) return;
|
|
405
|
+
for (const {
|
|
406
|
+
field,
|
|
407
|
+
name,
|
|
408
|
+
element
|
|
409
|
+
} of this.placements) {
|
|
410
|
+
this.ensureComponentsField(field).set(name, element);
|
|
411
|
+
}
|
|
412
|
+
this.placements.length = 0;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
336
415
|
async ReferenceElement(path) {
|
|
337
416
|
const referencingElement = path.node;
|
|
338
417
|
const $ref = toValue(referencingElement.$ref);
|
|
@@ -420,14 +499,19 @@ class OpenAPI3_1BundleVisitor {
|
|
|
420
499
|
if (!this.reservedNames.has(field)) this.reservedNames.set(field, new Set());
|
|
421
500
|
this.reservedNames.get(field).add(componentName);
|
|
422
501
|
|
|
423
|
-
// own a copy of the fragment and bundle its own external references
|
|
502
|
+
// own a copy of the fragment and bundle its own external references; the
|
|
503
|
+
// fragment ends up in the entry document, so that is where its content
|
|
504
|
+
// resolves relative references from
|
|
424
505
|
const hoistedElement = cloneDeep(referencedElement);
|
|
425
506
|
const visitor = new OpenAPI3_1BundleVisitor({
|
|
426
507
|
reference,
|
|
427
508
|
options: this.options,
|
|
428
509
|
assignments: this.assignments,
|
|
429
510
|
reservedNames: this.reservedNames,
|
|
430
|
-
refractCache: this.refractCache
|
|
511
|
+
refractCache: this.refractCache,
|
|
512
|
+
schema$idIndex: this.schema$idIndex,
|
|
513
|
+
placements: this.placements,
|
|
514
|
+
relocationBaseURI: this.entryURI
|
|
431
515
|
});
|
|
432
516
|
const bundledElement = await traverseAsync(hoistedElement, visitor, {
|
|
433
517
|
mutable: true
|
|
@@ -438,8 +522,12 @@ class OpenAPI3_1BundleVisitor {
|
|
|
438
522
|
bundledElement.meta.set('ref-origin', reference.uri);
|
|
439
523
|
}
|
|
440
524
|
|
|
441
|
-
//
|
|
442
|
-
this.
|
|
525
|
+
// queue the bundled fragment for placement into the entry document's components
|
|
526
|
+
this.placements.push({
|
|
527
|
+
field,
|
|
528
|
+
name: componentName,
|
|
529
|
+
element: bundledElement
|
|
530
|
+
});
|
|
443
531
|
|
|
444
532
|
// rewrite the referencing element to point at the hoisted fragment
|
|
445
533
|
referencingElement.set('$ref', internalPointer);
|
|
@@ -528,14 +616,19 @@ class OpenAPI3_1BundleVisitor {
|
|
|
528
616
|
if (!this.reservedNames.has(field)) this.reservedNames.set(field, new Set());
|
|
529
617
|
this.reservedNames.get(field).add(componentName);
|
|
530
618
|
|
|
531
|
-
// own a copy of the fragment and bundle its own external references
|
|
619
|
+
// own a copy of the fragment and bundle its own external references; the
|
|
620
|
+
// Path Item ends up in the entry document, so that is where its content
|
|
621
|
+
// resolves relative references from
|
|
532
622
|
const hoistedElement = cloneDeep(referencedElement);
|
|
533
623
|
const visitor = new OpenAPI3_1BundleVisitor({
|
|
534
624
|
reference,
|
|
535
625
|
options: this.options,
|
|
536
626
|
assignments: this.assignments,
|
|
537
627
|
reservedNames: this.reservedNames,
|
|
538
|
-
refractCache: this.refractCache
|
|
628
|
+
refractCache: this.refractCache,
|
|
629
|
+
schema$idIndex: this.schema$idIndex,
|
|
630
|
+
placements: this.placements,
|
|
631
|
+
relocationBaseURI: this.entryURI
|
|
539
632
|
});
|
|
540
633
|
const bundledElement = await traverseAsync(hoistedElement, visitor, {
|
|
541
634
|
mutable: true
|
|
@@ -546,8 +639,12 @@ class OpenAPI3_1BundleVisitor {
|
|
|
546
639
|
// internal `#/components/pathItems/...` pointer by the child traversal
|
|
547
640
|
bundledElement.meta.set('ref-origin', reference.uri);
|
|
548
641
|
|
|
549
|
-
//
|
|
550
|
-
this.
|
|
642
|
+
// queue the bundled Path Item for placement into the entry document's components
|
|
643
|
+
this.placements.push({
|
|
644
|
+
field,
|
|
645
|
+
name: componentName,
|
|
646
|
+
element: bundledElement
|
|
647
|
+
});
|
|
551
648
|
|
|
552
649
|
// rewrite the referencing Path Item to point at the hoisted one, keeping
|
|
553
650
|
// any sibling fields (e.g. summary, description) next to the $ref
|
|
@@ -582,7 +679,8 @@ class OpenAPI3_1BundleVisitor {
|
|
|
582
679
|
// toReference) so an internal $ref needs no resolution and never trips the
|
|
583
680
|
// resolve.maxDepth guard, matching how ReferenceElement returns early.
|
|
584
681
|
const retrievalURI = this.reference.uri;
|
|
585
|
-
const
|
|
682
|
+
const schemaBaseURI = resolveSchemaBaseURI(retrievalURI, path);
|
|
683
|
+
const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
|
|
586
684
|
const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
|
|
587
685
|
const file = new File({
|
|
588
686
|
uri: $refBaseURIStrippedHash
|
|
@@ -618,59 +716,95 @@ class OpenAPI3_1BundleVisitor {
|
|
|
618
716
|
// as a canonical URI / URL against the current document's $id graph; if
|
|
619
717
|
// that fails, fall back to fetching the external document and resolving
|
|
620
718
|
// the fragment there as a $anchor or JSON Pointer.
|
|
719
|
+
//
|
|
720
|
+
// the fragment being traversed is searched before the whole document:
|
|
721
|
+
// content hoisted out of an external document (a Response, Parameter, Path
|
|
722
|
+
// Item, ...) is refracted with its own semantics, so the Schema Objects it
|
|
723
|
+
// contains are indexed, whereas the external document as a whole is parsed
|
|
724
|
+
// generically and refracted as a bare JSON Schema, which leaves those Schema
|
|
725
|
+
// Objects (under `content`, `schema`, ...) unrecognized and their `$id`s out
|
|
726
|
+
// of the index
|
|
727
|
+
const documentElement = this.reference.value.result;
|
|
728
|
+
const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
|
|
729
|
+
const fragmentElement = isParseResultElement(rootNode) ? rootNode.result : rootNode;
|
|
730
|
+
const uriEvaluateOptions = {
|
|
731
|
+
baseURI: retrievalURI,
|
|
732
|
+
index: this.schema$idIndex
|
|
733
|
+
};
|
|
621
734
|
let schemaReference = this.reference;
|
|
735
|
+
let resourceRoot;
|
|
736
|
+
let resourceBaseURI;
|
|
622
737
|
try {
|
|
623
|
-
|
|
624
|
-
|
|
738
|
+
try {
|
|
739
|
+
uriEvaluate($refBaseURI, maybeRefractToSchemaElement(fragmentElement), uriEvaluateOptions);
|
|
740
|
+
// a $ref that resolves (by $id/$anchor/pointer) within the traversed
|
|
741
|
+
// content is internal even when its URI form (e.g. a URN or absolute
|
|
742
|
+
// $id) is not a bare fragment: the $id resource travels with the content
|
|
743
|
+
// wherever it is placed. Leave it untouched — it resolves against the
|
|
744
|
+
// in-content $id graph, and embedding the content into itself would be
|
|
745
|
+
// wrong.
|
|
746
|
+
return;
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (!(error instanceof EvaluationJsonSchemaUriError) || fragmentElement === documentElement) {
|
|
749
|
+
throw error;
|
|
750
|
+
}
|
|
751
|
+
const documentAsSchema = maybeRefractToSchemaElement(documentElement);
|
|
752
|
+
uriEvaluate($refBaseURI, documentAsSchema, uriEvaluateOptions);
|
|
753
|
+
// the $id resource sits in the current document but outside the
|
|
754
|
+
// traversed fragment, so it stays behind once the fragment is placed
|
|
755
|
+
// into the entry document. The resource to embed is that $id-bearing
|
|
756
|
+
// schema, addressed by its canonical URI (the $ref without fragment).
|
|
757
|
+
resourceRoot = uriEvaluate($refBaseURIStrippedHash, documentAsSchema, uriEvaluateOptions);
|
|
758
|
+
resourceBaseURI = $refBaseURIStrippedHash;
|
|
759
|
+
}
|
|
625
760
|
} catch (error) {
|
|
626
761
|
if (isURL && error instanceof EvaluationJsonSchemaUriError) {
|
|
762
|
+
schemaReference = await this.toReference(url.unsanitize($refBaseURI));
|
|
763
|
+
const referenceAsSchema = maybeRefractToSchemaElement(schemaReference.value.result);
|
|
627
764
|
if (isAnchor(uriToAnchor($refBaseURI))) {
|
|
628
|
-
schemaReference = await this.toReference(url.unsanitize($refBaseURI));
|
|
629
765
|
const selector = uriToAnchor($refBaseURI);
|
|
630
|
-
const referenceAsSchema = maybeRefractToSchemaElement(schemaReference.value.result);
|
|
631
766
|
$anchorEvaluate(selector, referenceAsSchema);
|
|
632
767
|
} else {
|
|
633
|
-
schemaReference = await this.toReference(url.unsanitize($refBaseURI));
|
|
634
768
|
const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
635
|
-
const referenceAsSchema = maybeRefractToSchemaElement(schemaReference.value.result);
|
|
636
769
|
jsonPointerEvaluate(referenceAsSchema, selector);
|
|
637
770
|
}
|
|
771
|
+
|
|
772
|
+
// the resource to embed is the WHOLE external document (its root
|
|
773
|
+
// schema), identified by its $id. A $ref into a fragment of the
|
|
774
|
+
// document embeds the entire document resource once; the fragment
|
|
775
|
+
// keeps resolving against the embedded $id.
|
|
776
|
+
resourceRoot = referenceAsSchema;
|
|
777
|
+
resourceBaseURI = resolveSchema$idField(url.stripHash(schemaReference.uri), resourceRoot) ?? url.stripHash(schemaReference.uri);
|
|
638
778
|
} else {
|
|
639
779
|
throw error;
|
|
640
780
|
}
|
|
641
781
|
}
|
|
642
|
-
|
|
643
|
-
// a $ref that resolves (by $id/$anchor/pointer) within the current document
|
|
644
|
-
// is internal even when its URI form (e.g. a URN or absolute $id) is not a
|
|
645
|
-
// bare fragment. Leave it untouched — it resolves against the in-document
|
|
646
|
-
// $id graph, and embedding the current document into itself would be wrong.
|
|
647
|
-
if (url.stripHash(schemaReference.uri) === url.stripHash(this.reference.uri)) {
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// the resource to embed is the WHOLE external document (its root schema),
|
|
652
|
-
// identified by its $id. A $ref into a fragment of the document embeds the
|
|
653
|
-
// entire document resource once; the fragment keeps resolving against the
|
|
654
|
-
// embedded $id.
|
|
655
|
-
const resourceRoot = maybeRefractToSchemaElement(schemaReference.value.result);
|
|
656
|
-
const resourceBaseURI = resolveSchema$idField(url.stripHash(schemaReference.uri), resourceRoot) ?? url.stripHash(schemaReference.uri);
|
|
657
782
|
const field = cf.schemas.name;
|
|
658
783
|
const canonicalKey = `${field}\t${resourceBaseURI}`;
|
|
659
784
|
|
|
660
|
-
//
|
|
661
|
-
// $
|
|
785
|
+
// a location-based $ref to a self-identifying resource is rebased onto the
|
|
786
|
+
// resource's $id (keeping its fragment) — once embedded, the resource is
|
|
787
|
+
// reachable only by its $id. A $ref already resolving to that $id is left
|
|
788
|
+
// as written. See rebaseSchema$ref for the spec trade-off.
|
|
789
|
+
//
|
|
790
|
+
// a $ref whose base URI changes on relocation into the entry document
|
|
791
|
+
// (inside a hoisted Response, Parameter, Path Item, ...) is instead
|
|
792
|
+
// rewritten to address the embedded resource from the base it will have
|
|
793
|
+
// there, otherwise it dangles once placed. See relocateSchema$ref.
|
|
794
|
+
const relocatedBaseURI = this.relocatedSchemaBaseURI(path, $refBaseURI);
|
|
795
|
+
const rebased$ref = relocatedBaseURI === undefined ? rebaseSchema$ref($refBaseURI, resourceBaseURI) : relocateSchema$ref($refBaseURI, resourceBaseURI, relocatedBaseURI, isStringElement(resourceRoot.$id));
|
|
796
|
+
|
|
797
|
+
// resource already embedded (or being embedded) — point the referencing
|
|
798
|
+
// $ref at it and stop. This also terminates circular external schema
|
|
662
799
|
// references, since the assignment is reserved BEFORE recursion.
|
|
663
800
|
if (this.assignments.has(canonicalKey)) {
|
|
801
|
+
if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
|
|
664
802
|
path.skip();
|
|
665
803
|
return;
|
|
666
804
|
}
|
|
667
805
|
|
|
668
|
-
// own a copy of the resource
|
|
669
|
-
// $ref still resolves against it once embedded
|
|
806
|
+
// own a copy of the resource
|
|
670
807
|
const embeddedElement = cloneDeep(resourceRoot);
|
|
671
|
-
if (!isStringElement(embeddedElement.$id)) {
|
|
672
|
-
embeddedElement.set('$id', resourceBaseURI);
|
|
673
|
-
}
|
|
674
808
|
|
|
675
809
|
// the embedded thing is the WHOLE resource (keyed by its $id), so its name
|
|
676
810
|
// is derived from the resource URI, not the $ref fragment — passing a root
|
|
@@ -692,20 +826,36 @@ class OpenAPI3_1BundleVisitor {
|
|
|
692
826
|
options: this.options,
|
|
693
827
|
assignments: this.assignments,
|
|
694
828
|
reservedNames: this.reservedNames,
|
|
695
|
-
refractCache: this.refractCache
|
|
829
|
+
refractCache: this.refractCache,
|
|
830
|
+
schema$idIndex: this.schema$idIndex,
|
|
831
|
+
placements: this.placements
|
|
696
832
|
});
|
|
697
833
|
const bundledElement = await traverseAsync(embeddedElement, visitor, {
|
|
698
834
|
mutable: true
|
|
699
835
|
});
|
|
700
836
|
|
|
837
|
+
// a resource without its own $id is identified by its retrieval URI; write
|
|
838
|
+
// it relative to the entry document (the base of components.schemas) so the
|
|
839
|
+
// bundle carries no absolute filesystem path and stays relocatable. Assigned
|
|
840
|
+
// after the nested traversal so a reader deriving a schema's base from live
|
|
841
|
+
// $ids (see #540) never resolves the resource's own $refs against the entry
|
|
842
|
+
// document.
|
|
843
|
+
if (!isStringElement(bundledElement.$id)) {
|
|
844
|
+
bundledElement.set('$id', url.relative(this.entryURI, resourceBaseURI));
|
|
845
|
+
}
|
|
846
|
+
|
|
701
847
|
// annotate the embedded resource with info about its origin
|
|
702
848
|
bundledElement.meta.set('ref-origin', schemaReference.uri);
|
|
703
849
|
|
|
704
|
-
//
|
|
705
|
-
this.
|
|
850
|
+
// queue the embedded resource for placement into the entry document's components
|
|
851
|
+
this.placements.push({
|
|
852
|
+
field,
|
|
853
|
+
name: componentName,
|
|
854
|
+
element: bundledElement
|
|
855
|
+
});
|
|
706
856
|
|
|
707
|
-
// the referencing $ref
|
|
708
|
-
|
|
857
|
+
// point the referencing $ref at the embedded resource's $id
|
|
858
|
+
if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
|
|
709
859
|
path.skip();
|
|
710
860
|
} catch (error) {
|
|
711
861
|
this.handleError(`Error while bundling Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
package/src/bundle/util.cjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
5
6
|
});
|
|
6
|
-
exports.uniqueName = exports.toPascalCase = exports.sanitizeComponentName = void 0;
|
|
7
|
+
exports.uniqueName = exports.toPascalCase = exports.sanitizeComponentName = exports.relocateSchema$ref = exports.rebaseSchema$ref = void 0;
|
|
8
|
+
var url = _interopRequireWildcard(require("../util/url.cjs"));
|
|
7
9
|
/**
|
|
8
10
|
* Upper-cases the first letter of each whitespace-separated word and joins
|
|
9
11
|
* them, e.g. `pet model` -> `PetModel`.
|
|
@@ -39,4 +41,57 @@ const uniqueName = (candidate, isTaken) => {
|
|
|
39
41
|
}
|
|
40
42
|
return `${candidate}-${counter}`;
|
|
41
43
|
};
|
|
42
|
-
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Computes the `$ref` value addressing an embedded JSON Schema resource by its
|
|
47
|
+
* canonical `$id`. `$refURI` is the `$ref` resolved against the referencing
|
|
48
|
+
* document's base URI; `resourceURI` is the embedded resource's canonical URI
|
|
49
|
+
* (its `$id`, or the retrieval URI when it has none).
|
|
50
|
+
*
|
|
51
|
+
* When the two differ — a location-based `$ref` (e.g. `./ex.json#/$defs/Pet`)
|
|
52
|
+
* to a resource that identifies itself differently (`$id: https://example.com/pets`)
|
|
53
|
+
* — the `$ref` is rebased onto the resource URI, keeping its `$anchor` or JSON
|
|
54
|
+
* Pointer fragment. Once embedded, the resource is reachable only by its `$id`,
|
|
55
|
+
* so an unchanged `$ref` would dangle. When they already agree, `undefined` is
|
|
56
|
+
* returned and the `$ref` is to be left as written.
|
|
57
|
+
*
|
|
58
|
+
* This knowingly departs from the letter of JSON Schema 2020-12 §9.3.1
|
|
59
|
+
* ("references ... MUST NOT be changed"), which presumes `$ref`s are written
|
|
60
|
+
* against `$id`s. The target schema — and thus evaluation output — is unchanged;
|
|
61
|
+
* only the alias the loader knew the resource by is normalized to its canonical URI.
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
exports.uniqueName = uniqueName;
|
|
66
|
+
const rebaseSchema$ref = ($refURI, resourceURI) => {
|
|
67
|
+
if (url.stripHash($refURI) === resourceURI) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const fragment = url.getHash($refURI);
|
|
71
|
+
return fragment === '#' ? resourceURI : `${resourceURI}${fragment}`;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Computes the `$ref` value addressing an embedded JSON Schema resource from a
|
|
76
|
+
* Schema Object whose base URI changes when it is relocated into the entry
|
|
77
|
+
* document — one carried inside a hoisted Response, Parameter, Path Item, etc.
|
|
78
|
+
* `$refURI` and `resourceURI` are as in `rebaseSchema$ref`; `baseURI` is the
|
|
79
|
+
* base the `$ref` resolves against after relocation.
|
|
80
|
+
*
|
|
81
|
+
* A resource that identifies itself (`isResourceIdentified`, i.e. it declares a
|
|
82
|
+
* `$id`) is addressed by that `$id`, exactly as `rebaseSchema$ref` does. A
|
|
83
|
+
* resource identified only by its retrieval URI is addressed by a URI reference
|
|
84
|
+
* relative to `baseURI`: it then resolves to the `$id` assigned from that
|
|
85
|
+
* retrieval URI wherever the bundle is placed, and no absolute retrieval
|
|
86
|
+
* location leaks into the `$ref`. The `$anchor` or JSON Pointer fragment is
|
|
87
|
+
* kept in both cases.
|
|
88
|
+
*
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
exports.rebaseSchema$ref = rebaseSchema$ref;
|
|
92
|
+
const relocateSchema$ref = ($refURI, resourceURI, baseURI, isResourceIdentified) => {
|
|
93
|
+
const target = isResourceIdentified ? resourceURI : url.relative(baseURI, resourceURI);
|
|
94
|
+
const fragment = url.getHash($refURI);
|
|
95
|
+
return fragment === '#' ? target : `${target}${fragment}`;
|
|
96
|
+
};
|
|
97
|
+
exports.relocateSchema$ref = relocateSchema$ref;
|
package/src/bundle/util.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as url from "../util/url.mjs";
|
|
1
2
|
/**
|
|
2
3
|
* Upper-cases the first letter of each whitespace-separated word and joins
|
|
3
4
|
* them, e.g. `pet model` -> `PetModel`.
|
|
@@ -30,4 +31,55 @@ export const uniqueName = (candidate, isTaken) => {
|
|
|
30
31
|
counter += 1;
|
|
31
32
|
}
|
|
32
33
|
return `${candidate}-${counter}`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Computes the `$ref` value addressing an embedded JSON Schema resource by its
|
|
38
|
+
* canonical `$id`. `$refURI` is the `$ref` resolved against the referencing
|
|
39
|
+
* document's base URI; `resourceURI` is the embedded resource's canonical URI
|
|
40
|
+
* (its `$id`, or the retrieval URI when it has none).
|
|
41
|
+
*
|
|
42
|
+
* When the two differ — a location-based `$ref` (e.g. `./ex.json#/$defs/Pet`)
|
|
43
|
+
* to a resource that identifies itself differently (`$id: https://example.com/pets`)
|
|
44
|
+
* — the `$ref` is rebased onto the resource URI, keeping its `$anchor` or JSON
|
|
45
|
+
* Pointer fragment. Once embedded, the resource is reachable only by its `$id`,
|
|
46
|
+
* so an unchanged `$ref` would dangle. When they already agree, `undefined` is
|
|
47
|
+
* returned and the `$ref` is to be left as written.
|
|
48
|
+
*
|
|
49
|
+
* This knowingly departs from the letter of JSON Schema 2020-12 §9.3.1
|
|
50
|
+
* ("references ... MUST NOT be changed"), which presumes `$ref`s are written
|
|
51
|
+
* against `$id`s. The target schema — and thus evaluation output — is unchanged;
|
|
52
|
+
* only the alias the loader knew the resource by is normalized to its canonical URI.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
export const rebaseSchema$ref = ($refURI, resourceURI) => {
|
|
57
|
+
if (url.stripHash($refURI) === resourceURI) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const fragment = url.getHash($refURI);
|
|
61
|
+
return fragment === '#' ? resourceURI : `${resourceURI}${fragment}`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Computes the `$ref` value addressing an embedded JSON Schema resource from a
|
|
66
|
+
* Schema Object whose base URI changes when it is relocated into the entry
|
|
67
|
+
* document — one carried inside a hoisted Response, Parameter, Path Item, etc.
|
|
68
|
+
* `$refURI` and `resourceURI` are as in `rebaseSchema$ref`; `baseURI` is the
|
|
69
|
+
* base the `$ref` resolves against after relocation.
|
|
70
|
+
*
|
|
71
|
+
* A resource that identifies itself (`isResourceIdentified`, i.e. it declares a
|
|
72
|
+
* `$id`) is addressed by that `$id`, exactly as `rebaseSchema$ref` does. A
|
|
73
|
+
* resource identified only by its retrieval URI is addressed by a URI reference
|
|
74
|
+
* relative to `baseURI`: it then resolves to the `$id` assigned from that
|
|
75
|
+
* retrieval URI wherever the bundle is placed, and no absolute retrieval
|
|
76
|
+
* location leaks into the `$ref`. The `$anchor` or JSON Pointer fragment is
|
|
77
|
+
* kept in both cases.
|
|
78
|
+
*
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export const relocateSchema$ref = ($refURI, resourceURI, baseURI, isResourceIdentified) => {
|
|
82
|
+
const target = isResourceIdentified ? resourceURI : url.relative(baseURI, resourceURI);
|
|
83
|
+
const fragment = url.getHash($refURI);
|
|
84
|
+
return fragment === '#' ? target : `${target}${fragment}`;
|
|
33
85
|
};
|
|
@@ -20,31 +20,31 @@ Object.defineProperty(exports, "dereferenceSourceDescriptions", {
|
|
|
20
20
|
Object.defineProperty(exports, "identifiedBy$self", {
|
|
21
21
|
enumerable: true,
|
|
22
22
|
get: function () {
|
|
23
|
-
return
|
|
23
|
+
return _util2.identifiedBy$self;
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
Object.defineProperty(exports, "maybeRefractToJSONSchemaElement", {
|
|
27
27
|
enumerable: true,
|
|
28
28
|
get: function () {
|
|
29
|
-
return
|
|
29
|
+
return _util2.maybeRefractToJSONSchemaElement;
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
Object.defineProperty(exports, "resolveArazzo$selfField", {
|
|
33
33
|
enumerable: true,
|
|
34
34
|
get: function () {
|
|
35
|
-
return
|
|
35
|
+
return _util2.resolveArazzo$selfField;
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
Object.defineProperty(exports, "resolveSchema$idField", {
|
|
39
39
|
enumerable: true,
|
|
40
40
|
get: function () {
|
|
41
|
-
return
|
|
41
|
+
return _util2.resolveSchema$idField;
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
Object.defineProperty(exports, "resolveSchema$refField", {
|
|
45
45
|
enumerable: true,
|
|
46
46
|
get: function () {
|
|
47
|
-
return
|
|
47
|
+
return _util2.resolveSchema$refField;
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
@@ -54,8 +54,9 @@ var _DereferenceStrategy = _interopRequireDefault(require("../DereferenceStrateg
|
|
|
54
54
|
var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
|
|
55
55
|
var _ReferenceSet = _interopRequireDefault(require("../../../ReferenceSet.cjs"));
|
|
56
56
|
var _visitor = _interopRequireDefault(require("./visitor.cjs"));
|
|
57
|
+
var _util = require("../openapi-3-1/util.cjs");
|
|
57
58
|
var _sourceDescriptions = require("./source-descriptions.cjs");
|
|
58
|
-
var
|
|
59
|
+
var _util2 = require("./util.cjs");
|
|
59
60
|
/**
|
|
60
61
|
* @public
|
|
61
62
|
*/
|
|
@@ -110,7 +111,8 @@ class Arazzo1DereferenceStrategy extends _DereferenceStrategy.default {
|
|
|
110
111
|
const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
|
|
111
112
|
const visitor = new _visitor.default({
|
|
112
113
|
reference,
|
|
113
|
-
options
|
|
114
|
+
options,
|
|
115
|
+
ancestorSchema$ids: (0, _util.detachedRootSchema$ids)(refSet.rootRef.value.result)
|
|
114
116
|
});
|
|
115
117
|
const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
|
|
116
118
|
mutable: true,
|
|
@@ -5,6 +5,7 @@ import DereferenceStrategy from "../DereferenceStrategy.mjs";
|
|
|
5
5
|
import Reference from "../../../Reference.mjs";
|
|
6
6
|
import ReferenceSet from "../../../ReferenceSet.mjs";
|
|
7
7
|
import Arazzo1DereferenceVisitor from "./visitor.mjs";
|
|
8
|
+
import { detachedRootSchema$ids } from "../openapi-3-1/util.mjs";
|
|
8
9
|
import { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
|
|
9
10
|
/**
|
|
10
11
|
* @public
|
|
@@ -59,7 +60,8 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
|
|
|
59
60
|
const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
|
|
60
61
|
const visitor = new Arazzo1DereferenceVisitor({
|
|
61
62
|
reference,
|
|
62
|
-
options
|
|
63
|
+
options,
|
|
64
|
+
ancestorSchema$ids: detachedRootSchema$ids(refSet.rootRef.value.result)
|
|
63
65
|
});
|
|
64
66
|
const dereferencedElement = await traverseAsync(refSet.rootRef.value, visitor, {
|
|
65
67
|
mutable: true,
|
|
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "isAnchor", {
|
|
|
33
33
|
return _$anchor.isAnchor;
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
+
Object.defineProperty(exports, "locate", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _$anchor.locate;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
36
42
|
Object.defineProperty(exports, "parse", {
|
|
37
43
|
enumerable: true,
|
|
38
44
|
get: function () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { isAnchor, uriToAnchor, parse, evaluate, EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError, JsonSchema$anchorError } from "../../openapi-3-1/selectors/$anchor.mjs";
|
|
1
|
+
export { isAnchor, uriToAnchor, parse, locate, evaluate, EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError, JsonSchema$anchorError } from "../../openapi-3-1/selectors/$anchor.mjs";
|
|
@@ -21,4 +21,10 @@ Object.defineProperty(exports, "evaluate", {
|
|
|
21
21
|
return _uri.evaluate;
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
|
+
Object.defineProperty(exports, "locate", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _uri.locate;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
24
30
|
var _uri = require("../../openapi-3-1/selectors/uri.cjs");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { evaluate, EvaluationJsonSchemaUriError, JsonSchemaUriError } from "../../openapi-3-1/selectors/uri.mjs";
|
|
1
|
+
export { locate, evaluate, EvaluationJsonSchemaUriError, JsonSchemaUriError } from "../../openapi-3-1/selectors/uri.mjs";
|
|
@@ -17,6 +17,12 @@ Object.defineProperty(exports, "resolveSchema$refField", {
|
|
|
17
17
|
return _util.resolveSchema$refField;
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
|
+
Object.defineProperty(exports, "resolveSchemaBaseURI", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () {
|
|
23
|
+
return _util.resolveSchemaBaseURI;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
20
26
|
exports.validateSourceDescriptionAPI = void 0;
|
|
21
27
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
22
28
|
var _apidomCore = require("@speclynx/apidom-core");
|