Skip to content

Commit 242ad30

Browse files
committed
fix: support $refs inside of allOfs
1 parent 77ae945 commit 242ad30

File tree

9 files changed

+352
-56
lines changed

9 files changed

+352
-56
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
},
4444
"dependencies": {
4545
"@stoplight/json": "^3.5.1",
46-
"@stoplight/json-schema-merge-allof": "^0.6.0",
46+
"@stoplight/json-schema-merge-allof": "^0.7.0",
4747
"@stoplight/react-error-boundary": "^1.0.0",
4848
"@stoplight/tree-list": "^5.0.0",
4949
"classnames": "^2.2.6",
@@ -83,6 +83,7 @@
8383
"react": "16.9.0",
8484
"react-dom": "16.9.0",
8585
"rollup-plugin-commonjs": "^10.1.0",
86+
"treeify": "^1.1.0",
8687
"ts-jest": "^24.0.2",
8788
"tslint": "^5.19.0",
8889
"tslint-config-stoplight": "^1.4.0",

src/components/JsonSchemaViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaVi
7777
@action
7878
public componentDidUpdate(prevProps: Readonly<IJsonSchemaViewer>) {
7979
if (prevProps.resolveRef !== this.props.resolveRef) {
80-
this.tree.resolveRef = this.props.resolveRef;
80+
this.tree.resolver = this.props.resolveRef;
8181
}
8282

8383
if (

src/components/__tests__/SchemaRow.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe('SchemaRow component', () => {
143143

144144
test('given a custom resolver, should render a message thrown by it', () => {
145145
const message = "I don't know how to resolve it. Sorry";
146-
tree.resolveRef = () => {
146+
tree.resolver = () => {
147147
throw new Error(message);
148148
};
149149

src/tree/__tests__/tree.spec.ts

Lines changed: 253 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { TreeListParentNode } from '@stoplight/tree-list';
22
import { JSONSchema4 } from 'json-schema';
33
import { getNodeMetadata } from '../metadata';
44
import { SchemaTree, SchemaTreeState } from '../tree';
5+
import { printTree } from './utils/printTree';
56

67
describe('SchemaTree', () => {
78
describe('expanding', () => {
@@ -61,7 +62,7 @@ describe('SchemaTree', () => {
6162
},
6263
},
6364
],
64-
}),
65+
})
6566
);
6667
});
6768

@@ -82,7 +83,7 @@ describe('SchemaTree', () => {
8283
type: 'string',
8384
},
8485
},
85-
}),
86+
})
8687
);
8788
});
8889

@@ -102,7 +103,7 @@ describe('SchemaTree', () => {
102103
type: 'string',
103104
},
104105
},
105-
}),
106+
})
106107
);
107108
});
108109
});
@@ -158,7 +159,7 @@ describe('SchemaTree', () => {
158159
type: 'string',
159160
},
160161
},
161-
}),
162+
})
162163
);
163164
});
164165
});
@@ -187,10 +188,7 @@ describe('SchemaTree', () => {
187188
tree.populate();
188189

189190
tree.unwrap(tree.itemAt(1) as TreeListParentNode);
190-
expect(getNodeMetadata(tree.itemAt(2) as TreeListParentNode)).toHaveProperty(
191-
'error',
192-
'The pointer is empty',
193-
);
191+
expect(getNodeMetadata(tree.itemAt(2) as TreeListParentNode)).toHaveProperty('error', 'The pointer is empty');
194192
});
195193

