@speclynx/apidom-reference 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +97 -0
  3. package/dist/apidom-reference.browser.js +3854 -3241
  4. package/dist/apidom-reference.browser.min.js +1 -1
  5. package/package.json +25 -25
  6. package/src/dereference/index.cjs +4 -0
  7. package/src/dereference/index.mjs +4 -0
  8. package/src/dereference/strategies/apidom/visitor.cjs +139 -59
  9. package/src/dereference/strategies/apidom/visitor.mjs +142 -62
  10. package/src/dereference/strategies/arazzo-1/index.cjs +1 -4
  11. package/src/dereference/strategies/arazzo-1/index.mjs +2 -4
  12. package/src/dereference/strategies/arazzo-1/visitor.cjs +288 -199
  13. package/src/dereference/strategies/arazzo-1/visitor.mjs +291 -203
  14. package/src/dereference/strategies/asyncapi-2/index.cjs +1 -4
  15. package/src/dereference/strategies/asyncapi-2/index.mjs +2 -4
  16. package/src/dereference/strategies/asyncapi-2/visitor.cjs +323 -229
  17. package/src/dereference/strategies/asyncapi-2/visitor.mjs +326 -233
  18. package/src/dereference/strategies/openapi-2/index.cjs +1 -4
  19. package/src/dereference/strategies/openapi-2/index.mjs +2 -4
  20. package/src/dereference/strategies/openapi-2/visitor.cjs +417 -318
  21. package/src/dereference/strategies/openapi-2/visitor.mjs +422 -324
  22. package/src/dereference/strategies/openapi-3-0/index.cjs +1 -4
  23. package/src/dereference/strategies/openapi-3-0/index.mjs +2 -4
  24. package/src/dereference/strategies/openapi-3-0/visitor.cjs +403 -286
  25. package/src/dereference/strategies/openapi-3-0/visitor.mjs +407 -291
  26. package/src/dereference/strategies/openapi-3-1/index.cjs +1 -4
  27. package/src/dereference/strategies/openapi-3-1/index.mjs +2 -4
  28. package/src/dereference/strategies/openapi-3-1/visitor.cjs +598 -484
  29. package/src/dereference/strategies/openapi-3-1/visitor.mjs +602 -489
  30. package/src/errors/DereferenceError.cjs +1 -1
  31. package/src/errors/DereferenceError.mjs +2 -2
  32. package/src/errors/ResolveError.cjs +1 -1
  33. package/src/errors/ResolveError.mjs +2 -2
  34. package/src/errors/UnresolvableReferenceError.cjs +11 -0
  35. package/src/errors/UnresolvableReferenceError.mjs +6 -0
  36. package/src/index.cjs +3 -1
  37. package/src/index.mjs +1 -0
  38. package/src/options/index.cjs +10 -1
  39. package/src/options/index.mjs +10 -1
  40. package/src/util/plugins.cjs +1 -6
  41. package/src/util/plugins.mjs +2 -5
  42. package/types/apidom-reference.d.ts +10 -2
  43. package/types/dereference/strategies/apidom/visitor.d.ts +10 -0
  44. package/types/dereference/strategies/arazzo-1/visitor.d.ts +19 -5
  45. package/types/dereference/strategies/asyncapi-2/visitor.d.ts +21 -7
  46. package/types/dereference/strategies/openapi-2/visitor.d.ts +21 -8
  47. package/types/dereference/strategies/openapi-3-0/visitor.d.ts +21 -7
  48. package/types/dereference/strategies/openapi-3-1/visitor.d.ts +21 -7
  49. package/types/errors/DereferenceError.d.ts +2 -2
  50. package/types/errors/ResolveError.d.ts +2 -2
  51. package/types/errors/UnresolvableReferenceError.d.ts +7 -0
  52. package/types/index.d.ts +1 -0
  53. package/types/options/index.d.ts +2 -0
@@ -16,6 +16,7 @@ var _$anchor = require("./selectors/$anchor.cjs");
16
16
  var _uri = require("./selectors/uri.cjs");
17
17
  var _util = require("../openapi-3-1/util.cjs");
18
18
  var _util2 = require("./util.cjs");
19
+ var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
19
20
  var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
