@speclynx/apidom-reference 3.0.0 → 3.2.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 +16 -0
  2. package/README.md +97 -0
  3. package/dist/apidom-reference.browser.js +3862 -3241
  4. package/dist/apidom-reference.browser.min.js +1 -1
  5. package/package.json +48 -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 +289 -199
  13. package/src/dereference/strategies/arazzo-1/visitor.mjs +292 -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 +325 -229
  17. package/src/dereference/strategies/asyncapi-2/visitor.mjs +328 -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 +420 -318
  21. package/src/dereference/strategies/openapi-2/visitor.mjs +425 -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 +405 -286
  25. package/src/dereference/strategies/openapi-3-0/visitor.mjs +409 -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
package/package.json CHANGED
@@ -1,7 +1,30 @@
1
1
  {
2
2
  "name": "@speclynx/apidom-reference",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "Advanced algorithms for semantic ApiDOM manipulations like dereferencing or resolution.",
5
+ "keywords": [
6
+ "apidom",
7
+ "api",
8
+ "$ref",
9
+ "dereference",
10
+ "reference",
11
+ "resolve",
12
+ "resolver",
13
+ "bundle",
14
+ "openapi",
15
+ "swagger",
16
+ "oas",
17
+ "asyncapi",
18
+ "arazzo",
19
+ "json-schema",
20
+ "json",
21
+ "yaml",
22
+ "spec",
23
+ "specification",
24
+ "parse",
25
+ "typescript",
26
+ "javascript"
27
+ ],
5
28
  "publishConfig": {
6
29
  "access": "public",
7
30
  "registry": "https://registry.npmjs.org",
@@ -231,29 +254,29 @@
231
254
  "license": "Apache-2.0",
232
255
  "dependencies": {
233
256
  "@babel/runtime-corejs3": "^7.28.4",
234
- "@speclynx/apidom-core": "3.0.0",
235
- "@speclynx/apidom-datamodel": "3.0.0",
236
- "@speclynx/apidom-error": "3.0.0",
237
- "@speclynx/apidom-json-pointer": "3.0.0",
238
- "@speclynx/apidom-ns-arazzo-1": "3.0.0",
239
- "@speclynx/apidom-ns-asyncapi-2": "3.0.0",
240
- "@speclynx/apidom-ns-json-schema-2020-12": "3.0.0",
241
- "@speclynx/apidom-ns-openapi-2": "3.0.0",
242
- "@speclynx/apidom-ns-openapi-3-0": "3.0.0",
243
- "@speclynx/apidom-ns-openapi-3-1": "3.0.0",
244
- "@speclynx/apidom-parser-adapter-arazzo-json-1": "3.0.0",
245
- "@speclynx/apidom-parser-adapter-arazzo-yaml-1": "3.0.0",
246
- "@speclynx/apidom-parser-adapter-asyncapi-json-2": "3.0.0",
247
- "@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "3.0.0",
248
- "@speclynx/apidom-parser-adapter-json": "3.0.0",
249
- "@speclynx/apidom-parser-adapter-openapi-json-2": "3.0.0",
250
- "@speclynx/apidom-parser-adapter-openapi-json-3-0": "3.0.0",
251
- "@speclynx/apidom-parser-adapter-openapi-json-3-1": "3.0.0",
252
- "@speclynx/apidom-parser-adapter-openapi-yaml-2": "3.0.0",
253
- "@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "3.0.0",
254
- "@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "3.0.0",
255
- "@speclynx/apidom-parser-adapter-yaml-1-2": "3.0.0",
256
- "@speclynx/apidom-traverse": "3.0.0",
257
+ "@speclynx/apidom-core": "3.2.0",
258
+ "@speclynx/apidom-datamodel": "3.2.0",
259
+ "@speclynx/apidom-error": "3.2.0",
260
+ "@speclynx/apidom-json-pointer": "3.2.0",
261
+ "@speclynx/apidom-ns-arazzo-1": "3.2.0",
262
+ "@speclynx/apidom-ns-asyncapi-2": "3.2.0",
263
+ "@speclynx/apidom-ns-json-schema-2020-12": "3.2.0",
264
+ "@speclynx/apidom-ns-openapi-2": "3.2.0",
265
+ "@speclynx/apidom-ns-openapi-3-0": "3.2.0",
266
+ "@speclynx/apidom-ns-openapi-3-1": "3.2.0",
267
+ "@speclynx/apidom-parser-adapter-arazzo-json-1": "3.2.0",
268
+ "@speclynx/apidom-parser-adapter-arazzo-yaml-1": "3.2.0",
269
+ "@speclynx/apidom-parser-adapter-asyncapi-json-2": "3.2.0",
270
+ "@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "3.2.0",
271
+ "@speclynx/apidom-parser-adapter-json": "3.2.0",
272
+ "@speclynx/apidom-parser-adapter-openapi-json-2": "3.2.0",
273
+ "@speclynx/apidom-parser-adapter-openapi-json-3-0": "3.2.0",
274
+ "@speclynx/apidom-parser-adapter-openapi-json-3-1": "3.2.0",
275
+ "@speclynx/apidom-parser-adapter-openapi-yaml-2": "3.2.0",
276
+ "@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "3.2.0",
277
+ "@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "3.2.0",
278
+ "@speclynx/apidom-parser-adapter-yaml-1-2": "3.2.0",
279
+ "@speclynx/apidom-traverse": "3.2.0",
257
280
  "@swaggerexpert/arazzo-runtime-expression": "^2.0.3",
258
281
  "axios": "^1.13.5",
259
282
  "picomatch": "^4.0.3",
@@ -275,5 +298,5 @@
275
298
  "README.md",
276
299
  "CHANGELOG.md"
277
300
  ],
278
- "gitHead": "cb94ec84ea789d121801065876e7a91799a363d0"
301
+ "gitHead": "eeb2f01cd34fb0a95617f02bc1b6683aefc028f2"
279
302
  }
@@ -10,6 +10,7 @@ var _File = _interopRequireDefault(require("../File.cjs"));
10
10
  var plugins = _interopRequireWildcard(require("../util/plugins.cjs"));
11
11
  var _UnmatchedDereferenceStrategyError = _interopRequireDefault(require("../errors/UnmatchedDereferenceStrategyError.cjs"));
12
12
  var _DereferenceError = _interopRequireDefault(require("../errors/DereferenceError.cjs"));
13
+ var _UnresolvableReferenceError = _interopRequireDefault(require("../errors/UnresolvableReferenceError.cjs"));
13
14
  var _index = _interopRequireDefault(require("../parse/index.cjs"));
14
15
  var _util = require("../options/util.cjs");
15
16
  var url = _interopRequireWildcard(require("../util/url.cjs"));
@@ -46,6 +47,9 @@ const dereferenceApiDOM = async (element, options) => {
46
47
  // unwrap the element from ParseResult assuming first element is the actual result
47
48
  return surrogateWrapping ? result.get(0) : result;
48
49
  } catch (error) {
50
+ if (error instanceof _UnresolvableReferenceError.default) {
51
+ throw error;
52
+ }
49
53
  throw new _DereferenceError.default(`Error while dereferencing file "${file.uri}"`, {
50
54
  cause: error
51
55
  });
@@ -4,6 +4,7 @@ import File from "../File.mjs";
4
4
  import * as plugins from "../util/plugins.mjs";
5
5
  import UnmatchedDereferenceStrategyError from "../errors/UnmatchedDereferenceStrategyError.mjs";
6
6
  import DereferenceError from "../errors/DereferenceError.mjs";
7
+ import UnresolvableReferenceError from "../errors/UnresolvableReferenceError.mjs";
7
8
  import parse from "../parse/index.mjs";
8
9
  import { merge as mergeOptions } from "../options/util.mjs";
9
10
  import * as url from "../util/url.mjs";
@@ -40,6 +41,9 @@ export const dereferenceApiDOM = async (element, options) => {
40
41
  // unwrap the element from ParseResult assuming first element is the actual result
41
42
  return surrogateWrapping ? result.get(0) : result;
42
43
  } catch (error) {
44
+ if (error instanceof UnresolvableReferenceError) {
45
+ throw error;
46
+ }
43
47
  throw new DereferenceError(`Error while dereferencing file "${file.uri}"`, {
44
48
  cause: error
45
49
  });
@@ -11,6 +11,7 @@ var _apidomCore = require("@speclynx/apidom-core");
11
11
  var _apidomTraverse = require("@speclynx/apidom-traverse");
12
12
  var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
13
13
  var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
14
+ var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
14
15
  var url = _interopRequireWildcard(require("../../../util/url.cjs"));
15
16
  var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
16
17
  var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
@@ -48,7 +49,10 @@ class ApiDOMDereferenceVisitor {
48
49
  async toReference(uri) {
49
50
  // detect maximum depth of resolution
50
51
  if (this.reference.depth >= this.options.resolve.maxDepth) {
51
- throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);
52
+ throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
53
+ maxDepth: this.options.resolve.maxDepth,
54
+ uri: this.reference.uri
55
+ });
52
56
  }
53
57
  const baseURI = this.toBaseURI(uri);
54
58
  const {
@@ -85,6 +89,72 @@ class ApiDOMDereferenceVisitor {
85
89
  }
86
90
  return mutableReference;
87
91
  }
92
+
93
+ /**
94
+ * Handles an error according to the continueOnError option.
95
+ *
96
+ * For new errors: wraps in UnresolvableReferenceError with structured context.
97
+ * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
98
+ *
99
+ * Inner/intermediate visitors always throw to let the trace accumulate.
100
+ * Only the entry document visitor respects continueOnError (callback/swallow/throw).
101
+ */
102
+ handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
103
+ const {
104
+ continueOnError
105
+ } = this.options.dereference;
106
+ const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
107
+ const uri = this.reference.uri;
108
+ const type = referencingElement.element;
109
+ let location;
110
+ (0, _apidomTraverse.traverse)(this.reference.value.result, {
111
+ enter(p) {
112
+ if (p.node === referencingElement) {
113
+ location = p.formatPath();
114
+ p.stop();
115
+ }
116
+ }
117
+ });
118
+ location ??= visitorPath.formatPath();
119
+ const codeFrame = (0, _apidomCore.toYAML)(referencingElement);
120
+ const hop = {
121
+ uri,
122
+ type,
123
+ refFieldName,
124
+ refFieldValue,
125
+ location,
126
+ codeFrame
127
+ };
128
+ let unresolvedError;
129
+ if (error instanceof _UnresolvableReferenceError.default) {
130
+ const refBaseURI = this.toBaseURI(refFieldValue);
131
+ const fragment = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refFieldValue);
132
+ if (fragment) {
133
+ if (refBaseURI === error.uri && error.location) {
134
+ error.location = fragment + error.location;
135
+ }
136
+ for (const h of error.trace) {
137
+ if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
138
+ }
139
+ }
140
+ // @ts-ignore
141
+ error.trace = [hop, ...error.trace];
142
+ unresolvedError = error;
143
+ } else {
144
+ unresolvedError = new _UnresolvableReferenceError.default(message, {
145
+ cause: error,
146
+ type,
147
+ uri,
148
+ location,
149
+ codeFrame,
150
+ refFieldName,
151
+ refFieldValue,
152
+ trace: []
153
+ });
154
+ }
155
+ if (!isEntryDocument || continueOnError === false) throw unresolvedError;
156
+ if (typeof continueOnError === 'function') continueOnError(unresolvedError);
157
+ }
88
158
  async RefElement(path) {
89
159
  const refElement = path.node;
90
160
  const {
@@ -110,69 +180,79 @@ class ApiDOMDereferenceVisitor {
110
180
  path.skip();
111
181
  return;
112
182
  }
113
- const reference = await this.toReference(refNormalizedURI);
114
- const refBaseURI = url.resolve(retrievalURI, refNormalizedURI);
115
- const elementID = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refBaseURI);
116
- let referencedElement = (0, _elementId.evaluate)(elementID, reference.value.result);
117
- if (!(0, _apidomDatamodel.isElement)(referencedElement)) {
118
- throw new _apidomError.ApiDOMError(`Referenced element with id="${elementID}" was not found`);
119
- }
120
- if (refElement === referencedElement) {
121
- throw new _apidomError.ApiDOMError('RefElement cannot reference itself');
122
- }
123
- if ((0, _apidomDatamodel.isRefElement)(referencedElement)) {
124
- throw new _apidomError.ApiDOMError('RefElement cannot reference another RefElement');
125
- }
126
- if (isExternalReference) {
127
- // dive deep into the fragment
128
- const visitor = new ApiDOMDereferenceVisitor({
129
- reference,
130
- options: this.options
131
- });
132
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
133
- mutable: true
134
- });
135
- }
136
-
137
- /**
138
- * When path is used, it references the given property of the referenced element.
139
- * Valid paths are: 'element', 'content', 'meta', 'attributes'.
140
- */
141
- const referencedElementPath = (0, _apidomCore.toValue)(refElement.path);
142
- if (referencedElementPath !== 'element' && (0, _apidomDatamodel.isElement)(referencedElement)) {
143
- referencedElement = (0, _apidomDatamodel.refract)(referencedElement[referencedElementPath]);
144
- }
183
+ try {
184
+ const reference = await this.toReference(refNormalizedURI);
185
+ const refBaseURI = url.resolve(retrievalURI, refNormalizedURI);
186
+ const elementID = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refBaseURI);
187
+ let referencedElement = (0, _elementId.evaluate)(elementID, reference.value.result);
188
+ if (!(0, _apidomDatamodel.isElement)(referencedElement)) {
189
+ throw new _apidomError.ApiDOMStructuredError(`Referenced element with id="${elementID}" was not found`, {
190
+ elementID
191
+ });
192
+ }
193
+ if (refElement === referencedElement) {
194
+ throw new _apidomError.ApiDOMStructuredError(`RefElement with id="${elementID}" cannot reference itself`, {
195
+ elementID
196
+ });
197
+ }
198
+ if ((0, _apidomDatamodel.isRefElement)(referencedElement)) {
199
+ throw new _apidomError.ApiDOMStructuredError(`RefElement with id="${elementID}" cannot reference another RefElement`, {
200
+ elementID
201
+ });
202
+ }
203
+ if (isExternalReference) {
204
+ // dive deep into the fragment
205
+ const visitor = new ApiDOMDereferenceVisitor({
206
+ reference,
207
+ options: this.options
208
+ });
209
+ referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
210
+ mutable: true
211
+ });
212
+ }
145
213
 
