@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
@@ -19,240 +19,161 @@ var _uri = require("./selectors/uri.cjs");
19
19
  var _jsonPointer = require("../openapi-3-1/selectors/json-pointer.cjs");
20
20
  var _util = require("../openapi-3-1/util.cjs");
21
21
  var _util2 = require("./util.cjs");
22
- var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
23
- var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
24
- var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
22
+ var _DereferenceVisitor = _interopRequireDefault(require("../DereferenceVisitor.cjs"));
25
23
  var url = _interopRequireWildcard(require("../../../util/url.cjs"));
26
- var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
27
- var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
28
24
  var _File = _interopRequireDefault(require("../../../File.cjs"));
29
- var _util3 = require("../../util.cjs");
30
25
  var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../errors/EvaluationJsonSchemaUriError.cjs"));
31
26
  /**
32
- * @public
27
+ * Type a JSON Schema `$ref` expects its target to have.
33
28
  */
29
+ const JSON_SCHEMA_TYPE = 'JSONSchema';
34
30
 
35
31
  /**
36
- * @public
32
+ * Context of a source being dereferenced: the `$id`s of the JSON Schema elements enclosing it.
37
33
  */
38
- class Arazzo1DereferenceVisitor {
39
- indirections;
40
- reference;
41
34
 
42
- /**
43
- * Base URI of the current document for resolving relative references.
44
- * Honors the Arazzo `$self` field when present, otherwise falls back
45
- * to the retrieval URI (`this.reference.uri`).
46
- */
47
- baseURI;
48
- options;
35
+ /**
36
+ * @public
37
+ */
49
38
 
39
+ /**
40
+ * Dereferences an Arazzo document. Reusable Objects expect the type of their
41
+ * `referenced-element` meta, JSON Schema `$ref`s expect a JSON Schema.
42
+ *
43
+ * @public
44
+ */
45
+ class Arazzo1DereferenceVisitor extends _DereferenceVisitor.default {
50
46
  /**
51
- * Index of `$id`-bearing schemas per document, shared across nested visitors
52
- * of a single dereference run so it never outlives mutations of the documents.
47
+ * Index of `$id`-bearing schemas per document, shared across the visitors
48
+ * of a single dereference run.
53
49
  */
54
50
  schema$idIndex;
55
- visited;
56
51
 
57
52
  /**
58
53
  * `$id`s of the JSON Schema elements enclosing the traversal root within its
59
54
  * document (outermost first). Empty when the root is the document itself; a
60
- * nested visitor traversing a fragment is seeded with the `$id`s of the
61
- * schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
62
- * against the same base as they would from the document root.
55
+ * visitor traversing a fragment is seeded with the `$id`s of the schemas the
56
+ * fragment sits under, so the `$ref`s and `$id`s within resolve against the
57
+ * same base as they would from the document root.
63
58
  */
64
59
  ancestorSchema$ids;
65
-
66
- /**
67
- * Tracks element ancestors across dive-deep traversal boundaries.
68
- * Used for cycle detection: if a referenced element is found in
69
- * the ancestor lineage, a circular reference is detected.
70
- */
71
- ancestors;
72
60
  constructor({
73
61
  reference,
74
62
  options,
75
- indirections = [],
76
63
  schema$idIndex = new WeakMap(),
77
- ancestors = new _util3.AncestorLineage(),
78
- visited = new WeakSet(),
79
64
  ancestorSchema$ids = []
80
65
  }) {
81
- this.indirections = indirections;
82
- this.reference = reference;
83
- this.baseURI = (0, _util2.resolveArazzo$selfField)(reference.uri, reference.value?.result);
84
- this.options = options;
66
+ super({
67
+ reference,
68
+ options
69
+ });
85
70
  this.schema$idIndex = schema$idIndex;
86
- this.ancestors = new _util3.AncestorLineage(...ancestors);
87
- this.visited = visited;
88
71
  this.ancestorSchema$ids = ancestorSchema$ids;
89
72
  }
90
- toAncestorLineage(path) {
91
- const ancestorNodes = path.getAncestorNodes();
92
- const directAncestors = new Set(ancestorNodes.filter(_apidomDatamodel.isElement));
93
- const ancestorsLineage = new _util3.AncestorLineage(...this.ancestors, directAncestors);
94
- return [ancestorsLineage, directAncestors];
73
+ get strategyName() {
74
+ return 'arazzo-1';
95
75
  }
96
- toBaseURI(uri) {
97
- return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
76
+ createVisitor(reference, {
77
+ ancestorSchema$ids
78
+ }) {
79
+ return new Arazzo1DereferenceVisitor({
80
+ reference,
81
+ options: this.options,
82
+ schema$idIndex: this.schema$idIndex,
83
+ ancestorSchema$ids
84
+ });
98
85
  }
99
- async toReference(uri) {
100
- // detect maximum depth of resolution
101
- if (this.reference.depth >= this.options.resolve.maxDepth) {
102
- throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
103
- maxDepth: this.options.resolve.maxDepth,
104
- uri: this.reference.uri
105
- });
106
- }
107
- const baseURI = this.toBaseURI(uri);
108
- const {
109
- refSet
110
- } = this.reference;
111
-
112
- // we've already processed this Reference in past
113
- if (refSet.has(baseURI)) {
114
- return refSet.find((0, _ramda.propEq)(baseURI, 'uri'));
115
- }
116
-
117
- // identity-based referencing: URI matches `$self` of an already processed Arazzo document
118
- const referenceBy$self = refSet.find((0, _util2.identifiedBy$self)(baseURI));
119
- if (referenceBy$self !== undefined) {
120
- return referenceBy$self;
121
- }
122
- const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
123
- ...this.options,
124
- parse: {
125
- ...this.options.parse,
126
- mediaType: 'text/plain'
127
- }
86
+ refractTo(element, type) {
87
+ return type === JSON_SCHEMA_TYPE ? (0, _apidomNsArazzo.refractJSONSchema)(element) : (0, _apidomNsArazzo.refract)(element, {
88
+ element: type
128
89
  });
90
+ }
91
+ isOfType(element, type) {
92
+ return type === JSON_SCHEMA_TYPE ? (0, _apidomNsArazzo.isJSONSchemaElement)(element) || (0, _apidomNsArazzo.isBooleanJSONSchemaElement)(element) : element.element === type;
93
+ }
129
94
 
130
- // register new mutable reference with a refSet
131
- //
132
- // NOTE(known limitation): the mutable reference is mutated in place during traversal
133
- // (via `{ mutable: true }`). When an external document evaluates a JSON pointer back
134
- // into this document, it may receive an already-resolved element instead of the original
135
- // $ref. That resolved element was produced using the entry document's resolution context
136
- // (ancestors, indirections), which may differ from the external document's context.
137
- // This can affect cycle detection in rare cross-document circular reference patterns.
138
- //
139
- // Remediation: evaluate JSON pointers against the immutable (original) parse tree
140
- // instead of the mutable working copy. The `immutable://` reference below preserves
141
- // the original tree and could be used for pointer evaluation, ensuring every resolution
142
- // context always sees raw, unresolved elements and processes them with its own
143
- // ancestors/indirections. The trade-off is that elements referenced by multiple
144
- // documents would be resolved once per context instead of being reused.
145
- const mutableReference = new _Reference.default({
146
- uri: baseURI,
147
- value: this.options.dereference.immutable ? (0, _apidomDatamodel.cloneDeep)(parseResult) : parseResult,
148
- depth: this.reference.depth + 1
149
- });
150
- refSet.add(mutableReference);
151
- if (this.options.dereference.immutable) {
152
- // register new immutable reference with a refSet
153
- const immutableReference = new _Reference.default({
154
- uri: `immutable://${baseURI}`,
155
- value: parseResult,
156
- depth: this.reference.depth + 1
157
- });
158
- refSet.add(immutableReference);
159
- }
160
- return mutableReference;
95
+ /**
96
+ * Honors the Arazzo `$self` field when present, otherwise falls back to the retrieval URI.
97
+ */
98
+ toDocumentBaseURI(reference) {
99
+ return (0, _util2.resolveArazzo$selfField)(reference.uri, reference.value?.result);
161
100
  }
162
101
 
163
102
  /**
164
- * Handles an error according to the continueOnError option.
165
- *
166
- * For new errors: wraps in UnresolvableReferenceError with structured context
167
- * (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
168
- * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
169
- *
170
- * Inner/intermediate visitors always throw to let the trace accumulate.
171
- * Only the entry document visitor respects continueOnError (callback/swallow/throw).
103
+ * Identity-based referencing: URI matches `$self` of an already processed Arazzo document.
172
104
  */
173
- handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
174
- const {
175
- continueOnError
176
- } = this.options.dereference;
177
- const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
178
- const uri = this.reference.uri;
179
- const type = referencingElement.element;
180
- const codeFrame = (0, _apidomCore.toYAML)(referencingElement);
105
+ findReferenceByIdentity(uri) {
106
+ return this.reference.refSet?.find((0, _util2.identifiedBy$self)(uri));
107
+ }
181
108
 
182
- // find element location by identity in the document tree.
183
- // guarded: this.reference.value may not be a ParseResultElement or may lack a result.
184
- // falls back to visitorPath which may produce an incomplete path when
185
- // dereferenceApiDOM is called with a fragment (cloneShallow creates a new root identity).
186
- let location;
187
- const root = this.reference.value.result;
188
- if ((0, _apidomDatamodel.isElement)(root)) {
189
- (0, _apidomTraverse.traverse)(root, {
190
- enter(p) {
191
- if (p.node === referencingElement) {
192
- location = p.formatPath();
193
- p.stop();
194
- }
195
- }
196
- });
109
+ /**
110
+ * A document as JSON Schema for locating schemas within it: a generic document is
111
+ * refracted to JSON Schema once per dereference run, a semantic one is used as it is.
112
+ */
113
+ toJSONSchemaDocument(document) {
114
+ if (!(0, _apidomDatamodel.isPrimitiveElement)(document)) return document;
115
+ let byType = this.run.refracted.get(document);
116
+ if (byType === undefined) {
117
+ byType = new Map();
118
+ this.run.refracted.set(document, byType);
119
+ }
120
+ let refracted = byType.get(JSON_SCHEMA_TYPE);
121
+ if (refracted === undefined) {
122
+ refracted = (0, _apidomNsArazzo.refractJSONSchema)(document);
123
+ byType.set(JSON_SCHEMA_TYPE, refracted);
197
124
  }
198
- location ??= visitorPath.formatPath();
199
- const hop = {
200
- uri,
201
- type,
202
- refFieldName,
203
- refFieldValue,
204
- location,
205
- codeFrame
206
- };
125
+ return refracted;
126
+ }
207
127
 
208
- // enrich existing error from nested visitor or create new one
209
- let unresolvedError;
210
- if (error instanceof _UnresolvableReferenceError.default) {
211
- // prefix relative locations for entries belonging to the referenced document
212
- const refBaseURI = this.toBaseURI(refFieldValue);
213
- const fragment = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refFieldValue);
214
- if (fragment) {
215
- if (refBaseURI === error.uri && error.location) {
216
- error.location = fragment + error.location;
217
- }
218
- for (const h of error.trace) {
219
- if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
128
+ /**
129
+ * Indexes the `$id`-bearing schemas of a document for the run. Transcluded elements aren't
130
+ * indexed: they're copies of structures indexed where they're defined,
131
+ * and in a document dereferenced in place their shared structure would be walked once per path.
132
+ */
133
+ index$ids(element) {
134
+ if (this.schema$idIndex.has(element)) return;
135
+ const entries = [];
136
+ (0, _apidomTraverse.traverse)(element, {
137
+ enter: path => {
138
+ if (this.isTranscluded(path.node)) {
139
+ path.skip();
140
+ } else if ((0, _util.isJSONSchemaElementWith$id)(path.node)) {
141
+ entries.push({
142
+ schema: path.node,
143
+ $ids: (0, _util.collectSchema$ids)(path)
144
+ });
220
145
  }
221
146
  }
222
- // @ts-ignore
223
- error.trace = [hop, ...error.trace];
224
- unresolvedError = error;
225
- } else {
226
- unresolvedError = new _UnresolvableReferenceError.default(message, {
227
- cause: error,
228
- type,
229
- uri,
230
- location,
231
- codeFrame,
232
- refFieldName,
233
- refFieldValue,
234
- trace: []
235
- });
147
+ });
148
+ this.schema$idIndex.set(element, entries);
149
+ }
150
+ enter(path) {
151
+ if (this.isTranscluded(path.node)) {
152
+ path.skip();
153
+ return;
154
+ }
155
+ // a Source Description parsed into the document has its own components: it's dereferenced
156
+ // in its own context by the strategy, never by the document referencing it
157
+ if (path.parentPath !== null && (0, _apidomDatamodel.isParseResultElement)(path.node) && (0, _apidomDatamodel.includesClasses)(path.node, ['source-description'])) {
158
+ path.skip();
236
159
  }
237
- if (!isEntryDocument || continueOnError === false) throw unresolvedError;
238
- if (typeof continueOnError === 'function') continueOnError(unresolvedError);
239
160
  }
240
- ReusableElement(path) {
161
+ async ReusableElement(path) {
241
162
  const referencingElement = path.node;
242
163
 
164
+ // the fields of a Reusable Object hold no references
165
+ path.skip();
166
+
243
167
  // skip current Reusable Object if reference field is not defined as a string
244
168
  if (!(0, _apidomDatamodel.isStringElement)(referencingElement.reference)) {
245
- path.skip();
246
169
  return;
247
170
  }
248
171
 
249
172
  // ignore if resolve.internal is false (Reusable Objects are internal references only)
250
173
  if (!this.options.resolve.internal) {
251
- path.skip();
252
174
  return;
253
175
  }
254
176
  const runtimeExpression = (0, _apidomCore.toValue)(referencingElement.reference);
255
- let referencedElement;
256
177
  try {
257
178
  // parse the runtime expression
258
179
  const {
@@ -274,6 +195,7 @@ class Arazzo1DereferenceVisitor {
274
195
 
275
196
  // evaluate runtime expression as JSON Pointer to get the referenced element
276
197
  const jsonPointer = (0, _apidomJsonPointer.compile)(['components', tree.componentType, tree.componentName]);
198
+ let referencedElement;
277
199
  try {
278
200
  referencedElement = (0, _apidomJsonPointer.evaluate)(this.reference.value.result, jsonPointer);
279
201
  } catch {
@@ -281,50 +203,62 @@ class Arazzo1DereferenceVisitor {
281
203
  runtimeExpression
282
204
  });
283
205
  }
206
+
207
+ // the reference decides the type of the referenced component
208
+ const type = (0, _apidomCore.toValue)(referencingElement.meta.get('referenced-element')) ?? referencedElement.element;
209
+ await this.addTransclusion(path, {
210
+ source: referencedElement,
211
+ type,
212
+ reference: this.reference,
213
+ context: {
214
+ ancestorSchema$ids: []
215
+ }
216
+ }, {
217
+ toTranscludedElement: (element, target) => {
218
+ const reusableElement = element;
219
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(target);
220
+ // annotate with info about original referencing element
221
+ mergedElement.meta.set('ref-fields', {
222
+ reference: runtimeExpression,
223
+ value: (0, _apidomCore.toValue)(reusableElement.value)
224
+ });
225
+ // annotate with info about origin
226
+ mergedElement.meta.set('ref-origin', this.reference.uri);
227
+ mergedElement.meta.set('ref-type', element.element);
228
+
229
+ // override value field if present for Parameter Objects
230
+ if ((0, _apidomNsArazzo.isParameterElement)(mergedElement) && reusableElement.hasKey('value')) {
231
+ mergedElement.remove('value');
232
+ mergedElement.set('value', reusableElement.get('value'));
233
+ }
234
+ return mergedElement;
235
+ },
236
+ circular: {
237
+ uri: runtimeExpression,
238
+ meta: {
239
+ uri: this.reference.uri,
240
+ reference: runtimeExpression
241
+ },
242
+ details: {
243
+ runtimeExpression
244
+ }
245
+ }
246
+ });
284
247
  } catch (error) {
285
248
  this.handleError(`Error while dereferencing Reusable Object. Cannot resolve reference "${runtimeExpression}": ${error.message}`, error, referencingElement, 'reference', runtimeExpression, path);
286
- return;
287
249
  }
288
-
289
- /**
290
- * Create a shallow clone of the referenced element to avoid modifying the original.
291
- */
292
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
293
- // annotate with info about original referencing element
294
- mergedElement.meta.set('ref-fields', {
295
- reference: runtimeExpression,
296
- value: (0, _apidomCore.toValue)(referencingElement.value)
297
- });
298
- // annotate with info about origin
299
- mergedElement.meta.set('ref-origin', this.reference.uri);
300
- mergedElement.meta.set('ref-type', referencingElement.element);
301
-
302
- // override value field if present for Parameter Objects
303
- if ((0, _apidomNsArazzo.isParameterElement)(mergedElement) && referencingElement.hasKey('value')) {
304
- mergedElement.remove('value');
305
- mergedElement.set('value', referencingElement.get('value'));
306
- }
307
-
308
- /**
309
- * Transclude referencing element with merged referenced element.
310
- */
311
- path.replaceWith(mergedElement);
312
- path.skip();
313
250
  }
314
251
  async JSONSchemaElement(path) {
315
252
  const referencingElement = path.node;
316
-
317
- // skip current referencing schema as $ref keyword was not defined
318
- if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
253
+ if (this.isTranscluded(referencingElement)) {
254
+ path.skip();
319
255
  return;
320
256
  }
321
257
 
322
- // skip current referencing element as it's already been accessed
323
- if (this.indirections.includes(referencingElement)) {
324
- path.skip();
258
+ // skip current referencing schema as $ref keyword was not defined
259
+ if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
325
260
  return;
326
261
  }
327
- const indirectionsSize = this.indirections.length;
328
262
  try {
329
263
  // compute baseURI using rules around $self, $id and $ref keywords
330
264
  let reference = await this.toReference(url.unsanitize(this.reference.uri));
@@ -355,11 +289,12 @@ class Arazzo1DereferenceVisitor {
355
289
  retrievalURI = this.toBaseURI($refBaseURI);
356
290
  const selector = $refBaseURI;
357
291
  const documentElement = reference.value.result;
358
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(documentElement);
292
+ const referenceAsSchema = this.toJSONSchemaDocument(documentElement);
293
+ this.index$ids(referenceAsSchema);
359
294
  // a document that is itself a fragment detached from its source carries
360
295
  // the $ids enclosing it only as `ancestorSchema$ids`: its $ids are then
361
296
  // indexed against the base that chain leaves in effect, and the chain
362
- // prefixes the one of the located schema. A nested visitor rooted at a
297
+ // prefixes the one of the located schema. A visitor rooted at a
363
298
  // fragment of a whole document indexes that document against its own base.
364
299
  const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
365
300
  const isDocumentRoot = ((0, _apidomDatamodel.isParseResultElement)(rootNode) ? rootNode.result : rootNode) === documentElement;
@@ -373,7 +308,6 @@ class Arazzo1DereferenceVisitor {
373
308
  if (isDocumentRoot) {
374
309
  ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
375
310
  }
376
- referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
377
311
 
378
312
  // ignore resolving internal Schema Objects
379
313
  if (!this.options.resolve.internal && isInternalReference) {
@@ -403,12 +337,11 @@ class Arazzo1DereferenceVisitor {
403
337
  }
404
338
  reference = await this.toReference(url.unsanitize($refBaseURI));
405
339
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
406
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
340
+ const referenceAsSchema = this.toJSONSchemaDocument(reference.value.result);
407
341
  ({
408
342
  element: referencedElement,
409
343
  ancestorSchema$ids
410
344
  } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
411
- referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
412
345
  }
413
346
  } catch (error) {
414
347
  /**
@@ -433,12 +366,11 @@ class Arazzo1DereferenceVisitor {
433
366
  }
434
367
  reference = await this.toReference(url.unsanitize($refBaseURI));
435
368
  const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
436
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
369
+ const referenceAsSchema = this.toJSONSchemaDocument(reference.value.result);
437
370
  ({
438
371
  element: referencedElement,
439
372
  ancestorSchema$ids
440
373
  } = (0, _$anchor.locate)(selector, referenceAsSchema));
441
- referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
442
374
  } else {
443
375
  // we're assuming here that we're dealing with JSON Pointer here
444
376
  retrievalURI = this.toBaseURI($refBaseURI);
@@ -457,140 +389,84 @@ class Arazzo1DereferenceVisitor {
457
389
  }
458
390
  reference = await this.toReference(url.unsanitize($refBaseURI));
459
391
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
460
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
392
+ const referenceAsSchema = this.toJSONSchemaDocument(reference.value.result);
461
393
  ({
462
394
  element: referencedElement,
463
395
  ancestorSchema$ids
464
396
  } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
465
- referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
466
397
  }
467
398
  } else {
468
399
  throw error;
469
400
  }
470
401
  }
471
- this.indirections.push(referencingElement);
472
402
 
473
- // detect direct or indirect reference
403
+ // detect direct reference
474
404
  if (referencingElement === referencedElement) {
475
405
  throw new _apidomError.ApiDOMStructuredError('Recursive JSON Schema reference detected', {
476
406
  $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
477
407
  });
478
408
  }
479
409
 
480
- // detect maximum depth of dereferencing
481
- if (this.indirections.length > this.options.dereference.maxDepth) {
482
- throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
483
- maxDepth: this.options.dereference.maxDepth,
484
- uri: this.reference.uri
485
- });
486
- }
487
-
488
- // detect cross-boundary cycle
489
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
490
- if (ancestorsLineage.includes(referencedElement)) {
491
- reference.refSet.circular = true;
492
- if (this.options.dereference.circular === 'error') {
493
- throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
494
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
410
+ // a JSON Schema $ref reads whatever it points at as a JSON Schema
411
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
412
+ const origin = reference.uri;
413
+ await this.addTransclusion(path, {
414
+ source: referencedElement,
415
+ type: JSON_SCHEMA_TYPE,
416
+ reference,
417
+ context: {
418
+ ancestorSchema$ids
419
+ }
420
+ }, {
421
+ toTranscludedElement: (element, target) => {
422
+ // Boolean JSON Schemas
423
+ if ((0, _apidomNsArazzo.isBooleanJSONSchemaElement)(target)) {
424
+ const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(target);
425
+ // annotate referenced element with info about original referencing element
426
+ booleanJsonSchemaElement.meta.set('ref-fields', {
427
+ $ref
428
+ });
429
+ // annotate referenced element with info about origin
430
+ booleanJsonSchemaElement.meta.set('ref-origin', origin);
431
+ booleanJsonSchemaElement.meta.set('ref-type', element.element);
432
+ return booleanJsonSchemaElement;
433
+ }
434
+ if (!(0, _apidomNsArazzo.isJSONSchemaElement)(target)) return target;
435
+
436
+ /**
437
+ * Creating a new version of JSON Schema by merging fields from referenced Schema with referencing one.
438
+ */
439
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(target);
440
+ // existing keywords from referencing schema overrides ones from referenced schema
441
+ element.forEach((value, keyElement, item) => {
442
+ mergedElement.remove((0, _apidomCore.toValue)(keyElement));
443
+ mergedElement.content.push(item);
495
444
  });
496
- } else if (this.options.dereference.circular === 'replace') {
497
- const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
498
- type: referencingElement.element,
499
- uri: reference.uri,
500
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
445
+ mergedElement.remove('$ref');
446
+ // annotate referenced element with info about original referencing element
447
+ mergedElement.meta.set('ref-fields', {
448
+ $ref
501
449
  });
502
- const replacer = this.options.dereference.strategyOpts['arazzo-1']?.circularReplacer ?? this.options.dereference.circularReplacer;
503
- const replacement = replacer(refElement);
504
- path.replaceWith(replacement);
505
- path.skip();
506
- return;
507
- }
508
- }
509
-
510
- /**
511
- * Dive deep into the fragment.
512
- *
513
- * Cases to consider:
514
- * 1. We're crossing document boundary
515
- * 2. Fragment is from non-entry document
516
- * 3. Fragment is a JSON Schema with $ref field. We need to follow it to get the eventual value
517
- * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
518
- */
519
- const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
520
- const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
521
- // content transplanted into the referencing site is traversed under that
522
- // site's $id chain; a fragment enclosed by a different chain has its own
523
- // $refs resolved in place first, by a nested visitor seeded with that chain
524
- const hasDifferentEnclosingBase = (0, _util.resolveSchema$ids)((0, _util2.resolveArazzo$selfField)(reference.uri, reference.value.result), ancestorSchema$ids) !== (0, _util.resolveSchema$ids)(rootBaseURI, (0, _util.schema$idsOf)(path.getAncestorNodes().reverse()));
525
- if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || (0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
526
- if (shouldDetectCircular) {
527
- this.visited.add(referencedElement);
450
+ // annotate fragment with info about origin
451
+ mergedElement.meta.set('ref-origin', origin);
452
+ mergedElement.meta.set('ref-type', element.element);
453
+ return mergedElement;
454
+ },
455
+ aliasField: '$ref',
456
+ circular: {
457
+ uri: $refBaseURI,
458
+ meta: {
459
+ uri: origin,
460
+ $ref
461
+ },
462
+ details: {
463
+ $ref
464
+ }
528
465
  }
529
-
530
- // append referencing reference to ancestors lineage
531
- directAncestors.add(referencingElement);
532
- const visitor = new Arazzo1DereferenceVisitor({
533
- reference,
534
- indirections: [...this.indirections],
535
- options: this.options,
536
- schema$idIndex: this.schema$idIndex,
537
- visited: this.visited,
538
- ancestors: ancestorsLineage,
539
- ancestorSchema$ids
540
- });
541
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
542
- mutable: true,
543
- skipVisited: 'skip'
544
- });
545
-
546
- // remove referencing reference from ancestors lineage
547
- directAncestors.delete(referencingElement);
548
- }
549
-
550
- // Boolean JSON Schemas
551
- if ((0, _apidomNsArazzo.isBooleanJSONSchemaElement)(referencedElement)) {
552
- const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(referencedElement);
553
- // annotate referenced element with info about original referencing element
554
- booleanJsonSchemaElement.meta.set('ref-fields', {
555
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
556
- });
557
- // annotate referenced element with info about origin
558
- booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
559
- booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
560
- path.replaceWith(booleanJsonSchemaElement);
561
- path.skip();
562
- return;
563
- }
564
-
565
- /**
566
- * Creating a new version of JSON Schema by merging fields from referenced Schema with referencing one.
567
- */
568
- if ((0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement)) {
569
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
570
- // existing keywords from referencing schema overrides ones from referenced schema
571
- referencingElement.forEach((value, keyElement, item) => {
572
- mergedElement.remove((0, _apidomCore.toValue)(keyElement));
573
- mergedElement.content.push(item);
574
- });
575
- mergedElement.remove('$ref');
576
- // annotate referenced element with info about original referencing element
577
- mergedElement.meta.set('ref-fields', {
578
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
579
- });
580
- // annotate fragment with info about origin
581
- mergedElement.meta.set('ref-origin', reference.uri);
582
- mergedElement.meta.set('ref-type', referencingElement.element);
583
- referencedElement = mergedElement;
584
- }
585
- /**
586
- * Transclude referencing element with merged referenced element.
587
- */
588
- path.replaceWith(referencedElement);
466
+ });
589
467
  } catch (error) {
590
468
  const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
591
469
  this.handleError(`Error while dereferencing Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
592
- } finally {
593
- if (this.indirections.length > indirectionsSize) this.indirections.pop();
594
470
  }
595
471
  }
596
472
  }