20
21
  var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
21
22
  var url = _interopRequireWildcard(require("../../../util/url.cjs"));
@@ -24,9 +25,6 @@ var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
24
25
  var _File = _interopRequireDefault(require("../../../File.cjs"));
25
26
  var _util3 = require("../../util.cjs");
26
27
  var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../errors/EvaluationJsonSchemaUriError.cjs"));
27
- // initialize element identity manager
28
- const identityManager = new _apidomCore.IdentityManager();
29
-
30
28
  /**
31
29
  * @public
32
30
  */
@@ -36,30 +34,42 @@ const identityManager = new _apidomCore.IdentityManager();
36
34
  */
37
35
  class Arazzo1DereferenceVisitor {
38
36
  indirections;
39
- namespace;
40
37
  reference;
41
38
  options;
39
+
40
+ /**
41
+ * Tracks element ancestors across dive-deep traversal boundaries.
42
+ * Used for cycle detection: if a referenced element is found in
43
+ * the ancestor lineage, a circular reference is detected.
44
+ */
42
45
  ancestors;
43
46
  constructor({
44
47
  reference,
45
- namespace,
46
48
  options,
47
49
  indirections = [],
48
50
  ancestors = new _util3.AncestorLineage()
49
51
  }) {
50
52
  this.indirections = indirections;
51
- this.namespace = namespace;
52
53
  this.reference = reference;
53
54
  this.options = options;
54
55
  this.ancestors = new _util3.AncestorLineage(...ancestors);
55
56
  }
57
+ toAncestorLineage(path) {
58
+ const ancestorNodes = path.getAncestorNodes();
59
+ const directAncestors = new Set(ancestorNodes.filter(_apidomDatamodel.isElement));
60
+ const ancestorsLineage = new _util3.AncestorLineage(...this.ancestors, directAncestors);
61
+ return [ancestorsLineage, directAncestors];
62
+ }
56
63
  toBaseURI(uri) {
57
64
  return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
58
65
  }
59
66
  async toReference(uri) {
60
67
  // detect maximum depth of resolution
61
68
  if (this.reference.depth >= this.options.resolve.maxDepth) {
62
- throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);
69
+ throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
70
+ maxDepth: this.options.resolve.maxDepth,
71
+ uri: this.reference.uri
72
+ });
63
73
  }
64
74
  const baseURI = this.toBaseURI(uri);
65
75
  const {
@@ -79,9 +89,23 @@ class Arazzo1DereferenceVisitor {
79
89
  });
80
90
 
81
91
  // register new mutable reference with a refSet
92
+ //
93
+ // NOTE(known limitation): the mutable reference is mutated in place during traversal
94
+ // (via `{ mutable: true }`). When an external document evaluates a JSON pointer back
95
+ // into this document, it may receive an already-resolved element instead of the original
96
+ // $ref. That resolved element was produced using the entry document's resolution context
97
+ // (ancestors, indirections), which may differ from the external document's context.
98
+ // This can affect cycle detection in rare cross-document circular reference patterns.
99
+ //
100
+ // Remediation: evaluate JSON pointers against the immutable (original) parse tree
101
+ // instead of the mutable working copy. The `immutable://` reference below preserves
102
+ // the original tree and could be used for pointer evaluation, ensuring every resolution
103
+ // context always sees raw, unresolved elements and processes them with its own
104
+ // ancestors/indirections. The trade-off is that elements referenced by multiple
105
+ // documents would be resolved once per context instead of being reused.
82
106
  const mutableReference = new _Reference.default({
83
107
  uri: baseURI,
84
- value: (0, _apidomDatamodel.cloneDeep)(parseResult),
108
+ value: this.options.dereference.immutable ? (0, _apidomDatamodel.cloneDeep)(parseResult) : parseResult,
85
109
  depth: this.reference.depth + 1
86
110
  });
87
111
  refSet.add(mutableReference);
@@ -96,15 +120,77 @@ class Arazzo1DereferenceVisitor {
96
120
  }
97
121
  return mutableReference;
98
122
  }