146
- /**
147
- * Transclusion of a Ref Element SHALL be defined in the if/else block below.
148
- */
149
- // ancestors[0] is the grandparent (nearest ancestor from getAncestorNodes())
150
- const grandparent = ancestors[0];
151
- if ((0, _apidomDatamodel.isObjectElement)(referencedElement) && (0, _apidomDatamodel.isObjectElement)(grandparent) && Array.isArray(parent) && typeof key === 'number') {
152
- /**
153
- * If the Ref Element is held by an Object Element and references an Object Element,
154
- * its content entries SHALL be inserted in place of the Ref Element.
155
- */
156
- parent.splice(key, 1, ...referencedElement.content);
157
- } else if ((0, _apidomDatamodel.isArrayElement)(referencedElement) && Array.isArray(parent) && typeof key === 'number') {
158
214
  /**
159
- * If the Ref Element is held by an Array Element and references an Array Element,
160
- * its content entries SHALL be inserted in place of the Ref Element.
215
+ * When path is used, it references the given property of the referenced element.
216
+ * Valid paths are: 'element', 'content', 'meta', 'attributes'.
161
217
  */
162
- parent.splice(key, 1, ...referencedElement.content);
163
- } else if ((0, _apidomDatamodel.isMemberElement)(parent)) {
164
- /**
165
- * The Ref Element is substituted by the Element it references.
166
- */
167
- parent.value = referencedElement;
168
- } else if (Array.isArray(parent)) {
218
+ const referencedElementPath = (0, _apidomCore.toValue)(refElement.path);
219
+ if (referencedElementPath !== 'element' && (0, _apidomDatamodel.isElement)(referencedElement)) {
220
+ referencedElement = (0, _apidomDatamodel.refract)(referencedElement[referencedElementPath]);
221
+ }
222
+
169
223
  /**
170
- * The Ref Element is substituted by the Element it references.
224
+ * Transclusion of a Ref Element SHALL be defined in the if/else block below.
171
225
  */
172
- parent[key] = referencedElement;
173
- }
174
- if (!parent) {
175
- path.replaceWith(referencedElement);
226
+ // ancestors[0] is the grandparent (nearest ancestor from getAncestorNodes())
227
+ const grandparent = ancestors[0];
228
+ if ((0, _apidomDatamodel.isObjectElement)(referencedElement) && (0, _apidomDatamodel.isObjectElement)(grandparent) && Array.isArray(parent) && typeof key === 'number') {
229
+ /**
230
+ * If the Ref Element is held by an Object Element and references an Object Element,
231
+ * its content entries SHALL be inserted in place of the Ref Element.
232
+ */
233
+ parent.splice(key, 1, ...referencedElement.content);
234
+ } else if ((0, _apidomDatamodel.isArrayElement)(referencedElement) && Array.isArray(parent) && typeof key === 'number') {
235
+ /**
236
+ * If the Ref Element is held by an Array Element and references an Array Element,
237
+ * its content entries SHALL be inserted in place of the Ref Element.
238
+ */
239
+ parent.splice(key, 1, ...referencedElement.content);
240
+ } else if ((0, _apidomDatamodel.isMemberElement)(parent)) {
241
+ /**
242
+ * The Ref Element is substituted by the Element it references.
243
+ */
244
+ parent.value = referencedElement;
245
+ } else if (Array.isArray(parent)) {
246
+ /**
247
+ * The Ref Element is substituted by the Element it references.
248
+ */
249
+ parent[key] = referencedElement;
250
+ }
251
+ if (!parent) {
252
+ path.replaceWith(referencedElement);
253
+ }
254
+ } catch (error) {
255
+ this.handleError(`Error while dereferencing Ref Element. Cannot resolve ref "${refURI}": ${error.message}`, error, refElement, null, refURI, path);
176
256
  }
