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