@speclynx/apidom-reference 4.0.2 → 4.0.3

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 (275) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +26 -27
  3. package/src/File.cjs +50 -0
  4. package/src/File.mjs +44 -0
  5. package/src/File.ts +63 -0
  6. package/src/Reference.cjs +31 -0
  7. package/src/Reference.mjs +27 -0
  8. package/src/Reference.ts +38 -0
  9. package/src/ReferenceSet.cjs +60 -0
  10. package/src/ReferenceSet.mjs +57 -0
  11. package/src/ReferenceSet.ts +73 -0
  12. package/src/bundle/index.cjs +61 -0
  13. package/src/bundle/index.mjs +55 -0
  14. package/src/bundle/index.ts +57 -0
  15. package/src/bundle/strategies/BundleStrategy.cjs +20 -0
  16. package/src/bundle/strategies/BundleStrategy.mjs +16 -0
  17. package/src/bundle/strategies/BundleStrategy.ts +27 -0
  18. package/src/bundle/strategies/openapi-3-1/index.cjs +35 -0
  19. package/src/bundle/strategies/openapi-3-1/index.mjs +29 -0
  20. package/src/bundle/strategies/openapi-3-1/index.ts +57 -0
  21. package/src/configuration/empty.cjs +9 -0
  22. package/src/configuration/empty.mjs +1 -0
  23. package/src/configuration/empty.ts +1 -0
  24. package/src/configuration/saturated.cjs +88 -0
  25. package/src/configuration/saturated.mjs +80 -0
  26. package/src/configuration/saturated.ts +72 -0
  27. package/src/dereference/index.cjs +90 -0
  28. package/src/dereference/index.mjs +83 -0
  29. package/src/dereference/index.ts +96 -0
  30. package/src/dereference/strategies/DereferenceStrategy.cjs +20 -0
  31. package/src/dereference/strategies/DereferenceStrategy.mjs +16 -0
  32. package/src/dereference/strategies/DereferenceStrategy.ts +27 -0
  33. package/src/dereference/strategies/apidom/index.cjs +89 -0
  34. package/src/dereference/strategies/apidom/index.mjs +83 -0
  35. package/src/dereference/strategies/apidom/index.ts +128 -0
  36. package/src/dereference/strategies/apidom/selectors/element-id.cjs +47 -0
  37. package/src/dereference/strategies/apidom/selectors/element-id.mjs +41 -0
  38. package/src/dereference/strategies/apidom/selectors/element-id.ts +48 -0
  39. package/src/dereference/strategies/apidom/visitor.cjs +266 -0
  40. package/src/dereference/strategies/apidom/visitor.mjs +259 -0
  41. package/src/dereference/strategies/apidom/visitor.ts +316 -0
  42. package/src/dereference/strategies/arazzo-1/index.cjs +109 -0
  43. package/src/dereference/strategies/arazzo-1/index.mjs +100 -0
  44. package/src/dereference/strategies/arazzo-1/index.ts +158 -0
  45. package/src/dereference/strategies/arazzo-1/selectors/$anchor.cjs +12 -0
  46. package/src/dereference/strategies/arazzo-1/selectors/$anchor.mjs +1 -0
  47. package/src/dereference/strategies/arazzo-1/selectors/$anchor.ts +9 -0
  48. package/src/dereference/strategies/arazzo-1/selectors/uri.cjs +8 -0
  49. package/src/dereference/strategies/arazzo-1/selectors/uri.mjs +1 -0
  50. package/src/dereference/strategies/arazzo-1/selectors/uri.ts +5 -0
  51. package/src/dereference/strategies/arazzo-1/source-descriptions.cjs +248 -0
  52. package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +243 -0
  53. package/src/dereference/strategies/arazzo-1/source-descriptions.ts +317 -0
  54. package/src/dereference/strategies/arazzo-1/util.cjs +37 -0
  55. package/src/dereference/strategies/arazzo-1/util.mjs +29 -0
  56. package/src/dereference/strategies/arazzo-1/util.ts +33 -0
  57. package/src/dereference/strategies/arazzo-1/visitor.cjs +507 -0
  58. package/src/dereference/strategies/arazzo-1/visitor.mjs +500 -0
  59. package/src/dereference/strategies/arazzo-1/visitor.ts +574 -0
  60. package/src/dereference/strategies/asyncapi-2/index.cjs +94 -0
  61. package/src/dereference/strategies/asyncapi-2/index.mjs +88 -0
  62. package/src/dereference/strategies/asyncapi-2/index.ts +133 -0
  63. package/src/dereference/strategies/asyncapi-2/visitor.cjs +501 -0
  64. package/src/dereference/strategies/asyncapi-2/visitor.mjs +494 -0
  65. package/src/dereference/strategies/asyncapi-2/visitor.ts +589 -0
  66. package/src/dereference/strategies/openapi-2/index.cjs +96 -0
  67. package/src/dereference/strategies/openapi-2/index.mjs +90 -0
  68. package/src/dereference/strategies/openapi-2/index.ts +136 -0
  69. package/src/dereference/strategies/openapi-2/visitor.cjs +629 -0
  70. package/src/dereference/strategies/openapi-2/visitor.mjs +622 -0
  71. package/src/dereference/strategies/openapi-2/visitor.ts +745 -0
  72. package/src/dereference/strategies/openapi-3-0/index.cjs +96 -0
  73. package/src/dereference/strategies/openapi-3-0/index.mjs +90 -0
  74. package/src/dereference/strategies/openapi-3-0/index.ts +134 -0
  75. package/src/dereference/strategies/openapi-3-0/visitor.cjs +622 -0
  76. package/src/dereference/strategies/openapi-3-0/visitor.mjs +615 -0
  77. package/src/dereference/strategies/openapi-3-0/visitor.ts +760 -0
  78. package/src/dereference/strategies/openapi-3-1/index.cjs +99 -0
  79. package/src/dereference/strategies/openapi-3-1/index.mjs +90 -0
  80. package/src/dereference/strategies/openapi-3-1/index.ts +141 -0
  81. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +65 -0
  82. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +54 -0
  83. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.ts +64 -0
  84. package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +50 -0
  85. package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +42 -0
  86. package/src/dereference/strategies/openapi-3-1/selectors/uri.ts +54 -0
  87. package/src/dereference/strategies/openapi-3-1/util.cjs +68 -0
  88. package/src/dereference/strategies/openapi-3-1/util.mjs +59 -0
  89. package/src/dereference/strategies/openapi-3-1/util.ts +83 -0
  90. package/src/dereference/strategies/openapi-3-1/visitor.cjs +874 -0
  91. package/src/dereference/strategies/openapi-3-1/visitor.mjs +867 -0
  92. package/src/dereference/strategies/openapi-3-1/visitor.ts +1053 -0
  93. package/src/dereference/util.cjs +31 -0
  94. package/src/dereference/util.mjs +27 -0
  95. package/src/dereference/util.ts +29 -0
  96. package/src/errors/BundleError.cjs +10 -0
  97. package/src/errors/BundleError.mjs +7 -0
  98. package/src/errors/BundleError.ts +8 -0
  99. package/src/errors/DereferenceError.cjs +10 -0
  100. package/src/errors/DereferenceError.mjs +7 -0
  101. package/src/errors/DereferenceError.ts +8 -0
  102. package/src/errors/EvaluationElementIdError.cjs +10 -0
  103. package/src/errors/EvaluationElementIdError.mjs +7 -0
  104. package/src/errors/EvaluationElementIdError.ts +8 -0
  105. package/src/errors/EvaluationJsonSchema$anchorError.cjs +11 -0
  106. package/src/errors/EvaluationJsonSchema$anchorError.mjs +6 -0
  107. package/src/errors/EvaluationJsonSchema$anchorError.ts +8 -0
  108. package/src/errors/EvaluationJsonSchemaUriError.cjs +11 -0
  109. package/src/errors/EvaluationJsonSchemaUriError.mjs +6 -0
  110. package/src/errors/EvaluationJsonSchemaUriError.ts +8 -0
  111. package/src/errors/InvalidJsonSchema$anchorError.cjs +15 -0
  112. package/src/errors/InvalidJsonSchema$anchorError.mjs +10 -0
  113. package/src/errors/InvalidJsonSchema$anchorError.ts +12 -0
  114. package/src/errors/JsonSchema$anchorError.cjs +10 -0
  115. package/src/errors/JsonSchema$anchorError.mjs +7 -0
  116. package/src/errors/JsonSchema$anchorError.ts +8 -0
  117. package/src/errors/JsonSchemaUriError.cjs +10 -0
  118. package/src/errors/JsonSchemaUriError.mjs +7 -0
  119. package/src/errors/JsonSchemaUriError.ts +8 -0
  120. package/src/errors/MaximumBundleDepthError.cjs +11 -0
  121. package/src/errors/MaximumBundleDepthError.mjs +6 -0
  122. package/src/errors/MaximumBundleDepthError.ts +8 -0
  123. package/src/errors/MaximumDereferenceDepthError.cjs +11 -0
  124. package/src/errors/MaximumDereferenceDepthError.mjs +6 -0
  125. package/src/errors/MaximumDereferenceDepthError.ts +8 -0
  126. package/src/errors/MaximumResolveDepthError.cjs +11 -0
  127. package/src/errors/MaximumResolveDepthError.mjs +6 -0
  128. package/src/errors/MaximumResolveDepthError.ts +8 -0
  129. package/src/errors/ParseError.cjs +10 -0
  130. package/src/errors/ParseError.mjs +7 -0
  131. package/src/errors/ParseError.ts +8 -0
  132. package/src/errors/ParserError.cjs +11 -0
  133. package/src/errors/ParserError.mjs +6 -0
  134. package/src/errors/ParserError.ts +8 -0
  135. package/src/errors/PluginError.cjs +18 -0
  136. package/src/errors/PluginError.mjs +15 -0
  137. package/src/errors/PluginError.ts +15 -0
  138. package/src/errors/ResolveError.cjs +10 -0
  139. package/src/errors/ResolveError.mjs +7 -0
  140. package/src/errors/ResolveError.ts +8 -0
  141. package/src/errors/ResolverError.cjs +11 -0
  142. package/src/errors/ResolverError.mjs +6 -0
  143. package/src/errors/ResolverError.ts +8 -0
  144. package/src/errors/UnmatchedBundleStrategyError.cjs +11 -0
  145. package/src/errors/UnmatchedBundleStrategyError.mjs +6 -0
  146. package/src/errors/UnmatchedBundleStrategyError.ts +8 -0
  147. package/src/errors/UnmatchedDereferenceStrategyError.cjs +11 -0
  148. package/src/errors/UnmatchedDereferenceStrategyError.mjs +6 -0
  149. package/src/errors/UnmatchedDereferenceStrategyError.ts +8 -0
  150. package/src/errors/UnmatchedParserError.cjs +11 -0
  151. package/src/errors/UnmatchedParserError.mjs +6 -0
  152. package/src/errors/UnmatchedParserError.ts +8 -0
  153. package/src/errors/UnmatchedResolveStrategyError.cjs +11 -0
  154. package/src/errors/UnmatchedResolveStrategyError.mjs +6 -0
  155. package/src/errors/UnmatchedResolveStrategyError.ts +8 -0
  156. package/src/errors/UnmatchedResolverError.cjs +11 -0
  157. package/src/errors/UnmatchedResolverError.mjs +6 -0
  158. package/src/errors/UnmatchedResolverError.ts +8 -0
  159. package/src/errors/UnresolvableReferenceError.cjs +11 -0
  160. package/src/errors/UnresolvableReferenceError.mjs +6 -0
  161. package/src/errors/UnresolvableReferenceError.ts +8 -0
  162. package/src/index.cjs +146 -0
  163. package/src/index.mjs +103 -0
  164. package/src/index.ts +135 -0
  165. package/src/options/index.cjs +194 -0
  166. package/src/options/index.mjs +191 -0
  167. package/src/options/index.ts +239 -0
  168. package/src/options/util.cjs +24 -0
  169. package/src/options/util.mjs +19 -0
  170. package/src/options/util.ts +22 -0
  171. package/src/parse/index.cjs +69 -0
  172. package/src/parse/index.mjs +63 -0
  173. package/src/parse/index.ts +67 -0
  174. package/src/parse/parsers/Parser.cjs +62 -0
  175. package/src/parse/parsers/Parser.mjs +58 -0
  176. package/src/parse/parsers/Parser.ts +80 -0
  177. package/src/parse/parsers/apidom-json/index.cjs +70 -0
  178. package/src/parse/parsers/apidom-json/index.mjs +64 -0
  179. package/src/parse/parsers/apidom-json/index.ts +78 -0
  180. package/src/parse/parsers/arazzo-json-1/index.cjs +62 -0
  181. package/src/parse/parsers/arazzo-json-1/index.mjs +56 -0
  182. package/src/parse/parsers/arazzo-json-1/index.ts +76 -0
  183. package/src/parse/parsers/arazzo-json-1/source-descriptions.cjs +221 -0
  184. package/src/parse/parsers/arazzo-json-1/source-descriptions.mjs +214 -0
  185. package/src/parse/parsers/arazzo-json-1/source-descriptions.ts +280 -0
  186. package/src/parse/parsers/arazzo-yaml-1/index.cjs +62 -0
  187. package/src/parse/parsers/arazzo-yaml-1/index.mjs +56 -0
  188. package/src/parse/parsers/arazzo-yaml-1/index.ts +77 -0
  189. package/src/parse/parsers/arazzo-yaml-1/source-descriptions.cjs +12 -0
  190. package/src/parse/parsers/arazzo-yaml-1/source-descriptions.mjs +7 -0
  191. package/src/parse/parsers/arazzo-yaml-1/source-descriptions.ts +16 -0
  192. package/src/parse/parsers/asyncapi-json-2/index.cjs +54 -0
  193. package/src/parse/parsers/asyncapi-json-2/index.mjs +48 -0
  194. package/src/parse/parsers/asyncapi-json-2/index.ts +58 -0
  195. package/src/parse/parsers/asyncapi-yaml-2/index.cjs +54 -0
  196. package/src/parse/parsers/asyncapi-yaml-2/index.mjs +48 -0
  197. package/src/parse/parsers/asyncapi-yaml-2/index.ts +58 -0
  198. package/src/parse/parsers/binary/index-browser.cjs +56 -0
  199. package/src/parse/parsers/binary/index-browser.mjs +50 -0
  200. package/src/parse/parsers/binary/index-browser.ts +60 -0
  201. package/src/parse/parsers/binary/index-node.cjs +51 -0
  202. package/src/parse/parsers/binary/index-node.mjs +45 -0
  203. package/src/parse/parsers/binary/index-node.ts +57 -0
  204. package/src/parse/parsers/json/index.cjs +53 -0
  205. package/src/parse/parsers/json/index.mjs +47 -0
  206. package/src/parse/parsers/json/index.ts +52 -0
  207. package/src/parse/parsers/openapi-json-2/index.cjs +54 -0
  208. package/src/parse/parsers/openapi-json-2/index.mjs +48 -0
  209. package/src/parse/parsers/openapi-json-2/index.ts +58 -0
  210. package/src/parse/parsers/openapi-json-3-0/index.cjs +54 -0
  211. package/src/parse/parsers/openapi-json-3-0/index.mjs +48 -0
  212. package/src/parse/parsers/openapi-json-3-0/index.ts +59 -0
  213. package/src/parse/parsers/openapi-json-3-1/index.cjs +54 -0
  214. package/src/parse/parsers/openapi-json-3-1/index.mjs +48 -0
  215. package/src/parse/parsers/openapi-json-3-1/index.ts +59 -0
  216. package/src/parse/parsers/openapi-yaml-2/index.cjs +54 -0
  217. package/src/parse/parsers/openapi-yaml-2/index.mjs +48 -0
  218. package/src/parse/parsers/openapi-yaml-2/index.ts +58 -0
  219. package/src/parse/parsers/openapi-yaml-3-0/index.cjs +54 -0
  220. package/src/parse/parsers/openapi-yaml-3-0/index.mjs +48 -0
  221. package/src/parse/parsers/openapi-yaml-3-0/index.ts +59 -0
  222. package/src/parse/parsers/openapi-yaml-3-1/index.cjs +54 -0
  223. package/src/parse/parsers/openapi-yaml-3-1/index.mjs +48 -0
  224. package/src/parse/parsers/openapi-yaml-3-1/index.ts +59 -0
  225. package/src/parse/parsers/yaml-1-2/index.cjs +56 -0
  226. package/src/parse/parsers/yaml-1-2/index.mjs +50 -0
  227. package/src/parse/parsers/yaml-1-2/index.ts +60 -0
  228. package/src/resolve/index.cjs +67 -0
  229. package/src/resolve/index.mjs +60 -0
  230. package/src/resolve/index.ts +75 -0
  231. package/src/resolve/resolvers/HTTPResolver.cjs +45 -0
  232. package/src/resolve/resolvers/HTTPResolver.mjs +37 -0
  233. package/src/resolve/resolvers/HTTPResolver.ts +58 -0
  234. package/src/resolve/resolvers/Resolver.cjs +20 -0
  235. package/src/resolve/resolvers/Resolver.mjs +16 -0
  236. package/src/resolve/resolvers/Resolver.ts +25 -0
  237. package/src/resolve/resolvers/file/index-browser.cjs +24 -0
  238. package/src/resolve/resolvers/file/index-browser.mjs +19 -0
  239. package/src/resolve/resolvers/file/index-browser.ts +24 -0
  240. package/src/resolve/resolvers/file/index-node.cjs +49 -0
  241. package/src/resolve/resolvers/file/index-node.mjs +42 -0
  242. package/src/resolve/resolvers/file/index-node.ts +55 -0
  243. package/src/resolve/resolvers/http-axios/cache/MemoryCache.cjs +41 -0
  244. package/src/resolve/resolvers/http-axios/cache/MemoryCache.mjs +37 -0
  245. package/src/resolve/resolvers/http-axios/cache/MemoryCache.ts +46 -0
  246. package/src/resolve/resolvers/http-axios/index.cjs +113 -0
  247. package/src/resolve/resolvers/http-axios/index.mjs +105 -0
  248. package/src/resolve/resolvers/http-axios/index.ts +130 -0
  249. package/src/resolve/strategies/ResolveStrategy.cjs +20 -0
  250. package/src/resolve/strategies/ResolveStrategy.mjs +16 -0
  251. package/src/resolve/strategies/ResolveStrategy.ts +26 -0
  252. package/src/resolve/strategies/apidom/index.cjs +49 -0
  253. package/src/resolve/strategies/apidom/index.mjs +43 -0
  254. package/src/resolve/strategies/apidom/index.ts +78 -0
  255. package/src/resolve/strategies/asyncapi-2/index.cjs +49 -0
  256. package/src/resolve/strategies/asyncapi-2/index.mjs +43 -0
  257. package/src/resolve/strategies/asyncapi-2/index.ts +78 -0
  258. package/src/resolve/strategies/openapi-2/index.cjs +49 -0
  259. package/src/resolve/strategies/openapi-2/index.mjs +43 -0
  260. package/src/resolve/strategies/openapi-2/index.ts +78 -0
  261. package/src/resolve/strategies/openapi-3-0/index.cjs +49 -0
  262. package/src/resolve/strategies/openapi-3-0/index.mjs +43 -0
  263. package/src/resolve/strategies/openapi-3-0/index.ts +78 -0
  264. package/src/resolve/strategies/openapi-3-1/index.cjs +49 -0
  265. package/src/resolve/strategies/openapi-3-1/index.mjs +43 -0
  266. package/src/resolve/strategies/openapi-3-1/index.ts +78 -0
  267. package/src/resolve/util.cjs +37 -0
  268. package/src/resolve/util.mjs +30 -0
  269. package/src/resolve/util.ts +39 -0
  270. package/src/util/plugins.cjs +39 -0
  271. package/src/util/plugins.mjs +34 -0
  272. package/src/util/plugins.ts +37 -0
  273. package/src/util/url.cjs +288 -0
  274. package/src/util/url.mjs +274 -0
  275. package/src/util/url.ts +285 -0