99
- toAncestorLineage(path) {
100
- /**
101
- * Compute full ancestors lineage.
102
- * Ancestors are flatten to unwrap all Element instances.
103
- */
104
- const ancestorNodes = path.getAncestorNodes();
105
- const directAncestors = new Set(ancestorNodes.filter(_apidomDatamodel.isElement));
106
- const ancestorsLineage = new _util3.AncestorLineage(...this.ancestors, directAncestors);
107
- return [ancestorsLineage, directAncestors];
123
+
124
+ /**
125
+ * Handles an error according to the continueOnError option.
126
+ *
127
+ * For new errors: wraps in UnresolvableReferenceError with structured context
128
+ * (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
129
+ * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
130
+ *
131
+ * Inner/intermediate visitors always throw to let the trace accumulate.
132
+ * Only the entry document visitor respects continueOnError (callback/swallow/throw).
133
+ */
134
+ handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
135
+ const {
136
+ continueOnError
137
+ } = this.options.dereference;
138
+ const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
139
+ const uri = this.reference.uri;
140
+ const type = referencingElement.element;
141
+ const codeFrame = (0, _apidomCore.toYAML)(referencingElement);
142
+
143
+ // find element location: tree search for entry documents, visitor path for external
144
+ let location;
145
+ (0, _apidomTraverse.traverse)(this.reference.value.result, {
146
+ enter: p => {
147
+ if (p.node === referencingElement) {
148
+ location = p.formatPath();
149
+ p.stop();
150
+ }
151
+ }
152
+ });
153
+ location ??= visitorPath.formatPath();
154
+ const hop = {
155
+ uri,
156
+ type,
157
+ refFieldName,
158
+ refFieldValue,
159
+ location,
160
+ codeFrame
161
+ };
162
+
163
+ // enrich existing error from nested visitor or create new one
164
+ let unresolvedError;
165
+ if (error instanceof _UnresolvableReferenceError.default) {
166
+ // prefix relative locations for entries belonging to the referenced document
167
+ const refBaseURI = this.toBaseURI(refFieldValue);
168
+ const fragment = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refFieldValue);
169
+ if (fragment) {
170
+ if (refBaseURI === error.uri && error.location) {
171
+ error.location = fragment + error.location;
172
+ }
173
+ for (const h of error.trace) {
174
+ if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
175
+ }
176
+ }
177
+ // @ts-ignore
178
+ error.trace = [hop, ...error.trace];
179
+ unresolvedError = error;
180
+ } else {
181
+ unresolvedError = new _UnresolvableReferenceError.default(message, {
182
+ cause: error,
183
+ type,
184
+ uri,
185
+ location,
186
+ codeFrame,
187
+ refFieldName,
188
+ refFieldValue,
189
+ trace: []
190
+ });
191
+ }
192
+ if (!isEntryDocument || continueOnError === false) throw unresolvedError;
193
+ if (typeof continueOnError === 'function') continueOnError(unresolvedError);
108
194
  }
109
195
  ReusableElement(path) {
110
196
  const referencingElement = path.node;
@@ -128,12 +214,16 @@ class Arazzo1DereferenceVisitor {
128
214
  tree
129
215
  } = (0, _arazzoRuntimeExpression.parse)(runtimeExpression);
130
216
  if (!result.success) {
131
- throw new _apidomError.ApiDOMError(`Invalid Reusable Object reference format: "${runtimeExpression}"`);
217
+ throw new _apidomError.ApiDOMStructuredError(`Invalid Reusable Object reference format: "${runtimeExpression}"`, {
218
+ runtimeExpression
219
+ });
132
220
  }
133
221
 
134
222
  // ReusableElement can only reference components
135
223
  if (tree.type !== 'ComponentsExpression') {
136
- throw new _apidomError.ApiDOMError(`Reusable Object reference "${runtimeExpression}" must be a components expression`);
224
+ throw new _apidomError.ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" must be a components expression`, {
225
+ runtimeExpression
226
+ });
137
227
  }
138
228
 
139
229
  // evaluate runtime expression as JSON Pointer to get the referenced element
@@ -142,15 +232,15 @@ class Arazzo1DereferenceVisitor {
142
232
  try {
143
233
  referencedElement = (0, _apidomJsonPointer.evaluate)(this.reference.value.result, jsonPointer);
144
234
  } catch {
145
- throw new _apidomError.ApiDOMError(`Reusable Object reference "${runtimeExpression}" cannot be resolved`);
235
+ throw new _apidomError.ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" cannot be resolved`, {
236
+ runtimeExpression
237
+ });
146
238
  }
