Skip to content

Commit f9e11b6

Browse files
authored
fix: combiners inside of arrays (#53)
1 parent 80612b3 commit f9e11b6

File tree

14 files changed

+335
-45
lines changed

14 files changed

+335
-45
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"title": "Test",
3+
"type": "object",
4+
"properties": {
5+
"array-all-objects": {
6+
"type": "array",
7+
"items": {
8+
"allOf": [
9+
{
10+
"properties": {
11+
"foo": {
12+
"type": "string"
13+
}
14+
}
15+
},
16+
{
17+
"properties": {
18+
"bar": {
19+
"type": "string"
20+
}
21+
}
22+
}
23+
],
24+
"type": "object"
25+
}
26+
}
27+
}
28+
}

src/components/JsonSchemaViewer.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import * as React from 'react';
66
import SchemaWorker, { WebWorker } from 'web-worker:../workers/schema.ts';
77

88
import { JSONSchema4 } from 'json-schema';
9-
import { GoToRefHandler, RowRenderer, SchemaTreeListNode } from '../types';
10-
import { isCombiner } from '../utils/isCombiner';
9+
import { GoToRefHandler, IArrayNode, IBaseNode, RowRenderer, SchemaKind, SchemaTreeListNode } from '../types';
10+
import { getArraySubtype } from '../utils/getArraySubtype';
1111
import { isSchemaViewerEmpty } from '../utils/isSchemaViewerEmpty';
12+
import { isCombinerNode } from '../utils/nodes';
1213
import { renderSchema } from '../utils/renderSchema';
1314
import { ComputeSchemaMessageData, isRenderedSchemaMessage } from '../workers/messages';
1415
import { SchemaTree } from './SchemaTree';
@@ -111,17 +112,14 @@ export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaVi
111112
if (i === 0) break;
112113
i--;
113114

114-
if (
115-
!hasAllOf &&
116-
this.props.mergeAllOf !== false &&
117-
node.metadata &&
118-
isCombiner(node.metadata) &&
119-
node.metadata.combiner === 'allOf'
120-
) {
121-
hasAllOf = true;
122-
}
123-
124115
nodes.push(node);
116+
117+
if (hasAllOf || this.props.mergeAllOf === false || !node.metadata) continue;
118+
119+
hasAllOf =
120+
((node.metadata as IBaseNode).type === SchemaKind.Array &&
121+
getArraySubtype(node.metadata as IArrayNode) === 'allOf') ||
122+
(node.metadata && isCombinerNode(node.metadata) && node.metadata.combiner === 'allOf');
125123
}
126124

127125
needsFullRendering = hasAllOf || this.props.maxRows <= nodes.length;

src/components/shared/Property.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { size as _size } from 'lodash-es';
22
import * as React from 'react';
33
import { GoToRefHandler, IArrayNode, IObjectNode, SchemaKind, SchemaNodeWithMeta } from '../../types';
44
import { inferType } from '../../utils/inferType';
5-
import { isCombiner } from '../../utils/isCombiner';
6-
import { isRef } from '../../utils/isRef';
5+
import { isCombinerNode, isRefNode } from '../../utils/nodes';
76
import { Types } from './Types';
87

