source: node_modules/@swagger-api/apidom-reference/cjs/dereference/strategies/openapi-3-0/visitor.cjs

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: 17.9 KB
Line 
1"use strict";
2
3var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
4var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
5exports.__esModule = true;
6exports.default = void 0;
7var _stampit = _interopRequireDefault(require("stampit"));
8var _ramda = require("ramda");
9var _ramdaAdjunct = require("ramda-adjunct");
10var _apidomCore = require("@swagger-api/apidom-core");
11var _apidomError = require("@swagger-api/apidom-error");
12var _apidomJsonPointer = require("@swagger-api/apidom-json-pointer");
13var _apidomNsOpenapi = require("@swagger-api/apidom-ns-openapi-3-0");
14var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
15var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
16var _util = require("../../util.cjs");
17var url = _interopRequireWildcard(require("../../../util/url.cjs"));
18var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
19var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
20// @ts-ignore
21const visitAsync = _apidomCore.visit[Symbol.for('nodejs.util.promisify.custom')];
22
23// initialize element identity manager
24const identityManager = (0, _apidomCore.IdentityManager)();
25
26/**
27 * Predicate for detecting if element was created by merging referencing
28 * element with particular element identity with a referenced element.
29 */
30const wasReferencedBy = referencingElement => element => element.meta.hasKey('ref-referencing-element-id') && element.meta.get('ref-referencing-element-id').equals((0, _apidomCore.toValue)(identityManager.identify(referencingElement)));
31
32// eslint-disable-next-line @typescript-eslint/naming-convention
33const OpenApi3_0DereferenceVisitor = (0, _stampit.default)({
34 props: {
35 indirections: [],
36 namespace: null,
37 reference: null,
38 options: null,
39 ancestors: null
40 },
41 init({
42 indirections = [],
43 reference,
44 namespace,
45 options,
46 ancestors = new _util.AncestorLineage()
47 }) {
48 this.indirections = indirections;
49 this.namespace = namespace;
50 this.reference = reference;
51 this.options = options;
52 this.ancestors = new _util.AncestorLineage(...ancestors);
53 },
54 methods: {
55 toBaseURI(uri) {
56 return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
57 },
58 async toReference(uri) {
59 // detect maximum depth of resolution
60 if (this.reference.depth >= this.options.resolve.maxDepth) {
61 throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);
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((0, _ramda.propEq)(baseURI, 'uri'));
71 }
72 const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
73 ...this.options,
74 parse: {
75 ...this.options.parse,
76 mediaType: 'text/plain'
77 }
78 });
79
80 // register new Reference with ReferenceSet
81 const reference = (0, _Reference.default)({
82 uri: baseURI,
83 value: parseResult,
84 depth: this.reference.depth + 1
85 });
86 refSet.add(reference);
87 return reference;
88 },
89 toAncestorLineage(ancestors) {
90 /**
91 * Compute full ancestors lineage.
92 * Ancestors are flatten to unwrap all Element instances.
93 */
94 const directAncestors = new Set(ancestors.filter(_apidomCore.isElement));
95 const ancestorsLineage = new _util.AncestorLineage(...this.ancestors, directAncestors);
96 return [ancestorsLineage, directAncestors];
97 },
98 async ReferenceElement(referencingElement, key, parent, path, ancestors) {
99 const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
100
101 // detect possible cycle in traversal and avoid it
102 if (ancestorsLineage.includesCycle(referencingElement)) {
103 return false;
104 }
105 const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
106
107 // ignore resolving external Reference Objects
108 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
109 // skip traversing this reference element but traverse all it's child elements
110 return undefined;
111 }
112 const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
113 const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
114 this.indirections.push(referencingElement);
115 const jsonPointer = (0, _apidomJsonPointer.uriToPointer)($refBaseURI);
116
117 // possibly non-semantic fragment
118 let referencedElement = (0, _apidomJsonPointer.evaluate)(jsonPointer, reference.value.result);
119
120 // applying semantics to a fragment
121 if ((0, _apidomCore.isPrimitiveElement)(referencedElement)) {
122 const referencedElementType = (0, _apidomCore.toValue)(referencingElement.meta.get('referenced-element'));
123 if ((0, _apidomNsOpenapi.isReferenceLikeElement)(referencedElement)) {
124 // handling indirect references
125 referencedElement = _apidomNsOpenapi.ReferenceElement.refract(referencedElement);
126 referencedElement.setMetaProperty('referenced-element', referencedElementType);
127 } else {
128 // handling direct references
129 const ElementClass = this.namespace.getElementClass(referencedElementType);
130 referencedElement = ElementClass.refract(referencedElement);
131 }
132 }
133
134 // detect direct or circular reference
135 if (this.indirections.includes(referencedElement)) {
136 throw new _apidomError.ApiDOMError('Recursive Reference Object detected');
137 }
138
139 // detect maximum depth of dereferencing
140 if (this.indirections.length > this.options.dereference.maxDepth) {
141 throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
142 }
143
144 // append referencing reference to ancestors lineage
145 directAncestors.add(referencingElement);
146
147 // dive deep into the fragment
148 const visitor = OpenApi3_0DereferenceVisitor({
149 reference,
150 namespace: this.namespace,
151 indirections: [...this.indirections],
152 options: this.options,
153 ancestors: ancestorsLineage
154 });
155 referencedElement = await visitAsync(referencedElement, visitor, {
156 keyMap: _apidomNsOpenapi.keyMap,
157 nodeTypeGetter: _apidomNsOpenapi.getNodeType
158 });
159
160 // remove referencing reference from ancestors lineage
161 directAncestors.delete(referencingElement);
162 this.indirections.pop();
163 const mergeAndAnnotateReferencedElement = refedElement => {
164 const copy = (0, _apidomCore.cloneShallow)(refedElement);
165
166 // annotate referenced element with info about original referencing element
167 copy.setMetaProperty('ref-fields', {
168 // @ts-ignore
169 $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
170 });
171 // annotate fragment with info about origin
172 copy.setMetaProperty('ref-origin', reference.uri);
173 // annotate fragment with info about referencing element
174 copy.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement)));
175 return copy;
176 };
177
178 // attempting to create cycle
179 if (ancestorsLineage.includes(referencingElement) || ancestorsLineage.includes(referencedElement)) {
180 var _ancestorsLineage$fin;
181 const replaceWith = (_ancestorsLineage$fin = ancestorsLineage.findItem(wasReferencedBy(referencingElement))) != null ? _ancestorsLineage$fin : mergeAndAnnotateReferencedElement(referencedElement);
182 if ((0, _apidomCore.isMemberElement)(parent)) {
183 parent.value = replaceWith; // eslint-disable-line no-param-reassign
184 } else if (Array.isArray(parent)) {
185 parent[key] = replaceWith; // eslint-disable-line no-param-reassign
186 }
187 return false;
188 }
189
190 // transclude referencing element with merged referenced element
191 return mergeAndAnnotateReferencedElement(referencedElement);
192 },
193 async PathItemElement(referencingElement, key, parent, path, ancestors) {
194 const [ancestorsLineage, directAncestors] = this.toAncestorLineage([...ancestors, parent]);
195
196 // ignore PathItemElement without $ref field
197 if (!(0, _apidomCore.isStringElement)(referencingElement.$ref)) {
198 return undefined;
199 }
200
201 // detect possible cycle in traversal and avoid it
202 if (ancestorsLineage.includesCycle(referencingElement)) {
203 return false;
204 }
205 const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
206
207 // ignore resolving external Path Item Objects
208 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
209 // skip traversing this Path Item element but traverse all it's child elements
210 return undefined;
211 }
212 const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
213 const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
214 this.indirections.push(referencingElement);
215 const jsonPointer = (0, _apidomJsonPointer.uriToPointer)($refBaseURI);
216
217 // possibly non-semantic referenced element
218 let referencedElement = (0, _apidomJsonPointer.evaluate)(jsonPointer, reference.value.result);
219
220 // applying semantics to a referenced element
221 if ((0, _apidomCore.isPrimitiveElement)(referencedElement)) {
222 referencedElement = _apidomNsOpenapi.PathItemElement.refract(referencedElement);
223 }
224
225 // detect direct or indirect reference
226 if (this.indirections.includes(referencedElement)) {
227 throw new _apidomError.ApiDOMError('Recursive Path Item Object reference detected');
228 }
229
230 // detect maximum depth of dereferencing
231 if (this.indirections.length > this.options.dereference.maxDepth) {
232 throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);
233 }
234
235 // append referencing path item to ancestors lineage
236 directAncestors.add(referencingElement);
237
238 // dive deep into the referenced element
239 const visitor = OpenApi3_0DereferenceVisitor({
240 reference,
241 namespace: this.namespace,
242 indirections: [...this.indirections],
243 options: this.options,
244 ancestors: ancestorsLineage
245 });
246 referencedElement = await visitAsync(referencedElement, visitor, {
247 keyMap: _apidomNsOpenapi.keyMap,
248 nodeTypeGetter: _apidomNsOpenapi.getNodeType
249 });
250
251 // remove referencing path item from ancestors lineage
252 directAncestors.delete(referencingElement);
253 this.indirections.pop();
254 const mergeAndAnnotateReferencedElement = refedElement => {
255 // merge fields from referenced Path Item with referencing one
256 const mergedElement = new _apidomNsOpenapi.PathItemElement([...refedElement.content], (0, _apidomCore.cloneDeep)(referencedElement.meta), (0, _apidomCore.cloneDeep)(referencedElement.attributes));
257 // existing keywords from referencing PathItemElement overrides ones from referenced element
258 referencingElement.forEach((value, keyElement, item) => {
259 mergedElement.remove((0, _apidomCore.toValue)(keyElement));
260 mergedElement.content.push(item);
261 });
262 mergedElement.remove('$ref');
263
264 // annotate referenced element with info about original referencing element
265 mergedElement.setMetaProperty('ref-fields', {
266 $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
267 });
268 // annotate referenced element with info about origin
269 mergedElement.setMetaProperty('ref-origin', reference.uri);
270 // annotate fragment with info about referencing element
271 mergedElement.setMetaProperty('ref-referencing-element-id', (0, _apidomCore.cloneDeep)(identityManager.identify(referencingElement)));
272 return mergedElement;
273 };
274
275 // attempting to create cycle
276 if (ancestorsLineage.includes(referencingElement) || ancestorsLineage.includes(referencedElement)) {
277 var _ancestorsLineage$fin2;
278 const replaceWith = (_ancestorsLineage$fin2 = ancestorsLineage.findItem(wasReferencedBy(referencingElement))) != null ? _ancestorsLineage$fin2 : mergeAndAnnotateReferencedElement(referencedElement);
279 if ((0, _apidomCore.isMemberElement)(parent)) {
280 parent.value = replaceWith; // eslint-disable-line no-param-reassign
281 } else if (Array.isArray(parent)) {
282 parent[key] = replaceWith; // eslint-disable-line no-param-reassign
283 }
284 return false;
285 }
286
287 // transclude referencing element with merged referenced element
288 return mergeAndAnnotateReferencedElement(referencedElement);
289 },
290 async LinkElement(linkElement) {
291 // ignore LinkElement without operationRef or operationId field
292 if (!(0, _apidomCore.isStringElement)(linkElement.operationRef) && !(0, _apidomCore.isStringElement)(linkElement.operationId)) {
293 return undefined;
294 }
295
296 // operationRef and operationId fields are mutually exclusive
297 if ((0, _apidomCore.isStringElement)(linkElement.operationRef) && (0, _apidomCore.isStringElement)(linkElement.operationId)) {
298 throw new _apidomError.ApiDOMError('LinkElement operationRef and operationId fields are mutually exclusive.');
299 }
300 let operationElement;
301 if ((0, _apidomCore.isStringElement)(linkElement.operationRef)) {
302 var _linkElementCopy$oper;
303 // possibly non-semantic referenced element
304 const jsonPointer = (0, _apidomJsonPointer.uriToPointer)((0, _apidomCore.toValue)(linkElement.operationRef));
305 const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(linkElement.operationRef));
306
307 // ignore resolving external Operation Object reference
308 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
309 // skip traversing this Link element but traverse all it's child elements
310 return undefined;
311 }
312 const reference = await this.toReference((0, _apidomCore.toValue)(linkElement.operationRef));
313 operationElement = (0, _apidomJsonPointer.evaluate)(jsonPointer, reference.value.result);
314 // applying semantics to a referenced element
315 if ((0, _apidomCore.isPrimitiveElement)(operationElement)) {
316 operationElement = _apidomNsOpenapi.OperationElement.refract(operationElement);
317 }
318 // create shallow clone to be able to annotate with metadata
319 operationElement = (0, _apidomCore.cloneShallow)(operationElement);
320 // annotate operation element with info about origin
321 operationElement.setMetaProperty('ref-origin', reference.uri);
322 const linkElementCopy = (0, _apidomCore.cloneShallow)(linkElement);
323 (_linkElementCopy$oper = linkElementCopy.operationRef) == null || _linkElementCopy$oper.meta.set('operation', operationElement);
324 return linkElementCopy;
325 }
326 if ((0, _apidomCore.isStringElement)(linkElement.operationId)) {
327 var _linkElementCopy$oper2;
328 const operationId = (0, _apidomCore.toValue)(linkElement.operationId);
329 const reference = await this.toReference(url.unsanitize(this.reference.uri));
330 operationElement = (0, _apidomCore.find)(e => (0, _apidomNsOpenapi.isOperationElement)(e) && (0, _apidomCore.isElement)(e.operationId) && e.operationId.equals(operationId), reference.value.result);
331 // OperationElement not found by its operationId
332 if ((0, _ramdaAdjunct.isUndefined)(operationElement)) {
333 throw new _apidomError.ApiDOMError(`OperationElement(operationId=${operationId}) not found.`);
334 }
335 const linkElementCopy = (0, _apidomCore.cloneShallow)(linkElement);
336 (_linkElementCopy$oper2 = linkElementCopy.operationId) == null || _linkElementCopy$oper2.meta.set('operation', operationElement);
337 return linkElementCopy;
338 }
339 return undefined;
340 },
341 async ExampleElement(exampleElement, key, parent, path, ancestors) {
342 const [ancestorsLineage] = this.toAncestorLineage([...ancestors, parent]);
343
344 // ignore ExampleElement without externalValue field
345 if (!(0, _apidomCore.isStringElement)(exampleElement.externalValue)) {
346 return undefined;
347 }
348
349 // detect possible cycle in traversal and avoid it
350 if (ancestorsLineage.includesCycle(exampleElement)) {
351 return false;
352 }
353
354 // value and externalValue fields are mutually exclusive
355 if (exampleElement.hasKey('value') && (0, _apidomCore.isStringElement)(exampleElement.externalValue)) {
356 throw new _apidomError.ApiDOMError('ExampleElement value and externalValue fields are mutually exclusive.');
357 }
358 const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(exampleElement.externalValue));
359
360 // ignore resolving external Example Objects
361 if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
362 // skip traversing this Example element but traverse all it's child elements
363 return undefined;
364 }
365 const reference = await this.toReference((0, _apidomCore.toValue)(exampleElement.externalValue));
366
367 // shallow clone of the referenced element
368 const valueElement = (0, _apidomCore.cloneShallow)(reference.value.result);
369 // annotate operation element with info about origin
370 valueElement.setMetaProperty('ref-origin', reference.uri);
371 const exampleElementCopy = (0, _apidomCore.cloneShallow)(exampleElement);
372 exampleElementCopy.value = valueElement;
373 return exampleElementCopy;
374 }
375 }
376});
377var _default = exports.default = OpenApi3_0DereferenceVisitor;
Note: See TracBrowser for help on using the repository browser.