@@ -0,0 +1,1053 @@
1
+ import { propEq, none } from 'ramda';
2
+ import { isUndefined } from 'ramda-adjunct';
3
+ import {
4
+ isElement,
5
+ isStringElement,
6
+ isObjectElement,
7
+ Element,
8
+ RefElement,
9
+ BooleanElement,
10
+ ParseResultElement,
11
+ cloneShallow,
12
+ cloneDeep,
13
+ } from '@speclynx/apidom-datamodel';
14
+ import { toValue, fixedFields, toYAML } from '@speclynx/apidom-core';
15
+ import { ApiDOMStructuredError } from '@speclynx/apidom-error';
16
+ import { traverse, traverseAsync, find, type Path } from '@speclynx/apidom-traverse';
17
+ import {
18
+ evaluate as jsonPointerEvaluate,
19
+ URIFragmentIdentifier,
20
+ } from '@speclynx/apidom-json-pointer';
21
+ import {
22
+ isReferenceLikeElement,
23
+ ReferenceElement,
24
+ PathItemElement,
25
+ LinkElement,
26
+ OperationElement,
27
+ ExampleElement,
28
+ SchemaElement,
29
+ isPathItemElement,
30
+ isReferenceElement,
31
+ isSchemaElement,
32
+ isOperationElement,
33
+ isBooleanJSONSchemaElement,
34
+ refract,
35
+ refractReference,
36
+ refractPathItem,
37
+ refractOperation,
38
+ } from '@speclynx/apidom-ns-openapi-3-1';
39
+
40
+ import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from './selectors/$anchor.ts';
41
+ import { evaluate as uriEvaluate } from './selectors/uri.ts';
42
+ import MaximumDereferenceDepthError from '../../../errors/MaximumDereferenceDepthError.ts';
43
+ import MaximumResolveDepthError from '../../../errors/MaximumResolveDepthError.ts';
44
+ import UnresolvableReferenceError from '../../../errors/UnresolvableReferenceError.ts';
45
+ import EvaluationJsonSchemaUriError from '../../../errors/EvaluationJsonSchemaUriError.ts';
46
+ import * as url from '../../../util/url.ts';
47
+ import parse from '../../../parse/index.ts';
48
+ import Reference from '../../../Reference.ts';
49
+ import ReferenceSet from '../../../ReferenceSet.ts';
50
+ import File from '../../../File.ts';
51
+ import Resolver from '../../../resolve/resolvers/Resolver.ts';
52
+ import { resolveSchema$refField, maybeRefractToSchemaElement } from './util.ts';
53
+ import { AncestorLineage } from '../../util.ts';
54
+ import type { ReferenceOptions } from '../../../options/index.ts';
55
+
56
+ /**
57
+ * @public
58
+ */
59
+ export interface OpenAPI3_1DereferenceVisitorOptions {
60
+ readonly reference: Reference;
61
+ readonly options: ReferenceOptions;
62
+ readonly indirections?: Element[];
63
+ readonly refractCache?: WeakMap<Element, Element>;
64
+ readonly ancestors?: AncestorLineage<Element>;
65
+ }
66
+
67
+ /**
68
+ * @public
69
+ */
70
+ class OpenAPI3_1DereferenceVisitor {
71
+ protected readonly indirections: Element[];
72
+
73
+ protected readonly reference: Reference;
74
+
75
+ protected readonly options: ReferenceOptions;
76
+
77
+ protected readonly refractCache: WeakMap<Element, Element>;
78
+
79
+ /**
80
+ * Tracks element ancestors across dive-deep traversal boundaries.
81
+ * Used for cycle detection: if a referenced element is found in
82
+ * the ancestor lineage, a circular reference is detected.
83
+ */
84
+ protected readonly ancestors: AncestorLineage<Element>;
85
+
86
+ constructor({
87
+ reference,
88
+ options,
89
+ indirections = [],
90
+ refractCache = new WeakMap(),
91
+ ancestors = new AncestorLineage(),
92
+ }: OpenAPI3_1DereferenceVisitorOptions) {
93
+ this.indirections = indirections;
94
+ this.reference = reference;
95
+ this.options = options;
96
+ this.refractCache = refractCache;
97
+ this.ancestors = new AncestorLineage(...ancestors);
98
+ }
99
+
100
+ protected toAncestorLineage(path: Path<Element>): [AncestorLineage<Element>, Set<Element>] {
101
+ const ancestorNodes = path.getAncestorNodes();
102
+ const directAncestors = new Set<Element>(ancestorNodes.filter(isElement));
103
+ const ancestorsLineage = new AncestorLineage<Element>(...this.ancestors, directAncestors);
104
+ return [ancestorsLineage, directAncestors];
105
+ }
106
+
107
+ protected toBaseURI(uri: string): string {
108
+ return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
109
+ }
110
+
111
+ protected async toReference(uri: string): Promise<Reference> {
112
+ // detect maximum depth of resolution
113
+ if (this.reference.depth >= this.options.resolve.maxDepth) {
114
+ throw new MaximumResolveDepthError(
115
+ `Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`,
116
+ { maxDepth: this.options.resolve.maxDepth, uri: this.reference.uri },
117
+ );
118
+ }
119
+
120
+ const baseURI = this.toBaseURI(uri);
121
+ const { refSet } = this.reference as { refSet: ReferenceSet };
122
+
123
+ // we've already processed this Reference in past
124
+ if (refSet.has(baseURI)) {
125
+ return refSet.find(propEq(baseURI, 'uri'))!;
126
+ }
127
+
128
+ const parseResult = await parse(url.unsanitize(baseURI), {
129
+ ...this.options,
130
+ parse: { ...this.options.parse, mediaType: 'text/plain' },
131
+ });
132
+
133
+ // register new mutable reference with a refSet
134
+ //
135
+ // NOTE(known limitation): the mutable reference is mutated in place during traversal
136
+ // (via `{ mutable: true }`). When an external document evaluates a JSON pointer back
137
+ // into this document, it may receive an already-resolved element instead of the original
138
+ // $ref. That resolved element was produced using the entry document's resolution context
139
+ // (ancestors, indirections), which may differ from the external document's context.
140
+ // This can affect cycle detection in rare cross-document circular reference patterns.
141
+ //
142
+ // Remediation: evaluate JSON pointers against the immutable (original) parse tree
143
+ // instead of the mutable working copy. The `immutable://` reference below preserves
144
+ // the original tree and could be used for pointer evaluation, ensuring every resolution
145
+ // context always sees raw, unresolved elements and processes them with its own
146
+ // ancestors/indirections. The trade-off is that elements referenced by multiple
147
+ // documents would be resolved once per context instead of being reused.
148
+ const mutableReference = new Reference({
149
+ uri: baseURI,
150
+ value: this.options.dereference.immutable ? cloneDeep(parseResult) : parseResult,
151
+ depth: this.reference.depth + 1,
152
+ });
153
+ refSet.add(mutableReference);
154
+
155
+ if (this.options.dereference.immutable) {
156
+ // register new immutable reference with original parseResult
157
+ const immutableReference = new Reference({
158
+ uri: `immutable://${baseURI}`,
159
+ value: parseResult,
160
+ depth: this.reference.depth + 1,
161
+ });
162
+ refSet.add(immutableReference);
163
+ }
164
+
165
+ return mutableReference;
166
+ }
167
+
168
+ /**
169
+ * Handles an error according to the continueOnError option.
170
+ *
171
+ * For new errors: wraps in UnresolvableReferenceError with structured context
172
+ * (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
173
+ * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
174
+ *
175
+ * Inner/intermediate visitors always throw to let the trace accumulate.
176
+ * Only the entry document visitor respects continueOnError (callback/swallow/throw).
177
+ */
178
+ protected handleError(
179
+ message: string,
180
+ error: Error,
181
+ referencingElement: Element,
182
+ refFieldName: string,
183
+ refFieldValue: string,
184
+ visitorPath: Path<Element>,
185
+ ): void {
186
+ const { continueOnError } = this.options.dereference;
187
+ const isEntryDocument =
188
+ url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
189
+ const uri = this.reference.uri;
190
+ const type = referencingElement.element as string;
191
+ const codeFrame = toYAML(referencingElement);
192
+
193
+ // find element location by identity in the document tree.
194
+ // guarded: this.reference.value may not be a ParseResultElement or may lack a result.
195
+ // falls back to visitorPath which may produce an incomplete path when
196
+ // dereferenceApiDOM is called with a fragment (cloneShallow creates a new root identity).
197
+ let location: string | undefined;
198
+ const root = (this.reference.value as ParseResultElement).result;
199
+ if (isElement(root)) {
200
+ traverse(root, {
201
+ enter: (p: Path<Element>) => {
202
+ if (
203
+ p.node === referencingElement ||
204
+ this.refractCache.get(p.node) === referencingElement
205
+ ) {
206
+ location = p.formatPath();
207
+ p.stop();
208
+ }
209
+ },
210
+ });
211
+ }
212
+ location ??= visitorPath.formatPath();
213
+
214
+ const hop = { uri, type, refFieldName, refFieldValue, location, codeFrame };
215
+
216
+ // enrich existing error from nested visitor or create new one
217
+ let unresolvedError: UnresolvableReferenceError;
218
+ if (error instanceof UnresolvableReferenceError) {
219
+ // prefix relative locations for entries belonging to the referenced document
220
+ const refBaseURI = this.toBaseURI(refFieldValue);
221
+ const fragment = URIFragmentIdentifier.fromURIReference(refFieldValue);
222
+ if (fragment) {
223
+ if (refBaseURI === (error as any).uri && (error as any).location) {
224
+ (error as any).location = fragment + (error as any).location;
225
+ }
226
+ for (const h of (error as any).trace) {
227
+ if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
228
+ }
229
+ }
230
+ // @ts-ignore
231
+ error.trace = [hop, ...error.trace];
232
+ unresolvedError = error;
233
+ } else {
234
+ unresolvedError = new UnresolvableReferenceError(message, {
235
+ cause: error,
236
+ type,
237
+ uri,
238
+ location,
239
+ codeFrame,
240
+ refFieldName,
241
+ refFieldValue,
242
+ trace: [],
243
+ });
244
+ }
245
+
246
+ if (!isEntryDocument || continueOnError === false) throw unresolvedError;
247
+ if (typeof continueOnError === 'function') continueOnError(unresolvedError);
248
+ }
249
+
250
+ public async ReferenceElement(path: Path<Element>) {
251
+ const referencingElement = path.node as ReferenceElement;
252
+
253
+ // skip current referencing element as it's already been accessed
254
+ if (this.indirections.includes(referencingElement)) {
255
+ path.skip();
256
+ return;
257
+ }
258
+
259
+ const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref) as string);
260
+ const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
261
+ const isExternalReference = !isInternalReference;
262
+
263
+ // ignore resolving internal Reference Objects
264
+ if (!this.options.resolve.internal && isInternalReference) {
265
+ // skip traversing this reference element and all it's child elements
266
+ path.skip();
267
+ return;
268
+ }
269
+ // ignore resolving external Reference Objects
270
+ if (!this.options.resolve.external && isExternalReference) {
271
+ // skip traversing this reference element and all it's child elements
272
+ path.skip();
273
+ return;
274
+ }
275
+
276
+ const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref) as string);
277
+ const indirectionsSize = this.indirections.length;
278
+
279
+ try {
280
+ const reference = await this.toReference(toValue(referencingElement.$ref) as string);
281
+
282
+ this.indirections.push(referencingElement);
283
+
284
+ const jsonPointer = URIFragmentIdentifier.fromURIReference($refBaseURI);
285
+
286
+ // possibly non-semantic fragment
287
+ let referencedElement = jsonPointerEvaluate<Element>(
288
+ (reference.value as ParseResultElement).result as Element,
289
+ jsonPointer,
290
+ );
291
+
292
+ // applying semantics to a fragment
293
+ const referencedElementType = referencingElement.meta.get('referenced-element') as string;
294
+ if (
295
+ referencedElement.element !== referencedElementType &&
296
+ !isReferenceElement(referencedElement)
297
+ ) {
298
+ if (this.refractCache.has(referencedElement)) {
299
+ referencedElement = this.refractCache.get(referencedElement)!;
300
+ } else if (isReferenceLikeElement(referencedElement)) {
301
+ // handling generic indirect references
302
+ const sourceElement = referencedElement;
303
+ referencedElement = refractReference(referencedElement);
304
+ referencedElement.meta.set('referenced-element', referencedElementType);
305
+ this.refractCache.set(sourceElement, referencedElement);
306
+ } else {
307
+ // handling direct references
308
+ const sourceElement = referencedElement;
309
+ referencedElement = refract(referencedElement, { element: referencedElementType });
310
+ this.refractCache.set(sourceElement, referencedElement);
311
+ }
312
+ }
313
+
314
+ // detect direct or indirect reference
315
+ if (referencingElement === referencedElement) {
316
+ throw new ApiDOMStructuredError('Recursive Reference Object detected', {
317
+ $ref: toValue(referencingElement.$ref),
318
+ });
319
+ }
320
+
321
+ // detect maximum depth of dereferencing
322
+ if (this.indirections.length > this.options.dereference.maxDepth) {
323
+ throw new MaximumDereferenceDepthError(
324
+ `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`,
325
+ { maxDepth: this.options.dereference.maxDepth, uri: this.reference.uri },
326
+ );
327
+ }
328
+
329
+ // detect cross-boundary cycle
330
+ const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
331
+ if (ancestorsLineage.includes(referencedElement)) {
332
+ reference.refSet!.circular = true;
333
+
334
+ if (this.options.dereference.circular === 'error') {
335
+ throw new ApiDOMStructuredError('Circular reference detected', {
336
+ $ref: toValue(referencingElement.$ref),
337
+ });
338
+ } else if (this.options.dereference.circular === 'replace') {
339
+ const refElement = new RefElement($refBaseURI, {
340
+ type: referencingElement.element,
341
+ uri: reference.uri,
342
+ $ref: toValue(referencingElement.$ref),
343
+ });
344
+ const replacer =
345
+ this.options.dereference.strategyOpts['openapi-3-1']?.circularReplacer ??
346
+ this.options.dereference.circularReplacer;
347
+ const replacement = replacer(refElement);
348
+
349
+ path.replaceWith(replacement);
350
+ return;
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Dive deep into the fragment.
356
+ *
357
+ * Cases to consider:
358
+ * 1. We're crossing document boundary
359
+ * 2. Fragment is from non-entry document
360
+ * 3. Fragment is a Reference Object. We need to follow it to get the eventual value
361
+ * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
362
+ */
363
+ const isNonEntryDocument = url.stripHash(reference.refSet!.rootRef!.uri) !== reference.uri;
364
+ const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
365
+ if (
366
+ (isExternalReference ||
367
+ isNonEntryDocument ||
368
+ isReferenceElement(referencedElement) ||
369
+ shouldDetectCircular) &&
370
+ !ancestorsLineage.includesCycle(referencedElement)
371
+ ) {
372
+ directAncestors.add(referencingElement);
373
+
374
+ const visitor = new OpenAPI3_1DereferenceVisitor({
375
+ reference,
376
+ indirections: [...this.indirections],
377
+ options: this.options,
378
+ refractCache: this.refractCache,
379
+ ancestors: ancestorsLineage,
380
+ });
381
+ referencedElement = await traverseAsync(referencedElement, visitor, { mutable: true });
382
+
383
+ directAncestors.delete(referencingElement);
384
+ }
385
+
386
+ /**
387
+ * Creating a new version of referenced element to avoid modifying the original one.
388
+ */
389
+ const mergedElement = cloneShallow(referencedElement);
390
+ // annotate fragment with info about original Reference element
391
+ mergedElement.meta.set('ref-fields', {
392
+ $ref: toValue(referencingElement.$ref),
393
+ // @ts-ignore
394
+ description: toValue(referencingElement.description),
395
+ // @ts-ignore
396
+ summary: toValue(referencingElement.summary),
397
+ });
398
+ // annotate fragment with info about origin and type
399
+ mergedElement.meta.set('ref-origin', reference.uri);
400
+ mergedElement.meta.set('ref-type', referencingElement.element);
401
+
402
+ // override description and summary (outer has higher priority then inner)
403
+ if (isObjectElement(referencedElement) && isObjectElement(mergedElement)) {
404
+ const fields = fixedFields(referencedElement, { indexed: true });
405
+
406
+ if (referencingElement.hasKey('description') && Object.hasOwn(fields, 'description')) {
407
+ mergedElement.remove('description');
408
+ mergedElement.set('description', referencingElement.get('description'));
409
+ }
410
+ if (referencingElement.hasKey('summary') && Object.hasOwn(fields, 'summary')) {
411
+ mergedElement.remove('summary');
412
+ mergedElement.set('summary', referencingElement.get('summary'));
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Transclude referencing element with merged referenced element.
418
+ */
419
+ path.replaceWith(mergedElement);
420
+ } catch (error: unknown) {
421
+ const $ref = toValue(referencingElement.$ref) as string;
422
+ this.handleError(
423
+ `Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${(error as Error).message}`,
424
+ error as Error,
425
+ referencingElement,
426
+ '$ref',
427
+ $ref,
428
+ path,
429
+ );
430
+ } finally {
431
+ if (this.indirections.length > indirectionsSize) this.indirections.pop();
432
+ }
433
+ }
434
+
435
+ public async PathItemElement(path: Path<Element>) {
436
+ const referencingElement = path.node as PathItemElement;
437
+
438
+ // ignore PathItemElement without $ref field
439
+ if (!isStringElement(referencingElement.$ref)) {
440
+ return;
441
+ }
442
+
443
+ // skip current referencing element as it's already been accessed
444
+ if (this.indirections.includes(referencingElement)) {
445
+ path.skip();
446
+ return;
447
+ }
448
+
449
+ const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref) as string);
450
+ const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
451
+ const isExternalReference = !isInternalReference;
452
+
453
+ // ignore resolving internal Path Item Objects
454
+ if (!this.options.resolve.internal && isInternalReference) {
455
+ // skip traversing this Path Item element but traverse all it's child elements
456
+ return;
457
+ }
458
+ // ignore resolving external Path Item Objects
459
+ if (!this.options.resolve.external && isExternalReference) {
460
+ // skip traversing this Path Item element but traverse all it's child elements
461
+ return;
462
+ }
463
+
464
+ const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref) as string);
465
+ const indirectionsSize = this.indirections.length;
466
+
467
+ try {
468
+ const reference = await this.toReference(toValue(referencingElement.$ref) as string);
469
+
470
+ this.indirections.push(referencingElement);
471
+
472
+ const jsonPointer = URIFragmentIdentifier.fromURIReference($refBaseURI);
473
+
474
+ // possibly non-semantic referenced element
475
+ let referencedElement = jsonPointerEvaluate<Element>(
476
+ (reference.value as ParseResultElement).result as Element,
477
+ jsonPointer,
478
+ );
479
+
480
+ // applying semantics to a referenced element
481
+ if (!isPathItemElement(referencedElement)) {
482
+ if (this.refractCache.has(referencedElement)) {
483
+ referencedElement = this.refractCache.get(referencedElement)!;
484
+ } else {
485
+ const sourceElement = referencedElement;
486
+ referencedElement = refractPathItem(referencedElement);
487
+ this.refractCache.set(sourceElement, referencedElement);
488
+ }
489
+ }
490
+
491
+ // detect direct or indirect reference
492
+ if (referencingElement === referencedElement) {
493
+ throw new ApiDOMStructuredError('Recursive Path Item Object reference detected', {
494
+ $ref: toValue(referencingElement.$ref),
495
+ });
496
+ }
497
+
498
+ // detect maximum depth of dereferencing
499
+ if (this.indirections.length > this.options.dereference.maxDepth) {
500
+ throw new MaximumDereferenceDepthError(
501
+ `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`,
502
+ { maxDepth: this.options.dereference.maxDepth, uri: this.reference.uri },
503
+ );
504
+ }
505
+
506
+ // detect cross-boundary cycle
507
+ const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
508
+ if (ancestorsLineage.includes(referencedElement)) {
509
+ reference.refSet!.circular = true;
510
+
511
+ if (this.options.dereference.circular === 'error') {
512
+ throw new ApiDOMStructuredError('Circular reference detected', {
513
+ $ref: toValue(referencingElement.$ref),
514
+ });
515
+ } else if (this.options.dereference.circular === 'replace') {
516
+ const refElement = new RefElement($refBaseURI, {
517
+ type: referencingElement.element,
518
+ uri: reference.uri,
519
+ $ref: toValue(referencingElement.$ref),
520
+ });
521
+ const replacer =
522
+ this.options.dereference.strategyOpts['openapi-3-1']?.circularReplacer ??
523
+ this.options.dereference.circularReplacer;
524
+ const replacement = replacer(refElement);
525
+
526
+ path.replaceWith(replacement);
527
+ return;
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Dive deep into the fragment.
533
+ *
534
+ * Cases to consider:
535
+ * 1. We're crossing document boundary
536
+ * 2. Fragment is from non-entry document
537
+ * 3. Fragment is a Path Item Object with $ref field. We need to follow it to get the eventual value
538
+ * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
539
+ */
540
+ const isNonEntryDocument = url.stripHash(reference.refSet!.rootRef!.uri) !== reference.uri;
541
+ const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
542
+ if (
543
+ (isExternalReference ||
544
+ isNonEntryDocument ||
545
+ (isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref)) ||
546
+ shouldDetectCircular) &&
547
+ !ancestorsLineage.includesCycle(referencedElement)
548
+ ) {
549
+ directAncestors.add(referencingElement);
550
+
551
+ const visitor = new OpenAPI3_1DereferenceVisitor({
552
+ reference,
553
+ indirections: [...this.indirections],
554
+ options: this.options,
555
+ refractCache: this.refractCache,
556
+ ancestors: ancestorsLineage,
557
+ });
558
+ referencedElement = await traverseAsync(referencedElement, visitor, { mutable: true });
559
+
560
+ directAncestors.delete(referencingElement);
561
+ }
562
+
563
+ /**
564
+ * Creating a new version of Path Item by merging fields from referenced Path Item with referencing one.
565
+ */
566
+ if (isPathItemElement(referencedElement)) {
567
+ const mergedElement = cloneShallow<PathItemElement>(referencedElement);
568
+ // existing keywords from referencing PathItemElement overrides ones from referenced element
569
+ referencingElement.forEach((value: Element, keyElement: Element, item: Element) => {
570
+ mergedElement.remove(toValue(keyElement) as string);
571
+ (mergedElement.content as Element[]).push(item);
572
+ });
573
+ mergedElement.remove('$ref');
574
+
575
+ // annotate referenced element with info about original referencing element
576
+ mergedElement.meta.set('ref-fields', {
577
+ $ref: toValue(referencingElement.$ref),
578
+ });
579
+ // annotate referenced element with info about origin and type
580
+ mergedElement.meta.set('ref-origin', reference.uri);
581
+ mergedElement.meta.set('ref-type', referencingElement.element);
582
+
583
+ referencedElement = mergedElement;
584
+ }
585
+
586
+ /**
587
+ * Transclude referencing element with merged referenced element.
588
+ */
589
+ path.replaceWith(referencedElement);
590
+ } catch (error: unknown) {
591
+ const $ref = toValue(referencingElement.$ref) as string;
592
+ this.handleError(
593
+ `Error while dereferencing Path Item Object. Cannot resolve $ref "${$ref}": ${(error as Error).message}`,
594
+ error as Error,
595
+ referencingElement,
596
+ '$ref',
597
+ $ref,
598
+ path,
599
+ );
600
+ } finally {
601
+ if (this.indirections.length > indirectionsSize) this.indirections.pop();
602
+ }
603
+ }
604
+
605
+ public async LinkElement(path: Path<Element>) {
606
+ const linkElement = path.node as LinkElement;
607
+
608
+ // ignore LinkElement without operationRef or operationId field
609
+ if (!isStringElement(linkElement.operationRef) && !isStringElement(linkElement.operationId)) {
610
+ return;
611
+ }
612
+
613
+ // operationRef and operationId fields are mutually exclusive
614
+ if (isStringElement(linkElement.operationRef) && isStringElement(linkElement.operationId)) {
615
+ throw new ApiDOMStructuredError(
616
+ 'LinkElement operationRef and operationId fields are mutually exclusive',
617
+ {
618
+ operationRef: toValue(linkElement.operationRef),
619
+ operationId: toValue(linkElement.operationId),
620
+ },
621
+ );
622
+ }
623
+
624
+ try {
625
+ let operationElement: Element | undefined;
626
+
627
+ if (isStringElement(linkElement.operationRef)) {
628
+ // possibly non-semantic referenced element
629
+ const jsonPointer = URIFragmentIdentifier.fromURIReference(
630
+ toValue(linkElement.operationRef) as string,
631
+ );
632
+ const retrievalURI = this.toBaseURI(toValue(linkElement.operationRef) as string);
633
+ const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
634
+ const isExternalReference = !isInternalReference;
635
+
636
+ // ignore resolving internal Operation Object reference
637
+ if (!this.options.resolve.internal && isInternalReference) {
638
+ // skip traversing this Link element but traverse all it's child elements
639
+ return;
640
+ }
641
+ // ignore resolving external Operation Object reference
642
+ if (!this.options.resolve.external && isExternalReference) {
643
+ // skip traversing this Link element but traverse all it's child elements
644
+ return;
645
+ }
646
+
647
+ const reference = await this.toReference(toValue(linkElement.operationRef) as string);
648
+
649
+ operationElement = jsonPointerEvaluate<OperationElement>(
650
+ (reference.value as ParseResultElement).result as Element,
651
+ jsonPointer,
652
+ );
653
+ // applying semantics to a referenced element
654
+ if (!isOperationElement(operationElement)) {
655
+ if (this.refractCache.has(operationElement)) {
656
+ operationElement = this.refractCache.get(operationElement)!;
657
+ } else {
658
+ const sourceElement = operationElement;
659
+ operationElement = refractOperation(operationElement);
660
+ this.refractCache.set(sourceElement, operationElement);
661
+ }
662
+ }
663
+ // create shallow clone to be able to annotate with metadata
664
+ operationElement = cloneShallow(operationElement);
665
+ // annotate operation element with info about origin and type
666
+ operationElement.meta.set('ref-origin', reference.uri);
667
+ operationElement.meta.set('ref-type', linkElement.element);
668
+
669
+ const linkElementCopy = cloneShallow(linkElement);
670
+ linkElementCopy.operationRef?.meta.set('operation', operationElement);
671
+
672
+ /**
673
+ * Transclude Link Object containing Operation Object in its meta.
674
+ */
675
+ path.replaceWith(linkElementCopy);
676
+ return;
677
+ }
678
+
679
+ if (isStringElement(linkElement.operationId)) {
680
+ const operationId = toValue(linkElement.operationId) as string;
681
+ const reference = await this.toReference(url.unsanitize(this.reference.uri));
682
+ const operationPath = find(
683
+ (reference.value as ParseResultElement).result as Element,
684
+ (path) =>
685
+ isOperationElement(path.node) &&
686
+ isElement(path.node.operationId) &&
687
+ path.node.operationId.equals(operationId),
688
+ );
689
+ operationElement = operationPath?.node;
690
+ // OperationElement not found by its operationId
691
+ if (isUndefined(operationElement)) {
692
+ throw new ApiDOMStructuredError(
693
+ `OperationElement(operationId=${operationId}) not found`,
694
+ { operationId },
695
+ );
696
+ }
697
+
698
+ const linkElementCopy = cloneShallow(linkElement);
699
+ linkElementCopy.operationId?.meta.set('operation', operationElement);
700
+
701
+ /**
702
+ * Transclude Link Object containing Operation Object in its meta.
703
+ */
704
+ path.replaceWith(linkElementCopy);
705
+ }
706
+ } catch (error: unknown) {
707
+ const refFieldName = isStringElement(linkElement.operationRef)
708
+ ? 'operationRef'
709
+ : 'operationId';
710
+ const refFieldValue = isStringElement(linkElement.operationRef)
711
+ ? (toValue(linkElement.operationRef) as string)
712
+ : (toValue(linkElement.operationId) as string);
713
+ this.handleError(
714
+ `Error while dereferencing Link Object. Cannot resolve ${refFieldName} "${refFieldValue}": ${(error as Error).message}`,
715
+ error as Error,
716
+ linkElement,
717
+ refFieldName,
718
+ refFieldValue,
719
+ path,
720
+ );
721
+ }
722
+ }
723
+
724
+ public async ExampleElement(path: Path<Element>) {
725
+ const exampleElement = path.node as ExampleElement;
726
+
727
+ // ignore ExampleElement without externalValue field
728
+ if (!isStringElement(exampleElement.externalValue)) {
729
+ return;
730
+ }
731
+
732
+ // value and externalValue fields are mutually exclusive
733
+ if (exampleElement.hasKey('value') && isStringElement(exampleElement.externalValue)) {
734
+ throw new ApiDOMStructuredError(
735
+ 'ExampleElement value and externalValue fields are mutually exclusive',
736
+ {
737
+ value: toValue(exampleElement.value),
738
+ externalValue: toValue(exampleElement.externalValue),
739
+ },
740
+ );
741
+ }
742
+
743
+ const retrievalURI = this.toBaseURI(toValue(exampleElement.externalValue) as string);
744
+ const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
745
+ const isExternalReference = !isInternalReference;
746
+
747
+ // ignore resolving internal Example Objects
748
+ if (!this.options.resolve.internal && isInternalReference) {
749
+ // skip traversing this Example element but traverse all it's child elements
750
+ return;
751
+ }
752
+ // ignore resolving external Example Objects
753
+ if (!this.options.resolve.external && isExternalReference) {
754
+ // skip traversing this Example element but traverse all it's child elements
755
+ return;
756
+ }
757
+
758
+ try {
759
+ const reference = await this.toReference(toValue(exampleElement.externalValue) as string);
760
+
761
+ // shallow clone of the referenced element
762
+ const valueElement = cloneShallow((reference.value as ParseResultElement).result as Element);
763
+ // annotate element with info about origin and type
764
+ valueElement.meta.set('ref-origin', reference.uri);
765
+ valueElement.meta.set('ref-type', exampleElement.element);
766
+
767
+ const exampleElementCopy = cloneShallow(exampleElement);
768
+ exampleElementCopy.value = valueElement;
769
+
770
+ /**
771
+ * Transclude Example Object containing external value.
772
+ */
773
+ path.replaceWith(exampleElementCopy);
774
+ } catch (error: unknown) {
775
+ const externalValue = toValue(exampleElement.externalValue) as string;
776
+ this.handleError(
777
+ `Error while dereferencing Example Object. Cannot resolve externalValue "${externalValue}": ${(error as Error).message}`,
778
+ error as Error,
779
+ exampleElement,
780
+ 'externalValue',
781
+ externalValue,
782
+ path,
783
+ );
784
+ }
785
+ }
786
+
787
+ public async SchemaElement(path: Path<Element>) {
788
+ const referencingElement = path.node as SchemaElement;
789
+
790
+ // skip current referencing schema as $ref keyword was not defined
791
+ if (!isStringElement(referencingElement.$ref)) {
792
+ return;
793
+ }
794
+
795
+ // skip current referencing element as it's already been accessed
796
+ if (this.indirections.includes(referencingElement)) {
797
+ path.skip();
798
+ return;
799
+ }
800
+
801
+ const indirectionsSize = this.indirections.length;
802
+
803
+ try {
804
+ // compute baseURI using rules around $id and $ref keywords
805
+ let reference = await this.toReference(url.unsanitize(this.reference.uri));
806
+ let { uri: retrievalURI } = reference;
807
+ const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement)!;
808
+ const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
809
+ const file = new File({ uri: $refBaseURIStrippedHash });
810
+ const isUnknownURI = none((r: Resolver) => r.canRead(file), this.options.resolve.resolvers);
811
+ const isURL = !isUnknownURI;
812
+ let isInternalReference = url.stripHash(this.reference.uri) === $refBaseURI;
813
+ let isExternalReference = !isInternalReference;
814
+
815
+ // determining reference, proper evaluation and selection mechanism
816
+ let referencedElement: Element;
817
+
818
+ try {
819
+ if (isUnknownURI || isURL) {
820
+ // we're dealing with canonical URI or URL with possible fragment
821
+ retrievalURI = this.toBaseURI($refBaseURI);
822
+ const selector = $refBaseURI;
823
+ const referenceAsSchema = maybeRefractToSchemaElement(
824
+ (reference.value as ParseResultElement).result as Element,
825
+ );
826
+ referencedElement = uriEvaluate(selector, referenceAsSchema)!;
827
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
828
+
829
+ // ignore resolving internal Schema Objects
830
+ if (!this.options.resolve.internal && isInternalReference) {
831
+ // skip traversing this schema element but traverse all it's child elements
832
+ return;
833
+ }
834
+ // ignore resolving external Schema Objects
835
+ if (!this.options.resolve.external && isExternalReference) {
836
+ // skip traversing this schema element but traverse all it's child elements
837
+ return;
838
+ }
839
+ } else {
840
+ // we're assuming here that we're dealing with JSON Pointer here
841
+ retrievalURI = this.toBaseURI($refBaseURI);
842
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
843
+ isExternalReference = !isInternalReference;
844
+
845
+ // ignore resolving internal Schema Objects
846
+ if (!this.options.resolve.internal && isInternalReference) {
847
+ // skip traversing this schema element but traverse all it's child elements
848
+ return;
849
+ }
850
+ // ignore resolving external Schema Objects
851
+ if (!this.options.resolve.external && isExternalReference) {
852
+ // skip traversing this schema element but traverse all it's child elements
853
+ return;
854
+ }
855
+
856
+ reference = await this.toReference(url.unsanitize($refBaseURI));
857
+ const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
858
+ const referenceAsSchema = maybeRefractToSchemaElement(
859
+ (reference.value as ParseResultElement).result as Element,
860
+ );
861
+ referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
862
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
863
+ }
864
+ } catch (error) {
865
+ /**
866
+ * SchemaElement($id=URL) was not found, so we're going to try to resolve
867
+ * the URL and assume the returned response is a JSON Schema.
868
+ */
869
+ if (isURL && error instanceof EvaluationJsonSchemaUriError) {
870
+ if (isAnchor(uriToAnchor($refBaseURI))) {
871
+ // we're dealing with JSON Schema $anchor here
872
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
873
+ isExternalReference = !isInternalReference;
874
+
875
+ // ignore resolving internal Schema Objects
876
+ if (!this.options.resolve.internal && isInternalReference) {
877
+ // skip traversing this schema element but traverse all it's child elements
878
+ return;
879
+ }
880
+ // ignore resolving external Schema Objects
881
+ if (!this.options.resolve.external && isExternalReference) {
882
+ // skip traversing this schema element but traverse all it's child elements
883
+ return;
884
+ }
885
+
886
+ reference = await this.toReference(url.unsanitize($refBaseURI));
887
+ const selector = uriToAnchor($refBaseURI);
888
+ const referenceAsSchema = maybeRefractToSchemaElement(
889
+ (reference.value as ParseResultElement).result as Element,
890
+ );
891
+ referencedElement = $anchorEvaluate(selector, referenceAsSchema)!;
892
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
893
+ } else {
894
+ // we're assuming here that we're dealing with JSON Pointer here
895
+ retrievalURI = this.toBaseURI($refBaseURI);
896
+ isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
897
+ isExternalReference = !isInternalReference;
898
+
899
+ // ignore resolving internal Schema Objects
900
+ if (!this.options.resolve.internal && isInternalReference) {
901
+ // skip traversing this schema element but traverse all it's child elements
902
+ return;
903
+ }
904
+ // ignore resolving external Schema Objects
905
+ if (!this.options.resolve.external && isExternalReference) {
906
+ // skip traversing this schema element but traverse all it's child elements
907
+ return;
908
+ }
909
+
910
+ reference = await this.toReference(url.unsanitize($refBaseURI));
911
+ const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
912
+ const referenceAsSchema = maybeRefractToSchemaElement(
913
+ (reference.value as ParseResultElement).result as Element,
914
+ );
915
+ referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
916
+ referencedElement = maybeRefractToSchemaElement(referencedElement);
917
+ }
918
+ } else {
919
+ throw error;
920
+ }
921
+ }
922
+
923
+ this.indirections.push(referencingElement);
924
+
925
+ // detect direct or indirect reference
926
+ if (referencingElement === referencedElement) {
927
+ throw new ApiDOMStructuredError('Recursive Schema Object reference detected', {
928
+ $ref: toValue(referencingElement.$ref),
929
+ });
930
+ }
931
+
932
+ // detect maximum depth of dereferencing
933
+ if (this.indirections.length > this.options.dereference.maxDepth) {
934
+ throw new MaximumDereferenceDepthError(
935
+ `Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`,
936
+ { maxDepth: this.options.dereference.maxDepth, uri: this.reference.uri },
937
+ );
938
+ }
939
+
940
+ // detect cross-boundary cycle
941
+ const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
942
+ if (ancestorsLineage.includes(referencedElement)) {
943
+ reference.refSet!.circular = true;
944
+
945
+ if (this.options.dereference.circular === 'error') {
946
+ throw new ApiDOMStructuredError('Circular reference detected', {
947
+ $ref: toValue(referencingElement.$ref),
948
+ });
949
+ } else if (this.options.dereference.circular === 'replace') {
950
+ const refElement = new RefElement($refBaseURI, {
951
+ type: referencingElement.element,
952
+ uri: reference.uri,
953
+ $ref: toValue(referencingElement.$ref),
954
+ });
955
+ const replacer =
956
+ this.options.dereference.strategyOpts['openapi-3-1']?.circularReplacer ??
957
+ this.options.dereference.circularReplacer;
958
+ const replacement = replacer(refElement);
959
+
960
+ path.replaceWith(replacement);
961
+ return;
962
+ }
963
+ }
964
+
965
+ /**
966
+ * Dive deep into the fragment.
967
+ *
968
+ * Cases to consider:
969
+ * 1. We're crossing document boundary
970
+ * 2. Fragment is from non-entry document
971
+ * 3. Fragment is a Schema Object with $ref field. We need to follow it to get the eventual value
972
+ * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
973
+ */
974
+ const isNonEntryDocument = url.stripHash(reference.refSet!.rootRef!.uri) !== reference.uri;
975
+ const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
976
+ if (
977
+ (isExternalReference ||
978
+ isNonEntryDocument ||
979
+ (isSchemaElement(referencedElement) && isStringElement(referencedElement.$ref)) ||
980
+ shouldDetectCircular) &&
981
+ !ancestorsLineage.includesCycle(referencedElement)
982
+ ) {
983
+ directAncestors.add(referencingElement);
984
+
985
+ const visitor = new OpenAPI3_1DereferenceVisitor({
986
+ reference,
987
+ indirections: [...this.indirections],
988
+ options: this.options,
989
+ refractCache: this.refractCache,
990
+ ancestors: ancestorsLineage,
991
+ });
992
+ referencedElement = await traverseAsync(referencedElement, visitor, { mutable: true });
993
+
994
+ directAncestors.delete(referencingElement);
995
+ }
996
+
997
+ // Boolean JSON Schemas
998
+ if (isBooleanJSONSchemaElement(referencedElement)) {
999
+ const booleanJsonSchemaElement = cloneDeep<BooleanElement>(referencedElement);
1000
+ // annotate referenced element with info about original referencing element
1001
+ booleanJsonSchemaElement.meta.set('ref-fields', {
1002
+ $ref: toValue(referencingElement.$ref),
1003
+ });
1004
+ // annotate referenced element with info about origin and type
1005
+ booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
1006
+ booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
1007
+
1008
+ path.replaceWith(booleanJsonSchemaElement);
1009
+ return;
1010
+ }
1011
+
1012
+ /**
1013
+ * Creating a new version of Schema Object by merging fields from referenced Schema Object with referencing one.
1014
+ */
1015
+ if (isSchemaElement(referencedElement)) {
1016
+ const mergedElement = cloneShallow<SchemaElement>(referencedElement);
1017
+ // existing keywords from referencing schema overrides ones from referenced schema
1018
+ referencingElement.forEach((value: Element, keyElement: Element, item: Element) => {
1019
+ mergedElement.remove(toValue(keyElement) as string);
1020
+ (mergedElement.content as Element[]).push(item);
1021
+ });
1022
+ mergedElement.remove('$ref');
1023
+ // annotate referenced element with info about original referencing element
1024
+ mergedElement.meta.set('ref-fields', {
1025
+ $ref: toValue(referencingElement.$ref),
1026
+ });
1027
+ // annotate fragment with info about origin and type
1028
+ mergedElement.meta.set('ref-origin', reference.uri);
1029
+ mergedElement.meta.set('ref-type', referencingElement.element);
1030
+
1031
+ referencedElement = mergedElement;
1032
+ }
1033
+ /**
1034
+ * Transclude referencing element with merged referenced element.
1035
+ */
1036
+ path.replaceWith(referencedElement);
1037
+ } catch (error: unknown) {
1038
+ const $ref = toValue(referencingElement.$ref) as string;
1039
+ this.handleError(
1040
+ `Error while dereferencing Schema Object. Cannot resolve $ref "${$ref}": ${(error as Error).message}`,
1041
+ error as Error,
1042
+ referencingElement,
1043
+ '$ref',
1044
+ $ref,
1045
+ path,
1046
+ );
1047
+ } finally {
1048
+ if (this.indirections.length > indirectionsSize) this.indirections.pop();
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ export default OpenAPI3_1DereferenceVisitor;