Skip to content

Commit be6df86

Browse files
authored
fix: all of circular references may freeze (#89)
* fix: better circular $refs handling in allOf * chore: tune down the console log severity * test: snapshot
1 parent 12337ee commit be6df86

File tree

2 files changed

+107
-6
lines changed

2 files changed

+107
-6
lines changed

src/tree/__tests__/tree.spec.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,96 @@ describe('SchemaTree', () => {
659659
`);
660660
});
661661

662+
test('given circular reference inside of resolved allOf member, should bail out and display unmerged allOf', () => {
663+
const schema: JSONSchema4 = {
664+
components: {
665+
schemas: {
666+
Campaign: {
667+
type: 'object',
668+
allOf: [
669+
{
670+
$ref: '#/components/schemas/Discount',
671+
},
672+
{
673+
properties: {
674+
startDate: {
675+
type: 'number',
676+
},
677+
},
678+
},
679+
],
680+
},
681+
Coupon: {
682+
type: 'object',
683+
allOf: [
684+
{
685+
$ref: '#/components/schemas/Discount',
686+
},
687+
{
688+
properties: {
689+
endDate: {
690+
type: 'number',
691+
},
692+
},
693+
},
694+
],
695+
},
696+
Discount: {
697+
oneOf: [
698+
{
699+
$ref: '#/components/schemas/Coupon',
700+
},
701+
{
702+
$ref: '#/components/schemas/Campaign',
703+
},
704+
],
705+
},
706+
},
707+
},
708+
properties: {
709+
Discount: {
710+
$ref: '#/components/schemas/Discount',
711+
},
712+
},
713+
};
714+
715+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
716+
expandedDepth: Infinity,
717+
mergeAllOf: true,
718+
resolveRef: void 0,
719+
shouldResolveEagerly: false,
720+
onPopulate: void 0,
721+
});
722+
723+
expect(tree.populate.bind(tree)).not.toThrow();
724+
725+
tree.unwrap(tree.itemAt(1) as TreeListParentNode);
726+
tree.unwrap(tree.itemAt(2) as TreeListParentNode);
727+
728+
expect(printTree(tree)).toMatchInlineSnapshot(`
729+
"└─ #
730+
├─ type: object
731+
└─ children
732+
└─ 0
733+
└─ #/properties/Discount
734+
├─ $ref: #/components/schemas/Discount
735+
└─ children
736+
└─ 0
737+
└─ #/properties/Discount
738+
├─ combiner: oneOf
739+
└─ children
740+
├─ 0
741+
│ └─ #/properties/Discount/oneOf/0
742+
│ ├─ $ref: #/components/schemas/Coupon
743+
│ └─ children
744+
└─ 1
745+
└─ #/properties/Discount/oneOf/1
746+
├─ $ref: #/components/schemas/Campaign
747+
└─ children
748+
"
749+
`);
750+
});
751+
662752
test('given very complex model with circular references, should bail out and display unmerged allOf', () => {
663753
const schema = require('../../__fixtures__/complex-allOf-model.json');
664754

src/tree/utils/mergeAllOf.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,29 @@ function _mergeAllOf(schema: JSONSchema4, path: JsonPath, opts: WalkingOptions)
2222
}
2323

2424
const allRefs = store.get(opts)!;
25-
const schemaRefs = allRefs.get(schema);
25+
let schemaRefs = allRefs.get(schema);
2626

2727
if (schemaRefs === void 0) {
28-
allRefs.set(schema, [$ref]);
28+
schemaRefs = [$ref];
29+
allRefs.set(schema, schemaRefs);
2930
} else if (schemaRefs.includes($ref)) {
30-
const resolved = JSON.parse(safeStringify(opts.resolveRef(null, $ref)));
31-
return 'allOf' in resolved ? _mergeAllOf(resolved, path, opts) : resolved;
31+
const safelyResolved = JSON.parse(safeStringify(opts.resolveRef(null, $ref)));
32+
return 'allOf' in safelyResolved ? _mergeAllOf(safelyResolved, path, opts) : safelyResolved;
3233
} else {
3334
schemaRefs.push($ref);
3435
}
3536

36-
return opts.resolveRef(null, $ref);
37+
const resolved = opts.resolveRef(null, $ref);
38+
39+
if (Array.isArray(resolved.allOf)) {
40+
for (const member of resolved.allOf) {
41+
if (typeof member.$ref === 'string' && schemaRefs.includes(member.$ref)) {
42+
throw new ResolvingError('Circular reference detected');
43+
}
44+
}
45+
}
46+
47+
return resolved;
3748
},
3849
});
3950
}
@@ -46,7 +57,7 @@ export const mergeAllOf = (schema: JSONSchema4, path: JsonPath, opts: WalkingOpt
4657

4758
return _mergeAllOf(schema, path, opts);
4859
} catch (ex) {
49-
console.error(ex.message);
60+
console.info(ex.message);
5061
throw ex;
5162
}
5263
};

0 commit comments

Comments
 (0)