source: node_modules/@swagger-api/apidom-reference/es/resolve/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: 7.6 KB
Line 
1import stampit from 'stampit';
2import { propEq, values, has, pipe } from 'ramda';
3import { allP } from 'ramda-adjunct';
4import { isPrimitiveElement, isStringElement, visit, toValue } from '@swagger-api/apidom-core';
5import { ApiDOMError } from '@swagger-api/apidom-error';
6import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
7import { getNodeType, isReferenceElement, isChannelItemElement, isReferenceLikeElement, keyMap, ReferenceElement, ChannelItemElement } from '@swagger-api/apidom-ns-asyncapi-2';
8import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
9import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.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')];
14const AsyncApi2ResolveVisitor = stampit({
15 props: {
16 indirections: [],
17 namespace: null,
18 reference: null,
19 crawledElements: null,
20 crawlingMap: null,
21 options: null
22 },
23 init({
24 reference,
25 namespace,
26 indirections = [],
27 options
28 }) {
29 this.indirections = indirections;
30 this.namespace = namespace;
31 this.reference = reference;
32 this.crawledElements = [];
33 this.crawlingMap = {};
34 this.options = options;
35 },
36 methods: {
37 toBaseURI(uri) {
38 return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
39 },
40 async toReference(uri) {
41 // detect maximum depth of resolution
42 if (this.reference.depth >= this.options.resolve.maxDepth) {
43 throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);
44 }
45 const baseURI = this.toBaseURI(uri);
46 const {
47 refSet
48 } = this.reference;
49
50 // we've already processed this Reference in past
51 if (refSet.has(baseURI)) {
52 return refSet.find(propEq(baseURI, 'uri'));
53 }
54 const parseResult = await parse(url.unsanitize(baseURI), {
55 ...this.options,
56 parse: {
57 ...this.options.parse,
58 mediaType: 'text/plain'
59 }
60 });
61
62 // register new Reference with ReferenceSet
63 const reference = Reference({
64 uri: baseURI,
65 value: parseResult,
66 depth: this.reference.depth + 1
67 });
68 refSet.add(reference);
69 return reference;
70 },
71 ReferenceElement(referenceElement) {
72 const uri = toValue(referenceElement.$ref);
73 const retrievalURI = this.toBaseURI(uri);
74
75 // ignore resolving external Reference Objects
76 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
77 return false;
78 }
79 if (!has(retrievalURI, this.crawlingMap)) {
80 this.crawlingMap[retrievalURI] = this.toReference(uri);
81 }
82 this.crawledElements.push(referenceElement);
83 return undefined;
84 },
85 ChannelItemElement(channelItemElement) {
86 // ignore PathItemElement without $ref field
87 if (!isStringElement(channelItemElement.$ref)) {
88 return undefined;
89 }
90 const uri = toValue(channelItemElement.$ref);
91 const retrievalURI = this.toBaseURI(uri);
92
93 // ignore resolving external Channel Item Objects
94 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
95 return undefined;
96 }
97 if (!has(retrievalURI, this.crawlingMap)) {
98 this.crawlingMap[retrievalURI] = this.toReference(uri);
99 }
100 this.crawledElements.push(channelItemElement);
101 return undefined;
102 },
103 async crawlReferenceElement(referenceElement) {
104 // @ts-ignore
105 const reference = await this.toReference(toValue(referenceElement.$ref));
106 this.indirections.push(referenceElement);
107 const jsonPointer = uriToPointer(toValue(referenceElement.$ref));
108
109 // possibly non-semantic fragment
110 let fragment = evaluate(jsonPointer, reference.value.result);
111
112 // applying semantics to a fragment
113 if (isPrimitiveElement(fragment)) {
114 const referencedElementType = toValue(referenceElement.meta.get('referenced-element'));
115 if (isReferenceLikeElement(fragment)) {
116 // handling indirect references
117 fragment = ReferenceElement.refract(fragment);
118 fragment.setMetaProperty('referenced-element', referencedElementType);
119 } else {
120 // handling direct references
121 const ElementClass = this.namespace.getElementClass(referencedElementType);
122 fragment = ElementClass.refract(fragment);
123 }
124 }
125
126 // detect direct or circular reference
127 if (this.indirections.includes(fragment)) {
128 throw new ApiDOMError('Recursive Reference Object detected');
129 }
130
131 // detect maximum depth of dereferencing
132 if (this.indirections.length > this.options.dereference.maxDepth) {
133 throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
134 }
135
136 // dive deep into the fragment
137 const visitor = AsyncApi2ResolveVisitor({
138 reference,
139 namespace: this.namespace,
140 indirections: [...this.indirections],
141 options: this.options
142 });
143 await visitAsync(fragment, visitor, {
144 keyMap,
145 nodeTypeGetter: getNodeType
146 });
147 await visitor.crawl();
148 this.indirections.pop();
149 },
150 async crawlChannelItemElement(channelItemElement) {
151 const reference = await this.toReference(toValue(channelItemElement.$ref));
152 this.indirections.push(channelItemElement);
153 const jsonPointer = uriToPointer(toValue(channelItemElement.$ref));
154
155 // possibly non-semantic referenced element
156 let referencedElement = evaluate(jsonPointer, reference.value.result);
157
158 // applying semantics to a referenced element
159 if (isPrimitiveElement(referencedElement)) {
160 referencedElement = ChannelItemElement.refract(referencedElement);
161 }
162
163 // detect direct or indirect reference
164 if (this.indirections.includes(referencedElement)) {
165 throw new ApiDOMError('Recursive Channel Item Object reference detected');
166 }
167
168 // detect maximum depth of dereferencing
169 if (this.indirections.length > this.options.dereference.maxDepth) {
170 throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
171 }
172
173 // dive deep into the referenced element
174 const visitor = AsyncApi2ResolveVisitor({
175 reference,
176 namespace: this.namespace,
177 indirections: [...this.indirections],
178 options: this.options
179 });
180 await visitAsync(referencedElement, visitor, {
181 keyMap,
182 nodeTypeGetter: getNodeType
183 });
184 await visitor.crawl();
185 this.indirections.pop();
186 },
187 async crawl() {
188 /**
189 * Synchronize all parallel resolutions in this place.
190 * After synchronization happened we can be sure that refSet
191 * contains resolved Reference objects.
192 */
193 await pipe(values, allP)(this.crawlingMap);
194 this.crawlingMap = null;
195
196 /* eslint-disable no-await-in-loop */
197 for (const element of this.crawledElements) {
198 if (isReferenceElement(element)) {
199 await this.crawlReferenceElement(element);
200 } else if (isChannelItemElement(element)) {
201 await this.crawlChannelItemElement(element);
202 }
203 }
204 /* eslint-enabled */
205 }
206 }
207});
208export default AsyncApi2ResolveVisitor;
Note: See TracBrowser for help on using the repository browser.