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