Skip to content

Commit 69d64ff

Browse files
committed
fix: filter out invalid properties/items
1 parent 5dd5dfa commit 69d64ff

File tree

3 files changed

+120
-6
lines changed

3 files changed

+120
-6
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { SchemaKind } from '../../../types';
2+
import { walk } from '../walk';
3+
4+
describe('Schema Walker', () => {
5+
describe('when type equals array', () => {
6+
test.each(['[circular]', 2, null])('given invalid items, should normalize them %s', items => {
7+
const schema = {
8+
type: SchemaKind.Array,
9+
items,
10+
};
11+
const { value: node } = walk(schema as any).next();
12+
13+
expect(node).toStrictEqual({
14+
fragment: schema,
15+
node: {
16+
id: expect.any(String),
17+
type: SchemaKind.Array,
18+
annotations: {},
19+
enum: void 0,
20+
validations: {},
21+
additionalItems: void 0,
22+
items: void 0,
23+
},
24+
});
25+
});
26+
27+
test.each([{ type: 'string' }, [{ type: 'number' }]])(
28+
'given valid items, should leave them untouched %s',
29+
items => {
30+
const schema = {
31+
type: SchemaKind.Array,
32+
items,
33+
};
34+
const { value: node } = walk(schema as any).next();
35+
36+
expect(node).toStrictEqual({
37+
fragment: schema,
38+
node: {
39+
id: expect.any(String),
40+
type: SchemaKind.Array,
41+
annotations: {},
42+
enum: void 0,
43+
validations: {},
44+
additionalItems: void 0,
45+
items,
46+
},
47+
});
48+
},
49+
);
50+
});
51+
52+
describe('when type equals object', () => {
53+
test.each(['[circular]', 2, null, [{}]])('given invalid properties, should normalize them %s', properties => {
54+
const schema = {
55+
type: SchemaKind.Object,
56+
properties,
57+
};
58+
const { value: node } = walk(schema as any).next();
59+
60+
expect(node).toStrictEqual({
61+
fragment: schema,
62+
node: {
63+
id: expect.any(String),
64+
type: SchemaKind.Object,
65+
annotations: {},
66+
enum: void 0,
67+
validations: {},
68+
additionalProperties: void 0,
69+
patternProperties: void 0,
70+
properties: void 0,
71+
},
72+
});
73+
});
74+
75+
test.each([{}, { foo: { type: 'string' } }])(
76+
'given valid properties, should leave them untouched %s',
77+
properties => {
78+
const schema = {
79+
type: SchemaKind.Object,
80+
properties,
81+
};
82+
const { value: node } = walk(schema as any).next();
83+
84+
expect(node).toStrictEqual({
85+
fragment: schema,
86+
node: {
87+
id: expect.any(String),
88+
type: SchemaKind.Object,
89+
annotations: {},
90+
enum: void 0,
91+
validations: {},
92+
additionalProperties: void 0,
93+
patternProperties: void 0,
94+
properties,
95+
},
96+
});
97+
},
98+
);
99+
});
100+
});

src/tree/utils/walk.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { Optional } from '@stoplight/types';
12
import { JSONSchema4 } from 'json-schema';
3+
import { isObject as _isObject } from 'lodash';
24
import { IArrayNode, IBaseNode, ICombinerNode, IObjectNode, SchemaKind, SchemaNode } from '../../types';
35
import { flattenTypes } from '../../utils/flattenTypes';
46
import { generateId } from '../../utils/generateId';
@@ -12,13 +14,17 @@ import { normalizeRequired } from '../../utils/normalizeRequired';
1214
function assignNodeSpecificFields(base: IBaseNode, node: JSONSchema4) {
1315
switch (getPrimaryType(node)) {
1416
case SchemaKind.Array:
15-
(base as IArrayNode).items = node.items;
16-
(base as IArrayNode).additionalItems = node.additionalItems;
17+
(base as IArrayNode).items = unwrapItemsOrUndefined(node.items);
18+
(base as IArrayNode).additionalItems =
19+
typeof node.additionalItems === 'boolean' ? node.additionalItems : unwrapItemsOrUndefined(node.additionalItems);
1720
break;
1821
case SchemaKind.Object:
19-
(base as IObjectNode).properties = node.properties;
20-
(base as IObjectNode).patternProperties = node.patternProperties;
21-
(base as IObjectNode).additionalProperties = node.additionalProperties;
22+
(base as IObjectNode).properties = unwrapPropertiesOrUndefined(node.properties);
23+
(base as IObjectNode).patternProperties = unwrapPropertiesOrUndefined(node.patternProperties);
24+
(base as IObjectNode).additionalProperties =
25+
typeof node.additionalProperties === 'boolean'
26+
? node.additionalProperties
27+
: unwrapPropertiesOrUndefined(node.additionalProperties);
2228
break;
2329
}
2430
}
@@ -100,3 +106,11 @@ export function* walk(schema: JSONSchema4[] | JSONSchema4): IterableIterator<Wal
100106
}
101107
}
102108
}
109+
110+
function unwrapItemsOrUndefined<T = unknown>(value: T): Optional<T> {
111+
return _isObject(value) ? value : void 0;
112+
}
113+
114+
function unwrapPropertiesOrUndefined<T = unknown>(value: T): Optional<T> {
115+
return _isObject(value) && !Array.isArray(value) ? value : void 0;
116+
}

src/utils/guards.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ export const isArrayNodeWithItems = (
1010
export const isRefNode = (node: SchemaNode): node is IRefNode => '$ref' in node;
1111

1212
export const hasRefItems = (node: SchemaNode): node is Omit<IArrayNode, 'items'> & { items: Omit<IRefNode, 'id'> } =>
13-
'items' in node && !!node.items && '$ref' in node.items;
13+
isArrayNodeWithItems(node) && '$ref' in node.items;
1414

1515
export const isCombinerNode = (node: SchemaNode): node is ICombinerNode => 'combiner' in node;

0 commit comments

Comments
 (0)