Skip to content

Commit 98cb77f

Browse files
authored
fix(reference): base AsyncAPI 2.x resolver strategy on dereference strategy (#3909)
Refs #3452
1 parent e1ae4d1 commit 98cb77f

File tree

2 files changed

+3
-231
lines changed

2 files changed

+3
-231
lines changed

packages/apidom-reference/src/resolve/strategies/asyncapi-2/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ const AsyncApi2ResolveStrategy: stampit.Stamp<IResolveStrategy> = stampit(Resolv
4646
keyMap,
4747
nodeTypeGetter: getNodeType,
4848
});
49-
await visitor.crawl();
5049

5150
return refSet;
5251
},
Original file line numberDiff line numberDiff line change
@@ -1,234 +1,7 @@
11
import stampit from 'stampit';
2-
import { propEq, values, has, pipe } from 'ramda';
3-
import { allP } from 'ramda-adjunct';
4-
import { isPrimitiveElement, isStringElement, visit, toValue } from '@swagger-api/apidom-core';
5-
import { ApiDOMError } from '@swagger-api/apidom-error';
6-
import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
7-
import {
8-
getNodeType,
9-
isReferenceElement,
10-
isChannelItemElement,
11-
isReferenceLikeElement,
12-
keyMap,
13-
ReferenceElement,
14-
ChannelItemElement,
15-
} from '@swagger-api/apidom-ns-asyncapi-2';
162

17-
import { Reference as IReference } from '../../../types';
18-
import MaximumDereferenceDepthError from '../../../errors/MaximumDereferenceDepthError';
19-
import MaximumResolveDepthError from '../../../errors/MaximumResolveDepthError';
20-
import * as url from '../../../util/url';
21-
import parse from '../../../parse';
22-
import Reference from '../../../Reference';
3+
import AsyncAPI2DereferenceVisitor from '../../../dereference/strategies/asyncapi-2/visitor';
234

24-
// @ts-ignore
25-
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
5+
const AsyncAPI2ResolveVisitor = stampit(AsyncAPI2DereferenceVisitor);
266