196194
test('given a custom resolver, should attempt to resolve the reference', () => {
@@ -244,13 +242,13 @@ describe('SchemaTree', () => {
244242
tree.unwrap(tree.itemAt(1) as TreeListParentNode);
245243
expect(getNodeMetadata(tree.itemAt(2) as TreeListParentNode)).toHaveProperty(
246244
'error',
247-
'Cannot dereference external references',
245+
'Cannot dereference external references'
248246
);
249247

250248
tree.unwrap(tree.itemAt(3) as TreeListParentNode);
251249
expect(getNodeMetadata(tree.itemAt(4) as TreeListParentNode)).toHaveProperty(
252250
'error',
253-
'Cannot dereference external references',
251+
'Cannot dereference external references'
254252
);
255253
});
256254

@@ -272,16 +270,256 @@ describe('SchemaTree', () => {
272270
tree.unwrap(tree.itemAt(1) as TreeListParentNode);
273271
expect(getNodeMetadata(tree.itemAt(2) as TreeListParentNode)).toHaveProperty(
274272
'error',
275-
'Could not read "../test"',
273+
'Could not read "../test"'
276274
);
277275

278276
tree.unwrap(tree.itemAt(3) as TreeListParentNode);
279277
expect(getNodeMetadata(tree.itemAt(4) as TreeListParentNode)).toHaveProperty(
280278
'error',
281-
'Pointer "#id" is missing',
279+
'Pointer "#id" is missing'
282280
);
283281
});
284282
});
283+
284+
describe('$refs in allOf', () => {
285+
test('given $ref in allOf', () => {
286+
const schema: JSONSchema4 = {
287+
type: 'object',
288+
properties: {
289+
foo: {
290+
type: 'object',
291+
properties: {
292+
name: {
293+
type: 'string',
294+
},
295+
id: {
296+
type: 'number',
297+
},
298+
},
299+
},
300+
bar: {
301+
allOf: [
302+
{
303+
$ref: '#/properties/foo',
304+
},
305+
{
306+
type: 'object',
307+
properties: {
308+
address: {
309+
type: 'string',
310+
},
311+
},
312+
},
313+
],
314+
},
315+
},
316+
};
317+
318+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
319+
expandedDepth: Infinity,
320+
mergeAllOf: true,
321+
resolveRef: void 0,
322+
});
323+
324+
tree.populate();
325+
expect(printTree(tree)).toMatchInlineSnapshot(`
326+
"└─ #
327+
├─ type: object
328+
└─ children
329+
├─ 0
330+
│ └─ #/properties/foo
331+
│ ├─ type: object
332+
│ └─ children
333+
│ ├─ 0
334+
│ │ └─ #/properties/foo/properties/name
335+
│ │ └─ type: string
336+
│ └─ 1
337+
│ └─ #/properties/foo/properties/id
338+
│ └─ type: number
339+
└─ 1
340+
└─ #/properties/bar
341+
├─ type: object
342+
└─ children
343+
├─ 0
344+
│ └─ #/properties/bar/properties/name
345+
│ └─ type: string
346+
├─ 1
347+
│ └─ #/properties/bar/properties/id
348+
│ └─ type: number
349+
└─ 2
350+
└─ #/properties/bar/properties/address
351+
└─ type: string
352+
"
353+
`);
354+
});
355+
356+
test('given $ref in allOf pointing at another allOf, should keep merging', () => {
357+
const schema: JSONSchema4 = {
358+
type: 'object',
359+
properties: {
360+
baz: {
361+
type: 'object',
362+
},
363+
foo: {
364+
allOf: [
365+
{
366+
type: 'object',
367+
properties: {
368+
name: {
369+
type: 'string',
370+
},
371+
id: {
372+
type: 'number',
373+
},
374+
},
375+
},
376+
{
377+
$ref: '#/properties/baz',
378+
},
379+
],
380+
},
381+
bar: {
382+
allOf: [
383+
{
384+
$ref: '#/properties/foo',
385+
},
386+
{
387+
type: 'object',
388+
properties: {
389+
address: {
390+
type: 'string',
391+
},
392+
},
393+
},
394+
],
395+
},
396+
},
397+
};
398+
399+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
400+
expandedDepth: Infinity,
401+
mergeAllOf: true,
402+
resolveRef: void 0,
403+
});
404+
405+
tree.populate();
406+
expect(printTree(tree)).toMatchInlineSnapshot(`
407+
"└─ #
408+
├─ type: object
409+
└─ children
410+
├─ 0
411+
│ └─ #/properties/baz
412+
│ └─ type: object
413+
├─ 1
414+
│ └─ #/properties/foo
415+
│ ├─ type: object
416+
│ └─ children
417+
│ ├─ 0
418+
│ │ └─ #/properties/foo/properties/name
419+
│ │ └─ type: string
420+
│ └─ 1
421+
│ └─ #/properties/foo/properties/id
422+
│ └─ type: number
423+
└─ 2
424+
└─ #/properties/bar
425+
├─ type: object
426+
└─ children
427+
├─ 0
428+
│ └─ #/properties/bar/properties/name
429+
│ └─ type: string
430+
├─ 1
431+
│ └─ #/properties/bar/properties/id
432+
│ └─ type: number
433+
└─ 2
434+
└─ #/properties/bar/properties/address
435+
└─ type: string
436+
"
437+
`);
438+
});
439+
440+
test('given direct circular reference pointing at allOf, should throw', () => {
441+
const schema: JSONSchema4 = {
442+
type: 'object',
443+
properties: {
444+
foo: {
445+
allOf: [
446+
{
447+
$ref: '#/properties/bar',
448+
},
449+
],
450+
},
451+
bar: {
452+
allOf: [
453+
{
454+
$ref: '#/properties/foo',
455+
},
456+
{
457+
type: 'object',
458+
properties: {
459+
name: {
460+
type: 'string',
461+
},
462+
},
463+
},
464+
],
465+
},
466+
},
467+
};
468+
469+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
470+
expandedDepth: Infinity,
471+
mergeAllOf: true,
472+
resolveRef: void 0,
473+
});
474+
475+
expect(tree.populate.bind(tree)).toThrowError('Circular reference detected');
476+
});
477+
478+
test('given indirect circular reference pointing at allOf, should throw', () => {
479+
const schema: JSONSchema4 = {
480+
type: 'object',
481+
properties: {
482+
baz: {
483+
allOf: [
484+
{
485+
$ref: '#/properties/bar',
486+
},
487+
],
488+
},
489+
foo: {
490+
allOf: [
491+
{
492+
$ref: '#/properties/baz',
493+
},
494+
],
495+
},
496+
bar: {
497+
allOf: [
498+
{
499+
$ref: '#/properties/foo',
500+
},
501+
{
502+
type: 'object',
503+
properties: {
504+
name: {
505+
type: 'string',
506+
},
507+
},
508+
},
509+
],
510+
},
511+
},
512+
};
513+
514+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
515+
expandedDepth: Infinity,
516+
mergeAllOf: true,
517+
resolveRef: void 0,
518+
});
519+
520+
expect(tree.populate.bind(tree)).toThrowError('Circular reference detected');
521+
});
522+
});
285523
});
286524