98
export interface IProperty {
@@ -12,7 +11,7 @@ export interface IProperty {
1211
}
1312

1413
export const Property: React.FunctionComponent<IProperty> = ({ node, onGoToRef }) => {
15-
const type = isRef(node) ? '$ref' : isCombiner(node) ? node.combiner : node.type;
14+
const type = isRefNode(node) ? '$ref' : isCombinerNode(node) ? node.combiner : node.type;
1615
const subtype =
1716
type === SchemaKind.Array && (node as IArrayNode).items !== undefined
1817
? inferType((node as IArrayNode).items!)

src/utils/__tests__/__snapshots__/renderSchema.spec.ts.snap

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,234 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`renderSchema util should match array-of-allofs.json 1`] = `
4+
Array [
5+
Object {
6+
"canHaveChildren": true,
7+
"id": "random-id",
8+
"level": 0,
9+
"metadata": Object {
10+
"additionalProperties": undefined,
11+
"annotations": Object {
12+
"title": "Test",
13+
},
14+
"enum": undefined,
15+
"id": "random-id",
16+
"path": Array [],
17+
"patternProperties": undefined,
18+
"properties": Object {
19+
"array-all-objects": Object {
20+
"items": Object {
21+
"allOf": Array [
22+
Object {
23+
"properties": Object {
24+
"foo": Object {
25+
"type": "string",
26+
},
27+
},
28+
"type": "object",
29+
},
30+
Object {
31+
"properties": Object {
32+
"bar": Object {
33+
"type": "string",
34+
},
35+
},
36+
"type": "object",
37+
},
38+
],
39+
"type": "object",
40+
},
41+
"type": "array",
42+
},
43+
},
44+
"type": "object",
45+
"validations": Object {},
46+
},
47+
"name": "",
48+
},
49+
Object {
50+
"canHaveChildren": true,
51+
"id": "random-id",
52+
"level": 1,
53+
"metadata": Object {
54+
"additionalItems": undefined,
55+
"annotations": Object {},
56+
"enum": undefined,
57+
"id": "random-id",
58+
"items": Object {
59+
"allOf": Array [
60+
Object {
61+
"properties": Object {
62+
"foo": Object {
63+
"type": "string",
64+
},
65+
},
66+
"type": "object",
67+
},
68+
Object {
69+
"properties": Object {
70+
"bar": Object {
71+
"type": "string",
72+
},
73+
},
74+
"type": "object",
75+
},
76+
],
77+
"type": "object",
78+
},
79+
"name": "array-all-objects",
80+
"path": Array [
81+
"properties",
82+
"array-all-objects",
83+
],
84+
"required": false,
85+
"subtype": "allOf",
86+
"type": "array",
87+
"validations": Object {},
88+
},
89+
"name": "",
90+
},
91+
Object {
92+
"canHaveChildren": true,
93+
"id": "random-id",
94+
"level": 2,
95+
"metadata": Object {
96+
"annotations": Object {},
97+
"combiner": "allOf",
98+
"id": "random-id",
99+
"name": "array-all-objects",
100+
"path": Array [
101+
"properties",
102+
"array-all-objects",
103+
"items",
104+
],
105+
"properties": Array [
106+
Object {
107+
"properties": Object {
108+
"foo": Object {
109+
"type": "string",
110+
},
111+
},
112+
"type": "object",
113+
},
114+
Object {
115+
"properties": Object {
116+
"bar": Object {
117+
"type": "string",
118+
},
119+
},
120+
"type": "object",
121+
},
122+
],
123+
"required": false,
124+
"type": "object",
125+
},
126+
"name": "",
127+
},
128+
Object {
129+
"canHaveChildren": true,
130+
"id": "random-id",
131+
"level": 3,
132+
"metadata": Object {
133+
"additionalProperties": undefined,
134+
"annotations": Object {},
135+
"enum": undefined,
136+
"id": "random-id",
137+
"path": Array [
138+
"properties",
139+
"array-all-objects",
140+
"items",
141+
"allOf",
142+
0,
143+
],
144+
"patternProperties": undefined,
145+
"properties": Object {
146+
"foo": Object {
147+
"type": "string",
148+
},
149+
},
150+
"type": "object",
151+
"validations": Object {},
152+
},
153+
"name": "",
154+
},
155+
Object {
156+
"id": "random-id",
157+
"level": 4,
158+
"metadata": Object {
159+
"annotations": Object {},
160+
"enum": undefined,
161+
"id": "random-id",
162+
"name": "foo",
163+
"path": Array [
164+
"properties",
165+
"array-all-objects",
166+
"items",
167+
"allOf",
168+
0,
169+
"properties",
170+
"foo",
171+
],
172+
"required": false,
173+
"type": "string",
174+
"validations": Object {},
175+
},
176+
"name": "",
177+
},
178+
Object {
179+
"canHaveChildren": true,
180+
"id": "random-id",
181+
"level": 3,
182+
"metadata": Object {
183+
"additionalProperties": undefined,
184+
"annotations": Object {},
185+
"divider": "and",
186+
"enum": undefined,
187+
"id": "random-id",
188+
"path": Array [
189+
"properties",
190+
"array-all-objects",
191+
"items",
192+
"allOf",
193+
1,
194+
],
195+
"patternProperties": undefined,
196+
"properties": Object {
197+
"bar": Object {
198+
"type": "string",
199+
},
200+
},
201+
"type": "object",
202+
"validations": Object {},
203+
},
204+
"name": "",
205+
},
206+
Object {
207+
"id": "random-id",
208+
"level": 4,
209+
"metadata": Object {
210+
"annotations": Object {},
211+
"enum": undefined,
212+
"id": "random-id",
213+
"name": "bar",
214+
"path": Array [
215+
"properties",
216+
"array-all-objects",
217+
"items",
218+
"allOf",
219+
1,
220+
"properties",
221+
"bar",
222+
],
223+
"required": false,
224+
"type": "string",
225+
"validations": Object {},
226+
},
227+
"name": "",
228+
},
229+
]
230+
`;
231+
3232
exports[`renderSchema util should match array-of-objects.json 1`] = `
4233
Array [
5234
Object {
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { isCombiner } from '../isCombiner';
1+
import { isCombinerNode } from '../nodes';
22

3-
describe('isCombiner function', () => {
3+
describe('isCombinerNode function', () => {
44
test('should return false if object without combiner is given', () => {
5-
expect(isCombiner({} as any)).toBe(false);
6-
expect(isCombiner({ properties: [] } as any)).toBe(false);
5+
expect(isCombinerNode({} as any)).toBe(false);
6+
expect(isCombinerNode({ properties: [] } as any)).toBe(false);
77
});
88

99
test.each(['allOf', 'anyOf', 'oneOf'])('should return true if object with %s is given', combiner => {
10-
expect(isCombiner({ combiner } as any)).toBe(true);
10+
expect(isCombinerNode({ combiner } as any)).toBe(true);
1111
});
1212
});

src/utils/__tests__/renderSchema.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe('renderSchema util', () => {
1515
['combiner-schema.json', ''],
1616
['array-of-objects.json', ''],
1717
['array-of-refs.json', ''],
18+
['array-of-allofs.json', ''],
1819
['tickets.schema.json', ''],
1920
])('should match %s', (schema, dereferenced) => {
2021
expect(

src/utils/getArraySubtype.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { JSONSchema4TypeName } from 'json-schema';
2+
import { IArrayNode, JSONSchema4CombinerName } from '../types';
3+
import { getCombiner } from './getCombiner';
4+
import { inferType } from './inferType';
5+
6+
export function getArraySubtype(
7+
node: IArrayNode,
8+
): JSONSchema4TypeName | JSONSchema4TypeName[] | JSONSchema4CombinerName | string | undefined {
9+
if (!node.items || Array.isArray(node.items)) return;
10+
if ('$ref' in node.items) {
11+
return `$ref( ${node.items.$ref} )`;
12+
}
13+
14+
const combiner = getCombiner(node.items);
15+
16+
if (combiner !== undefined) {
17+
return combiner;
18+
}
19+
20+
return inferType(node.items);
21+
}

src/utils/getCombiner.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { JSONSchema4 } from 'json-schema';
2+
import { JSONSchema4CombinerName } from '../types';
3+
4+
export const getCombiner = (node: JSONSchema4): JSONSchema4CombinerName | void => {
5+
if ('allOf' in node) return 'allOf';
6+
if ('anyOf' in node) return 'anyOf';
7+
if ('oneOf' in node) return 'oneOf';
8+
};

src/utils/isCombiner.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
import { ICombinerNode, SchemaNode } from '../types';
1+
import { JSONSchema4CombinerName } from '../types';
22

3-
export const isCombiner = (node: SchemaNode): node is ICombinerNode => 'combiner' in node;
3+
const combinerTypes = ['allOf', 'oneOf', 'anyOf'];
4+
5+
export const isCombiner = (type: string): type is JSONSchema4CombinerName => combinerTypes.includes(type);

src/utils/isRef.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)