27-
const AsyncApi2ResolveVisitor = stampit({
28-
props: {
29-
indirections: [],
30-
namespace: null,
31-
reference: null,
32-
crawledElements: null,
33-
crawlingMap: null,
34-
options: null,
35-
},
36-
init({ reference, namespace, indirections = [], options }) {
37-
this.indirections = indirections;
38-
this.namespace = namespace;
39-
this.reference = reference;
40-
this.crawledElements = [];
41-
this.crawlingMap = {};
42-
this.options = options;
43-
},
44-
methods: {
45-
toBaseURI(uri: string): string {
46-
return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
47-
},
48-
49-
async toReference(uri: string): Promise<IReference> {
50-
// detect maximum depth of resolution
51-
if (this.reference.depth >= this.options.resolve.maxDepth) {
52-
throw new MaximumResolveDepthError(
53-
`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`,
54-
);
55-
}
56-
57-
const baseURI = this.toBaseURI(uri);
58-
const { refSet } = this.reference;
59-
60-
// we've already processed this Reference in past
61-
if (refSet.has(baseURI)) {
62-
return refSet.find(propEq(baseURI, 'uri'));
63-
}
64-
65-
const parseResult = await parse(url.unsanitize(baseURI), {
66-
...this.options,
67-
parse: { ...this.options.parse, mediaType: 'text/plain' },
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-
77-
refSet.add(reference);
78-
79-
return reference;
80-
},
81-
82-
ReferenceElement(referenceElement: ReferenceElement) {
83-
const uri = toValue(referenceElement.$ref);
84-
const retrievalURI = this.toBaseURI(uri);
85-
86-
// ignore resolving external Reference Objects
87-
if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
88-
return false;
89-
}
90-
91-
if (!has(retrievalURI, this.crawlingMap)) {
92-
this.crawlingMap[retrievalURI] = this.toReference(uri);
93-
}
94-
this.crawledElements.push(referenceElement);
95-
96-
return undefined;
97-
},
98-
99-
ChannelItemElement(channelItemElement: ChannelItemElement) {
100-
// ignore PathItemElement without $ref field
101-
if (!isStringElement(channelItemElement.$ref)) {
102-
return undefined;
103-
}
104-
105-
const uri = toValue(channelItemElement.$ref);
106-
const retrievalURI = this.toBaseURI(uri);
107-
108-
// ignore resolving external Channel Item Objects
109-
if (!this.options.resolve.external && url.stripHash(this.reference.uri) !== retrievalURI) {
110-
return undefined;
111-
}
112-
113-
if (!has(retrievalURI, this.crawlingMap)) {
114-
this.crawlingMap[retrievalURI] = this.toReference(uri);
115-
}
116-
this.crawledElements.push(channelItemElement);
117-
118-
return undefined;
119-
},
120-
121-
async crawlReferenceElement(referenceElement: ReferenceElement) {
122-
// @ts-ignore
123-
const reference = await this.toReference(toValue(referenceElement.$ref));
124-
125-
this.indirections.push(referenceElement);
126-
127-
const jsonPointer = uriToPointer(toValue(referenceElement.$ref));
128-
129-
// possibly non-semantic fragment
130-
let fragment = evaluate(jsonPointer, reference.value.result);
131-
132-
// applying semantics to a fragment
133-
if (isPrimitiveElement(fragment)) {
134-
const referencedElementType = toValue(referenceElement.meta.get('referenced-element'));
135-
136-
if (isReferenceLikeElement(fragment)) {
137-
// handling indirect references
138-
fragment = ReferenceElement.refract(fragment);
139-
fragment.setMetaProperty('referenced-element', referencedElementType);
140-
} else {
141-
// handling direct references
142-
const ElementClass = this.namespace.getElementClass(referencedElementType);
143-
fragment = ElementClass.refract(fragment);
144-
}
145-
}
146-
147-
// detect direct or circular reference
148-
if (this.indirections.includes(fragment)) {
149-
throw new ApiDOMError('Recursive Reference Object detected');
150-
}
151-
152-
// detect maximum depth of dereferencing
153-
if (this.indirections.length > this.options.dereference.maxDepth) {
154-
throw new MaximumDereferenceDepthError(
155-
`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`,
156-
);
157-
}
158-
159-
// dive deep into the fragment
160-
const visitor = AsyncApi2ResolveVisitor({
161-
reference,
162-
namespace: this.namespace,
163-
indirections: [...this.indirections],
164-
options: this.options,
165-
});
166-
await visitAsync(fragment, visitor, { keyMap, nodeTypeGetter: getNodeType });
167-
await visitor.crawl();
168-
169-
this.indirections.pop();
170-
},
171-
172-
async crawlChannelItemElement(channelItemElement: ChannelItemElement) {
173-
const reference = await this.toReference(toValue(channelItemElement.$ref));
174-
175-
this.indirections.push(channelItemElement);
176-
177-
const jsonPointer = uriToPointer(toValue(channelItemElement.$ref));
178-
179-
// possibly non-semantic referenced element
180-
let referencedElement = evaluate(jsonPointer, reference.value.result);
181-
182-
// applying semantics to a referenced element
183-
if (isPrimitiveElement(referencedElement)) {
184-
referencedElement = ChannelItemElement.refract(referencedElement);
185-
}
186-
187-
// detect direct or indirect reference
188-
if (this.indirections.includes(referencedElement)) {
189-
throw new ApiDOMError('Recursive Channel Item Object reference detected');
190-
}
191-
192-
// detect maximum depth of dereferencing
193-
if (this.indirections.length > this.options.dereference.maxDepth) {
194-
throw new MaximumDereferenceDepthError(
195-
`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`,
196-
);
197-
}
198-
199-
// dive deep into the referenced element
200-
const visitor: any = AsyncApi2ResolveVisitor({
201-
reference,
202-
namespace: this.namespace,
203-
indirections: [...this.indirections],
204-
options: this.options,
205-
});
206-
await visitAsync(referencedElement, visitor, { keyMap, nodeTypeGetter: getNodeType });
207-
await visitor.crawl();
208-
209-
this.indirections.pop();
210-
},
211-
212-
async crawl() {
213-
/**
214-
* Synchronize all parallel resolutions in this place.
215-
* After synchronization happened we can be sure that refSet
216-
* contains resolved Reference objects.
217-
*/
218-
await pipe(values, allP)(this.crawlingMap);
219-
this.crawlingMap = null;
220-
221-
/* eslint-disable no-await-in-loop */
222-
for (const element of this.crawledElements) {
223-
if (isReferenceElement(element)) {
224-
await this.crawlReferenceElement(element);
225-
} else if (isChannelItemElement(element)) {
226-
await this.crawlChannelItemElement(element);
227-
}
228-
}
229-
/* eslint-enabled */
230-
},
231-
},
232-
});
233-
234-
export default AsyncApi2ResolveVisitor;
7+
export default AsyncAPI2ResolveVisitor;

0 commit comments

Comments
 (0)