@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
@@ -17,399 +17,247 @@ var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-3-1");
17
17
  var _$anchor = require("./selectors/$anchor.cjs");
18
18
  var _uri = require("./selectors/uri.cjs");
19
19
  var _jsonPointer = require("./selectors/json-pointer.cjs");
20
- var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
21
- var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
22
- var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
23
20
  var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../errors/EvaluationJsonSchemaUriError.cjs"));
21
+ var _DereferenceVisitor = _interopRequireDefault(require("../DereferenceVisitor.cjs"));
24
22
  var url = _interopRequireWildcard(require("../../../util/url.cjs"));
25
- var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
26
- var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
27
23
  var _File = _interopRequireDefault(require("../../../File.cjs"));
28
24
  var _util = require("./util.cjs");
29
- var _util2 = require("../../util.cjs");
30
25
  /**
31
- * @public
26
+ * Type a Schema Object `$ref` expects its target to have.
27
+ */
28
+ const SCHEMA_TYPE = 'schema';
29
+
30
+ /**
31
+ * Context of a source being dereferenced: the `$id`s of the Schema Objects enclosing it.
32
32
  */
33
33
 
34
34
  /**
35
35
  * @public
36
36
  */
37
- class OpenAPI3_1DereferenceVisitor {
38
- indirections;
39
- reference;
40
- options;
41
- refractCache;
42
37
 
38
+ /**
39
+ * Dereferences an OpenAPI 3.1 document. Reference Objects expect the type of their
40
+ * `referenced-element` meta, Path Item `$ref`s a Path Item, Schema Object `$ref`s a Schema Object.
41
+ * Link Objects get the Operation Object they point at attached, Example Objects
42
+ * the external value.
43
+ *
44
+ * @public
45
+ */
46
+ class OpenAPI3_1DereferenceVisitor extends _DereferenceVisitor.default {
43
47
  /**
44
- * Index of `$id`-bearing schemas per document, shared across nested visitors
45
- * of a single dereference run so it never outlives mutations of the documents.
48
+ * Index of `$id`-bearing schemas per document, shared across the visitors
49
+ * of a single dereference run.
46
50
  */
47
51
  schema$idIndex;
48
- visited;
49
52
 
50
53
  /**
51
54
  * `$id`s of the JSON Schema elements enclosing the traversal root within its
52
55
  * document (outermost first). Empty when the root is the document itself; a
53
- * nested visitor traversing a fragment is seeded with the `$id`s of the
54
- * schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
55
- * against the same base as they would from the document root.
56
+ * visitor traversing a fragment is seeded with the `$id`s of the schemas the
57
+ * fragment sits under, so the `$ref`s and `$id`s within resolve against the
58
+ * same base as they would from the document root.
56
59
  */
57
60
  ancestorSchema$ids;
58
-
59
- /**
60
- * Tracks element ancestors across dive-deep traversal boundaries.
61
- * Used for cycle detection: if a referenced element is found in
62
- * the ancestor lineage, a circular reference is detected.
63
- */
64
- ancestors;
65
61
  constructor({
66
62
  reference,
67
63
  options,
68
- indirections = [],
69
- refractCache = new WeakMap(),
70
64
  schema$idIndex = new WeakMap(),
71
- ancestors = new _util2.AncestorLineage(),
72
- visited = new WeakSet(),
73
65
  ancestorSchema$ids = []
74
66
  }) {
75
- this.indirections = indirections;
76
- this.reference = reference;
77
- this.options = options;
78
- this.refractCache = refractCache;
67
+ super({
68
+ reference,
69
+ options
70
+ });
79
71
  this.schema$idIndex = schema$idIndex;
80
- this.ancestors = new _util2.AncestorLineage(...ancestors);
81
- this.visited = visited;
82
72
  this.ancestorSchema$ids = ancestorSchema$ids;
83
73
  }
84
- toAncestorLineage(path) {
85
- const ancestorNodes = path.getAncestorNodes();
86
- const directAncestors = new Set(ancestorNodes.filter(_apidomDatamodel.isElement));
87
- const ancestorsLineage = new _util2.AncestorLineage(...this.ancestors, directAncestors);
88
- return [ancestorsLineage, directAncestors];
74
+ get strategyName() {
75
+ return 'openapi-3-1';
89
76
  }
90
- toBaseURI(uri) {
91
- return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
77
+ createVisitor(reference, {
78
+ ancestorSchema$ids
79
+ }) {
80
+ return new OpenAPI3_1DereferenceVisitor({
81
+ reference,
82
+ options: this.options,
83
+ schema$idIndex: this.schema$idIndex,
84
+ ancestorSchema$ids
85
+ });
92
86
  }
93
- async toReference(uri) {
94
- // detect maximum depth of resolution
95
- if (this.reference.depth >= this.options.resolve.maxDepth) {
96
- throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
97
- maxDepth: this.options.resolve.maxDepth,
98
- uri: this.reference.uri
99
- });
100
- }
101
- const baseURI = this.toBaseURI(uri);
102
- const {
103
- refSet
104
- } = this.reference;
105
-
106
- // we've already processed this Reference in past
107
- if (refSet.has(baseURI)) {
108
- return refSet.find((0, _ramda.propEq)(baseURI, 'uri'));
87
+
88
+ /**
89
+ * A Reference Object stands for the type its reference expects: it's dereferenced as that type.
90
+ */
91
+ isOfType(element, type) {
92
+ if ((0, _apidomNsOpenapi.isReferenceElement)(element)) return true;
93
+ return type === SCHEMA_TYPE ? (0, _apidomNsOpenapi.isSchemaElement)(element) || (0, _apidomNsOpenapi.isBooleanJSONSchemaElement)(element) : element.element === type;
94
+ }
95
+
96
+ /**
97
+ * A generic structure holding a `$ref` is a Reference Object to the expected type,
98
+ * unless the type has its own `$ref` field (Path Item Object, Schema Object).
99
+ */
100
+ refractTo(element, type) {
101
+ if (type !== SCHEMA_TYPE && type !== 'pathItem' && (0, _apidomNsOpenapi.isReferenceLikeElement)(element)) {
102
+ const referenceElement = (0, _apidomNsOpenapi.refractReference)(element);
103
+ referenceElement.meta.set('referenced-element', type);
104
+ return referenceElement;
109
105
  }
110
- const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
111
- ...this.options,
112
- parse: {
113
- ...this.options.parse,
114
- mediaType: 'text/plain'
115
- }
106
+ return (0, _apidomNsOpenapi.refract)(element, {
107
+ element: type
116
108
  });
109
+ }
117
110
 
118
- // register new mutable reference with a refSet
119
- //
120
- // NOTE(known limitation): the mutable reference is mutated in place during traversal
121
- // (via `{ mutable: true }`). When an external document evaluates a JSON pointer back
122
- // into this document, it may receive an already-resolved element instead of the original
123
- // $ref. That resolved element was produced using the entry document's resolution context
124
- // (ancestors, indirections), which may differ from the external document's context.
125
- // This can affect cycle detection in rare cross-document circular reference patterns.
126
- //
127
- // Remediation: evaluate JSON pointers against the immutable (original) parse tree
128
- // instead of the mutable working copy. The `immutable://` reference below preserves
129
- // the original tree and could be used for pointer evaluation, ensuring every resolution
130
- // context always sees raw, unresolved elements and processes them with its own
131
- // ancestors/indirections. The trade-off is that elements referenced by multiple
132
- // documents would be resolved once per context instead of being reused.
133
- const mutableReference = new _Reference.default({
134
- uri: baseURI,
135
- value: this.options.dereference.immutable ? (0, _apidomDatamodel.cloneDeep)(parseResult) : parseResult,
136
- depth: this.reference.depth + 1
137
- });
138
- refSet.add(mutableReference);
139
- if (this.options.dereference.immutable) {
140
- // register new immutable reference with original parseResult
141
- const immutableReference = new _Reference.default({
142
- uri: `immutable://${baseURI}`,
143
- value: parseResult,
144
- depth: this.reference.depth + 1
145
- });
146
- refSet.add(immutableReference);
111
+ /**
112
+ * A document as Schema Object for locating schemas within it: a generic document is
113
+ * refracted to Schema Object once per dereference run, a semantic one is used as it is.
114
+ */
115
+ toSchemaDocument(document) {
116
+ if (!(0, _apidomDatamodel.isPrimitiveElement)(document)) return document;
117
+ let byType = this.run.refracted.get(document);
118
+ if (byType === undefined) {
119
+ byType = new Map();
120
+ this.run.refracted.set(document, byType);
147
121
  }
148
- return mutableReference;
122
+ let refracted = byType.get(SCHEMA_TYPE);
123
+ if (refracted === undefined) {
124
+ refracted = (0, _apidomNsOpenapi.refractSchema)(document);
125
+ byType.set(SCHEMA_TYPE, refracted);
126
+ }
127
+ return refracted;
149
128
  }
150
129
 
151
130
  /**
152
- * Handles an error according to the continueOnError option.
153
- *
154
- * For new errors: wraps in UnresolvableReferenceError with structured context
155
- * (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
156
- * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
157
- *
158
- * Inner/intermediate visitors always throw to let the trace accumulate.
159
- * Only the entry document visitor respects continueOnError (callback/swallow/throw).
131
+ * Indexes the `$id`-bearing schemas of an element for the run. Transcluded elements aren't
132
+ * indexed: they're copies of structures indexed where they're defined,
133
+ * and in a document dereferenced in place their shared structure would be walked once per path.
160
134
  */
161
- handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
162
- const {
163
- continueOnError
164
- } = this.options.dereference;
165
- const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
166
- const uri = this.reference.uri;
167
- const type = referencingElement.element;
168
- const codeFrame = (0, _apidomCore.toYAML)(referencingElement);
169
-
170
- // find element location by identity in the document tree.
171
- // guarded: this.reference.value may not be a ParseResultElement or may lack a result.
172
- // falls back to visitorPath which may produce an incomplete path when
173
- // dereferenceApiDOM is called with a fragment (cloneShallow creates a new root identity).
174
- let location;
175
- const root = this.reference.value.result;
176
- if ((0, _apidomDatamodel.isElement)(root)) {
177
- (0, _apidomTraverse.traverse)(root, {
178
- enter: p => {
179
- if (p.node === referencingElement || this.refractCache.get(p.node) === referencingElement) {
180
- location = p.formatPath();
181
- p.stop();
182
- }
183
- }
184
- });
185
- }
186
- location ??= visitorPath.formatPath();
187
- const hop = {
188
- uri,
189
- type,
190
- refFieldName,
191
- refFieldValue,
192
- location,
193
- codeFrame
194
- };
195
-
196
- // enrich existing error from nested visitor or create new one
197
- let unresolvedError;
198
- if (error instanceof _UnresolvableReferenceError.default) {
199
- // prefix relative locations for entries belonging to the referenced document
200
- const refBaseURI = this.toBaseURI(refFieldValue);
201
- const fragment = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refFieldValue);
202
- if (fragment) {
203
- if (refBaseURI === error.uri && error.location) {
204
- error.location = fragment + error.location;
205
- }
206
- for (const h of error.trace) {
207
- if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
135
+ index$ids(element) {
136
+ if (this.schema$idIndex.has(element)) return;
137
+ const entries = [];
138
+ (0, _apidomTraverse.traverse)(element, {
139
+ enter: path => {
140
+ if (this.isTranscluded(path.node)) {
141
+ path.skip();
142
+ } else if ((0, _util.isJSONSchemaElementWith$id)(path.node)) {
143
+ entries.push({
144
+ schema: path.node,
145
+ $ids: (0, _util.collectSchema$ids)(path)
146
+ });
208
147
  }
209
148
  }
210
- // @ts-ignore
211
- error.trace = [hop, ...error.trace];
212
- unresolvedError = error;
213
- } else {
214
- unresolvedError = new _UnresolvableReferenceError.default(message, {
215
- cause: error,
216
- type,
217
- uri,
218
- location,
219
- codeFrame,
220
- refFieldName,
221
- refFieldValue,
222
- trace: []
223
- });
224
- }
225
- if (!isEntryDocument || continueOnError === false) throw unresolvedError;
226
- if (typeof continueOnError === 'function') continueOnError(unresolvedError);
149
+ });
150
+ this.schema$idIndex.set(element, entries);
227
151
  }
228
152
  async ReferenceElement(path) {
229
153
  const referencingElement = path.node;
230
154
 
231
- // ignore Reference Object without a string $ref, e.g. an empty YAML value
232
- if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
233
- return;
234
- }
155
+ // the fields of a Reference Object hold no references
156
+ path.skip();
235
157
 
236
- // skip current referencing element as it's already been accessed
237
- if (this.indirections.includes(referencingElement)) {
238
- path.skip();
158
+ // ignore Reference Object without a string $ref, e.g. an empty YAML value
159
+ if (this.isTranscluded(referencingElement) || !(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
239
160
  return;
240
161
  }
241
- const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
162
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
163
+ const retrievalURI = this.toBaseURI($ref);
242
164
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
243
165
  const isExternalReference = !isInternalReference;
244
166
 
245
167
  // ignore resolving internal Reference Objects
246
168
  if (!this.options.resolve.internal && isInternalReference) {
247
- // skip traversing this reference element and all it's child elements
248
- path.skip();
249
169
  return;
250
170
  }
251
171
  // ignore resolving external Reference Objects
252
172
  if (!this.options.resolve.external && isExternalReference) {
253
- // skip traversing this reference element and all it's child elements
254
- path.skip();
255
173
  return;
256
174
  }
257
- const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
258
- const indirectionsSize = this.indirections.length;
175
+ const $refBaseURI = url.resolve(retrievalURI, $ref);
259
176
  try {
260
- const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
261
- this.indirections.push(referencingElement);
177
+ const reference = await this.toReference($ref);
262
178
  const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
263
-
264
179
  // possibly non-semantic fragment
265
- let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
266
-
267
- // applying semantics to a fragment
268
- const referencedElementType = referencingElement.meta.get('referenced-element');
269
- if (referencedElement.element !== referencedElementType && !(0, _apidomNsOpenapi.isReferenceElement)(referencedElement)) {
270
- if (this.refractCache.has(referencedElement)) {
271
- referencedElement = this.refractCache.get(referencedElement);
272
- } else if ((0, _apidomNsOpenapi.isReferenceLikeElement)(referencedElement)) {
273
- // handling generic indirect references
274
- const sourceElement = referencedElement;
275
- referencedElement = (0, _apidomNsOpenapi.refractReference)(referencedElement);
276
- referencedElement.meta.set('referenced-element', referencedElementType);
277
- this.refractCache.set(sourceElement, referencedElement);
278
- } else {
279
- // handling direct references
280
- const sourceElement = referencedElement;
281
- referencedElement = (0, _apidomNsOpenapi.refract)(referencedElement, {
282
- element: referencedElementType
283
- });
284
- this.refractCache.set(sourceElement, referencedElement);
285
- }
286
- }
180
+ const referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
287
181
 
288
- // detect direct or indirect reference
182
+ // detect direct reference
289
183
  if (referencingElement === referencedElement) {
290
184
  throw new _apidomError.ApiDOMStructuredError('Recursive Reference Object detected', {
291
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
292
- });
293
- }
294
-
295
- // detect maximum depth of dereferencing
296
- if (this.indirections.length > this.options.dereference.maxDepth) {
297
- throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
298
- maxDepth: this.options.dereference.maxDepth,
299
- uri: this.reference.uri
185
+ $ref
300
186
  });
301
187
  }
302
188
 
303
- // detect cross-boundary cycle
304
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
305
- if (ancestorsLineage.includes(referencedElement)) {
306
- reference.refSet.circular = true;
307
- if (this.options.dereference.circular === 'error') {
308
- throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
309
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
189
+ // a reference without an expected type reads its target as its own type
190
+ const type = (0, _apidomCore.toValue)(referencingElement.meta.get('referenced-element')) ?? referencedElement.element;
191
+ await this.addTransclusion(path, {
192
+ source: referencedElement,
193
+ type,
194
+ reference,
195
+ context: {
196
+ ancestorSchema$ids: []
197
+ }
198
+ }, {
199
+ toTranscludedElement: (element, target) => {
200
+ // a chain of Reference Objects leading back to itself never reaches a value
201
+ if ((0, _apidomNsOpenapi.isReferenceElement)(target)) return element;
202
+ const reusingElement = element;
203
+ /**
204
+ * Creating a new version of referenced element to avoid modifying the original one.
205
+ */
206
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(target);
207
+ // annotate fragment with info about original Reference element
208
+ mergedElement.meta.set('ref-fields', {
209
+ $ref,
210
+ description: (0, _apidomCore.toValue)(reusingElement.get('description')),
211
+ summary: (0, _apidomCore.toValue)(reusingElement.get('summary'))
310
212
  });
311
- } else if (this.options.dereference.circular === 'replace') {
312
- const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
313
- type: referencingElement.element,
213
+ // annotate fragment with info about origin and type
214
+ mergedElement.meta.set('ref-origin', reference.uri);
215
+ mergedElement.meta.set('ref-type', element.element);
216
+
217
+ // override description and summary (outer has higher priority then inner)
218
+ if ((0, _apidomDatamodel.isObjectElement)(target) && (0, _apidomDatamodel.isObjectElement)(mergedElement)) {
219
+ const fields = (0, _apidomCore.fixedFields)(target, {
220
+ indexed: true
221
+ });
222
+ if (reusingElement.hasKey('description') && Object.hasOwn(fields, 'description')) {
223
+ mergedElement.remove('description');
224
+ mergedElement.set('description', reusingElement.get('description'));
225
+ }
226
+ if (reusingElement.hasKey('summary') && Object.hasOwn(fields, 'summary')) {
227
+ mergedElement.remove('summary');
228
+ mergedElement.set('summary', reusingElement.get('summary'));
229
+ }
230
+ }
231
+ return mergedElement;
232
+ },
233
+ circular: {
234
+ uri: $refBaseURI,
235
+ meta: {
314
236
  uri: reference.uri,
315
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
316
- });
317
- const replacer = this.options.dereference.strategyOpts['openapi-3-1']?.circularReplacer ?? this.options.dereference.circularReplacer;
318
- const replacement = replacer(refElement);
319
- path.replaceWith(replacement);
320
- path.skip();
321
- return;
322
- }
323
- }
324
-
325
- /**
326
- * Dive deep into the fragment.
327
- *
328
- * Cases to consider:
329
- * 1. We're crossing document boundary
330
- * 2. Fragment is from non-entry document
331
- * 3. Fragment is a Reference Object. We need to follow it to get the eventual value
332
- * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
333
- */
334
- const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
335
- const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
336
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
337
- if (shouldDetectCircular) {
338
- this.visited.add(referencedElement);
237
+ $ref
238
+ },
239
+ details: {
240
+ $ref
241
+ }
339
242
  }
340
- directAncestors.add(referencingElement);
341
- const visitor = new OpenAPI3_1DereferenceVisitor({
342
- reference,
343
- indirections: [...this.indirections],
344
- options: this.options,
345
- refractCache: this.refractCache,
346
- schema$idIndex: this.schema$idIndex,
347
- visited: this.visited,
348
- ancestors: ancestorsLineage
349
- });
350
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
351
- mutable: true,
352
- skipVisited: 'skip'
353
- });
354
- directAncestors.delete(referencingElement);
355
- }
356
-
357
- /**
358
- * Creating a new version of referenced element to avoid modifying the original one.
359
- */
360
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
361
- // annotate fragment with info about original Reference element
362
- mergedElement.meta.set('ref-fields', {
363
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref),
364
- // @ts-ignore
365
- description: (0, _apidomCore.toValue)(referencingElement.description),
366
- // @ts-ignore
367
- summary: (0, _apidomCore.toValue)(referencingElement.summary)
368
243
  });
369
- // annotate fragment with info about origin and type
370
- mergedElement.meta.set('ref-origin', reference.uri);
371
- mergedElement.meta.set('ref-type', referencingElement.element);
372
-
373
- // override description and summary (outer has higher priority then inner)
374
- if ((0, _apidomDatamodel.isObjectElement)(referencedElement) && (0, _apidomDatamodel.isObjectElement)(mergedElement)) {
375
- const fields = (0, _apidomCore.fixedFields)(referencedElement, {
376
- indexed: true
377
- });
378
- if (referencingElement.hasKey('description') && Object.hasOwn(fields, 'description')) {
379
- mergedElement.remove('description');
380
- mergedElement.set('description', referencingElement.get('description'));
381
- }
382
- if (referencingElement.hasKey('summary') && Object.hasOwn(fields, 'summary')) {
383
- mergedElement.remove('summary');
384
- mergedElement.set('summary', referencingElement.get('summary'));
385
- }
386
- }
387
-
388
- /**
389
- * Transclude referencing element with merged referenced element.
390
- */
391
- path.replaceWith(mergedElement);
392
244
  } catch (error) {
393
- const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
394
245
  this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
395
- } finally {
396
- if (this.indirections.length > indirectionsSize) this.indirections.pop();
397
246
  }
398
247
  }
399
248
  async PathItemElement(path) {
400
249
  const referencingElement = path.node;
401
-
402
- // ignore PathItemElement without $ref field
403
- if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
250
+ if (this.isTranscluded(referencingElement)) {
251
+ path.skip();
404
252
  return;
405
253
  }
406
254
 
407
- // skip current referencing element as it's already been accessed
408
- if (this.indirections.includes(referencingElement)) {
409
- path.skip();
255
+ // ignore PathItemElement without $ref field
256
+ if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
410
257
  return;
411
258
  }
412
- const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
259
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
260
+ const retrievalURI = this.toBaseURI($ref);
413
261
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
414
262
  const isExternalReference = !isInternalReference;
415
263
 
@@ -423,131 +271,72 @@ class OpenAPI3_1DereferenceVisitor {
423
271
  // skip traversing this Path Item element but traverse all it's child elements
424
272
  return;
425
273
  }
426
- const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
427
- const indirectionsSize = this.indirections.length;
274
+ const $refBaseURI = url.resolve(retrievalURI, $ref);
428
275
  try {
429
- const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
430
- this.indirections.push(referencingElement);
276
+ const reference = await this.toReference($ref);
431
277
  const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
432
-
433
278
  // possibly non-semantic referenced element
434
- let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
435
-
436
- // applying semantics to a referenced element
437
- if (!(0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) {
438
- if (this.refractCache.has(referencedElement)) {
439
- referencedElement = this.refractCache.get(referencedElement);
440
- } else {
441
- const sourceElement = referencedElement;
442
- referencedElement = (0, _apidomNsOpenapi.refractPathItem)(referencedElement);
443
- this.refractCache.set(sourceElement, referencedElement);
444
- }
445
- }
279
+ const referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
446
280
 
447
- // detect direct or indirect reference
281
+ // detect direct reference
448
282
  if (referencingElement === referencedElement) {
449
283
  throw new _apidomError.ApiDOMStructuredError('Recursive Path Item Object reference detected', {
450
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
451
- });
452
- }
453
-
454
- // detect maximum depth of dereferencing
455
- if (this.indirections.length > this.options.dereference.maxDepth) {
456
- throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
457
- maxDepth: this.options.dereference.maxDepth,
458
- uri: this.reference.uri
284
+ $ref
459
285
  });
460
286
  }
287
+ await this.addTransclusion(path, {
288
+ source: referencedElement,
289
+ type: 'pathItem',
290
+ reference,
291
+ context: {
292
+ ancestorSchema$ids: []
293
+ }
294
+ }, {
295
+ toTranscludedElement: (element, target) => {
296
+ if (!(0, _apidomNsOpenapi.isPathItemElement)(target)) return target;
297
+
298
+ /**
299
+ * Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
300
+ */
301
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(target);
302
+ // existing keywords from referencing PathItemElement overrides ones from referenced element
303
+ element.forEach((value, keyElement, item) => {
304
+ mergedElement.remove((0, _apidomCore.toValue)(keyElement));
305
+ mergedElement.content.push(item);
306
+ });
307
+ mergedElement.remove('$ref');
461
308
 
462
- // detect cross-boundary cycle
463
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
464
- if (ancestorsLineage.includes(referencedElement)) {
465
- reference.refSet.circular = true;
466
- if (this.options.dereference.circular === 'error') {
467
- throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
468
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
309
+ // annotate referenced element with info about original referencing element
310
+ mergedElement.meta.set('ref-fields', {
311
+ $ref
469
312
  });
470
- } else if (this.options.dereference.circular === 'replace') {
471
- const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
472
- type: referencingElement.element,
313
+ // annotate referenced element with info about origin and type
314
+ mergedElement.meta.set('ref-origin', reference.uri);
315
+ mergedElement.meta.set('ref-type', element.element);
316
+ return mergedElement;
317
+ },
318
+ aliasField: '$ref',
319
+ circular: {
320
+ uri: $refBaseURI,
321
+ meta: {
473
322
  uri: reference.uri,
474
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
475
- });
476
- const replacer = this.options.dereference.strategyOpts['openapi-3-1']?.circularReplacer ?? this.options.dereference.circularReplacer;
477
- const replacement = replacer(refElement);
478
- path.replaceWith(replacement);
479
- path.skip();
480
- return;
481
- }
482
- }
483
-
484
- /**
485
- * Dive deep into the fragment.
486
- *
487
- * Cases to consider:
488
- * 1. We're crossing document boundary
489
- * 2. Fragment is from non-entry document
490
- * 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value
491
- * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
492
- */
493
- const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
494
- const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
495
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
496
- if (shouldDetectCircular) {
497
- this.visited.add(referencedElement);
323
+ $ref
324
+ },
325
+ details: {
326
+ $ref
327
+ }
498
328
  }
499
- directAncestors.add(referencingElement);
500
- const visitor = new OpenAPI3_1DereferenceVisitor({
501
- reference,
502
- indirections: [...this.indirections],
503
- options: this.options,
504
- refractCache: this.refractCache,
505
- schema$idIndex: this.schema$idIndex,
506
- visited: this.visited,
507
- ancestors: ancestorsLineage
508
- });
509
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
510
- mutable: true,
511
- skipVisited: 'skip'
512
- });
513
- directAncestors.delete(referencingElement);
514
- }
515
-
516
- /**
517
- * Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
518
- */
519
- if ((0, _apidomNsOpenapi.isPathItemElement)(referencedElement)) {
520
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
521
- // existing keywords from referencing PathItemElement overrides ones from referenced element
522
- referencingElement.forEach((value, keyElement, item) => {
523
- mergedElement.remove((0, _apidomCore.toValue)(keyElement));
524
- mergedElement.content.push(item);
525
- });
526
- mergedElement.remove('$ref');
527
-
528
- // annotate referenced element with info about original referencing element
529
- mergedElement.meta.set('ref-fields', {
530
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
531
- });
532
- // annotate referenced element with info about origin and type
533
- mergedElement.meta.set('ref-origin', reference.uri);
534
- mergedElement.meta.set('ref-type', referencingElement.element);
535
- referencedElement = mergedElement;
536
- }
537
-
538
- /**
539
- * Transclude referencing element with merged referenced element.
540
- */
541
- path.replaceWith(referencedElement);
329
+ });
542
330
  } catch (error) {
543
- const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
544
331
  this.handleError(`Error while dereferencing Path Item Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
545
- } finally {
546
- if (this.indirections.length > indirectionsSize) this.indirections.pop();
547
332
  }
548
333
  }
549
334
  async LinkElement(path) {
550
335
  const linkElement = path.node;
336
+ if (this.isTranscluded(linkElement)) {
337
+ path.skip();
338
+ return;
339
+ }
551
340
 
552
341
  // ignore LinkElement without operationRef or operationId field
553
342
  if (!(0, _apidomDatamodel.isStringElement)(linkElement.operationRef) && !(0, _apidomDatamodel.isStringElement)(linkElement.operationId)) {
@@ -562,11 +351,11 @@ class OpenAPI3_1DereferenceVisitor {
562
351
  });
563
352
  }
564
353
  try {
565
- let operationElement;
566
354
  if ((0, _apidomDatamodel.isStringElement)(linkElement.operationRef)) {
355
+ const operationRef = (0, _apidomCore.toValue)(linkElement.operationRef);
567
356
  // possibly non-semantic referenced element
568
- const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference((0, _apidomCore.toValue)(linkElement.operationRef));
569
- const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(linkElement.operationRef));
357
+ const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(operationRef);
358
+ const retrievalURI = this.toBaseURI(operationRef);
570
359
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
571
360
  const isExternalReference = !isInternalReference;
572
361
 
@@ -580,51 +369,56 @@ class OpenAPI3_1DereferenceVisitor {
580
369
  // skip traversing this Link element but traverse all it's child elements
581
370
  return;
582
371
  }
583
- const reference = await this.toReference((0, _apidomCore.toValue)(linkElement.operationRef));
584
- operationElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
585
- // applying semantics to a referenced element
586
- if (!(0, _apidomNsOpenapi.isOperationElement)(operationElement)) {
587
- if (this.refractCache.has(operationElement)) {
588
- operationElement = this.refractCache.get(operationElement);
589
- } else {
590
- const sourceElement = operationElement;
591
- operationElement = (0, _apidomNsOpenapi.refractOperation)(operationElement);
592
- this.refractCache.set(sourceElement, operationElement);
372
+ const reference = await this.toReference(operationRef);
373
+ const operationElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
374
+ await this.addAttachment(path, {
375
+ source: operationElement,
376
+ type: 'operation',
377
+ reference,
378
+ context: {
379
+ ancestorSchema$ids: []
593
380
  }
594
- }
595
- // create shallow clone to be able to annotate with metadata
596
- operationElement = (0, _apidomDatamodel.cloneShallow)(operationElement);
597
- // annotate operation element with info about origin and type
598
- operationElement.meta.set('ref-origin', reference.uri);
599
- operationElement.meta.set('ref-type', linkElement.element);
600
- const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(linkElement);
601
- linkElementCopy.operationRef?.meta.set('operation', operationElement);
602
-
603
- /**
604
- * Transclude Link Object containing Operation Object in its meta.
605
- */
606
- path.replaceWith(linkElementCopy);
381
+ }, (element, target) => {
382
+ // create shallow clone to be able to annotate with metadata
383
+ const operationCopy = (0, _apidomDatamodel.cloneShallow)(target);
384
+ // annotate operation element with info about origin and type
385
+ operationCopy.meta.set('ref-origin', reference.uri);
386
+ operationCopy.meta.set('ref-type', element.element);
387
+
388
+ /**
389
+ * Transclude Link Object containing Operation Object in its meta.
390
+ */
391
+ const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(element);
392
+ linkElementCopy.operationRef?.meta.set('operation', operationCopy);
393
+ return linkElementCopy;
394
+ });
607
395
  return;
608
396
  }
609
- if ((0, _apidomDatamodel.isStringElement)(linkElement.operationId)) {
610
- const operationId = (0, _apidomCore.toValue)(linkElement.operationId);
611
- const reference = await this.toReference(url.unsanitize(this.reference.uri));
612
- 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));
613
- operationElement = operationPath?.node;
614
- // OperationElement not found by its operationId
615
- if ((0, _ramdaAdjunct.isUndefined)(operationElement)) {
616
- throw new _apidomError.ApiDOMStructuredError(`OperationElement(operationId=${operationId}) not found`, {
617
- operationId
618
- });
397
+ const operationId = (0, _apidomCore.toValue)(linkElement.operationId);
398
+ const reference = await this.toReference(url.unsanitize(this.reference.uri));
399
+ 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));
400
+ const operationElement = operationPath?.node;
401
+ // OperationElement not found by its operationId
402
+ if ((0, _ramdaAdjunct.isUndefined)(operationElement)) {
403
+ throw new _apidomError.ApiDOMStructuredError(`OperationElement(operationId=${operationId}) not found`, {
404
+ operationId
405
+ });
406
+ }
407
+ await this.addAttachment(path, {
408
+ source: operationElement,
409
+ type: 'operation',
410
+ reference,
411
+ context: {
412
+ ancestorSchema$ids: []
619
413
  }
620
- const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(linkElement);
621
- linkElementCopy.operationId?.meta.set('operation', operationElement);
622
-
414
+ }, (element, target) => {
623
415
  /**
624
416
  * Transclude Link Object containing Operation Object in its meta.
625
417
  */
626
- path.replaceWith(linkElementCopy);
627
- }
418
+ const linkElementCopy = (0, _apidomDatamodel.cloneShallow)(element);
419
+ linkElementCopy.operationId?.meta.set('operation', target);
420
+ return linkElementCopy;
421
+ });
628
422
  } catch (error) {
629
423
  const refFieldName = (0, _apidomDatamodel.isStringElement)(linkElement.operationRef) ? 'operationRef' : 'operationId';
630
424
  const refFieldValue = (0, _apidomDatamodel.isStringElement)(linkElement.operationRef) ? (0, _apidomCore.toValue)(linkElement.operationRef) : (0, _apidomCore.toValue)(linkElement.operationId);
@@ -633,6 +427,10 @@ class OpenAPI3_1DereferenceVisitor {
633
427
  }
634
428
  async ExampleElement(path) {
635
429
  const exampleElement = path.node;
430
+ if (this.isTranscluded(exampleElement)) {
431
+ path.skip();
432
+ return;
433
+ }
636
434
 
637
435
  // ignore ExampleElement without externalValue field
638
436
  if (!(0, _apidomDatamodel.isStringElement)(exampleElement.externalValue)) {
@@ -646,7 +444,8 @@ class OpenAPI3_1DereferenceVisitor {
646
444
  externalValue: (0, _apidomCore.toValue)(exampleElement.externalValue)
647
445
  });
648
446
  }
649
- const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(exampleElement.externalValue));
447
+ const externalValue = (0, _apidomCore.toValue)(exampleElement.externalValue);
448
+ const retrievalURI = this.toBaseURI(externalValue);
650
449
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
651
450
  const isExternalReference = !isInternalReference;
652
451
 
@@ -661,39 +460,44 @@ class OpenAPI3_1DereferenceVisitor {
661
460
  return;
662
461
  }
663
462
  try {
664
- const reference = await this.toReference((0, _apidomCore.toValue)(exampleElement.externalValue));
665
-
666
- // shallow clone of the referenced element
667
- const valueElement = (0, _apidomDatamodel.cloneShallow)(reference.value.result);
668
- // annotate element with info about origin and type
669
- valueElement.meta.set('ref-origin', reference.uri);
670
- valueElement.meta.set('ref-type', exampleElement.element);
671
- const exampleElementCopy = (0, _apidomDatamodel.cloneShallow)(exampleElement);
672
- exampleElementCopy.value = valueElement;
673
-
674
- /**
675
- * Transclude Example Object containing external value.
676
- */
677
- path.replaceWith(exampleElementCopy);
463
+ const reference = await this.toReference(externalValue);
464
+ const valueSource = reference.value.result;
465
+ await this.addAttachment(path, {
466
+ source: valueSource,
467
+ type: valueSource.element,
468
+ reference,
469
+ context: {
470
+ ancestorSchema$ids: []
471
+ }
472
+ }, (element, target) => {
473
+ // shallow clone of the referenced element
474
+ const valueElement = (0, _apidomDatamodel.cloneShallow)(target);
475
+ // annotate element with info about origin and type
476
+ valueElement.meta.set('ref-origin', reference.uri);
477
+ valueElement.meta.set('ref-type', element.element);
478
+
479
+ /**
480
+ * Transclude Example Object containing external value.
481
+ */
482
+ const exampleElementCopy = (0, _apidomDatamodel.cloneShallow)(element);
483
+ exampleElementCopy.value = valueElement;
484
+ return exampleElementCopy;
485
+ });
678
486
  } catch (error) {
679
- const externalValue = (0, _apidomCore.toValue)(exampleElement.externalValue);
680
487
  this.handleError(`Error while dereferencing Example Object. Cannot resolve externalValue "${externalValue}": ${error.message}`, error, exampleElement, 'externalValue', externalValue, path);
681
488
  }
682
489
  }
683
490
  async SchemaElement(path) {
684
491
  const referencingElement = path.node;
685
-
686
- // skip current referencing schema as $ref keyword was not defined
687
- if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
492
+ if (this.isTranscluded(referencingElement)) {
493
+ path.skip();
688
494
  return;
689
495
  }
690
496
 
691
- // skip current referencing element as it's already been accessed
692
- if (this.indirections.includes(referencingElement)) {
693
- path.skip();
497
+ // skip current referencing schema as $ref keyword was not defined
498
+ if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
694
499
  return;
695
500
  }
696
- const indirectionsSize = this.indirections.length;
697
501
  try {
698
502
  // compute baseURI using rules around $id and $ref keywords
699
503
  let reference = await this.toReference(url.unsanitize(this.reference.uri));
@@ -736,10 +540,12 @@ class OpenAPI3_1DereferenceVisitor {
736
540
  try {
737
541
  // the fragment's $ids resolve against the base its enclosing chain
738
542
  // leaves in effect, which then also prefixes the located schema's chain
543
+ const fragmentAsSchema = this.toSchemaDocument(fragmentElement);
544
+ this.index$ids(fragmentAsSchema);
739
545
  ({
740
546
  element: referencedElement,
741
547
  ancestorSchema$ids
742
- } = (0, _uri.locate)(selector, (0, _util.maybeRefractToSchemaElement)(fragmentElement), {
548
+ } = (0, _uri.locate)(selector, fragmentAsSchema, {
743
549
  baseURI: rootBaseURI,
744
550
  index: this.schema$idIndex
745
551
  }));
@@ -750,15 +556,16 @@ class OpenAPI3_1DereferenceVisitor {
750
556
  }
751
557
  // the document index is rooted at the document, so the located chain
752
558
  // is already document-relative and takes no prefix
559
+ const documentAsSchema = this.toSchemaDocument(documentElement);
560
+ this.index$ids(documentAsSchema);
753
561
  ({
754
562
  element: referencedElement,
755
563
  ancestorSchema$ids
756
- } = (0, _uri.locate)(selector, (0, _util.maybeRefractToSchemaElement)(documentElement), {
564
+ } = (0, _uri.locate)(selector, documentAsSchema, {
757
565
  baseURI: reference.uri,
758
566
  index: this.schema$idIndex
759
567
  }));
760
568
  }
761
- referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
762
569
 
763
570
  // ignore resolving internal Schema Objects
764
571
  if (!this.options.resolve.internal && isInternalReference) {
@@ -788,12 +595,10 @@ class OpenAPI3_1DereferenceVisitor {
788
595
  }
789
596
  reference = await this.toReference(url.unsanitize($refBaseURI));
790
597
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
791
- const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
792
598
  ({
793
599
  element: referencedElement,
794
600
  ancestorSchema$ids
795
- } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
796
- referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
601
+ } = (0, _jsonPointer.locate)(this.toSchemaDocument(reference.value.result), selector));
797
602
  }
798
603
  } catch (error) {
799
604
  /**
@@ -818,12 +623,10 @@ class OpenAPI3_1DereferenceVisitor {
818
623
  }
819
624
  reference = await this.toReference(url.unsanitize($refBaseURI));
820
625
  const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
821
- const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
822
626
  ({
823
627
  element: referencedElement,
824
628
  ancestorSchema$ids
825
- } = (0, _$anchor.locate)(selector, referenceAsSchema));
826
- referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
629
+ } = (0, _$anchor.locate)(selector, this.toSchemaDocument(reference.value.result)));
827
630
  } else {
828
631
  // we're assuming here that we're dealing with JSON Pointer here
829
632
  retrievalURI = this.toBaseURI($refBaseURI);
@@ -842,137 +645,82 @@ class OpenAPI3_1DereferenceVisitor {
842
645
  }
843
646
  reference = await this.toReference(url.unsanitize($refBaseURI));
844
647
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
845
- const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
846
648
  ({
847
649
  element: referencedElement,
848
650
  ancestorSchema$ids
849
- } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
850
- referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
651
+ } = (0, _jsonPointer.locate)(this.toSchemaDocument(reference.value.result), selector));
851
652
  }
852
653
  } else {
853
654
  throw error;
854
655
  }
855
656
  }
856
- this.indirections.push(referencingElement);
657
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
857
658
 
858
- // detect direct or indirect reference
659
+ // detect direct reference
859
660
  if (referencingElement === referencedElement) {
860
661
  throw new _apidomError.ApiDOMStructuredError('Recursive Schema Object reference detected', {
861
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
662
+ $ref
862
663
  });
863
664
  }
864
-
865
- // detect maximum depth of dereferencing
866
- if (this.indirections.length > this.options.dereference.maxDepth) {
867
- throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
868
- maxDepth: this.options.dereference.maxDepth,
869
- uri: this.reference.uri
870
- });
871
- }
872
-
873
- // detect cross-boundary cycle
874
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
875
- if (ancestorsLineage.includes(referencedElement)) {
876
- reference.refSet.circular = true;
877
- if (this.options.dereference.circular === 'error') {
878
- throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
879
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
665
+ const origin = reference.uri;
666
+ // a Schema Object $ref reads whatever it points at as a Schema Object
667
+ await this.addTransclusion(path, {
668
+ source: referencedElement,
669
+ type: SCHEMA_TYPE,
670
+ reference,
671
+ context: {
672
+ ancestorSchema$ids
673
+ }
674
+ }, {
675
+ toTranscludedElement: (element, target) => {
676
+ // Boolean JSON Schemas
677
+ if ((0, _apidomNsOpenapi.isBooleanJSONSchemaElement)(target)) {
678
+ const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(target);
679
+ // annotate referenced element with info about original referencing element
680
+ booleanJsonSchemaElement.meta.set('ref-fields', {
681
+ $ref
682
+ });
683
+ // annotate referenced element with info about origin and type
684
+ booleanJsonSchemaElement.meta.set('ref-origin', origin);
685
+ booleanJsonSchemaElement.meta.set('ref-type', element.element);
686
+ return booleanJsonSchemaElement;
687
+ }
688
+ if (!(0, _apidomNsOpenapi.isSchemaElement)(target)) return target;
689
+
690
+ /**
691
+ * Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one.
692
+ */
693
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(target);
694
+ // existing keywords from referencing schema overrides ones from referenced schema
695
+ element.forEach((value, keyElement, item) => {
696
+ mergedElement.remove((0, _apidomCore.toValue)(keyElement));
697
+ mergedElement.content.push(item);
880
698
  });
881
- } else if (this.options.dereference.circular === 'replace') {
882
- const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
883
- type: referencingElement.element,
884
- uri: reference.uri,
885
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
699
+ mergedElement.remove('$ref');
700
+ // annotate referenced element with info about original referencing element
701
+ mergedElement.meta.set('ref-fields', {
702
+ $ref
886
703
  });
887
- const replacer = this.options.dereference.strategyOpts['openapi-3-1']?.circularReplacer ?? this.options.dereference.circularReplacer;
888
- const replacement = replacer(refElement);
889
- path.replaceWith(replacement);
890
- path.skip();
891
- return;
892
- }
893
- }
894
-
895
- /**
896
- * Dive deep into the fragment.
897
- *
898
- * Cases to consider:
899
- * 1. We're crossing document boundary
900
- * 2. Fragment is from non-entry document
901
- * 3. Fragment is a Schema Object with $ref field. We need to follow it to get the eventual value
902
- * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
903
- */
904
- const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
905
- const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
906
- // content transplanted into the referencing site is traversed under that
907
- // site's $id chain; a fragment enclosed by a different chain has its own
908
- // $refs resolved in place first, by a nested visitor seeded with that chain
909
- const hasDifferentEnclosingBase = (0, _util.resolveSchema$ids)(reference.uri, ancestorSchema$ids) !== (0, _util.resolveSchema$ids)(rootBaseURI, (0, _util.schema$idsOf)(path.getAncestorNodes().reverse()));
910
- if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || (0, _apidomNsOpenapi.isSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
911
- if (shouldDetectCircular) {
912
- this.visited.add(referencedElement);
704
+ // annotate fragment with info about origin and type
705
+ mergedElement.meta.set('ref-origin', origin);
706
+ mergedElement.meta.set('ref-type', element.element);
707
+ return mergedElement;
708
+ },
709
+ aliasField: '$ref',
710
+ circular: {
711
+ uri: $refBaseURI,
712
+ meta: {
713
+ uri: origin,
714
+ $ref
715
+ },
716
+ details: {
717
+ $ref
718
+ }
913
719
  }
914
- directAncestors.add(referencingElement);
915
- const visitor = new OpenAPI3_1DereferenceVisitor({
916
- reference,
917
- indirections: [...this.indirections],
918
- options: this.options,
919
- refractCache: this.refractCache,
920
- schema$idIndex: this.schema$idIndex,
921
- visited: this.visited,
922
- ancestors: ancestorsLineage,
923
- ancestorSchema$ids
924
- });
925
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
926
- mutable: true,
927
- skipVisited: 'skip'
928
- });
929
- directAncestors.delete(referencingElement);
930
- }
931
-
932
- // Boolean JSON Schemas
933
- if ((0, _apidomNsOpenapi.isBooleanJSONSchemaElement)(referencedElement)) {
934
- const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(referencedElement);
935
- // annotate referenced element with info about original referencing element
936
- booleanJsonSchemaElement.meta.set('ref-fields', {
937
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
938
- });
939
- // annotate referenced element with info about origin and type
940
- booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
941
- booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
942
- path.replaceWith(booleanJsonSchemaElement);
943
- path.skip();
944
- return;
945
- }
946
-
947
- /**
948
- * Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one.
949
- */
950
- if ((0, _apidomNsOpenapi.isSchemaElement)(referencedElement)) {
951
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
952
- // existing keywords from referencing schema overrides ones from referenced schema
953
- referencingElement.forEach((value, keyElement, item) => {
954
- mergedElement.remove((0, _apidomCore.toValue)(keyElement));
955
- mergedElement.content.push(item);
956
- });
957
- mergedElement.remove('$ref');
958
- // annotate referenced element with info about original referencing element
959
- mergedElement.meta.set('ref-fields', {
960
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
961
- });
962
- // annotate fragment with info about origin and type
963
- mergedElement.meta.set('ref-origin', reference.uri);
964
- mergedElement.meta.set('ref-type', referencingElement.element);
965
- referencedElement = mergedElement;
966
- }
967
- /**
968
- * Transclude referencing element with merged referenced element.
969
- */
970
- path.replaceWith(referencedElement);
720
+ });
971
721
  } catch (error) {
972
722
  const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
973
723
  this.handleError(`Error while dereferencing Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
974
- } finally {
975
- if (this.indirections.length > indirectionsSize) this.indirections.pop();
976
724
  }
977
725
  }
978
726
  }