147
239
 
148
240
  /**
149
241
  * Create a shallow clone of the referenced element to avoid modifying the original.
150
242
  */
151
243
  const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
152
- // assign unique id to merged element
153
- mergedElement.meta.set('id', identityManager.generateId());
154
244
  // annotate with info about original referencing element
155
245
  mergedElement.meta.set('ref-fields', {
156
246
  reference: runtimeExpression,
@@ -158,8 +248,7 @@ class Arazzo1DereferenceVisitor {
158
248
  });
159
249
  // annotate with info about origin
160
250
  mergedElement.meta.set('ref-origin', this.reference.uri);
161
- // annotate with info about referencing element
162
- mergedElement.meta.set('ref-referencing-element-id', identityManager.identify(referencingElement));
251
+ mergedElement.meta.set('ref-type', referencingElement.element);
163
252
 
164
253
  // override value field if present for Parameter Objects
165
254
  if ((0, _apidomNsArazzo.isParameterElement)(mergedElement) && referencingElement.hasKey('value')) {
@@ -185,78 +274,32 @@ class Arazzo1DereferenceVisitor {
185
274
  path.skip();
186
275
  return;
187
276
  }
188
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
189
-
190
- // compute baseURI using rules around $id and $ref keywords
191
- let reference = await this.toReference(url.unsanitize(this.reference.uri));
192
- let {
193
- uri: retrievalURI
194
- } = reference;
195
- const $refBaseURI = (0, _util.resolveSchema$refField)(retrievalURI, referencingElement);
196
- const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
197
- const file = new _File.default({
198
- uri: $refBaseURIStrippedHash
199
- });
200
- const isUnknownURI = (0, _ramda.none)(r => r.canRead(file), this.options.resolve.resolvers);
201
- const isURL = !isUnknownURI;
202
- let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI;
203
- let isExternalReference = !isInternalReference;
204
-
205
- // determining reference, proper evaluation and selection mechanism
206
- let referencedElement;
207
277
  try {
208
- if (isUnknownURI || isURL) {
209
- // we're dealing with canonical URI or URL with possible fragment
210
- retrievalURI = this.toBaseURI($refBaseURI);
211
- const selector = $refBaseURI;
212
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
213
- referencedElement = (0, _uri.evaluate)(selector, referenceAsSchema);
214
- referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
215
- referencedElement.id = identityManager.identify(referencedElement);
216
-
217
- // ignore resolving internal Schema Objects
218
- if (!this.options.resolve.internal && isInternalReference) {
219
- // skip traversing this schema element but traverse all its child elements
220
- return;
221
- }
222
- // ignore resolving external Schema Objects
223
- if (!this.options.resolve.external && isExternalReference) {
224
- // skip traversing this schema element but traverse all its child elements
225
- return;
226
- }
227
- } else {
228
- // we're assuming here that we're dealing with JSON Pointer here
229
- retrievalURI = this.toBaseURI($refBaseURI);
230
- isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
231
- isExternalReference = !isInternalReference;
278
+ // compute baseURI using rules around $id and $ref keywords
279
+ let reference = await this.toReference(url.unsanitize(this.reference.uri));
280
+ let {
281
+ uri: retrievalURI
282
+ } = reference;
283
+ const $refBaseURI = (0, _util.resolveSchema$refField)(retrievalURI, referencingElement);
284
+ const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
285
+ const file = new _File.default({
286
+ uri: $refBaseURIStrippedHash
287
+ });
288
+ const isUnknownURI = (0, _ramda.none)(r => r.canRead(file), this.options.resolve.resolvers);
289
+ const isURL = !isUnknownURI;
290
+ let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI;
291
+ let isExternalReference = !isInternalReference;
232
292
 
233
- // ignore resolving internal Schema Objects
234
- if (!this.options.resolve.internal && isInternalReference) {
235
- // skip traversing this schema element but traverse all its child elements
236
- return;
237
- }
238
- // ignore resolving external Schema Objects
239
- if (!this.options.resolve.external && isExternalReference) {
240
- // skip traversing this schema element but traverse all its child elements
241
- return;
242
- }
243
- reference = await this.toReference(url.unsanitize($refBaseURI));
244
- const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
245
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
246
- referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
247
- referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
248
- referencedElement.id = identityManager.identify(referencedElement);
249
- }
250
- } catch (error) {
251
- /**
252
- * JSONSchemaElement($id=URL) was not found, so we're going to try to resolve
253
- * the URL and assume the returned response is a JSON Schema.
254
- */
255
- if (isURL && error instanceof _EvaluationJsonSchemaUriError.default) {
256
- if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)($refBaseURI))) {
257
- // we're dealing with JSON Schema $anchor here
258
- isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
259
- isExternalReference = !isInternalReference;
293
+ // determining reference, proper evaluation and selection mechanism
294
+ let referencedElement;
295
+ try {
296
+ if (isUnknownURI || isURL) {
297
+ // we're dealing with canonical URI or URL with possible fragment
298
+ retrievalURI = this.toBaseURI($refBaseURI);
299
+ const selector = $refBaseURI;
300
+ const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
301
+ referencedElement = (0, _uri.evaluate)(selector, referenceAsSchema);
302
+ referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
260
303
 
261
304
  // ignore resolving internal Schema Objects
262
305
  if (!this.options.resolve.internal && isInternalReference) {
@@ -268,12 +311,6 @@ class Arazzo1DereferenceVisitor {
268
311
  // skip traversing this schema element but traverse all its child elements
269
312
  return;
270
313
  }
271
- reference = await this.toReference(url.unsanitize($refBaseURI));
272
- const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
273
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
274
- referencedElement = (0, _$anchor.evaluate)(selector, referenceAsSchema);
275
- referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
276
- referencedElement.id = identityManager.identify(referencedElement);
277
314
  } else {
278
315
  // we're assuming here that we're dealing with JSON Pointer here
279
316
  retrievalURI = this.toBaseURI($refBaseURI);
@@ -295,117 +332,169 @@ class Arazzo1DereferenceVisitor {
295
332
  const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
296
333
  referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
297
334
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
298
- referencedElement.id = identityManager.identify(referencedElement);
299
335
  }
300
- } else {
301
- throw error;
302
- }
303
- }
304
- this.indirections.push(referencingElement);
336
+ } catch (error) {
337
+ /**
338
+ * JSONSchemaElement($id=URL) was not found, so we're going to try to resolve
339
+ * the URL and assume the returned response is a JSON Schema.
340
+ */
341
+ if (isURL && error instanceof _EvaluationJsonSchemaUriError.default) {
342
+ if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)($refBaseURI))) {
343
+ // we're dealing with JSON Schema $anchor here
344
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
345
+ isExternalReference = !isInternalReference;
305
346
 
306
- // detect direct or indirect reference
307
- if (referencingElement === referencedElement) {
308
- throw new _apidomError.ApiDOMError('Recursive JSON Schema reference detected');
309
- }
347
+ // ignore resolving internal Schema Objects
348
+ if (!this.options.resolve.internal && isInternalReference) {
349
+ // skip traversing this schema element but traverse all its child elements
350
+ return;
351
+ }
352
+ // ignore resolving external Schema Objects
353
+ if (!this.options.resolve.external && isExternalReference) {
354
+ // skip traversing this schema element but traverse all its child elements
355
+ return;
356
+ }
357
+ reference = await this.toReference(url.unsanitize($refBaseURI));
358
+ const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
359
+ const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
360
+ referencedElement = (0, _$anchor.evaluate)(selector, referenceAsSchema);
361
+ referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
362
+ } else {
363
+ // we're assuming here that we're dealing with JSON Pointer here
364
+ retrievalURI = this.toBaseURI($refBaseURI);
365
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
366
+ isExternalReference = !isInternalReference;
310
367
 
311
- // detect maximum depth of dereferencing
312
- if (this.indirections.length > this.options.dereference.maxDepth) {
313
- throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
314
- }
368
+ // ignore resolving internal Schema Objects
369
+ if (!this.options.resolve.internal && isInternalReference) {
370
+ // skip traversing this schema element but traverse all its child elements
371
+ return;
372
+ }
373
+ // ignore resolving external Schema Objects
374
+ if (!this.options.resolve.external && isExternalReference) {
375
+ // skip traversing this schema element but traverse all its child elements
376
+ return;
377
+ }
378
+ reference = await this.toReference(url.unsanitize($refBaseURI));
379
+ const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
380
+ const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
381
+ referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
382
+ referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
383
+ }
384
+ } else {
385
+ throw error;
386
+ }
387
+ }
388
+ this.indirections.push(referencingElement);
315
389
 
