Skip to content

Commit 910a974

Browse files
authored
fix(reference): add support for external cycles detection in AsyncAPI 2.x dereference strategy (#3872)
Refs #3863
1 parent ada33bc commit 910a974

File tree

4 files changed

+64
-3
lines changed
  • packages/apidom-reference

4 files changed

+64
-3
lines changed

packages/apidom-reference/src/dereference/strategies/asyncapi-2/visitor.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,22 @@ const AsyncApi2DereferenceVisitor = stampit({
5656
reference: null,
5757
options: null,
5858
ancestors: null,
59+
refractCache: null,
5960
},
60-
init({ indirections = [], reference, namespace, options, ancestors = new AncestorLineage() }) {
61+
init({
62+
indirections = [],
63+
reference,
64+
namespace,
65+
options,
66+
ancestors = new AncestorLineage(),
67+
refractCache = new Map(),
68+
}) {
6169
this.indirections = indirections;
6270
this.namespace = namespace;
6371
this.reference = reference;
6472
this.options = options;
6573
this.ancestors = new AncestorLineage(...ancestors);
74+
this.refractCache = refractCache;
6675
},
6776
methods: {
6877
toBaseURI(uri: string): string {
@@ -148,15 +157,20 @@ const AsyncApi2DereferenceVisitor = stampit({
148157
// applying semantics to a fragment
149158
if (isPrimitiveElement(referencedElement)) {
150159
const referencedElementType = toValue(referencingElement.meta.get('referenced-element'));
160+
const cacheKey = `${referencedElementType}-${toValue(identityManager.identify(referencedElement))}`;
151161

152-
if (isReferenceLikeElement(referencedElement)) {
162+
if (this.refractCache.has(cacheKey)) {
163+
referencedElement = this.refractCache.get(cacheKey);
164+
} else if (isReferenceLikeElement(referencedElement)) {
153165
// handling indirect references
154166
referencedElement = ReferenceElement.refract(referencedElement);
155167
referencedElement.setMetaProperty('referenced-element', referencedElementType);
168+
this.refractCache.set(cacheKey, referencedElement);
156169
} else {
157170
// handling direct references
158171
const ElementClass = this.namespace.getElementClass(referencedElementType);
159172
referencedElement = ElementClass.refract(referencedElement);
173+
this.refractCache.set(cacheKey, referencedElement);
160174
}
161175
}
162176

@@ -182,6 +196,7 @@ const AsyncApi2DereferenceVisitor = stampit({
182196
indirections: [...this.indirections],
183197
options: this.options,
184198
ancestors: ancestorsLineage,
199+
refractCache: this.refractCache,
185200
});
186201
referencedElement = await visitAsync(referencedElement, visitor, {
187202
keyMap,
@@ -288,7 +303,14 @@ const AsyncApi2DereferenceVisitor = stampit({
288303

289304
// applying semantics to a referenced element
290305
if (isPrimitiveElement(referencedElement)) {
291-
referencedElement = ChannelItemElement.refract(referencedElement);
306+
const cacheKey = `channel-${toValue(identityManager.identify(referencedElement))}`;
307+
308+
if (this.refractCache.has(cacheKey)) {
309+
referencedElement = this.refractCache.get(cacheKey);
310+
} else {
311+
referencedElement = ChannelItemElement.refract(referencedElement);
312+
this.refractCache.set(cacheKey, referencedElement);
313+
}
292314
}
293315

294316
// detect direct or indirect reference
@@ -313,6 +335,7 @@ const AsyncApi2DereferenceVisitor = stampit({
313335
indirections: [...this.indirections],
314336
options: this.options,
315337
ancestors: ancestorsLineage,
338+
refractCache: this.refractCache,
316339
});
317340
referencedElement = await visitAsync(referencedElement, visitor, {
318341
keyMap,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"properties": {
3+
"parent": {
4+
"$ref": "#"
5+
}
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"asyncapi": "2.6.0",
3+
"components": {
4+
"schemas": {
5+
"externalSchema": {
6+
"$ref": "./ex.json"
7+
}
8+
}
9+
}
10+
}

packages/apidom-reference/test/dereference/strategies/asyncapi-2/reference-object/index.ts

+21
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,27 @@ describe('dereference', function () {
8484
});
8585
});
8686

87+
context('given Reference Objects pointing to external cycles', function () {
88+
const fixturePath = path.join(rootFixturePath, 'external-cycle');
89+
90+
specify('should dereference', async function () {
91+
const rootFilePath = path.join(fixturePath, 'root.json');
92+
const dereferenced = await dereference(rootFilePath, {
93+
parse: { mediaType: mediaTypes.latest('json') },
94+
});
95+
const parent = evaluate(
96+
'/0/components/schemas/externalSchema/properties',
97+
dereferenced,
98+
);
99+
const cyclicParent = evaluate(
100+
'/0/components/schemas/externalSchema/properties/parent/properties',
101+
dereferenced,
102+
);
103+
104+
assert.strictEqual(parent, cyclicParent);
105+
});
106+
});
107+
87108
context('given Reference Objects pointing to external indirections', function () {
88109
const fixturePath = path.join(rootFixturePath, 'external-indirections');
89110

0 commit comments

Comments
 (0)