@speclynx/apidom-reference 6.1.2 → 7.0.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/CHANGELOG.md +63 -0
- package/README.md +57 -0
- package/dist/apidom-reference.browser.js +5783 -3741
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +31 -30
- package/src/File.cjs +5 -2
- package/src/File.mjs +5 -2
- package/src/bundle/index.cjs +6 -4
- package/src/bundle/index.mjs +6 -4
- package/src/bundle/strategies/arazzo-1/index.cjs +3 -2
- package/src/bundle/strategies/arazzo-1/index.mjs +3 -2
- package/src/bundle/strategies/asyncapi-2/index.cjs +3 -2
- package/src/bundle/strategies/asyncapi-2/index.mjs +3 -2
- package/src/bundle/strategies/openapi-2/index.cjs +3 -2
- package/src/bundle/strategies/openapi-2/index.mjs +3 -2
- package/src/bundle/strategies/openapi-3-0/index.cjs +3 -2
- package/src/bundle/strategies/openapi-3-0/index.mjs +3 -2
- package/src/bundle/strategies/openapi-3-1/index.cjs +3 -2
- package/src/bundle/strategies/openapi-3-1/index.mjs +3 -2
- package/src/dereference/index.cjs +8 -5
- package/src/dereference/index.mjs +8 -5
- package/src/dereference/strategies/DereferenceVisitor.cjs +588 -0
- package/src/dereference/strategies/DereferenceVisitor.mjs +580 -0
- package/src/dereference/strategies/arazzo-1/index.cjs +14 -39
- package/src/dereference/strategies/arazzo-1/index.mjs +15 -40
- package/src/dereference/strategies/arazzo-1/visitor.cjs +207 -331
- package/src/dereference/strategies/arazzo-1/visitor.mjs +215 -338
- package/src/dereference/strategies/asyncapi-2/index.cjs +11 -39
- package/src/dereference/strategies/asyncapi-2/index.mjs +11 -39
- package/src/dereference/strategies/asyncapi-2/visitor.cjs +133 -425
- package/src/dereference/strategies/asyncapi-2/visitor.mjs +137 -429
- package/src/dereference/strategies/openapi-2/index.cjs +12 -42
- package/src/dereference/strategies/openapi-2/index.mjs +12 -42
- package/src/dereference/strategies/openapi-2/visitor.cjs +168 -532
- package/src/dereference/strategies/openapi-2/visitor.mjs +172 -536
- package/src/dereference/strategies/openapi-3-0/index.cjs +12 -42
- package/src/dereference/strategies/openapi-3-0/index.mjs +12 -42
- package/src/dereference/strategies/openapi-3-0/visitor.cjs +194 -468
- package/src/dereference/strategies/openapi-3-0/visitor.mjs +199 -473
- package/src/dereference/strategies/openapi-3-1/index.cjs +16 -42
- package/src/dereference/strategies/openapi-3-1/index.mjs +16 -42
- package/src/dereference/strategies/openapi-3-1/visitor.cjs +350 -602
- package/src/dereference/strategies/openapi-3-1/visitor.mjs +357 -608
- package/src/dereference/strategies/overlay-1/index.cjs +3 -2
- package/src/dereference/strategies/overlay-1/index.mjs +3 -2
- package/src/index.cjs +0 -7
- package/src/index.mjs +0 -1
- package/src/options/index.cjs +4 -0
- package/src/options/index.mjs +4 -0
- package/src/options/util.cjs +7 -1
- package/src/options/util.mjs +8 -2
- package/src/parse/index.cjs +9 -1
- package/src/parse/index.mjs +9 -1
- package/src/parse/parsers/apidom-json/index.cjs +2 -1
- package/src/parse/parsers/apidom-json/index.mjs +2 -1
- package/src/parse/parsers/arazzo-json-1/index.cjs +2 -1
- package/src/parse/parsers/arazzo-json-1/index.mjs +2 -1
- package/src/parse/parsers/arazzo-yaml-1/index.cjs +2 -1
- package/src/parse/parsers/arazzo-yaml-1/index.mjs +2 -1
- package/src/parse/parsers/asyncapi-json-2/index.cjs +2 -1
- package/src/parse/parsers/asyncapi-json-2/index.mjs +2 -1
- package/src/parse/parsers/asyncapi-yaml-2/index.cjs +2 -1
- package/src/parse/parsers/asyncapi-yaml-2/index.mjs +2 -1
- package/src/parse/parsers/json/index.cjs +2 -1
- package/src/parse/parsers/json/index.mjs +2 -1
- package/src/parse/parsers/openapi-json-2/index.cjs +2 -1
- package/src/parse/parsers/openapi-json-2/index.mjs +2 -1
- package/src/parse/parsers/openapi-json-3-0/index.cjs +2 -1
- package/src/parse/parsers/openapi-json-3-0/index.mjs +2 -1
- package/src/parse/parsers/openapi-json-3-1/index.cjs +2 -1
- package/src/parse/parsers/openapi-json-3-1/index.mjs +2 -1
- package/src/parse/parsers/openapi-yaml-2/index.cjs +2 -1
- package/src/parse/parsers/openapi-yaml-2/index.mjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-0/index.cjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-0/index.mjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-1/index.cjs +2 -1
- package/src/parse/parsers/openapi-yaml-3-1/index.mjs +2 -1
- package/src/parse/parsers/overlay-json-1/index.cjs +2 -1
- package/src/parse/parsers/overlay-json-1/index.mjs +2 -1
- package/src/parse/parsers/overlay-yaml-1/index.cjs +2 -1
- package/src/parse/parsers/overlay-yaml-1/index.mjs +2 -1
- package/src/parse/parsers/yaml-1-2/index.cjs +2 -1
- package/src/parse/parsers/yaml-1-2/index.mjs +2 -1
- package/src/resolve/index.cjs +6 -3
- package/src/resolve/index.mjs +6 -3
- package/src/util/url.cjs +38 -8
- package/src/util/url.mjs +35 -6
- package/types/apidom-reference.d.ts +13 -9
- package/types/dereference/strategies/DereferenceVisitor.d.ts +224 -0
- package/types/dereference/strategies/arazzo-1/index.d.ts +0 -1
- package/types/dereference/strategies/arazzo-1/visitor.d.ts +38 -38
- package/types/dereference/strategies/asyncapi-2/index.d.ts +0 -1
- package/types/dereference/strategies/asyncapi-2/visitor.d.ts +13 -29
- package/types/dereference/strategies/openapi-2/index.d.ts +0 -1
- package/types/dereference/strategies/openapi-2/visitor.d.ts +14 -29
- package/types/dereference/strategies/openapi-3-0/index.d.ts +0 -1
- package/types/dereference/strategies/openapi-3-0/visitor.d.ts +13 -28
- package/types/dereference/strategies/openapi-3-1/index.d.ts +0 -1
- package/types/dereference/strategies/openapi-3-1/visitor.d.ts +37 -33
- package/types/index.d.ts +0 -1
- package/types/util/url.d.ts +11 -0
- package/LICENSES/AFL-3.0.txt +0 -182
- package/LICENSES/BSD-3-Clause.txt +0 -26
- package/src/dereference/util.cjs +0 -33
- package/src/dereference/util.mjs +0 -27
- package/types/dereference/util.d.ts +0 -9
|
@@ -1,516 +1,224 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { toValue, toYAML } from '@speclynx/apidom-core';
|
|
1
|
+
import { isStringElement, cloneDeep, cloneShallow } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { toValue } from '@speclynx/apidom-core';
|
|
4
3
|
import { ApiDOMStructuredError } from '@speclynx/apidom-error';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
|
|
9
|
-
import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
|
|
10
|
-
import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
|
|
11
|
-
import { AncestorLineage } from "../../util.mjs";
|
|
4
|
+
import { evaluate as jsonPointerEvaluate, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
|
|
5
|
+
import { isReferenceLikeElement, isChannelItemElement, isReferenceElement, isSchemaElement, isBooleanJSONSchemaElement, refract, refractReference } from '@speclynx/apidom-ns-asyncapi-2';
|
|
6
|
+
import DereferenceVisitor from "../DereferenceVisitor.mjs";
|
|
12
7
|
import * as url from "../../../util/url.mjs";
|
|
13
|
-
import parse from "../../../parse/index.mjs";
|
|
14
|
-
import Reference from "../../../Reference.mjs";
|
|
15
8
|
/**
|
|
16
9
|
* @public
|
|
17
10
|
*/
|
|
18
11
|
/**
|
|
12
|
+
* Dereferences an AsyncAPI 2 document. Reference Objects expect the type of their
|
|
13
|
+
* `referenced-element` meta, Channel Item `$ref`s a Channel Item Object.
|
|
14
|
+
*
|
|
19
15
|
* @public
|
|
20
16
|
*/
|
|
21
|
-
class AsyncAPI2DereferenceVisitor {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
options;
|
|
25
|
-
refractCache;
|
|
26
|
-
visited;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Tracks element ancestors across dive-deep traversal boundaries.
|
|
30
|
-
* Used for cycle detection: if a referenced element is found in
|
|
31
|
-
* the ancestor lineage, a circular reference is detected.
|
|
32
|
-
*/
|
|
33
|
-
ancestors;
|
|
34
|
-
constructor({
|
|
35
|
-
reference,
|
|
36
|
-
options,
|
|
37
|
-
indirections = [],
|
|
38
|
-
ancestors = new AncestorLineage(),
|
|
39
|
-
refractCache = new WeakMap(),
|
|
40
|
-
visited = new WeakSet()
|
|
41
|
-
}) {
|
|
42
|
-
this.indirections = indirections;
|
|
43
|
-
this.reference = reference;
|
|
44
|
-
this.options = options;
|
|
45
|
-
this.ancestors = new AncestorLineage(...ancestors);
|
|
46
|
-
this.refractCache = refractCache;
|
|
47
|
-
this.visited = visited;
|
|
17
|
+
class AsyncAPI2DereferenceVisitor extends DereferenceVisitor {
|
|
18
|
+
get strategyName() {
|
|
19
|
+
return 'asyncapi-2';
|
|
48
20
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return [ancestorsLineage, directAncestors];
|
|
54
|
-
}
|
|
55
|
-
toBaseURI(uri) {
|
|
56
|
-
return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
|
|
57
|
-
}
|
|
58
|
-
async toReference(uri) {
|
|
59
|
-
// detect maximum depth of resolution
|
|
60
|
-
if (this.reference.depth >= this.options.resolve.maxDepth) {
|
|
61
|
-
throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
|
|
62
|
-
maxDepth: this.options.resolve.maxDepth,
|
|
63
|
-
uri: this.reference.uri
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
const baseURI = this.toBaseURI(uri);
|
|
67
|
-
const {
|
|
68
|
-
refSet
|
|
69
|
-
} = this.reference;
|
|
70
|
-
|
|
71
|
-
// we've already processed this Reference in past
|
|
72
|
-
if (refSet.has(baseURI)) {
|
|
73
|
-
return refSet.find(propEq(baseURI, 'uri'));
|
|
74
|
-
}
|
|
75
|
-
const parseResult = await parse(url.unsanitize(baseURI), {
|
|
76
|
-
...this.options,
|
|
77
|
-
parse: {
|
|
78
|
-
...this.options.parse,
|
|
79
|
-
mediaType: 'text/plain'
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// register new mutable reference with a refSet
|
|
84
|
-
//
|
|
85
|
-
// NOTE(known limitation): the mutable reference is mutated in place during traversal
|
|
86
|
-
// (via `{ mutable: true }`). When an external document evaluates a JSON pointer back
|
|
87
|
-
// into this document, it may receive an already-resolved element instead of the original
|
|
88
|
-
// $ref. That resolved element was produced using the entry document's resolution context
|
|
89
|
-
// (ancestors, indirections), which may differ from the external document's context.
|
|
90
|
-
// This can affect cycle detection in rare cross-document circular reference patterns.
|
|
91
|
-
//
|
|
92
|
-
// Remediation: evaluate JSON pointers against the immutable (original) parse tree
|
|
93
|
-
// instead of the mutable working copy. The `immutable://` reference below preserves
|
|
94
|
-
// the original tree and could be used for pointer evaluation, ensuring every resolution
|
|
95
|
-
// context always sees raw, unresolved elements and processes them with its own
|
|
96
|
-
// ancestors/indirections. The trade-off is that elements referenced by multiple
|
|
97
|
-
// documents would be resolved once per context instead of being reused.
|
|
98
|
-
const mutableReference = new Reference({
|
|
99
|
-
uri: baseURI,
|
|
100
|
-
value: this.options.dereference.immutable ? cloneDeep(parseResult) : parseResult,
|
|
101
|
-
depth: this.reference.depth + 1
|
|
21
|
+
createVisitor(reference) {
|
|
22
|
+
return new AsyncAPI2DereferenceVisitor({
|
|
23
|
+
reference,
|
|
24
|
+
options: this.options
|
|
102
25
|
});
|
|
103
|
-
refSet.add(mutableReference);
|
|
104
|
-
if (this.options.dereference.immutable) {
|
|
105
|
-
// register new immutable reference with a refSet
|
|
106
|
-
const immutableReference = new Reference({
|
|
107
|
-
uri: `immutable://${baseURI}`,
|
|
108
|
-
value: parseResult,
|
|
109
|
-
depth: this.reference.depth + 1
|
|
110
|
-
});
|
|
111
|
-
refSet.add(immutableReference);
|
|
112
|
-
}
|
|
113
|
-
return mutableReference;
|
|
114
26
|
}
|
|
115
27
|
|
|
116
28
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* For new errors: wraps in UnresolvableReferenceError with structured context
|
|
120
|
-
* (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
|
|
121
|
-
* For errors already wrapped by a nested visitor: prepends the current hop to the trace.
|
|
122
|
-
*
|
|
123
|
-
* Inner/intermediate visitors always throw to let the trace accumulate.
|
|
124
|
-
* Only the entry document visitor respects continueOnError (callback/swallow/throw).
|
|
29
|
+
* A Reference Object stands for the type its reference expects: it's dereferenced as that type.
|
|
125
30
|
*/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
|
|
131
|
-
const uri = this.reference.uri;
|
|
132
|
-
const type = referencingElement.element;
|
|
133
|
-
const codeFrame = toYAML(referencingElement);
|
|
134
|
-
|
|
135
|
-
// find element location by identity in the document tree.
|
|
136
|
-
// guarded: this.reference.value may not be a ParseResultElement or may lack a result.
|
|
137
|
-
// falls back to visitorPath which may produce an incomplete path when
|
|
138
|
-
// dereferenceApiDOM is called with a fragment (cloneShallow creates a new root identity).
|
|
139
|
-
let location;
|
|
140
|
-
const root = this.reference.value.result;
|
|
141
|
-
if (isElement(root)) {
|
|
142
|
-
traverse(root, {
|
|
143
|
-
enter: p => {
|
|
144
|
-
if (p.node === referencingElement || this.refractCache.get(p.node) === referencingElement) {
|
|
145
|
-
location = p.formatPath();
|
|
146
|
-
p.stop();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
location ??= visitorPath.formatPath();
|
|
152
|
-
const hop = {
|
|
153
|
-
uri,
|
|
154
|
-
type,
|
|
155
|
-
refFieldName,
|
|
156
|
-
refFieldValue,
|
|
157
|
-
location,
|
|
158
|
-
codeFrame
|
|
159
|
-
};
|
|
31
|
+
isOfType(element, type) {
|
|
32
|
+
if (isReferenceElement(element)) return true;
|
|
33
|
+
return type === 'schema' ? isSchemaElement(element) || isBooleanJSONSchemaElement(element) : element.element === type;
|
|
34
|
+
}
|
|
160
35
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
for (const h of error.trace) {
|
|
172
|
-
if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// @ts-ignore
|
|
176
|
-
error.trace = [hop, ...error.trace];
|
|
177
|
-
unresolvedError = error;
|
|
178
|
-
} else {
|
|
179
|
-
unresolvedError = new UnresolvableReferenceError(message, {
|
|
180
|
-
cause: error,
|
|
181
|
-
type,
|
|
182
|
-
uri,
|
|
183
|
-
location,
|
|
184
|
-
codeFrame,
|
|
185
|
-
refFieldName,
|
|
186
|
-
refFieldValue,
|
|
187
|
-
trace: []
|
|
188
|
-
});
|
|
36
|
+
/**
|
|
37
|
+
* A generic structure holding a `$ref` is a Reference Object to the expected type,
|
|
38
|
+
* unless the type has its own `$ref` field (Channel Item Object).
|
|
39
|
+
*/
|
|
40
|
+
refractTo(element, type) {
|
|
41
|
+
if (type !== 'channelItem' && isReferenceLikeElement(element)) {
|
|
42
|
+
const referenceElement = refractReference(element);
|
|
43
|
+
referenceElement.meta.set('referenced-element', type);
|
|
44
|
+
return referenceElement;
|
|
189
45
|
}
|
|
190
|
-
|
|
191
|
-
|
|
46
|
+
return refract(element, {
|
|
47
|
+
element: type
|
|
48
|
+
});
|
|
192
49
|
}
|
|
193
50
|
async ReferenceElement(path) {
|
|
194
51
|
const referencingElement = path.node;
|
|
195
52
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
53
|
+
// the fields of a Reference Object hold no references
|
|
54
|
+
path.skip();
|
|
200
55
|
|
|
201
|
-
//
|
|
202
|
-
if (this.
|
|
203
|
-
path.skip();
|
|
56
|
+
// ignore Reference Object without a string $ref, e.g. an empty YAML value
|
|
57
|
+
if (this.isTranscluded(referencingElement) || !isStringElement(referencingElement.$ref)) {
|
|
204
58
|
return;
|
|
205
59
|
}
|
|
206
|
-
const
|
|
60
|
+
const $ref = toValue(referencingElement.$ref);
|
|
61
|
+
const retrievalURI = this.toBaseURI($ref);
|
|
207
62
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
208
63
|
const isExternalReference = !isInternalReference;
|
|
209
64
|
|
|
210
65
|
// ignore resolving internal Reference Objects
|
|
211
66
|
if (!this.options.resolve.internal && isInternalReference) {
|
|
212
|
-
// skip traversing this reference and all it's child elements
|
|
213
|
-
path.skip();
|
|
214
67
|
return;
|
|
215
68
|
}
|
|
216
69
|
// ignore resolving external Reference Objects
|
|
217
70
|
if (!this.options.resolve.external && isExternalReference) {
|
|
218
|
-
// skip traversing this reference and all it's child elements
|
|
219
|
-
path.skip();
|
|
220
71
|
return;
|
|
221
72
|
}
|
|
222
|
-
const $refBaseURI = url.resolve(retrievalURI,
|
|
223
|
-
const indirectionsSize = this.indirections.length;
|
|
73
|
+
const $refBaseURI = url.resolve(retrievalURI, $ref);
|
|
224
74
|
try {
|
|
225
|
-
const reference = await this.toReference(
|
|
226
|
-
this.indirections.push(referencingElement);
|
|
75
|
+
const reference = await this.toReference($ref);
|
|
227
76
|
const jsonPointer = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
228
|
-
|
|
229
77
|
// possibly non-semantic fragment
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// applying semantics to a fragment
|
|
233
|
-
const referencedElementType = referencingElement.meta.get('referenced-element');
|
|
234
|
-
if (referencedElement.element !== referencedElementType && !isReferenceElement(referencedElement)) {
|
|
235
|
-
if (this.refractCache.has(referencedElement)) {
|
|
236
|
-
referencedElement = this.refractCache.get(referencedElement);
|
|
237
|
-
} else if (isReferenceLikeElement(referencedElement)) {
|
|
238
|
-
// handling generic indirect references
|
|
239
|
-
const sourceElement = referencedElement;
|
|
240
|
-
referencedElement = refractReference(referencedElement);
|
|
241
|
-
referencedElement.meta.set('referenced-element', referencedElementType);
|
|
242
|
-
this.refractCache.set(sourceElement, referencedElement);
|
|
243
|
-
} else {
|
|
244
|
-
// handling direct references
|
|
245
|
-
const sourceElement = referencedElement;
|
|
246
|
-
referencedElement = refract(referencedElement, {
|
|
247
|
-
element: referencedElementType
|
|
248
|
-
});
|
|
249
|
-
this.refractCache.set(sourceElement, referencedElement);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
78
|
+
const referencedElement = jsonPointerEvaluate(reference.value.result, jsonPointer);
|
|
252
79
|
|
|
253
|
-
// detect direct
|
|
80
|
+
// detect direct reference
|
|
254
81
|
if (referencingElement === referencedElement) {
|
|
255
82
|
throw new ApiDOMStructuredError('Recursive Reference Object detected', {
|
|
256
|
-
$ref
|
|
83
|
+
$ref
|
|
257
84
|
});
|
|
258
85
|
}
|
|
259
86
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
87
|
+
// a reference without an expected type reads its target as its own type
|
|
88
|
+
const type = toValue(referencingElement.meta.get('referenced-element')) ?? referencedElement.element;
|
|
89
|
+
await this.addTransclusion(path, {
|
|
90
|
+
source: referencedElement,
|
|
91
|
+
type,
|
|
92
|
+
reference,
|
|
93
|
+
context: undefined
|
|
94
|
+
}, {
|
|
95
|
+
toTranscludedElement: (element, target) => {
|
|
96
|
+
// a chain of Reference Objects leading back to itself never reaches a value
|
|
97
|
+
if (isReferenceElement(target)) return element;
|
|
98
|
+
|
|
99
|
+
// Boolean JSON Schemas
|
|
100
|
+
if (isBooleanJSONSchemaElement(target)) {
|
|
101
|
+
const booleanJsonSchemaElement = cloneDeep(target);
|
|
102
|
+
// annotate referenced element with info about original referencing element
|
|
103
|
+
booleanJsonSchemaElement.meta.set('ref-fields', {
|
|
104
|
+
$ref
|
|
105
|
+
});
|
|
106
|
+
// annotate referenced element with info about origin
|
|
107
|
+
booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
|
|
108
|
+
booleanJsonSchemaElement.meta.set('ref-type', element.element);
|
|
109
|
+
return booleanJsonSchemaElement;
|
|
110
|
+
}
|
|
267
111
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
$ref
|
|
112
|
+
/**
|
|
113
|
+
* Creating a new version of referenced element to avoid modifying the original one.
|
|
114
|
+
*/
|
|
115
|
+
const mergedElement = cloneShallow(target);
|
|
116
|
+
// annotate referenced element with info about original referencing element
|
|
117
|
+
mergedElement.meta.set('ref-fields', {
|
|
118
|
+
$ref
|
|
275
119
|
});
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
120
|
+
// annotate fragment with info about origin
|
|
121
|
+
mergedElement.meta.set('ref-origin', reference.uri);
|
|
122
|
+
mergedElement.meta.set('ref-type', element.element);
|
|
123
|
+
return mergedElement;
|
|
124
|
+
},
|
|
125
|
+
circular: {
|
|
126
|
+
uri: $refBaseURI,
|
|
127
|
+
meta: {
|
|
279
128
|
uri: reference.uri,
|
|
280
|
-
$ref
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
path.skip();
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Dive deep into the fragment.
|
|
292
|
-
*
|
|
293
|
-
* Cases to consider:
|
|
294
|
-
* 1. We're crossing document boundary
|
|
295
|
-
* 2. Fragment is from non-entry document
|
|
296
|
-
* 3. Fragment is a Reference Object. We need to follow it to get the eventual value
|
|
297
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
298
|
-
*/
|
|
299
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
300
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
301
|
-
if ((isExternalReference || isNonEntryDocument || isReferenceElement(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
302
|
-
if (shouldDetectCircular) {
|
|
303
|
-
this.visited.add(referencedElement);
|
|
129
|
+
$ref
|
|
130
|
+
},
|
|
131
|
+
details: {
|
|
132
|
+
$ref
|
|
133
|
+
}
|
|
304
134
|
}
|
|
305
|
-
|
|
306
|
-
// append referencing reference to ancestors lineage
|
|
307
|
-
directAncestors.add(referencingElement);
|
|
308
|
-
const visitor = new AsyncAPI2DereferenceVisitor({
|
|
309
|
-
reference,
|
|
310
|
-
indirections: [...this.indirections],
|
|
311
|
-
options: this.options,
|
|
312
|
-
refractCache: this.refractCache,
|
|
313
|
-
visited: this.visited,
|
|
314
|
-
ancestors: ancestorsLineage
|
|
315
|
-
});
|
|
316
|
-
referencedElement = await traverseAsync(referencedElement, visitor, {
|
|
317
|
-
mutable: true,
|
|
318
|
-
skipVisited: 'skip'
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// remove referencing reference from ancestors lineage
|
|
322
|
-
directAncestors.delete(referencingElement);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Boolean JSON Schemas
|
|
326
|
-
if (isBooleanJSONSchemaElement(referencedElement)) {
|
|
327
|
-
const booleanJsonSchemaElement = cloneDeep(referencedElement);
|
|
328
|
-
// annotate referenced element with info about original referencing element
|
|
329
|
-
booleanJsonSchemaElement.meta.set('ref-fields', {
|
|
330
|
-
$ref: toValue(referencingElement.$ref)
|
|
331
|
-
});
|
|
332
|
-
// annotate referenced element with info about origin
|
|
333
|
-
booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
|
|
334
|
-
booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
|
|
335
|
-
path.replaceWith(booleanJsonSchemaElement);
|
|
336
|
-
path.skip();
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Creating a new version of referenced element to avoid modifying the original one.
|
|
342
|
-
*/
|
|
343
|
-
const mergedElement = cloneShallow(referencedElement);
|
|
344
|
-
// annotate referenced element with info about original referencing element
|
|
345
|
-
mergedElement.meta.set('ref-fields', {
|
|
346
|
-
$ref: toValue(referencingElement.$ref)
|
|
347
135
|
});
|
|
348
|
-
// annotate fragment with info about origin
|
|
349
|
-
mergedElement.meta.set('ref-origin', reference.uri);
|
|
350
|
-
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Transclude referencing element with merged referenced element.
|
|
354
|
-
*/
|
|
355
|
-
path.replaceWith(mergedElement);
|
|
356
|
-
path.skip();
|
|
357
136
|
} catch (error) {
|
|
358
|
-
const $ref = toValue(referencingElement.$ref);
|
|
359
137
|
this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
360
|
-
} finally {
|
|
361
|
-
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
362
138
|
}
|
|
363
139
|
}
|
|
364
140
|
async ChannelItemElement(path) {
|
|
365
141
|
const referencingElement = path.node;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (!isStringElement(referencingElement.$ref)) {
|
|
142
|
+
if (this.isTranscluded(referencingElement)) {
|
|
143
|
+
path.skip();
|
|
369
144
|
return;
|
|
370
145
|
}
|
|
371
146
|
|
|
372
|
-
//
|
|
373
|
-
if (
|
|
374
|
-
path.skip();
|
|
147
|
+
// ignore ChannelItemElement without $ref field
|
|
148
|
+
if (!isStringElement(referencingElement.$ref)) {
|
|
375
149
|
return;
|
|
376
150
|
}
|
|
377
|
-
const
|
|
151
|
+
const $ref = toValue(referencingElement.$ref);
|
|
152
|
+
const retrievalURI = this.toBaseURI($ref);
|
|
378
153
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
379
154
|
const isExternalReference = !isInternalReference;
|
|
380
155
|
|
|
381
156
|
// ignore resolving internal Channel Item Objects
|
|
382
157
|
if (!this.options.resolve.internal && isInternalReference) {
|
|
383
|
-
// skip traversing this
|
|
158
|
+
// skip traversing this Channel Item element but traverse all it's child elements
|
|
384
159
|
return;
|
|
385
160
|
}
|
|
386
161
|
// ignore resolving external Channel Item Objects
|
|
387
162
|
if (!this.options.resolve.external && isExternalReference) {
|
|
388
|
-
// skip traversing this
|
|
163
|
+
// skip traversing this Channel Item element but traverse all it's child elements
|
|
389
164
|
return;
|
|
390
165
|
}
|
|
391
|
-
const $refBaseURI = url.resolve(retrievalURI,
|
|
392
|
-
const indirectionsSize = this.indirections.length;
|
|
166
|
+
const $refBaseURI = url.resolve(retrievalURI, $ref);
|
|
393
167
|
try {
|
|
394
|
-
const reference = await this.toReference(
|
|
395
|
-
this.indirections.push(referencingElement);
|
|
168
|
+
const reference = await this.toReference($ref);
|
|
396
169
|
const jsonPointer = URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
397
|
-
|
|
398
170
|
// possibly non-semantic referenced element
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// applying semantics to a referenced element
|
|
402
|
-
if (!isChannelItemElement(referencedElement)) {
|
|
403
|
-
if (this.refractCache.has(referencedElement)) {
|
|
404
|
-
referencedElement = this.refractCache.get(referencedElement);
|
|
405
|
-
} else {
|
|
406
|
-
const sourceElement = referencedElement;
|
|
407
|
-
referencedElement = refractChannelItem(referencedElement);
|
|
408
|
-
this.refractCache.set(sourceElement, referencedElement);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
171
|
+
const referencedElement = jsonPointerEvaluate(reference.value.result, jsonPointer);
|
|
411
172
|
|
|
412
|
-
// detect direct
|
|
173
|
+
// detect direct reference
|
|
413
174
|
if (referencingElement === referencedElement) {
|
|
414
175
|
throw new ApiDOMStructuredError('Recursive Channel Item Object reference detected', {
|
|
415
|
-
$ref
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// detect maximum depth of dereferencing
|
|
420
|
-
if (this.indirections.length > this.options.dereference.maxDepth) {
|
|
421
|
-
throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
422
|
-
maxDepth: this.options.dereference.maxDepth,
|
|
423
|
-
uri: this.reference.uri
|
|
176
|
+
$ref
|
|
424
177
|
});
|
|
425
178
|
}
|
|
179
|
+
await this.addTransclusion(path, {
|
|
180
|
+
source: referencedElement,
|
|
181
|
+
type: 'channelItem',
|
|
182
|
+
reference,
|
|
183
|
+
context: undefined
|
|
184
|
+
}, {
|
|
185
|
+
toTranscludedElement: (element, target) => {
|
|
186
|
+
if (!isChannelItemElement(target)) return target;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Creating a new version of Channel Item by merging fields from referenced Channel Item with referencing one.
|
|
190
|
+
*/
|
|
191
|
+
const mergedElement = cloneShallow(target);
|
|
192
|
+
// existing keywords from referencing ChannelItemElement overrides ones from referenced element
|
|
193
|
+
element.forEach((value, keyElement, item) => {
|
|
194
|
+
mergedElement.remove(toValue(keyElement));
|
|
195
|
+
mergedElement.content.push(item);
|
|
196
|
+
});
|
|
197
|
+
mergedElement.remove('$ref');
|
|
426
198
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
reference.refSet.circular = true;
|
|
431
|
-
if (this.options.dereference.circular === 'error') {
|
|
432
|
-
throw new ApiDOMStructuredError('Circular reference detected', {
|
|
433
|
-
$ref: toValue(referencingElement.$ref)
|
|
199
|
+
// annotate referenced element with info about original referencing element
|
|
200
|
+
mergedElement.meta.set('ref-fields', {
|
|
201
|
+
$ref
|
|
434
202
|
});
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
203
|
+
// annotate referenced element with info about origin and type
|
|
204
|
+
mergedElement.meta.set('ref-origin', reference.uri);
|
|
205
|
+
mergedElement.meta.set('ref-type', element.element);
|
|
206
|
+
return mergedElement;
|
|
207
|
+
},
|
|
208
|
+
aliasField: '$ref',
|
|
209
|
+
circular: {
|
|
210
|
+
uri: $refBaseURI,
|
|
211
|
+
meta: {
|
|
438
212
|
uri: reference.uri,
|
|
439
|
-
$ref
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Dive deep into the fragment.
|
|
450
|
-
*
|
|
451
|
-
* Cases to consider:
|
|
452
|
-
* 1. We're crossing document boundary
|
|
453
|
-
* 2. Fragment is from non-entry document
|
|
454
|
-
* 3. Fragment is a Channel Item Object with $ref field. We need to follow it to get the eventual value
|
|
455
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
456
|
-
*/
|
|
457
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
458
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
459
|
-
if ((isExternalReference || isNonEntryDocument || isChannelItemElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
460
|
-
if (shouldDetectCircular) {
|
|
461
|
-
this.visited.add(referencedElement);
|
|
213
|
+
$ref
|
|
214
|
+
},
|
|
215
|
+
details: {
|
|
216
|
+
$ref
|
|
217
|
+
}
|
|
462
218
|
}
|
|
463
|
-
|
|
464
|
-
// append referencing reference to ancestors lineage
|
|
465
|
-
directAncestors.add(referencingElement);
|
|
466
|
-
const visitor = new AsyncAPI2DereferenceVisitor({
|
|
467
|
-
reference,
|
|
468
|
-
indirections: [...this.indirections],
|
|
469
|
-
options: this.options,
|
|
470
|
-
refractCache: this.refractCache,
|
|
471
|
-
visited: this.visited,
|
|
472
|
-
ancestors: ancestorsLineage
|
|
473
|
-
});
|
|
474
|
-
referencedElement = await traverseAsync(referencedElement, visitor, {
|
|
475
|
-
mutable: true,
|
|
476
|
-
skipVisited: 'skip'
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
// remove referencing reference from ancestors lineage
|
|
480
|
-
directAncestors.delete(referencingElement);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* Creating a new version of Channel Item by merging fields from referenced Channel Item with referencing one.
|
|
485
|
-
*/
|
|
486
|
-
if (isChannelItemElement(referencedElement)) {
|
|
487
|
-
const mergedElement = cloneShallow(referencedElement);
|
|
488
|
-
// existing keywords from referencing ChannelItemElement overrides ones from referenced ChannelItemElement
|
|
489
|
-
referencingElement.forEach((value, keyElement, item) => {
|
|
490
|
-
mergedElement.remove(toValue(keyElement));
|
|
491
|
-
mergedElement.content.push(item);
|
|
492
|
-
});
|
|
493
|
-
mergedElement.remove('$ref');
|
|
494
|
-
|
|
495
|
-
// annotate referenced element with info about original referencing element
|
|
496
|
-
mergedElement.meta.set('ref-fields', {
|
|
497
|
-
$ref: toValue(referencingElement.$ref)
|
|
498
|
-
});
|
|
499
|
-
// annotate referenced with info about origin
|
|
500
|
-
mergedElement.meta.set('ref-origin', reference.uri);
|
|
501
|
-
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
502
|
-
referencedElement = mergedElement;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Transclude referencing element with merged referenced element.
|
|
507
|
-
*/
|
|
508
|
-
path.replaceWith(referencedElement);
|
|
219
|
+
});
|
|
509
220
|
} catch (error) {
|
|
510
|
-
const $ref = toValue(referencingElement.$ref);
|
|
511
221
|
this.handleError(`Error while dereferencing Channel Item Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
512
|
-
} finally {
|
|
513
|
-
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
514
222
|
}
|
|
515
223
|
}
|
|
516
224
|
}
|