Skip to content

Commit 8b5d37a

Browse files
authored
fix: don't mutate the object being traversed (#54)
* chore: add test for bundling circule $refs * fix: lint * fix: handle circular $refs
1 parent 7038cb3 commit 8b5d37a

File tree

2 files changed

+87
-6
lines changed

2 files changed

+87
-6
lines changed

src/__tests__/bundle.spec.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { cloneDeep } from 'lodash';
22

33
import { BUNDLE_ROOT, bundleTarget } from '../bundle';
4+
import { safeStringify } from '../safeStringify';
45

56
describe('bundleTargetPath()', () => {
67
it('should work', () => {
@@ -258,4 +259,76 @@ describe('bundleTargetPath()', () => {
258259
},
259260
});
260261
});
262+
263+
it('should handle circular $ref', () => {
264+
const document = {
265+
openapi: '3.0.0',
266+
components: {
267+
schemas: {
268+
Hello: {
269+
title: 'Hello',
270+
type: 'object',
271+
properties: {
272+
Hello: {
273+
$ref: '#/components/schemas/Hello',
274+
},
275+
World: {
276+
$ref: '#/components/schemas/World',
277+
},
278+
},
279+
},
280+
World: {
281+
title: 'World',
282+
type: 'object',
283+
properties: {
284+
name: {
285+
type: 'string',
286+
},
287+
},
288+
},
289+
},
290+
},
291+
};
292+
293+
const clone = cloneDeep(document);
294+
295+
const result = bundleTarget({
296+
document: clone,
297+
path: '#/components/schemas/Hello',
298+
});
299+
300+
// Do not mutate document
301+
expect(clone).toEqual(document);
302+
303+
expect(safeStringify(result)).toEqual(
304+
safeStringify({
305+
[BUNDLE_ROOT]: {
306+
components: {
307+
schemas: {
308+
Hello: '[Circular]',
309+
World: {
310+
properties: {
311+
name: {
312+
type: 'string',
313+
},
314+
},
315+
title: 'World',
316+
type: 'object',
317+
},
318+
},
319+
},
320+
},
321+
properties: {
322+
Hello: {
323+
$ref: `#/${BUNDLE_ROOT}/components/schemas/Hello`,
324+
},
325+
World: {
326+
$ref: `#/${BUNDLE_ROOT}/components/schemas/World`,
327+
},
328+
},
329+
title: 'Hello',
330+
type: 'object',
331+
}),
332+
);
333+
});
261334
});

src/bundle.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ export const BUNDLE_ROOT = '__bundled__';
1111
export const bundleTarget = <T = unknown>({ document, path }: { document: T; path: string }, cur?: unknown) =>
1212
_bundle(cloneDeep(document), path, cur);
1313

14-
const _bundle = (document: unknown, path: string, cur?: unknown, bundledRefInventory: any = {}) => {
14+
const _bundle = (
15+
document: unknown,
16+
path: string,
17+
cur?: unknown,
18+
bundledRefInventory: any = {},
19+
bundledObj: any = {},
20+
) => {
1521
const objectToBundle = get(document, pointerToPath(path));
1622

1723
traverse(cur ? cur : objectToBundle, ({ parent }) => {
@@ -39,21 +45,23 @@ const _bundle = (document: unknown, path: string, cur?: unknown, bundledRefInven
3945
pathProcessed.push(key);
4046

4147
const inventoryPathProcessed = [BUNDLE_ROOT, ...pathProcessed];
42-
if (has(objectToBundle, inventoryPathProcessed)) continue;
48+
if (has(bundledObj, inventoryPathProcessed)) continue;
4349

4450
const target = get(document, pathProcessed);
4551
if (Array.isArray(target)) {
46-
set(objectToBundle, inventoryPathProcessed, []);
52+
set(bundledObj, inventoryPathProcessed, []);
4753
} else if (typeof target === 'object') {
48-
set(objectToBundle, inventoryPathProcessed, {});
54+
set(bundledObj, inventoryPathProcessed, {});
4955
}
5056
}
5157

52-
set(objectToBundle, inventoryPath, bundled$Ref);
53-
_bundle(document, path, bundled$Ref, bundledRefInventory);
58+
set(bundledObj, inventoryPath, bundled$Ref);
59+
_bundle(document, path, bundled$Ref, bundledRefInventory, bundledObj);
5460
}
5561
}
5662
});
5763

64+
set(objectToBundle, BUNDLE_ROOT, bundledObj[BUNDLE_ROOT]);
65+
5866
return objectToBundle;
5967
};

0 commit comments

Comments
 (0)