316
- // detect second deep dive into the same fragment and avoid it
317
- if (ancestorsLineage.includes(referencedElement)) {
318
- reference.refSet.circular = true;
319
- if (this.options.dereference.circular === 'error') {
320
- throw new _apidomError.ApiDOMError('Circular reference detected');
321
- } else if (this.options.dereference.circular === 'replace') {
322
- const refElement = new _apidomDatamodel.RefElement(referencedElement.id, {
323
- type: 'json-schema',
324
- uri: reference.uri,
390
+ // detect direct or indirect reference
391
+ if (referencingElement === referencedElement) {
392
+ throw new _apidomError.ApiDOMStructuredError('Recursive JSON Schema reference detected', {
325
393
  $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
326
394
  });
327
- const replacer = this.options.dereference.strategyOpts['arazzo-1']?.circularReplacer ?? this.options.dereference.circularReplacer;
328
- const replacement = replacer(refElement);
329
- this.indirections.pop();
330
- path.replaceWith(replacement);
331
- return;
332
395
  }
333
- }
334
396
 
335
- /**
336
- * Dive deep into the fragment.
337
- *
338
- * Cases to consider:
339
- * 1. We're crossing document boundary
340
- * 2. Fragment is from non-root document
341
- * 3. Fragment is a JSON Schema with $ref field. We need to follow it to get the eventual value
342
- * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
343
- */
344
- const isNonRootDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
345
- const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
346
- if ((isExternalReference || isNonRootDocument || (0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
347
- // append referencing reference to ancestors lineage
348
- directAncestors.add(referencingElement);
349
- const visitor = new Arazzo1DereferenceVisitor({
350
- reference,
351
- namespace: this.namespace,
352
- indirections: [...this.indirections],
353
- options: this.options,
354
- ancestors: ancestorsLineage
355
- });
356
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
357
- mutable: true
358
- });
397
+ // detect maximum depth of dereferencing
398
+ if (this.indirections.length > this.options.dereference.maxDepth) {
399
+ throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
400
+ maxDepth: this.options.dereference.maxDepth,
401
+ uri: this.reference.uri
402
+ });
403
+ }
359
404
 
360
- // remove referencing reference from ancestors lineage
361
- directAncestors.delete(referencingElement);
362
- }
363
- this.indirections.pop();
405
+ // detect cross-boundary cycle
406
+ const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
407
+ if (ancestorsLineage.includes(referencedElement)) {
408
+ reference.refSet.circular = true;
409
+ if (this.options.dereference.circular === 'error') {
410
+ throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
411
+ $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
412
+ });
413
+ } else if (this.options.dereference.circular === 'replace') {
414
+ const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
415
+ type: referencingElement.element,
416
+ uri: reference.uri,
417
+ $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
418
+ });
419
+ const replacer = this.options.dereference.strategyOpts['arazzo-1']?.circularReplacer ?? this.options.dereference.circularReplacer;
420
+ const replacement = replacer(refElement);
421
+ this.indirections.pop();
422
+ path.replaceWith(replacement);
423
+ return;
424
+ }
425
+ }
364
426
 