287525
describe('paths generation', () => {
@@ -328,30 +566,19 @@ describe('SchemaTree', () => {
328566
test('for plain object member', () => {
329567
tree.populate();
330568

331-
expect(getNodeMetadata(tree.itemAt(1)!)).toHaveProperty('path', [
332-
'properties',
333-
'user'
334-
]);
569+
expect(getNodeMetadata(tree.itemAt(1)!)).toHaveProperty('path', ['properties', 'user']);
335570
});
336571

337572
test('for deep object member', () => {
338573
tree.populate();
339574

340-
expect(getNodeMetadata(tree.itemAt(2)!)).toHaveProperty('path', [
341-
'properties',
342-
'user',
343-
'properties',
344-
'name',
345-
]);
575+
expect(getNodeMetadata(tree.itemAt(2)!)).toHaveProperty('path', ['properties', 'user', 'properties', 'name']);
346576
});
347577

348578
test('for array items', () => {
349579
tree.populate();
350580

351-
expect(getNodeMetadata(tree.itemAt(tree.count - 1)!)).toHaveProperty('path', [
352-
'properties',
353-
'permissions',
354-
]);
581+
expect(getNodeMetadata(tree.itemAt(tree.count - 1)!)).toHaveProperty('path', ['properties', 'permissions']);
355582
});
356583

357584
test('for $reffed array items', () => {

0 commit comments

Comments
 (0)