177
257
  }
178
258
  }
@@ -1,10 +1,11 @@
1
1
  import { propEq } from 'ramda';
2
- import { ApiDOMError } from '@speclynx/apidom-error';
2
+ import { ApiDOMStructuredError } from '@speclynx/apidom-error';
3
3
  import { isElement, isMemberElement, isArrayElement, isObjectElement, isRefElement, refract, cloneDeep } from '@speclynx/apidom-datamodel';
4
- import { toValue } from '@speclynx/apidom-core';
5
- import { traverseAsync } from '@speclynx/apidom-traverse';
4
+ import { toValue, toYAML } from '@speclynx/apidom-core';
5
+ import { traverse, traverseAsync } from '@speclynx/apidom-traverse';
6
6
  import { URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
7
7
  import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
8
+ import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
8
9
  import * as url from "../../../util/url.mjs";
9
10
  import parse from "../../../parse/index.mjs";
10
11
  import Reference from "../../../Reference.mjs";
@@ -41,7 +42,10 @@ class ApiDOMDereferenceVisitor {
41
42
  async toReference(uri) {
42
43
  // detect maximum depth of resolution
43
44
  if (this.reference.depth >= this.options.resolve.maxDepth) {
44
- throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);
45
+ throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
46
+ maxDepth: this.options.resolve.maxDepth,
47
+ uri: this.reference.uri
48
+ });
45
49
  }