365
- // Boolean JSON Schemas
366
- if ((0, _apidomNsArazzo.isBooleanJSONSchemaElement)(referencedElement)) {
367
- const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(referencedElement);
368
- // assign unique id to merged element
369
- booleanJsonSchemaElement.meta.set('id', identityManager.generateId());
370
- // annotate referenced element with info about original referencing element
371
- booleanJsonSchemaElement.meta.set('ref-fields', {
372
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
373
- });
374
- // annotate referenced element with info about origin
375
- booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
376
- // annotate fragment with info about referencing element
377
- booleanJsonSchemaElement.meta.set('ref-referencing-element-id', identityManager.identify(referencingElement));
378
- path.replaceWith(booleanJsonSchemaElement);
379
- return;
380
- }
427
+ /**
428
+ * Dive deep into the fragment.
429
+ *
430
+ * Cases to consider:
431
+ * 1. We're crossing document boundary
432
+ * 2. Fragment is from non-entry document
433
+ * 3. Fragment is a JSON Schema with $ref field. We need to follow it to get the eventual value
434
+ * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
435
+ */
436
+ const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
437
+ const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
438
+ if ((isExternalReference || isNonEntryDocument || (0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
439
+ // append referencing reference to ancestors lineage
440
+ directAncestors.add(referencingElement);
441
+ const visitor = new Arazzo1DereferenceVisitor({
442
+ reference,
443
+ indirections: [...this.indirections],
444
+ options: this.options,
445
+ ancestors: ancestorsLineage
446
+ });
447
+ referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
448
+ mutable: true
449
+ });
381
450
 
382
- /**
383
- * Creating a new version of JSON Schema by merging fields from referenced Schema with referencing one.
384
- */
385
- if ((0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement)) {
386
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
387
- // assign unique id to merged element
388
- mergedElement.meta.set('id', identityManager.generateId());
389
- // existing keywords from referencing schema overrides ones from referenced schema
390
- referencingElement.forEach((value, keyElement, item) => {
391
- mergedElement.remove((0, _apidomCore.toValue)(keyElement));
392
- mergedElement.content.push(item);
393
- });
394
- mergedElement.remove('$ref');
395
- // annotate referenced element with info about original referencing element
396
- mergedElement.meta.set('ref-fields', {
397
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
398
- });
399
- // annotate fragment with info about origin
400
- mergedElement.meta.set('ref-origin', reference.uri);
401
- // annotate fragment with info about referencing element
402
- mergedElement.meta.set('ref-referencing-element-id', identityManager.identify(referencingElement));
403
- referencedElement = mergedElement;
451
+ // remove referencing reference from ancestors lineage
452
+ directAncestors.delete(referencingElement);
453
+ }
454
+ this.indirections.pop();
455
+
456
+ // Boolean JSON Schemas
457
+ if ((0, _apidomNsArazzo.isBooleanJSONSchemaElement)(referencedElement)) {
458
+ const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(referencedElement);
459
+ // annotate referenced element with info about original referencing element
460
+ booleanJsonSchemaElement.meta.set('ref-fields', {
461
+ $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
462
+ });
463
+ // annotate referenced element with info about origin
464
+ booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
465
+ booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
466
+ path.replaceWith(booleanJsonSchemaElement);
467
+ return;
468
+ }
469
+
470
+ /**
471
+ * Creating a new version of JSON Schema by merging fields from referenced Schema with referencing one.
472
+ */
473
+ if ((0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement)) {
474
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
475
+ // existing keywords from referencing schema overrides ones from referenced schema
476
+ referencingElement.forEach((value, keyElement, item) => {
477
+ mergedElement.remove((0, _apidomCore.toValue)(keyElement));
478
+ mergedElement.content.push(item);
479
+ });
480
+ mergedElement.remove('$ref');
481
+ // annotate referenced element with info about original referencing element
482
+ mergedElement.meta.set('ref-fields', {
483
+ $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
484
+ });
485
+ // annotate fragment with info about origin
486
+ mergedElement.meta.set('ref-origin', reference.uri);
487
+ mergedElement.meta.set('ref-type', referencingElement.element);
488
+ referencedElement = mergedElement;
489
+ }
490
+ /**
491
+ * Transclude referencing element with merged referenced element.
492
+ */
493
+ path.replaceWith(referencedElement);
494
+ } catch (error) {
495
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
496
+ this.handleError(`Error while dereferencing Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
404
497
  }
405
- /**
406
- * Transclude referencing element with merged referenced element.
407
- */
408
- path.replaceWith(referencedElement);
409
498
  }
410
499
  }
411
500
  var _default = exports.default = Arazzo1DereferenceVisitor;