Skip to content

Commit d84397c

Browse files
authored
fix(reference): fix handling cycles in all dereference strategies (#3361)
Refs swagger-api/swagger-ui#9337
1 parent 65dcd0e commit d84397c

File tree

6 files changed

+261
-49
lines changed

6 files changed

+261
-49
lines changed

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

+67-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import stampit from 'stampit';
22
import { propEq } from 'ramda';
33
import {
4-
cloneDeep,
5-
cloneShallow,
6-
Element,
74
isElement,
85
isMemberElement,
96
isPrimitiveElement,
107
isStringElement,
8+
IdentityManager,
9+
cloneDeep,
10+
cloneShallow,
1111
visit,
1212
toValue,
13+
Element,
1314
} from '@swagger-api/apidom-core';
1415
import { ApiDOMError } from '@swagger-api/apidom-error';
1516
import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
@@ -19,6 +20,7 @@ import {
1920
isChannelItemElementExternal,
2021
isReferenceElementExternal,
2122
isReferenceLikeElement,
23+
isBooleanJsonSchemaElement,
2224
keyMap,
2325
ReferenceElement,
2426
} from '@swagger-api/apidom-ns-asyncapi-2';
@@ -34,6 +36,21 @@ import Reference from '../../../Reference';
3436
// @ts-ignore
3537
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
3638

39+
// initialize element identity manager
40+
const identityManager = IdentityManager();
41+
42+
/**
43+
* Predicate for detecting if element was created by merging referencing
44+
* element with particular element identity with a referenced element.
45+
*/
46+
const wasReferencedBy =
47+
<T extends Element, U extends Element>(referencingElement: T) =>
48+
(element: U) =>
49+
element.meta.hasKey('ref-referencing-element-id') &&
50+
element.meta
51+
.get('ref-referencing-element-id')
52+
.equals(toValue(identityManager.identify(referencingElement)));
53+
3754
const AsyncApi2DereferenceVisitor = stampit({
3855
props: {
3956
indirections: [],
@@ -55,7 +72,7 @@ const AsyncApi2DereferenceVisitor = stampit({
5572
* Compute full ancestors lineage.
5673
* Ancestors are flatten to unwrap all Element instances.
5774
*/
58-
const directAncestors = new WeakSet(ancestors.filter(isElement));
75+
const directAncestors = new Set<Element>(ancestors.filter(isElement));
5976
const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);
6077

6178
return [ancestorsLineage, directAncestors];
@@ -174,6 +191,24 @@ const AsyncApi2DereferenceVisitor = stampit({
174191

175192
this.indirections.pop();
176193

194+
// Boolean JSON Schemas
195+
if (isBooleanJsonSchemaElement(referencedElement)) {
196+
const booleanJsonSchemaElement = cloneDeep(referencedElement);
197+
// annotate referenced element with info about original referencing element
198+
booleanJsonSchemaElement.setMetaProperty('ref-fields', {
199+
$ref: toValue(referencingElement.$ref),
200+
});
201+
// annotate referenced element with info about origin
202+
booleanJsonSchemaElement.setMetaProperty('ref-origin', reference.uri);
203+
// annotate fragment with info about referencing element
204+
booleanJsonSchemaElement.setMetaProperty(
205+
'ref-referencing-element-id',
206+
cloneDeep(identityManager.identify(referencingElement)),
207+
);
208+
209+
return booleanJsonSchemaElement;
210+
}
211+
177212
const mergeAndAnnotateReferencedElement = <T extends Element>(refedElement: T): T => {
178213
const copy = cloneShallow(refedElement);
179214

@@ -183,18 +218,28 @@ const AsyncApi2DereferenceVisitor = stampit({
183218
});
184219
// annotate fragment with info about origin
185220
copy.setMetaProperty('ref-origin', reference.uri);
221+
// annotate fragment with info about referencing element
222+
copy.setMetaProperty(
223+
'ref-referencing-element-id',
224+
cloneDeep(identityManager.identify(referencingElement)),
225+
);
186226

187227
return copy;
188228
};
189229

190230
// attempting to create cycle
191-
if (ancestorsLineage.includes(referencedElement)) {
231+
if (
232+
ancestorsLineage.includes(referencingElement) ||
233+
ancestorsLineage.includes(referencedElement)
234+
) {
235+
const replaceWith =
236+
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
237+
mergeAndAnnotateReferencedElement(referencedElement);
192238
if (isMemberElement(parent)) {
193-
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
239+
parent.value = replaceWith; // eslint-disable-line no-param-reassign
194240
} else if (Array.isArray(parent)) {
195-
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
241+
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
196242
}
197-
198243
return false;
199244
}
200245

@@ -297,18 +342,28 @@ const AsyncApi2DereferenceVisitor = stampit({
297342
});
298343
// annotate referenced with info about origin
299344
mergedElement.setMetaProperty('ref-origin', reference.uri);
345+
// annotate fragment with info about referencing element
346+
mergedElement.setMetaProperty(
347+
'ref-referencing-element-id',
348+
cloneDeep(identityManager.identify(referencingElement)),
349+
);
300350

301351
return mergedElement;
302352
};
303353

304354
// attempting to create cycle
305-
if (ancestorsLineage.includes(referencedElement)) {
355+
if (
356+
ancestorsLineage.includes(referencingElement) ||
357+
ancestorsLineage.includes(referencedElement)
358+
) {
359+
const replaceWith =
360+
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
361+
mergeAndAnnotateReferencedElement(referencedElement);
306362
if (isMemberElement(parent)) {
307-
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
363+
parent.value = replaceWith; // eslint-disable-line no-param-reassign
308364
} else if (Array.isArray(parent)) {
309-
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
365+
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
310366
}
311-
312367
return false;
313368
}
314369

packages/apidom-reference/src/dereference/strategies/openapi-3-0/visitor.ts

+48-12
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import stampit from 'stampit';
22
import { propEq } from 'ramda';
33
import { isUndefined } from 'ramda-adjunct';
44
import {
5-
Element,
65
isPrimitiveElement,
76
isStringElement,
7+
isMemberElement,
8+
isElement,
9+
IdentityManager,
810
visit,
911
find,
10-
isElement,
1112
cloneShallow,
1213
cloneDeep,
1314
toValue,
14-
isMemberElement,
15+
Element,
1516
} from '@swagger-api/apidom-core';
1617
import { ApiDOMError } from '@swagger-api/apidom-error';
1718
import { evaluate, uriToPointer } from '@swagger-api/apidom-json-pointer';
@@ -41,6 +42,21 @@ import Reference from '../../../Reference';
4142
// @ts-ignore
4243
const visitAsync = visit[Symbol.for('nodejs.util.promisify.custom')];
4344

45+
// initialize element identity manager
46+
const identityManager = IdentityManager();
47+
48+
/**
49+
* Predicate for detecting if element was created by merging referencing
50+
* element with particular element identity with a referenced element.
51+
*/
52+
const wasReferencedBy =
53+
<T extends Element, U extends Element>(referencingElement: T) =>
54+
(element: U) =>
55+
element.meta.hasKey('ref-referencing-element-id') &&
56+
element.meta
57+
.get('ref-referencing-element-id')
58+
.equals(toValue(identityManager.identify(referencingElement)));
59+
4460
// eslint-disable-next-line @typescript-eslint/naming-convention
4561
const OpenApi3_0DereferenceVisitor = stampit({
4662
props: {
@@ -97,7 +113,7 @@ const OpenApi3_0DereferenceVisitor = stampit({
97113
* Compute full ancestors lineage.
98114
* Ancestors are flatten to unwrap all Element instances.
99115
*/
100-
const directAncestors = new WeakSet(ancestors.filter(isElement));
116+
const directAncestors = new Set<Element>(ancestors.filter(isElement));
101117
const ancestorsLineage = new AncestorLineage(...this.ancestors, directAncestors);
102118

103119
return [ancestorsLineage, directAncestors];
@@ -192,18 +208,28 @@ const OpenApi3_0DereferenceVisitor = stampit({
192208
});
193209
// annotate fragment with info about origin
194210
copy.setMetaProperty('ref-origin', reference.uri);
211+
// annotate fragment with info about referencing element
212+
copy.setMetaProperty(
213+
'ref-referencing-element-id',
214+
cloneDeep(identityManager.identify(referencingElement)),
215+
);
195216

196217
return copy;
197218
};
198219

199220
// attempting to create cycle
200-
if (ancestorsLineage.includes(referencedElement)) {
221+
if (
222+
ancestorsLineage.includes(referencingElement) ||
223+
ancestorsLineage.includes(referencedElement)
224+
) {
225+
const replaceWith =
226+
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
227+
mergeAndAnnotateReferencedElement(referencedElement);
201228
if (isMemberElement(parent)) {
202-
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
229+
parent.value = replaceWith; // eslint-disable-line no-param-reassign
203230
} else if (Array.isArray(parent)) {
204-
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
231+
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
205232
}
206-
207233
return false;
208234
}
209235

@@ -306,18 +332,28 @@ const OpenApi3_0DereferenceVisitor = stampit({
306332
});
307333
// annotate referenced element with info about origin
308334
mergedElement.setMetaProperty('ref-origin', reference.uri);
335+
// annotate fragment with info about referencing element
336+
mergedElement.setMetaProperty(
337+
'ref-referencing-element-id',
338+
cloneDeep(identityManager.identify(referencingElement)),
339+
);
309340

310341
return mergedElement;
311342
};
312343

313344
// attempting to create cycle
314-
if (ancestorsLineage.includes(referencedElement)) {
345+
if (
346+
ancestorsLineage.includes(referencingElement) ||
347+
ancestorsLineage.includes(referencedElement)
348+
) {
349+
const replaceWith =
350+
ancestorsLineage.findItem(wasReferencedBy(referencingElement)) ??
351+
mergeAndAnnotateReferencedElement(referencedElement);
315352
if (isMemberElement(parent)) {
316-
parent.value = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
353+
parent.value = replaceWith; // eslint-disable-line no-param-reassign
317354
} else if (Array.isArray(parent)) {
318-
parent[key] = mergeAndAnnotateReferencedElement(referencedElement); // eslint-disable-line no-param-reassign
355+
parent[key] = replaceWith; // eslint-disable-line no-param-reassign
319356
}
320-
321357
return false;
322358
}
323359

0 commit comments

Comments
 (0)