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