Skip to content

Commit 8de4616

Browse files
committed
Improve circular reference detection
1 parent 85e7d73 commit 8de4616

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

packages/docusaurus-plugin-openapi-docs/src/openapi/utils/loadAndResolveSpec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ function serializer(replacer: any, cycleReplacer: any) {
2222

2323
if (cycleReplacer === undefined)
2424
cycleReplacer = function (key: any, value: any) {
25+
if (value?.["x-circular-ref"]) {
26+
return value.title ? `circular(${value.title})` : "circular()";
27+
}
2528
if (stack[0] === value) return "circular()";
2629
return value.title ? `circular(${value.title})` : "circular()";
2730
};
2831

2932
return function (key: any, value: any) {
33+
// Convert objects flagged as circular
34+
if (value?.["x-circular-ref"]) {
35+
return value.title ? `circular(${value.title})` : "circular()";
36+
}
37+
3038
// Resolve discriminator ref pointers
3139
if (value?.discriminator !== undefined) {
3240
const parser = new OpenAPIParser(stack[0]);
@@ -115,6 +123,49 @@ async function resolveJsonRefs(specUrlOrObject: object | string) {
115123
}
116124
}
117125

126+
function markCircularRefs(obj: any, stack: any[] = []): boolean {
127+
if (!obj || typeof obj !== "object") {
128+
return false;
129+
}
130+
131+
const pos = stack.indexOf(obj);
132+
if (pos !== -1) {
133+
for (let i = pos + 1; i < stack.length; i++) {
134+
const cyc = stack[i];
135+
if (cyc && typeof cyc === "object") {
136+
cyc["x-circular-ref"] = true;
137+
}
138+
}
139+
return true;
140+
}
141+
142+
stack.push(obj);
143+
let foundCircular = false;
144+
145+
if (Array.isArray(obj)) {
146+
for (let i = 0; i < obj.length; i++) {
147+
const val = obj[i];
148+
if (typeof val === "object" && val !== null) {
149+
if (markCircularRefs(val, stack)) {
150+
foundCircular = true;
151+
}
152+
}
153+
}
154+
} else {
155+
for (const key of Object.keys(obj)) {
156+
const val = obj[key];
157+
if (typeof val === "object" && val !== null) {
158+
if (markCircularRefs(val, stack)) {
159+
foundCircular = true;
160+
}
161+
}
162+
}
163+
}
164+
165+
stack.pop();
166+
return foundCircular;
167+
}
168+
118169
export async function loadAndResolveSpec(specUrlOrObject: object | string) {
119170
const config = new Config({} as ResolvedConfig);
120171
const bundleOpts = {
@@ -154,6 +205,9 @@ export async function loadAndResolveSpec(specUrlOrObject: object | string) {
154205

155206
const resolved = await resolveJsonRefs(parsed);
156207

208+
// Mark any remaining circular references
209+
markCircularRefs(resolved);
210+
157211
// Force serialization and replace circular $ref pointers
158212
// @ts-ignore
159213
const serialized = JSON.stringify(resolved, serializer());

0 commit comments

Comments
 (0)