46
50
  const baseURI = this.toBaseURI(uri);
47
51
  const {
@@ -78,6 +82,72 @@ class ApiDOMDereferenceVisitor {
78
82
  }
79
83
  return mutableReference;
80
84
  }
85
+
86
+ /**
87
+ * Handles an error according to the continueOnError option.
88
+ *
89
+ * For new errors: wraps in UnresolvableReferenceError with structured context.
90
+ * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
91
+ *
92
+ * Inner/intermediate visitors always throw to let the trace accumulate.
93
+ * Only the entry document visitor respects continueOnError (callback/swallow/throw).
94
+ */
95
+ handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
96
+ const {
97
+ continueOnError
98
+ } = this.options.dereference;
99
+ const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
100
+ const uri = this.reference.uri;
101
+ const type = referencingElement.element;
102
+ let location;
103
+ traverse(this.reference.value.result, {
104
+ enter(p) {
105
+ if (p.node === referencingElement) {
106
+ location = p.formatPath();
107
+ p.stop();
108
+ }
109
+ }
110
+ });
111
+ location ??= visitorPath.formatPath();
112
+ const codeFrame = toYAML(referencingElement);
113
+ const hop = {
114
+ uri,
115
+ type,
116
+ refFieldName,
117
+ refFieldValue,
118
+ location,
119
+ codeFrame
120
+ };
121
+ let unresolvedError;
122
+ if (error instanceof UnresolvableReferenceError) {
123
+ const refBaseURI = this.toBaseURI(refFieldValue);
124
+ const fragment = URIFragmentIdentifier.fromURIReference(refFieldValue);
125
+ if (fragment) {
126
+ if (refBaseURI === error.uri && error.location) {
127
+ error.location = fragment + error.location;
128
+ }
129
+ for (const h of error.trace) {
130
+ if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
131
+ }
132
+ }
133
+ // @ts-ignore
134
+ error.trace = [hop, ...error.trace];
135
+ unresolvedError = error;
136
+ } else {
137
+ unresolvedError = new UnresolvableReferenceError(message, {
138
+ cause: error,
139
+ type,
140
+ uri,
141
+ location,
142
+ codeFrame,
143
+ refFieldName,
144
+ refFieldValue,
145
+ trace: []
146
+ });
147
+ }
148
+ if (!isEntryDocument || continueOnError === false) throw unresolvedError;
149
+ if (typeof continueOnError === 'function') continueOnError(unresolvedError);
150
+ }
81
151
  async RefElement(path) {
82
152
  const refElement = path.node;
83
153
  const {
@@ -103,69 +173,79 @@ class ApiDOMDereferenceVisitor {
103
173
  path.skip();
104
174
  return;
105
175
  }
106
- const reference = await this.toReference(refNormalizedURI);
107
- const refBaseURI = url.resolve(retrievalURI, refNormalizedURI);
108
- const elementID = URIFragmentIdentifier.fromURIReference(refBaseURI);
109
- let referencedElement = evaluate(elementID, reference.value.result);
110
- if (!isElement(referencedElement)) {
111
- throw new ApiDOMError(`Referenced element with id="${elementID}" was not found`);
112
- }
113
- if (refElement === referencedElement) {
114
- throw new ApiDOMError('RefElement cannot reference itself');
115
- }
116
- if (isRefElement(referencedElement)) {
117
- throw new ApiDOMError('RefElement cannot reference another RefElement');
118
- }
119
- if (isExternalReference) {
120
- // dive deep into the fragment
121
- const visitor = new ApiDOMDereferenceVisitor({
122
- reference,
123
- options: this.options
124
- });
125
- referencedElement = await traverseAsync(referencedElement, visitor, {
126
- mutable: true
127
- });
128
- }
129
-
130
- /**
131
- * When path is used, it references the given property of the referenced element.
132
- * Valid paths are: 'element', 'content', 'meta', 'attributes'.
133
- */
134
- const referencedElementPath = toValue(refElement.path);
135
- if (referencedElementPath !== 'element' && isElement(referencedElement)) {
136
- referencedElement = refract(referencedElement[referencedElementPath]);
137
- }
176
+ try {
177
+ const reference = await this.toReference(refNormalizedURI);
178
+ const refBaseURI = url.resolve(retrievalURI, refNormalizedURI);
179
+ const elementID = URIFragmentIdentifier.fromURIReference(refBaseURI);
180
+ let referencedElement = evaluate(elementID, reference.value.result);
181
+ if (!isElement(referencedElement)) {
182
+ throw new ApiDOMStructuredError(`Referenced element with id="${elementID}" was not found`, {
183
+ elementID
184
+ });
185
+ }
186
+ if (refElement === referencedElement) {
187
+ throw new ApiDOMStructuredError(`RefElement with id="${elementID}" cannot reference itself`, {
188
+ elementID
189
+ });
190
+ }
191
+ if (isRefElement(referencedElement)) {
192
+ throw new ApiDOMStructuredError(`RefElement with id="${elementID}" cannot reference another RefElement`, {
193
+ elementID
194
+ });
195
+ }
196
+ if (isExternalReference) {
197
+ // dive deep into the fragment
198
+ const visitor = new ApiDOMDereferenceVisitor({
199
+ reference,
200
+ options: this.options
201
+ });
202
+ referencedElement = await traverseAsync(referencedElement, visitor, {
203
+ mutable: true
204
+ });
205
+ }
138
206
 
139
- /**
140
- * Transclusion of a Ref Element SHALL be defined in the if/else block below.
141
- */
142
- // ancestors[0] is the grandparent (nearest ancestor from getAncestorNodes())
143
- const grandparent = ancestors[0];
144
- if (isObjectElement(referencedElement) && isObjectElement(grandparent) && Array.isArray(parent) && typeof key === 'number') {
145
- /**
146
- * If the Ref Element is held by an Object Element and references an Object Element,
147
- * its content entries SHALL be inserted in place of the Ref Element.
148
- */
149
- parent.splice(key, 1, ...referencedElement.content);
150
- } else if (isArrayElement(referencedElement) && Array.isArray(parent) && typeof key === 'number') {
151
207
  /**
152
- * If the Ref Element is held by an Array Element and references an Array Element,
153
- * its content entries SHALL be inserted in place of the Ref Element.
208
+ * When path is used, it references the given property of the referenced element.
209
+ * Valid paths are: 'element', 'content', 'meta', 'attributes'.
154
210
  */
155
- parent.splice(key, 1, ...referencedElement.content);
156
- } else if (isMemberElement(parent)) {
157
- /**
158
- * The Ref Element is substituted by the Element it references.
159
- */
160
- parent.value = referencedElement;
161
- } else if (Array.isArray(parent)) {
211
+ const referencedElementPath = toValue(refElement.path);
212
+ if (referencedElementPath !== 'element' && isElement(referencedElement)) {
213
+ referencedElement = refract(referencedElement[referencedElementPath]);
214
+ }
215
+
162
216
  /**
163
- * The Ref Element is substituted by the Element it references.
217
+ * Transclusion of a Ref Element SHALL be defined in the if/else block below.
164
218
  */
165
- parent[key] = referencedElement;
166
- }
167
- if (!parent) {
168
- path.replaceWith(referencedElement);
219
+ // ancestors[0] is the grandparent (nearest ancestor from getAncestorNodes())
220
+ const grandparent = ancestors[0];
221
+ if (isObjectElement(referencedElement) && isObjectElement(grandparent) && Array.isArray(parent) && typeof key === 'number') {
222
+ /**
223
+ * If the Ref Element is held by an Object Element and references an Object Element,
224
+ * its content entries SHALL be inserted in place of the Ref Element.
225
+ */
226
+ parent.splice(key, 1, ...referencedElement.content);
227
+ } else if (isArrayElement(referencedElement) && Array.isArray(parent) && typeof key === 'number') {
228
+ /**
229
+ * If the Ref Element is held by an Array Element and references an Array Element,
230
+ * its content entries SHALL be inserted in place of the Ref Element.
231
+ */
232
+ parent.splice(key, 1, ...referencedElement.content);
233
+ } else if (isMemberElement(parent)) {
234
+ /**
235
+ * The Ref Element is substituted by the Element it references.
236
+ */
237
+ parent.value = referencedElement;
238
+ } else if (Array.isArray(parent)) {
239
+ /**
240
+ * The Ref Element is substituted by the Element it references.
241
+ */
242
+ parent[key] = referencedElement;
243
+ }
244
+ if (!parent) {
245
+ path.replaceWith(referencedElement);
246
+ }
247
+ } catch (error) {
248
+ this.handleError(`Error while dereferencing Ref Element. Cannot resolve ref "${refURI}": ${error.message}`, error, refElement, null, refURI, path);
169
249
  }
170
250
  }
171
251
  }
@@ -1,12 +1,11 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
4
- var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
5
4
  exports.__esModule = true;
6
5
  exports.resolveSchema$refField = exports.resolveSchema$idField = exports.maybeRefractToJSONSchemaElement = exports.default = void 0;
7
6
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
8
7
  var _apidomTraverse = require("@speclynx/apidom-traverse");
9
- var _apidomNsArazzo = _interopRequireWildcard(require("@speclynx/apidom-ns-arazzo-1"));
8
+ var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
10
9
  var _DereferenceStrategy = _interopRequireDefault(require("../DereferenceStrategy.cjs"));
11
10
  var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
12
11
  var _ReferenceSet = _interopRequireDefault(require("../../../ReferenceSet.cjs"));
@@ -42,7 +41,6 @@ class Arazzo1DereferenceStrategy extends _DereferenceStrategy.default {
42
41
  return (0, _apidomNsArazzo.isArazzoSpecification1Element)(file.parseResult?.result);
43
42
  }
44
43
  async dereference(file, options) {
45
- const namespace = new _apidomDatamodel.Namespace().use(_apidomNsArazzo.default);
46
44
  const immutableRefSet = options.dereference.refSet ?? new _ReferenceSet.default();
47
45
  const mutableRefSet = new _ReferenceSet.default();
48
46
  let refSet = immutableRefSet;
@@ -72,7 +70,6 @@ class Arazzo1DereferenceStrategy extends _DereferenceStrategy.default {
72
70
  }
73
71
  const visitor = new _visitor.default({
74
72
  reference,
75
- namespace,
76
73
  options
77
74
  });
78
75
  const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
@@ -1,6 +1,6 @@
1
- import { Namespace, cloneDeep } from '@speclynx/apidom-datamodel';
1
+ import { cloneDeep } from '@speclynx/apidom-datamodel';
2
2
  import { traverseAsync } from '@speclynx/apidom-traverse';
3
- import arazzo1Namespace, { isArazzoSpecification1Element, mediaTypes } from '@speclynx/apidom-ns-arazzo-1';
3
+ import { isArazzoSpecification1Element, mediaTypes } from '@speclynx/apidom-ns-arazzo-1';
4
4
  import DereferenceStrategy from "../DereferenceStrategy.mjs";
5
5
  import Reference from "../../../Reference.mjs";
6
6
  import ReferenceSet from "../../../ReferenceSet.mjs";
@@ -29,7 +29,6 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
29
29
  return isArazzoSpecification1Element(file.parseResult?.result);
30
30
  }
31
31
  async dereference(file, options) {
32
- const namespace = new Namespace().use(arazzo1Namespace);
33
32
  const immutableRefSet = options.dereference.refSet ?? new ReferenceSet();
34
33
  const mutableRefSet = new ReferenceSet();
35
34
  let refSet = immutableRefSet;
@@ -59,7 +58,6 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
59
58
  }
60
59
  const visitor = new Arazzo1DereferenceVisitor({
61
60
  reference,
62
- namespace,
63
61
  options
64
62
  });
65
63
  const dereferencedElement = await traverseAsync(refSet.rootRef.value, visitor, {