@speclynx/apidom-reference 3.0.0 → 3.1.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 +6 -0
- package/README.md +97 -0
- package/dist/apidom-reference.browser.js +3854 -3241
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +25 -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 +288 -199
- package/src/dereference/strategies/arazzo-1/visitor.mjs +291 -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 +323 -229
- package/src/dereference/strategies/asyncapi-2/visitor.mjs +326 -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 +417 -318
- package/src/dereference/strategies/openapi-2/visitor.mjs +422 -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 +403 -286
- package/src/dereference/strategies/openapi-3-0/visitor.mjs +407 -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,139 @@ 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
|
-
|
|
134
|
-
|
|
217
|
+
try {
|
|
218
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
219
|
+
this.indirections.push(referencingElement);
|
|
220
|
+
const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
135
221
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
referencedElement.id = identityManager.identify(referencedElement);
|
|
222
|
+
// possibly non-semantic fragment
|
|
223
|
+
let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
|
|
139
224
|
|
|
140
|
-
|
|
141
|
-
* Applying semantics to a referenced element if semantics are missing.
|
|
142
|
-
*/
|
|
143
|
-
if ((0, _apidomDatamodel.isPrimitiveElement)(referencedElement)) {
|
|
225
|
+
// applying semantics to a fragment
|
|
144
226
|
const referencedElementType = referencingElement.meta.get('referenced-element');
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
227
|
+
if (referencedElement.element !== referencedElementType && !(0, _apidomNsOpenapi.isReferenceElement)(referencedElement)) {
|
|
228
|
+
if (this.refractCache.has(referencedElement)) {
|
|
229
|
+
referencedElement = this.refractCache.get(referencedElement);
|
|
230
|
+
} else if ((0, _apidomNsOpenapi.isReferenceLikeElement)(referencedElement)) {
|
|
231
|
+
// handling generic indirect references
|
|
232
|
+
const sourceElement = referencedElement;
|
|
233
|
+
referencedElement = (0, _apidomNsOpenapi.refractReference)(referencedElement);
|
|
234
|
+
referencedElement.meta.set('referenced-element', referencedElementType);
|
|
235
|
+
this.refractCache.set(sourceElement, referencedElement);
|
|
236
|
+
} else {
|
|
237
|
+
// handling direct references
|
|
238
|
+
const sourceElement = referencedElement;
|
|
239
|
+
referencedElement = (0, _apidomNsOpenapi.refract)(referencedElement, {
|
|
240
|
+
element: referencedElementType
|
|
241
|
+
});
|
|
242
|
+
this.refractCache.set(sourceElement, referencedElement);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// detect direct or indirect reference
|
|
247
|
+
if (referencingElement === referencedElement) {
|
|
248
|
+
throw new _apidomError.ApiDOMStructuredError('Recursive Reference Object detected', {
|
|
249
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
157
250
|
});
|
|
158
|
-
this.refractCache.set(cacheKey, referencedElement);
|
|
159
251
|
}
|
|
160
|
-
}
|
|
161
252
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
253
|
+
// detect maximum depth of dereferencing
|
|
254
|
+
if (this.indirections.length > this.options.dereference.maxDepth) {
|
|
255
|
+
throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
256
|
+
maxDepth: this.options.dereference.maxDepth,
|
|
257
|
+
uri: this.reference.uri
|
|
258
|
+
});
|
|
259
|
+
}
|
|
166
260
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
261
|
+
// detect second deep dive into the same fragment and avoid it
|
|
262
|
+
const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
|
|
263
|
+
if (ancestorsLineage.includes(referencedElement)) {
|
|
264
|
+
reference.refSet.circular = true;
|
|
265
|
+
if (this.options.dereference.circular === 'error') {
|
|
266
|
+
throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
|
|
267
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
268
|
+
});
|
|
269
|
+
} else if (this.options.dereference.circular === 'replace') {
|
|
270
|
+
const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
|
|
271
|
+
type: referencingElement.element,
|
|
272
|
+
uri: reference.uri,
|
|
273
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
274
|
+
});
|
|
275
|
+
const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
|
|
276
|
+
const replacement = replacer(refElement);
|
|
277
|
+
this.indirections.pop();
|
|
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
|
-
|
|
311
|
+
this.indirections.pop();
|
|
190
312
|
|
|
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
|
|
313
|
+
/**
|
|
314
|
+
* Creating a new version of referenced element to avoid modifying the original one.
|
|
315
|
+
*/
|
|
316
|
+
const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
|
|
317
|
+
// annotate referenced element with info about original referencing element
|
|
318
|
+
mergedElement.meta.set('ref-fields', {
|
|
319
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
215
320
|
});
|
|
321
|
+
// annotate fragment with info about origin
|
|
322
|
+
mergedElement.meta.set('ref-origin', reference.uri);
|
|
323
|
+
mergedElement.meta.set('ref-type', referencingElement.element);
|
|
216
324
|
|
|
217
|
-
|
|
218
|
-
|
|
325
|
+
/**
|
|
326
|
+
* Transclude referencing element with merged referenced element.
|
|
327
|
+
*/
|
|
328
|
+
path.replaceWith(mergedElement);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
|
|
331
|
+
this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
|
|
219
332
|
}
|
|
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
333
|
}
|
|
242
334
|
async PathItemElement(path) {
|
|
243
335
|
const referencingElement = path.node;
|
|
@@ -252,7 +344,6 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
252
344
|
path.skip();
|
|
253
345
|
return;
|
|
254
346
|
}
|
|
255
|
-
const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
|
|
256
347
|
const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
257
348
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
258
349
|
const isExternalReference = !isInternalReference;
|
|
@@ -267,117 +358,123 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
267
358
|
// skip traversing this Path Item element but traverse all it's child elements
|
|
268
359
|
return;
|
|
269
360
|
}
|
|
270
|
-
const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
271
361
|
const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
|
|
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
|
-
}
|
|
362
|
+
try {
|
|
363
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
|
|
364
|
+
this.indirections.push(referencingElement);
|
|
365
|
+
const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
|
|
291
366
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
throw new _apidomError.ApiDOMError('Recursive Path Item Object reference detected');
|
|
295
|
-
}
|
|
367
|
+
// possibly non-semantic referenced element
|
|
368
|
+
let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
|
|
296
369
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
370
|
+
// applying semantics to a referenced element
|
|
371
|
+
if (!(0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) {
|
|
372
|
+
if (this.refractCache.has(referencedElement)) {
|
|
373
|
+
referencedElement = this.refractCache.get(referencedElement);
|
|
374
|
+
} else {
|
|
375
|
+
const sourceElement = referencedElement;
|
|
376
|
+
referencedElement = (0, _apidomNsOpenapi.refractPathItem)(referencedElement);
|
|
377
|
+
this.refractCache.set(sourceElement, referencedElement);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
301
380
|
|
|
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,
|
|
381
|
+
// detect direct or indirect reference
|
|
382
|
+
if (referencingElement === referencedElement) {
|
|
383
|
+
throw new _apidomError.ApiDOMStructuredError('Recursive Path Item Object reference detected', {
|
|
311
384
|
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
312
385
|
});
|
|
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
386
|
}
|
|
319
|
-
}
|
|
320
387
|
|
|
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
|
-
});
|
|
388
|
+
// detect maximum depth of dereferencing
|
|
389
|
+
if (this.indirections.length > this.options.dereference.maxDepth) {
|
|
390
|
+
throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
|
|
391
|
+
maxDepth: this.options.dereference.maxDepth,
|
|
392
|
+
uri: this.reference.uri
|
|
393
|
+
});
|
|
394
|
+
}
|
|
346
395
|
|
|
347
|
-
//
|
|
348
|
-
directAncestors.
|
|
349
|
-
|
|
350
|
-
|
|
396
|
+
// detect cross-boundary cycle
|
|
397
|
+
const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
|
|
398
|
+
if (ancestorsLineage.includes(referencedElement)) {
|
|
399
|
+
reference.refSet.circular = true;
|
|
400
|
+
if (this.options.dereference.circular === 'error') {
|
|
401
|
+
throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
|
|
402
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
403
|
+
});
|
|
404
|
+
} else if (this.options.dereference.circular === 'replace') {
|
|
405
|
+
const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
|
|
406
|
+
type: referencingElement.element,
|
|
407
|
+
uri: reference.uri,
|
|
408
|
+
$ref: (0, _apidomCore.toValue)(referencingElement.$ref)
|
|
409
|
+
});
|
|
410
|
+
const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
|
|
411
|
+
const replacement = replacer(refElement);
|
|
412
|
+
this.indirections.pop();
|
|
413
|
+
path.replaceWith(replacement);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
351
417
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
418
|
+
/**
|
|
419
|
+
* Dive deep into the fragment.
|
|
420
|
+
*
|
|
421
|
+
* Cases to consider:
|
|
422
|
+
* 1. We're crossing document boundary
|
|
423
|
+
* 2. Fragment is from non-entry document
|
|
424
|
+
* 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value
|
|
425
|
+
* 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
|
|
426
|
+
*/
|
|
427
|
+
const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
|
|
428
|
+
const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
|
|
429
|
+
if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
|
|
430
|
+
// append referencing reference to ancestors lineage
|
|
431
|
+
directAncestors.add(referencingElement);
|
|
432
|
+
const visitor = new OpenAPI3_0DereferenceVisitor({
|
|
433
|
+
reference,
|
|
434
|
+
indirections: [...this.indirections],
|
|
435
|
+
options: this.options,
|
|
436
|
+
refractCache: this.refractCache,
|
|
437
|
+
ancestors: ancestorsLineage
|
|
438
|
+
});
|
|
439
|
+
referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
|
|
440
|
+
mutable: true
|
|
441
|
+
});
|
|
365
442
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
referencedElement
|
|
375
|
-
|
|
443
|
+
// remove referencing reference from ancestors lineage
|
|
444
|
+
directAncestors.delete(referencingElement);
|
|
445
|
+
}
|
|
446
|
+
this.indirections.pop();
|
|
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
|
+
}
|
|
381
478
|
}
|
|
382
479
|
async LinkElement(path) {
|
|
383
480
|
const linkElement = path.node;
|
|
@@ -389,67 +486,78 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
389
486
|
|
|
390
487
|
// operationRef and operationId fields are mutually exclusive
|
|
391
488
|
if ((0, _apidomDatamodel.isStringElement)(linkElement.operationRef) && (0, _apidomDatamodel.isStringElement)(linkElement.operationId)) {
|
|
392
|
-
throw new _apidomError.
|
|
489
|
+
throw new _apidomError.ApiDOMStructuredError('LinkElement operationRef and operationId fields are mutually exclusive', {
|
|
490
|
+
operationRef: (0, _apidomCore.toValue)(linkElement.operationRef),
|
|
491
|
+
operationId: (0, _apidomCore.toValue)(linkElement.operationId)
|
|
492
|
+
});
|
|
393
493
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
494
|
+
try {
|
|
495
|
+
let operationElement;
|
|
496
|
+
if ((0, _apidomDatamodel.isStringElement)(linkElement.operationRef)) {
|
|
497
|
+
// possibly non-semantic referenced element
|
|
498
|
+
const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference((0, _apidomCore.toValue)(linkElement.operationRef));
|
|
499
|
+
const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(linkElement.operationRef));
|
|
500
|
+
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
501
|
+
const isExternalReference = !isInternalReference;
|
|
502
|
+
|
|
503
|
+
// ignore resolving internal Operation Object reference
|
|
504
|
+
if (!this.options.resolve.internal && isInternalReference) {
|
|
505
|
+
// skip traversing this Link element but traverse all it's child elements
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// ignore resolving external Operation Object reference
|
|
509
|
+
if (!this.options.resolve.external && isExternalReference) {
|
|
510
|
+
// skip traversing this Link element but traverse all it's child elements
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(linkElement.operationRef));
|
|
514
|
+
operationElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
|
|
515
|
+
// applying semantics to a referenced element
|
|
516
|
+
if (!(0, _apidomNsOpenapi.isOperationElement)(operationElement)) {
|
|
517
|
+
if (this.refractCache.has(operationElement)) {
|
|
518
|
+
operationElement = this.refractCache.get(operationElement);
|
|
519
|
+
} else {
|
|
520
|
+
const sourceElement = operationElement;
|
|
521
|
+
operationElement = (0, _apidomNsOpenapi.refractOperation)(operationElement);
|
|
522
|
+
this.refractCache.set(sourceElement, operationElement);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// create shallow clone to be able to annotate with metadata
|
|
526
|
+
operationElement = (0, _apidomDatamodel.cloneShallow)(operationElement);
|
|
527
|
+
// annotate operation element with info about origin
|
|
528
|
+
operationElement.meta.set('ref-origin', reference.uri);
|
|
529
|
+
operationElement.meta.set('ref-type', linkElement.element);
|
|
530
|
+
const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(linkElement);
|
|
531
|
+
linkElementCopy.operationRef?.meta.set('operation', operationElement);
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Transclude Link Object containing Operation Object in its meta.
|
|
535
|
+
*/
|
|
536
|
+
path.replaceWith(linkElementCopy);
|
|
410
537
|
return;
|
|
411
538
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
this.refractCache.set(cacheKey, operationElement);
|
|
539
|
+
if ((0, _apidomDatamodel.isStringElement)(linkElement.operationId)) {
|
|
540
|
+
const operationId = (0, _apidomCore.toValue)(linkElement.operationId);
|
|
541
|
+
const reference = await this.toReference(url.unsanitize(this.reference.uri));
|
|
542
|
+
operationElement = (0, _apidomTraverse.find)(reference.value.result, e => (0, _apidomNsOpenapi.isOperationElement)(e) && (0, _apidomDatamodel.isElement)(e.operationId) && e.operationId.equals(operationId));
|
|
543
|
+
// OperationElement not found by its operationId
|
|
544
|
+
if ((0, _ramdaAdjunct.isUndefined)(operationElement)) {
|
|
545
|
+
throw new _apidomError.ApiDOMStructuredError(`OperationElement(operationId=${operationId}) not found`, {
|
|
546
|
+
operationId
|
|
547
|
+
});
|
|
422
548
|
}
|
|
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);
|
|
549
|
+
const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(linkElement);
|
|
550
|
+
linkElementCopy.operationId?.meta.set('operation', operationElement);
|
|
430
551
|
|
|
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`);
|
|
552
|
+
/**
|
|
553
|
+
* Transclude Link Object containing Operation Object in its meta.
|
|
554
|
+
*/
|
|
555
|
+
path.replaceWith(linkElementCopy);
|
|
444
556
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
* Transclude Link Object containing Operation Object in its meta.
|
|
450
|
-
*/
|
|
451
|
-
path.replaceWith(linkElementCopy);
|
|
452
|
-
return;
|
|
557
|
+
} catch (error) {
|
|
558
|
+
const refFieldName = (0, _apidomDatamodel.isStringElement)(linkElement.operationRef) ? 'operationRef' : 'operationId';
|
|
559
|
+
const refFieldValue = (0, _apidomDatamodel.isStringElement)(linkElement.operationRef) ? (0, _apidomCore.toValue)(linkElement.operationRef) : (0, _apidomCore.toValue)(linkElement.operationId);
|
|
560
|
+
this.handleError(`Error while dereferencing Link Object. Cannot resolve ${refFieldName} "${refFieldValue}": ${error.message}`, error, linkElement, refFieldName, refFieldValue, path);
|
|
453
561
|
}
|
|
454
562
|
}
|
|
455
563
|
async ExampleElement(path) {
|
|
@@ -462,7 +570,10 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
462
570
|
|
|
463
571
|
// value and externalValue fields are mutually exclusive
|
|
464
572
|
if (exampleElement.hasKey('value') && (0, _apidomDatamodel.isStringElement)(exampleElement.externalValue)) {
|
|
465
|
-
throw new _apidomError.
|
|
573
|
+
throw new _apidomError.ApiDOMStructuredError('ExampleElement value and externalValue fields are mutually exclusive', {
|
|
574
|
+
value: (0, _apidomCore.toValue)(exampleElement.value),
|
|
575
|
+
externalValue: (0, _apidomCore.toValue)(exampleElement.externalValue)
|
|
576
|
+
});
|
|
466
577
|
}
|
|
467
578
|
const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(exampleElement.externalValue));
|
|
468
579
|
const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
|
|
@@ -478,19 +589,25 @@ class OpenAPI3_0DereferenceVisitor {
|
|
|
478
589
|
// skip traversing this Example element but traverse all it's child elements
|
|
479
590
|
return;
|
|
480
591
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
592
|
+
try {
|
|
593
|
+
const reference = await this.toReference((0, _apidomCore.toValue)(exampleElement.externalValue));
|
|
594
|
+
|
|
595
|
+
// shallow clone of the referenced element
|
|
596
|
+
const valueElement = (0, _apidomDatamodel.cloneShallow)(reference.value.result);
|
|
597
|
+
// annotate operation element with info about origin
|
|
598
|
+
valueElement.meta.set('ref-origin', reference.uri);
|
|
599
|
+
valueElement.meta.set('ref-type', exampleElement.element);
|
|
600
|
+
const exampleElementCopy = (0, _apidomDatamodel.cloneShallow)(exampleElement);
|
|
601
|
+
exampleElementCopy.value = valueElement;
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Transclude Example Object containing external value.
|
|
605
|
+
*/
|
|
606
|
+
path.replaceWith(exampleElementCopy);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
const externalValue = (0, _apidomCore.toValue)(exampleElement.externalValue);
|
|
609
|
+
this.handleError(`Error while dereferencing Example Object. Cannot resolve externalValue "${externalValue}": ${error.message}`, error, exampleElement, 'externalValue', externalValue, path);
|
|
610
|
+
}
|
|
494
611
|
}
|
|
495
612
|
}
|
|
496
613
|
var _default = exports.default = OpenAPI3_0DereferenceVisitor;
|