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