@speclynx/apidom-reference 3.0.0 → 3.2.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 +16 -0
- package/README.md +97 -0
- package/dist/apidom-reference.browser.js +3862 -3241
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +48 -25
- package/src/dereference/index.cjs +4 -0
- package/src/dereference/index.mjs +4 -0
- package/src/dereference/strategies/apidom/visitor.cjs +139 -59
- package/src/dereference/strategies/apidom/visitor.mjs +142 -62
- package/src/dereference/strategies/arazzo-1/index.cjs +1 -4
- package/src/dereference/strategies/arazzo-1/index.mjs +2 -4
- package/src/dereference/strategies/arazzo-1/visitor.cjs +289 -199
- package/src/dereference/strategies/arazzo-1/visitor.mjs +292 -203
- package/src/dereference/strategies/asyncapi-2/index.cjs +1 -4
- package/src/dereference/strategies/asyncapi-2/index.mjs +2 -4
- package/src/dereference/strategies/asyncapi-2/visitor.cjs +325 -229
- package/src/dereference/strategies/asyncapi-2/visitor.mjs +328 -233
- package/src/dereference/strategies/openapi-2/index.cjs +1 -4
- package/src/dereference/strategies/openapi-2/index.mjs +2 -4
- package/src/dereference/strategies/openapi-2/visitor.cjs +420 -318
- package/src/dereference/strategies/openapi-2/visitor.mjs +425 -324
- package/src/dereference/strategies/openapi-3-0/index.cjs +1 -4
- package/src/dereference/strategies/openapi-3-0/index.mjs +2 -4
- package/src/dereference/strategies/openapi-3-0/visitor.cjs +405 -286
- package/src/dereference/strategies/openapi-3-0/visitor.mjs +409 -291
- package/src/dereference/strategies/openapi-3-1/index.cjs +1 -4
- package/src/dereference/strategies/openapi-3-1/index.mjs +2 -4
- package/src/dereference/strategies/openapi-3-1/visitor.cjs +598 -484
- package/src/dereference/strategies/openapi-3-1/visitor.mjs +602 -489
- package/src/errors/DereferenceError.cjs +1 -1
- package/src/errors/DereferenceError.mjs +2 -2
- package/src/errors/ResolveError.cjs +1 -1
- package/src/errors/ResolveError.mjs +2 -2
- package/src/errors/UnresolvableReferenceError.cjs +11 -0
- package/src/errors/UnresolvableReferenceError.mjs +6 -0
- package/src/index.cjs +3 -1
- package/src/index.mjs +1 -0
- package/src/options/index.cjs +10 -1
- package/src/options/index.mjs +10 -1
- package/src/util/plugins.cjs +1 -6
- package/src/util/plugins.mjs +2 -5
- package/types/apidom-reference.d.ts +10 -2
- package/types/dereference/strategies/apidom/visitor.d.ts +10 -0
- package/types/dereference/strategies/arazzo-1/visitor.d.ts +19 -5
- package/types/dereference/strategies/asyncapi-2/visitor.d.ts +21 -7
- package/types/dereference/strategies/openapi-2/visitor.d.ts +21 -8
- package/types/dereference/strategies/openapi-3-0/visitor.d.ts +21 -7
- package/types/dereference/strategies/openapi-3-1/visitor.d.ts +21 -7
- package/types/errors/DereferenceError.d.ts +2 -2
- package/types/errors/ResolveError.d.ts +2 -2
- package/types/errors/UnresolvableReferenceError.d.ts +7 -0
- package/types/index.d.ts +1 -0
- package/types/options/index.d.ts +2 -0
|
@@ -12,15 +12,13 @@ var _apidomError = require("@speclynx/apidom-error");
|
|
|
12
12
|
var _apidomTraverse = require("@speclynx/apidom-traverse");
|
|
13
13
|
var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
|
|
14
14
|
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-3-0");
|
|
15
|
+
var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
|
|
15
16
|
var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
|
|
16
17
|
var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
|
|
17
18
|
var url = _interopRequireWildcard(require("../../../util/url.cjs"));
|
|
18
19
|
var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
|
|
19
20
|
var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
|
|
20
21
|
var _util = require("../../util.cjs");
|
|
21
|
-
// initialize element identity manager
|
|
22
|
-
const identityManager = new _apidomCore.IdentityManager();
|
|
23
|
-
|
|
24
22
|
/**
|
|
25
23
|
* @public
|
|
26
24
|
*/
|
|
@@ -30,33 +28,45 @@ const identityManager = new _apidomCore.IdentityManager();
|
|
|
30
28
|
*/
|
|
31
29
|
class OpenAPI3_0DereferenceVisitor {
|
|
32
30
|
indirections;
|
|
33
|
-
namespace;
|
|
34
31
|
reference;
|
|
35
32
|
options;
|
|
36
|
-
ancestors;
|
|
37
33
|
refractCache;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tracks element ancestors across dive-deep traversal boundaries.
|
|
37
|
+
* Used for cycle detection: if a referenced element is found in
|
|
38
|
+
* the ancestor lineage, a circular reference is detected.
|
|
39
|
+
*/
|
|
40
|
+
ancestors;
|
|
38
41
|
constructor({
|
|
39
42
|
reference,
|
|
40
|
-
namespace,
|
|
41
43
|
options,
|
|
42
44
|
indirections = [],
|
|
43
45
|
ancestors = new _util.AncestorLineage(),
|
|
44
|
-
refractCache = new
|
|
46
|
+
refractCache = new WeakMap()
|
|
45
47
|
}) {
|
|
46
48
|
this.indirections = indirections;
|
|
47
|
-
this.namespace = namespace;
|
|
48
49
|
this.reference = reference;
|
|
49
50
|
this.options = options;
|
|
50
51
|
this.ancestors = new _util.AncestorLineage(...ancestors);
|
|
51
52
|
this.refractCache = refractCache;
|
|
52
53
|
}
|
|
54
|
+
toAncestorLineage(path) {
|
|
55
|
+
const ancestorNodes = path.getAncestorNodes();
|
|
56
|
+
const directAncestors = new Set(ancestorNodes.filter(_apidomDatamodel.isElement));
|
|
57
|
+
const ancestorsLineage = new _util.AncestorLineage(...this.ancestors, directAncestors);
|
|
58
|
+
return [ancestorsLineage, directAncestors];
|
|
59
|
+
}
|
|
53
60
|
toBaseURI(uri) {
|
|
54
61
|
return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
|
|
55
62
|
}
|
|
56
63
|
async toReference(uri) {
|
|
57
64
|
// detect maximum depth of resolution
|
|
58
65
|
if (this.reference.depth >= this.options.resolve.maxDepth) {
|
|
59
|
-
throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"
|
|
66
|
+
throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
|
|
67
|
+
maxDepth: this.options.resolve.maxDepth,
|
|
68
|
+
uri: this.reference.uri
|
|
69
|
+
});
|
|
60
70
|
}
|
|
61
71
|
const baseURI = this.toBaseURI(uri);
|
|
62
72
|
const {
|
|
@@ -76,9 +86,23 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
76
86
|
});
|
|
77
87
|
|
|
78
88
|
// register new mutable reference with a refSet
|
|
89
|
+
//
|
|
90
|
+
// NOTE(known limitation): the mutable reference is mutated in place during traversal
|
|
91
|
+
// (via `{ mutable: true }`). When an external document evaluates a JSON pointer back
|
|
92
|
+
// into this document, it may receive an already-resolved element instead of the original
|
|
93
|
+
// $ref. That resolved element was produced using the entry document's resolution context
|
|
94
|
+
// (ancestors, indirections), which may differ from the external document's context.
|
|
95
|
+
// This can affect cycle detection in rare cross-document circular reference patterns.
|
|
96
|
+
//
|
|
97
|
+
// Remediation: evaluate JSON pointers against the immutable (original) parse tree
|
|
98
|
+
// instead of the mutable working copy. The `immutable://` reference below preserves
|
|
99
|
+
// the original tree and could be used for pointer evaluation, ensuring every resolution
|
|
100
|
+
// context always sees raw, unresolved elements and processes them with its own
|
|
101
|
+
// ancestors/indirections. The trade-off is that elements referenced by multiple
|
|
102
|
+
// documents would be resolved once per context instead of being reused.
|
|
79
103
|
const mutableReference = new _Reference.default({
|
|
80
104
|
uri: baseURI,
|
|
81
|
-
value: (0, _apidomDatamodel.cloneDeep)(parseResult),
|
|
105
|
+
value: this.options.dereference.immutable ? (0, _apidomDatamodel.cloneDeep)(parseResult) : parseResult,
|
|
82
106
|
depth: this.reference.depth + 1
|
|
83
107
|
});
|
|
84
108
|
refSet.add(mutableReference);
|
|
@@ -93,15 +117,77 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
93
117
|
}
|
|
94
118
|
return mutableReference;
|
|
95
119
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Handles an error according to the continueOnError option.
|
|
123
|
+
*
|
|
124
|
+
* For new errors: wraps in UnresolvableReferenceError with structured context
|
|
125
|
+
* (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
|
|
126
|
+
* For errors already wrapped by a nested visitor: prepends the current hop to the trace.
|
|
127
|
+
*
|
|
128
|
+
* Inner/intermediate visitors always throw to let the trace accumulate.
|
|
129
|
+
* Only the entry document visitor respects continueOnError (callback/swallow/throw).
|
|
130
|
+
*/
|
|
131
|
+
handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
|
|
132
|
+
const {
|
|
133
|
+
continueOnError
|
|
134
|
+
} = this.options.dereference;
|
|
135
|
+
const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
|
|
136
|
+
const uri = this.reference.uri;
|
|
137
|
+
const type = referencingElement.element;
|
|
138
|
+
const codeFrame = (0, _apidomCore.toYAML)(referencingElement);
|
|
139
|
+
|
|
140
|
+
// find element location: tree search for entry documents, visitor path for external
|
|
141
|
+
let location;
|
|
142
|
+
(0, _apidomTraverse.traverse)(this.reference.value.result, {
|
|
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
|
+
location ??= visitorPath.formatPath();
|
|
151
|
+
const hop = {
|
|
152
|
+
uri,
|
|
153
|
+
type,
|
|
154
|
+
refFieldName,
|
|
155
|
+
refFieldValue,
|
|
156
|
+
location,
|
|
157
|
+
codeFrame
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// enrich existing error from nested visitor or create new one
|
|
161
|
+
let unresolvedError;
|
|
162
|
+
if (error instanceof _UnresolvableReferenceError.default) {
|
|
163
|
+
// prefix relative locations for entries belonging to the referenced document
|
|
164
|
+
const refBaseURI = this.toBaseURI(refFieldValue);
|
|
165
|
+
const fragment = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refFieldValue);
|
|
166
|
+
if (fragment) {
|
|
167
|
+
if (refBaseURI === error.uri && error.location) {
|
|
168
|
+
error.location = fragment + error.location;
|
|
169
|
+
}
|
|
170
|
+
for (const h of error.trace) {
|
|
171
|
+
if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// @ts-ignore
|
|
175
|
+
error.trace = [hop, ...error.trace];
|
|
176
|
+
unresolvedError = error;
|
|
177
|
+
} else {
|
|
178
|
+
unresolvedError = new _UnresolvableReferenceError.default(message, {
|
|
179
|
+
cause: error,
|
|
180
|
+
type,
|
|
181
|
+
uri,
|
|
182
|
+
location,
|
|
183
|
+
codeFrame,
|
|
184
|
+
refFieldName,
|
|
185
|
+
refFieldValue,
|
|
186
|
+
trace: []
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (!isEntryDocument || continueOnError === false) throw unresolvedError;
|
|
190
|
+
if (typeof continueOnError === 'function') continueOnError(unresolvedError);
|
|
105
191
|
}
|
|
106
192
|
async ReferenceElement(path) {
|
|
107
193
|
const referencingElement = path.node;
|
|
@@ -111,133 +197,140 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
111
197
|
path.skip();
|
|
112
198
|
return;
|
|
113
199
|
}
|
|
114
|
-
const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
|
|
115
200
|
const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
116
201
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
117
202
|
const isExternalReference = !isInternalReference;
|
|
118
203
|
|
|
119
204
|
// ignore resolving internal Reference Objects
|
|
120
205
|
if (!this.options.resolve.internal && isInternalReference) {
|
|
121
|
-
// skip traversing this reference element
|
|
206
|
+
// skip traversing this reference element and all it's child elements
|
|
122
207
|
path.skip();
|
|
123
208
|
return;
|
|
124
209
|
}
|
|
125
210
|
// ignore resolving external Reference Objects
|
|
126
211
|
if (!this.options.resolve.external && isExternalReference) {
|
|
127
|
-
// skip traversing this reference element
|
|
212
|
+
// skip traversing this reference element and all it's child elements
|
|
128
213
|
path.skip();
|
|
129
214
|
return;
|
|
130
215
|
}
|
|
131
|
-
const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
132
216
|
const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
|
|
133
|
-
this.indirections.
|
|
134
|
-
|
|
217
|
+
const indirectionsSize = this.indirections.length;
|
|
218
|
+
try {
|
|
219
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
220
|
+
this.indirections.push(referencingElement);
|
|
221
|
+
const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
135
222
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
referencedElement.id = identityManager.identify(referencedElement);
|
|
223
|
+
// possibly non-semantic fragment
|
|
224
|
+
let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
|
|
139
225
|
|
|
140
|
-
|
|
141
|
-
* Applying semantics to a referenced element if semantics are missing.
|
|
142
|
-
*/
|
|
143
|
-
if ((0, _apidomDatamodel.isPrimitiveElement)(referencedElement)) {
|
|
226
|
+
// applying semantics to a fragment
|
|
144
227
|
const referencedElementType = referencingElement.meta.get('referenced-element');
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
228
|
+
if (referencedElement.element !== referencedElementType && !(0, _apidomNsOpenapi.isReferenceElement)(referencedElement)) {
|
|
229
|
+
if (this.refractCache.has(referencedElement)) {
|
|
230
|
+
referencedElement = this.refractCache.get(referencedElement);
|
|
231
|
+
} else if ((0, _apidomNsOpenapi.isReferenceLikeElement)(referencedElement)) {
|
|
232
|
+
// handling generic indirect references
|
|
233
|
+
const sourceElement = referencedElement;
|
|
234
|
+
referencedElement = (0, _apidomNsOpenapi.refractReference)(referencedElement);
|
|
235
|
+
referencedElement.meta.set('referenced-element', referencedElementType);
|
|
236
|
+
this.refractCache.set(sourceElement, referencedElement);
|
|
237
|
+
} else {
|
|
238
|
+
// handling direct references
|
|
239
|
+
const sourceElement = referencedElement;
|
|
240
|
+
referencedElement = (0, _apidomNsOpenapi.refract)(referencedElement, {
|
|
241
|
+
element: referencedElementType
|
|
242
|
+
});
|
|
243
|
+
this.refractCache.set(sourceElement, referencedElement);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// detect direct or indirect reference
|
|
248
|
+
if (referencingElement === referencedElement) {
|
|
249
|
+
throw new _apidomError.ApiDOMStructuredError('Recursive Reference Object detected', {
|
|
250
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
157
251
|
});
|
|
158
|
-
this.refractCache.set(cacheKey, referencedElement);
|
|
159
252
|
}
|
|
160
|
-
}
|
|
161
253
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
254
|
+
// detect maximum depth of dereferencing
|
|
255
|
+
if (this.indirections.length > this.options.dereference.maxDepth) {
|
|
256
|
+
throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
257
|
+
maxDepth: this.options.dereference.maxDepth,
|
|
258
|
+
uri: this.reference.uri
|
|
259
|
+
});
|
|
260
|
+
}
|
|
166
261
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
262
|
+
// detect second deep dive into the same fragment and avoid it
|
|
263
|
+
const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
|
|
264
|
+
if (ancestorsLineage.includes(referencedElement)) {
|
|
265
|
+
reference.refSet.circular = true;
|
|
266
|
+
if (this.options.dereference.circular === 'error') {
|
|
267
|
+
throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
|
|
268
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
269
|
+
});
|
|
270
|
+
} else if (this.options.dereference.circular === 'replace') {
|
|
271
|
+
const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
|
|
272
|
+
type: referencingElement.element,
|
|
273
|
+
uri: reference.uri,
|
|
274
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
275
|
+
});
|
|
276
|
+
const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
|
|
277
|
+
const replacement = replacer(refElement);
|
|
278
|
+
path.replaceWith(replacement);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
171
282
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Dive deep into the fragment.
|
|
285
|
+
*
|
|
286
|
+
* Cases to consider:
|
|
287
|
+
* 1. We're crossing document boundary
|
|
288
|
+
* 2. Fragment is from non-entry document
|
|
289
|
+
* 3. Fragment is a Reference Object. We need to follow it to get the eventual value
|
|
290
|
+
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
291
|
+
*/
|
|
292
|
+
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
293
|
+
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
294
|
+
if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
295
|
+
// append referencing reference to ancestors lineage
|
|
296
|
+
directAncestors.add(referencingElement);
|
|
297
|
+
const visitor = new OpenAPI3_0DereferenceVisitor({
|
|
298
|
+
reference,
|
|
299
|
+
indirections: [...this.indirections],
|
|
300
|
+
options: this.options,
|
|
301
|
+
refractCache: this.refractCache,
|
|
302
|
+
ancestors: ancestorsLineage
|
|
182
303
|
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
304
|
+
referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
|
|
305
|
+
mutable: true
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// remove referencing reference from ancestors lineage
|
|
309
|
+
directAncestors.delete(referencingElement);
|
|
188
310
|
}
|
|
189
|
-
}
|
|
190
311
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
199
|
-
*/
|
|
200
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
201
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
202
|
-
if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
203
|
-
// append referencing reference to ancestors lineage
|
|
204
|
-
directAncestors.add(referencingElement);
|
|
205
|
-
const visitor = new OpenAPI3_0DereferenceVisitor({
|
|
206
|
-
reference,
|
|
207
|
-
namespace: this.namespace,
|
|
208
|
-
indirections: [...this.indirections],
|
|
209
|
-
options: this.options,
|
|
210
|
-
refractCache: this.refractCache,
|
|
211
|
-
ancestors: ancestorsLineage
|
|
212
|
-
});
|
|
213
|
-
referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
|
|
214
|
-
mutable: true
|
|
312
|
+
/**
|
|
313
|
+
* Creating a new version of referenced element to avoid modifying the original one.
|
|
314
|
+
*/
|
|
315
|
+
const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
|
|
316
|
+
// annotate referenced element with info about original referencing element
|
|
317
|
+
mergedElement.meta.set('ref-fields', {
|
|
318
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
215
319
|
});
|
|
320
|
+
// annotate fragment with info about origin
|
|
321
|
+
mergedElement.meta.set('ref-origin', reference.uri);
|
|
322
|
+
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
216
323
|
|
|
217
|
-
|
|
218
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Transclude referencing element with merged referenced element.
|
|
326
|
+
*/
|
|
327
|
+
path.replaceWith(mergedElement);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
|
|
330
|
+
this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
331
|
+
} finally {
|
|
332
|
+
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
219
333
|
}
|
|
220
|
-
this.indirections.pop();
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Creating a new version of referenced element to avoid modifying the original one.
|
|
224
|
-
*/
|
|
225
|
-
const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
|
|
226
|
-
// assign unique id to merged element
|
|
227
|
-
mergedElement.meta.set('id', identityManager.generateId());
|
|
228
|
-
// annotate referenced element with info about original referencing element
|
|
229
|
-
mergedElement.meta.set('ref-fields', {
|
|
230
|
-
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
231
|
-
});
|
|
232
|
-
// annotate fragment with info about origin
|
|
233
|
-
mergedElement.meta.set('ref-origin', reference.uri);
|
|
234
|
-
// annotate fragment with info about referencing element
|
|
235
|
-
mergedElement.meta.set('ref-referencing-element-id', identityManager.identify(referencingElement));
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Transclude referencing element with merged referenced element.
|
|
239
|
-
*/
|
|
240
|
-
path.replaceWith(mergedElement);
|
|
241
334
|
}
|
|
242
335
|
async PathItemElement(path) {
|
|
243
336
|
const referencingElement = path.node;
|
|
@@ -252,7 +345,6 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
252
345
|
path.skip();
|
|
253
346
|
return;
|
|
254
347
|
}
|
|
255
|
-
const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
|
|
256
348
|
const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
257
349
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
258
350
|
const isExternalReference = !isInternalReference;
|
|
@@ -267,117 +359,124 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
267
359
|
// skip traversing this Path Item element but traverse all it's child elements
|
|
268
360
|
return;
|
|
269
361
|
}
|
|
270
|
-
const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
271
362
|
const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
|
|
272
|
-
this.indirections.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
referencedElement.id = identityManager.identify(referencedElement);
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Applying semantics to a referenced element if semantics are missing.
|
|
281
|
-
*/
|
|
282
|
-
if (!(0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) {
|
|
283
|
-
const cacheKey = `path-item-${identityManager.identify(referencedElement)}`;
|
|
284
|
-
if (this.refractCache.has(cacheKey)) {
|
|
285
|
-
referencedElement = this.refractCache.get(cacheKey);
|
|
286
|
-
} else {
|
|
287
|
-
referencedElement = (0, _apidomNsOpenapi.refractPathItem)(referencedElement);
|
|
288
|
-
this.refractCache.set(cacheKey, referencedElement);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
363
|
+
const indirectionsSize = this.indirections.length;
|
|
364
|
+
try {
|
|
365
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
366
|
+
this.indirections.push(referencingElement);
|
|
367
|
+
const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
291
368
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
throw new _apidomError.ApiDOMError('Recursive Path Item Object reference detected');
|
|
295
|
-
}
|
|
369
|
+
// possibly non-semantic referenced element
|
|
370
|
+
let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
|
|
296
371
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
372
|
+
// applying semantics to a referenced element
|
|
373
|
+
if (!(0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) {
|
|
374
|
+
if (this.refractCache.has(referencedElement)) {
|
|
375
|
+
referencedElement = this.refractCache.get(referencedElement);
|
|
376
|
+
} else {
|
|
377
|
+
const sourceElement = referencedElement;
|
|
378
|
+
referencedElement = (0, _apidomNsOpenapi.refractPathItem)(referencedElement);
|
|
379
|
+
this.refractCache.set(sourceElement, referencedElement);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
301
382
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (this.options.dereference.circular === 'error') {
|
|
306
|
-
throw new _apidomError.ApiDOMError('Circular reference detected');
|
|
307
|
-
} else if (this.options.dereference.circular === 'replace') {
|
|
308
|
-
const refElement = new _apidomDatamodel.RefElement(referencedElement.id, {
|
|
309
|
-
type: 'path-item',
|
|
310
|
-
uri: reference.uri,
|
|
383
|
+
// detect direct or indirect reference
|
|
384
|
+
if (referencingElement === referencedElement) {
|
|
385
|
+
throw new _apidomError.ApiDOMStructuredError('Recursive Path Item Object reference detected', {
|
|
311
386
|
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
312
387
|
});
|
|
313
|
-
const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
|
|
314
|
-
const replacement = replacer(refElement);
|
|
315
|
-
this.indirections.pop();
|
|
316
|
-
path.replaceWith(replacement);
|
|
317
|
-
return;
|
|
318
388
|
}
|
|
319
|
-
}
|
|
320
389
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
329
|
-
*/
|
|
330
|
-
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
331
|
-
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
332
|
-
if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
333
|
-
// append referencing reference to ancestors lineage
|
|
334
|
-
directAncestors.add(referencingElement);
|
|
335
|
-
const visitor = new OpenAPI3_0DereferenceVisitor({
|
|
336
|
-
reference,
|
|
337
|
-
namespace: this.namespace,
|
|
338
|
-
indirections: [...this.indirections],
|
|
339
|
-
options: this.options,
|
|
340
|
-
refractCache: this.refractCache,
|
|
341
|
-
ancestors: ancestorsLineage
|
|
342
|
-
});
|
|
343
|
-
referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
|
|
344
|
-
mutable: true
|
|
345
|
-
});
|
|
390
|
+
// detect maximum depth of dereferencing
|
|
391
|
+
if (this.indirections.length > this.options.dereference.maxDepth) {
|
|
392
|
+
throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
393
|
+
maxDepth: this.options.dereference.maxDepth,
|
|
394
|
+
uri: this.reference.uri
|
|
395
|
+
});
|
|
396
|
+
}
|
|
346
397
|
|
|
347
|
-
//
|
|
348
|
-
directAncestors.
|
|
349
|
-
|
|
350
|
-
|
|
398
|
+
// detect cross-boundary cycle
|
|
399
|
+
const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
|
|
400
|
+
if (ancestorsLineage.includes(referencedElement)) {
|
|
401
|
+
reference.refSet.circular = true;
|
|
402
|
+
if (this.options.dereference.circular === 'error') {
|
|
403
|
+
throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
|
|
404
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
405
|
+
});
|
|
406
|
+
} else if (this.options.dereference.circular === 'replace') {
|
|
407
|
+
const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
|
|
408
|
+
type: referencingElement.element,
|
|
409
|
+
uri: reference.uri,
|
|
410
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
411
|
+
});
|
|
412
|
+
const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
|
|
413
|
+
const replacement = replacer(refElement);
|
|
414
|
+
path.replaceWith(replacement);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
351
418
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Dive deep into the fragment.
|
|
421
|
+
*
|
|
422
|
+
* Cases to consider:
|
|
423
|
+
* 1. We're crossing document boundary
|
|
424
|
+
* 2. Fragment is from non-entry document
|
|
425
|
+
* 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value
|
|
426
|
+
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
427
|
+
*/
|
|
428
|
+
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
429
|
+
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
430
|
+
if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
431
|
+
// append referencing reference to ancestors lineage
|
|
432
|
+
directAncestors.add(referencingElement);
|
|
433
|
+
const visitor = new OpenAPI3_0DereferenceVisitor({
|
|
434
|
+
reference,
|
|
435
|
+
indirections: [...this.indirections],
|
|
436
|
+
options: this.options,
|
|
437
|
+
refractCache: this.refractCache,
|
|
438
|
+
ancestors: ancestorsLineage
|
|
439
|
+
});
|
|
440
|
+
referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
|
|
441
|
+
mutable: true
|
|
442
|
+
});
|
|
365
443
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
444
|
+
// remove referencing reference from ancestors lineage
|
|
445
|
+
directAncestors.delete(referencingElement);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
|
|
450
|
+
*/
|
|
451
|
+
if ((0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) {
|
|
452
|
+
const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
|
|
453
|
+
// existing keywords from referencing PathItemElement overrides ones from referenced element
|
|
454
|
+
referencingElement.forEach((value, keyElement, item) => {
|
|
455
|
+
mergedElement.remove((0, _apidomCore.toValue)(keyElement));
|
|
456
|
+
mergedElement.content.push(item);
|
|
457
|
+
});
|
|
458
|
+
mergedElement.remove('$ref');
|
|
459
|
+
|
|
460
|
+
// annotate referenced element with info about original referencing element
|
|
461
|
+
mergedElement.meta.set('ref-fields', {
|
|
462
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
463
|
+
});
|
|
464
|
+
// annotate referenced element with info about origin and type
|
|
465
|
+
mergedElement.meta.set('ref-origin', reference.uri);
|
|
466
|
+
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
467
|
+
referencedElement = mergedElement;
|
|
468
|
+
}
|
|
376
469
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
470
|
+
/**
|
|
471
|
+
* Transclude referencing element with merged referenced element.
|
|
472
|
+
*/
|
|
473
|
+
path.replaceWith(referencedElement);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
|
|
476
|
+
this.handleError(`Error while dereferencing Path Item Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
477
|
+
} finally {
|
|
478
|
+
if (this.indirections.length > indirectionsSize) this.indirections.pop();
|
|
479
|
+
}
|
|
381
480
|
}
|
|
382
481
|
async LinkElement(path) {
|
|
383
482
|
const linkElement = path.node;
|
|
@@ -389,67 +488,78 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
389
488
|
|
|
390
489
|
// operationRef and operationId fields are mutually exclusive
|
|
391
490
|
if ((0, _apidomDatamodel.isStringElement)(linkElement.operationRef) && (0, _apidomDatamodel.isStringElement)(linkElement.operationId)) {
|
|
392
|
-
throw new _apidomError.
|
|
491
|
+
throw new _apidomError.ApiDOMStructuredError('LinkElement operationRef and operationId fields are mutually exclusive', {
|
|
492
|
+
operationRef: (0, _apidomCore.toValue)(linkElement.operationRef),
|
|
493
|
+
operationId: (0, _apidomCore.toValue)(linkElement.operationId)
|
|
494
|
+
});
|
|
393
495
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
496
|
+
try {
|
|
497
|
+
let operationElement;
|
|
498
|
+
if ((0, _apidomDatamodel.isStringElement)(linkElement.operationRef)) {
|
|
499
|
+
// possibly non-semantic referenced element
|
|
500
|
+
const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference((0, _apidomCore.toValue)(linkElement.operationRef));
|
|
501
|
+
const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(linkElement.operationRef));
|
|
502
|
+
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
503
|
+
const isExternalReference = !isInternalReference;
|
|
504
|
+
|
|
505
|
+
// ignore resolving internal Operation Object reference
|
|
506
|
+
if (!this.options.resolve.internal && isInternalReference) {
|
|
507
|
+
// skip traversing this Link element but traverse all it's child elements
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
// ignore resolving external Operation Object reference
|
|
511
|
+
if (!this.options.resolve.external && isExternalReference) {
|
|
512
|
+
// skip traversing this Link element but traverse all it's child elements
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(linkElement.operationRef));
|
|
516
|
+
operationElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
|
|
517
|
+
// applying semantics to a referenced element
|
|
518
|
+
if (!(0, _apidomNsOpenapi.isOperationElement)(operationElement)) {
|
|
519
|
+
if (this.refractCache.has(operationElement)) {
|
|
520
|
+
operationElement = this.refractCache.get(operationElement);
|
|
521
|
+
} else {
|
|
522
|
+
const sourceElement = operationElement;
|
|
523
|
+
operationElement = (0, _apidomNsOpenapi.refractOperation)(operationElement);
|
|
524
|
+
this.refractCache.set(sourceElement, operationElement);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// create shallow clone to be able to annotate with metadata
|
|
528
|
+
operationElement = (0, _apidomDatamodel.cloneShallow)(operationElement);
|
|
529
|
+
// annotate operation element with info about origin
|
|
530
|
+
operationElement.meta.set('ref-origin', reference.uri);
|
|
531
|
+
operationElement.meta.set('ref-type', linkElement.element);
|
|
532
|
+
const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(linkElement);
|
|
533
|
+
linkElementCopy.operationRef?.meta.set('operation', operationElement);
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Transclude Link Object containing Operation Object in its meta.
|
|
537
|
+
*/
|
|
538
|
+
path.replaceWith(linkElementCopy);
|
|
410
539
|
return;
|
|
411
540
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
this.refractCache.set(cacheKey, operationElement);
|
|
541
|
+
if ((0, _apidomDatamodel.isStringElement)(linkElement.operationId)) {
|
|
542
|
+
const operationId = (0, _apidomCore.toValue)(linkElement.operationId);
|
|
543
|
+
const reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
544
|
+
operationElement = (0, _apidomTraverse.find)(reference.value.result, e => (0, _apidomNsOpenapi.isOperationElement)(e) && (0, _apidomDatamodel.isElement)(e.operationId) && e.operationId.equals(operationId));
|
|
545
|
+
// OperationElement not found by its operationId
|
|
546
|
+
if ((0, _ramdaAdjunct.isUndefined)(operationElement)) {
|
|
547
|
+
throw new _apidomError.ApiDOMStructuredError(`OperationElement(operationId=${operationId}) not found`, {
|
|
548
|
+
operationId
|
|
549
|
+
});
|
|
422
550
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
operationElement = (0, _apidomDatamodel.cloneShallow)(operationElement);
|
|
426
|
-
// annotate operation element with info about origin
|
|
427
|
-
operationElement.meta.set('ref-origin', reference.uri);
|
|
428
|
-
const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(linkElement);
|
|
429
|
-
linkElementCopy.operationRef?.meta.set('operation', operationElement);
|
|
551
|
+
const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(linkElement);
|
|
552
|
+
linkElementCopy.operationId?.meta.set('operation', operationElement);
|
|
430
553
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
if ((0, _apidomDatamodel.isStringElement)(linkElement.operationId)) {
|
|
438
|
-
const operationId = (0, _apidomCore.toValue)(linkElement.operationId);
|
|
439
|
-
const reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
440
|
-
operationElement = (0, _apidomTraverse.find)(reference.value.result, e => (0, _apidomNsOpenapi.isOperationElement)(e) && (0, _apidomDatamodel.isElement)(e.operationId) && e.operationId.equals(operationId));
|
|
441
|
-
// OperationElement not found by its operationId
|
|
442
|
-
if ((0, _ramdaAdjunct.isUndefined)(operationElement)) {
|
|
443
|
-
throw new _apidomError.ApiDOMError(`OperationElement(operationId=${operationId}) not found`);
|
|
554
|
+
/**
|
|
555
|
+
* Transclude Link Object containing Operation Object in its meta.
|
|
556
|
+
*/
|
|
557
|
+
path.replaceWith(linkElementCopy);
|
|
444
558
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
* Transclude Link Object containing Operation Object in its meta.
|
|
450
|
-
*/
|
|
451
|
-
path.replaceWith(linkElementCopy);
|
|
452
|
-
return;
|
|
559
|
+
} catch (error) {
|
|
560
|
+
const refFieldName = (0, _apidomDatamodel.isStringElement)(linkElement.operationRef) ? 'operationRef' : 'operationId';
|
|
561
|
+
const refFieldValue = (0, _apidomDatamodel.isStringElement)(linkElement.operationRef) ? (0, _apidomCore.toValue)(linkElement.operationRef) : (0, _apidomCore.toValue)(linkElement.operationId);
|
|
562
|
+
this.handleError(`Error while dereferencing Link Object. Cannot resolve ${refFieldName} "${refFieldValue}": ${error.message}`, error, linkElement, refFieldName, refFieldValue, path);
|
|
453
563
|
}
|
|
454
564
|
}
|
|
455
565
|
async ExampleElement(path) {
|
|
@@ -462,7 +572,10 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
462
572
|
|
|
463
573
|
// value and externalValue fields are mutually exclusive
|
|
464
574
|
if (exampleElement.hasKey('value') && (0, _apidomDatamodel.isStringElement)(exampleElement.externalValue)) {
|
|
465
|
-
throw new _apidomError.
|
|
575
|
+
throw new _apidomError.ApiDOMStructuredError('ExampleElement value and externalValue fields are mutually exclusive', {
|
|
576
|
+
value: (0, _apidomCore.toValue)(exampleElement.value),
|
|
577
|
+
externalValue: (0, _apidomCore.toValue)(exampleElement.externalValue)
|
|
578
|
+
});
|
|
466
579
|
}
|
|
467
580
|
const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(exampleElement.externalValue));
|
|
468
581
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
@@ -478,19 +591,25 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
478
591
|
// skip traversing this Example element but traverse all it's child elements
|
|
479
592
|
return;
|
|
480
593
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
594
|
+
try {
|
|
595
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(exampleElement.externalValue));
|
|
596
|
+
|
|
597
|
+
// shallow clone of the referenced element
|
|
598
|
+
const valueElement = (0, _apidomDatamodel.cloneShallow)(reference.value.result);
|
|
599
|
+
// annotate operation element with info about origin
|
|
600
|
+
valueElement.meta.set('ref-origin', reference.uri);
|
|
601
|
+
valueElement.meta.set('ref-type', exampleElement.element);
|
|
602
|
+
const exampleElementCopy = (0, _apidomDatamodel.cloneShallow)(exampleElement);
|
|
603
|
+
exampleElementCopy.value = valueElement;
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Transclude Example Object containing external value.
|
|
607
|
+
*/
|
|
608
|
+
path.replaceWith(exampleElementCopy);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
const externalValue = (0, _apidomCore.toValue)(exampleElement.externalValue);
|
|
611
|
+
this.handleError(`Error while dereferencing Example Object. Cannot resolve externalValue "${externalValue}": ${error.message}`, error, exampleElement, 'externalValue', externalValue, path);
|
|
612
|
+
}
|
|
494
613
|
}
|
|
495
614
|
}
|
|
496
615
|
var _default = exports.default = OpenAPI3_0DereferenceVisitor;
|