source: node_modules/@swagger-api/apidom-reference/es/dereference/strategies/asyncapi-2/visitor.mjs

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 12.9 KB
Line 
1import stampit from 'stampit';
2import { propEq } from 'ramda';
3import { isElement, isMemberElement, isPrimitiveElement, isStringElement, IdentityManager, cloneDeep, cloneShallow, visit, toValue } from '@swagger-api/apidom-core';
4import { ApiDOMError } from '@swagger-api/apidom-error';
5import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
6import { ChannelItemElement, getNodeType, isReferenceLikeElement, isBooleanJsonSchemaElement, keyMap, ReferenceElement } from '@swagger-api/apidom-ns-asyncapi-2';
7import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
8import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
9import { AncestorLineage } from "../../util.mjs";
10import * as url from "../../../util/url.mjs";
11import parse from "../../../parse/index.mjs";
12import Reference from "../../../Reference.mjs"; // @ts-ignore
13const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
14
15// initialize element identity manager
16const identityManager = IdentityManager();
17
18/**
19 * Predicate for detecting if element was created by merging referencing
20 * element with particular element identity with a referenced element.
21 */
22const wasReferencedBy = referencingElement => element => element.meta.hasKey('ref-referencing-element-id') && element.meta.get('ref-referencing-element-id').equals(toValue(identityManager.identify(referencingElement)));
23const AsyncApi2DereferenceVisitor = stampit({
24 props: {
25 indirections: [],
26 namespace: null,
27 reference: null,
28 options: null,
29 ancestors: null
30 },
31 init({
32 indirections = [],
33 reference,
34 namespace,
35 options,
36 ancestors = new AncestorLineage()
37 }) {
38 this.indirections = indirections;
39 this.namespace = namespace;
40 this.reference = reference;
41 this.options = options;
42 this.ancestors = new AncestorLineage(...ancestors);
43 },
44 methods: {
45 toBaseURI(uri) {
46 return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
47 },
48 async toReference(uri) {
49 // detect maximum depth of resolution
50 if (this.reference.depth >= this.options.resolve.maxDepth) {
51 throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);
52 }
53 const baseURI = this.toBaseURI(uri);
54 const {
55 refSet
56 } = this.reference;
57
58 // we've already processed this Reference in past
59 if (refSet.has(baseURI)) {
60 return refSet.find(propEq(baseURI, 'uri'));
61 }
62 const parseResult = await parse(url.unsanitize(baseURI), {
63 ...this.options,
64 parse: {
65 ...this.options.parse,
66 mediaType: 'text/plain'
67 }
68 });
69
70 // register new Reference with ReferenceSet
71 const reference = Reference({
72 uri: baseURI,
73 value: parseResult,
74 depth: this.reference.depth + 1
75 });
76 refSet.add(reference);
77 return reference;
78 },
79 toAncestorLineage(ancestors) {
80 /**
81 * Compute full ancestors lineage.
82 * Ancestors are flatten to unwrap all Element instances.
83 */
84 const directAncestors = new Set(ancestors.filter(isElement));
85 const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);
86 return [ancestorsLineage, directAncestors];
87 },
88 async ReferenceElement(referencingElement, key, parent, path, ancestors) {
89 const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
90
91 // detect possible cycle in traversal and avoid it
92 if (ancestorsLineage.includesCycle(referencingElement)) {
93 return false;
94 }
95 const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
96
97 // ignore resolving external Reference Objects
98 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
99 // skip traversing this reference element but traverse all it's child elements
100 return false;
101 }
102 const reference = await this.toReference(toValue(referencingElement.$ref));
103 const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref));
104 this.indirections.push(referencingElement);
105 const jsonPointer = uriToPointer($refBaseURI);
106
107 // possibly non-semantic fragment
108 let referencedElement = evaluate(jsonPointer, reference.value.result);
109
110 // applying semantics to a fragment
111 if (isPrimitiveElement(referencedElement)) {
112 const referencedElementType = toValue(referencingElement.meta.get('referenced-element'));
113 if (isReferenceLikeElement(referencedElement)) {
114 // handling indirect references
115 referencedElement = ReferenceElement.refract(referencedElement);
116 referencedElement.setMetaProperty('referenced-element', referencedElementType);
117 } else {
118 // handling direct references
119 const ElementClass = this.namespace.getElementClass(referencedElementType);
120 referencedElement = ElementClass.refract(referencedElement);
121 }
122 }
123
124 // detect direct or circular reference
125 if (this.indirections.includes(referencedElement)) {
126 throw new ApiDOMError('Recursive Reference Object detected');
127 }
128
129 // detect maximum depth of dereferencing
130 if (this.indirections.length > this.options.dereference.maxDepth) {
131 throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
132 }
133
134 // append referencing reference to ancestors lineage
135 directAncestors.add(referencingElement);
136
137 // dive deep into the fragment
138 const visitor = AsyncApi2DereferenceVisitor({
139 reference,
140 namespace: this.namespace,
141 indirections: [...this.indirections],
142 options: this.options,
143 ancestors: ancestorsLineage
144 });
145 referencedElement = await visitAsync(referencedElement, visitor, {
146 keyMap,
147 nodeTypeGetter: getNodeType
148 });
149
150 // remove referencing reference from ancestors lineage
151 directAncestors.delete(referencingElement);
152 this.indirections.pop();
153
154 // Boolean JSON Schemas
155 if (isBooleanJsonSchemaElement(referencedElement)) {
156 const booleanJsonSchemaElement = cloneDeep(referencedElement);
157 // annotate referenced element with info about original referencing element
158 booleanJsonSchemaElement.setMetaProperty('ref-fields', {
159 $ref: toValue(referencingElement.$ref)
160 });
161 // annotate referenced element with info about origin
162 booleanJsonSchemaElement.setMetaProperty('ref-origin', reference.uri);
163 // annotate fragment with info about referencing element
164 booleanJsonSchemaElement.setMetaProperty('ref-referencing-element-id', cloneDeep(identityManager.identify(referencingElement)));
165 return booleanJsonSchemaElement;
166 }
167 const mergeAndAnnotateReferencedElement = refedElement => {
168 const copy = cloneShallow(refedElement);
169
170 // annotate referenced element with info about original referencing element
171 copy.setMetaProperty('ref-fields', {
172 $ref: toValue(referencingElement.$ref)
173 });
174 // annotate fragment with info about origin
175 copy.setMetaProperty('ref-origin', reference.uri);
176 // annotate fragment with info about referencing element
177 copy.setMetaProperty('ref-referencing-element-id', cloneDeep(identityManager.identify(referencingElement)));
178 return copy;
179 };
180
181 // attempting to create cycle
182 if (ancestorsLineage.includes(referencingElement) || ancestorsLineage.includes(referencedElement)) {
183 var _ancestorsLineage$fin;
184 const replaceWith = (_ancestorsLineage$fin = ancestorsLineage.findItem(wasReferencedBy(referencingElement))) !== null && _ancestorsLineage$fin !== void 0 ? _ancestorsLineage$fin : mergeAndAnnotateReferencedElement(referencedElement);
185 if (isMemberElement(parent)) {
186 parent.value = replaceWith; // eslint-disable-line no-param-reassign
187 } else if (Array.isArray(parent)) {
188 parent[key] = replaceWith; // eslint-disable-line no-param-reassign
189 }
190 return false;
191 }
192
193 // transclude referencing element with merged referenced element
194 return mergeAndAnnotateReferencedElement(referencedElement);
195 },
196 async ChannelItemElement(referencingElement, key, parent, path, ancestors) {
197 const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
198
199 // ignore ChannelItemElement without $ref field
200 if (!isStringElement(referencingElement.$ref)) {
201 return undefined;
202 }
203
204 // detect possible cycle in traversal and avoid it
205 if (ancestorsLineage.includesCycle(referencingElement)) {
206 return false;
207 }
208 const retrievalURI = this.toBaseURI(toValue(referencingElement.$ref));
209
210 // ignore resolving external Channel Item Objects
211 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
212 // skip traversing this channel item but traverse all it's child elements
213 return undefined;
214 }
215 const reference = await this.toReference(toValue(referencingElement.$ref));
216 const $refBaseURI = url.resolve(retrievalURI, toValue(referencingElement.$ref));
217 this.indirections.push(referencingElement);
218 const jsonPointer = uriToPointer($refBaseURI);
219
220 // possibly non-semantic referenced element
221 let referencedElement = evaluate(jsonPointer, reference.value.result);
222
223 // applying semantics to a referenced element
224 if (isPrimitiveElement(referencedElement)) {
225 referencedElement = ChannelItemElement.refract(referencedElement);
226 }
227
228 // detect direct or indirect reference
229 if (this.indirections.includes(referencedElement)) {
230 throw new ApiDOMError('Recursive Channel Item Object reference detected');
231 }
232
233 // detect maximum depth of dereferencing
234 if (this.indirections.length > this.options.dereference.maxDepth) {
235 throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
236 }
237
238 // append referencing channel item to ancestors lineage
239 directAncestors.add(referencingElement);
240
241 // dive deep into the referenced element
242 const visitor = AsyncApi2DereferenceVisitor({
243 reference,
244 namespace: this.namespace,
245 indirections: [...this.indirections],
246 options: this.options,
247 ancestors: ancestorsLineage
248 });
249 referencedElement = await visitAsync(referencedElement, visitor, {
250 keyMap,
251 nodeTypeGetter: getNodeType
252 });
253
254 // remove referencing channel item from ancestors lineage
255 directAncestors.delete(referencingElement);
256 this.indirections.pop();
257 const mergeAndAnnotateReferencedElement = refedElement => {
258 // merge fields from referenced Channel Item with referencing one
259 const mergedElement = new ChannelItemElement([...refedElement.content], cloneDeep(refedElement.meta), cloneDeep(refedElement.attributes));
260 // existing keywords from referencing ChannelItemElement overrides ones from referenced ChannelItemElement
261 referencingElement.forEach((value, keyElement, item) => {
262 mergedElement.remove(toValue(keyElement));
263 mergedElement.content.push(item);
264 });
265 mergedElement.remove('$ref');
266
267 // annotate referenced element with info about original referencing element
268 mergedElement.setMetaProperty('ref-fields', {
269 $ref: toValue(referencingElement.$ref)
270 });
271 // annotate referenced with info about origin
272 mergedElement.setMetaProperty('ref-origin', reference.uri);
273 // annotate fragment with info about referencing element
274 mergedElement.setMetaProperty('ref-referencing-element-id', cloneDeep(identityManager.identify(referencingElement)));
275 return mergedElement;
276 };
277
278 // attempting to create cycle
279 if (ancestorsLineage.includes(referencingElement) || ancestorsLineage.includes(referencedElement)) {
280 var _ancestorsLineage$fin2;
281 const replaceWith = (_ancestorsLineage$fin2 = ancestorsLineage.findItem(wasReferencedBy(referencingElement))) !== null && _ancestorsLineage$fin2 !== void 0 ? _ancestorsLineage$fin2 : mergeAndAnnotateReferencedElement(referencedElement);
282 if (isMemberElement(parent)) {
283 parent.value = replaceWith; // eslint-disable-line no-param-reassign
284 } else if (Array.isArray(parent)) {
285 parent[key] = replaceWith; // eslint-disable-line no-param-reassign
286 }
287 return false;
288 }
289
290 // transclude referencing element with merged referenced element
291 return mergeAndAnnotateReferencedElement(referencedElement);
292 }
293 }
294});
295export default AsyncApi2DereferenceVisitor;
Note: See TracBrowser for help on using the repository browser.