Skip to content

Commit 232da54

Browse files
clydinhansl
authored andcommitted
feat(@angular-devkit/core): add not/anyOf/allOf/oneOf support to undefined defaults
1 parent 2dbd089 commit 232da54

File tree

3 files changed

+167
-20
lines changed

3 files changed

+167
-20
lines changed

packages/angular_devkit/core/src/json/interface.ts

+6
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,9 @@ export interface JsonAstComment extends JsonAstNodeBase {
109109

110110

111111
export type JsonValue = JsonAstNode['value'];
112+
113+
export const JsonValue = {
114+
isJsonObject(value: JsonValue): value is JsonObject {
115+
return value != null && typeof value === 'object' && !Array.isArray(value);
116+
},
117+
};

packages/angular_devkit/core/src/json/schema/registry_spec.ts

+58
Original file line numberDiff line numberDiff line change
@@ -366,4 +366,62 @@ describe('CoreSchemaRegistry', () => {
366366
)
367367
.toPromise().then(done, done.fail);
368368
});
369+
370+
it('adds undefined properties', done => {
371+
const registry = new CoreSchemaRegistry();
372+
const data: any = {}; // tslint:disable-line:no-any
373+
374+
registry
375+
.compile({
376+
properties: {
377+
bool: { type: 'boolean' },
378+
str: { type: 'string', default: 'someString' },
379+
obj: {
380+
properties: {
381+
num: { type: 'number' },
382+
other: { type: 'number', default: 0 },
383+
},
384+
},
385+
objAllOk: {
386+
allOf: [
387+
{ type: 'object' },
388+
],
389+
},
390+
objAllBad: {
391+
allOf: [
392+
{ type: 'object' },
393+
{ type: 'number' },
394+
],
395+
},
396+
objOne: {
397+
oneOf: [
398+
{ type: 'object' },
399+
],
400+
},
401+
objNotOk: {
402+
not: { not: { type: 'object' } },
403+
},
404+
objNotBad: {
405+
type: 'object',
406+
not: { type: 'object' },
407+
},
408+
},
409+
})
410+
.pipe(
411+
mergeMap(validator => validator(data)),
412+
map(result => {
413+
expect(result.success).toBe(true);
414+
expect(data.bool).toBeUndefined();
415+
expect(data.str).toBe('someString');
416+
expect(data.obj.num).toBeUndefined();
417+
expect(data.obj.other).toBe(0);
418+
expect(data.objAllOk).toEqual({});
419+
expect(data.objOne).toEqual({});
420+
expect(data.objAllBad).toBeUndefined();
421+
expect(data.objNotOk).toEqual({});
422+
expect(data.objNotBad).toBeUndefined();
423+
}),
424+
)
425+
.toPromise().then(done, done.fail);
426+
});
369427
});

packages/angular_devkit/core/src/json/schema/transforms.ts

+103-20
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,121 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import { JsonArray, JsonObject, JsonValue } from '../interface';
8+
import { JsonObject, JsonValue } from '../interface';
99
import { JsonPointer } from './interface';
1010

11+
const allTypes = ['string', 'integer', 'number', 'object', 'array', 'boolean', 'null'];
12+
13+
function findTypes(schema: JsonObject): Set<string> {
14+
if (!schema) {
15+
return new Set();
16+
}
17+
18+
let potentials: Set<string>;
19+
if (typeof schema.type === 'string') {
20+
potentials = new Set([schema.type]);
21+
} else if (Array.isArray(schema.type)) {
22+
potentials = new Set(schema.type as string[]);
23+
} else {
24+
potentials = new Set(allTypes);
25+
}
26+
27+
if (JsonValue.isJsonObject(schema.not)) {
28+
const notTypes = findTypes(schema.not);
29+
potentials = new Set([...potentials].filter(p => !notTypes.has(p)));
30+
}
31+
32+
if (Array.isArray(schema.allOf)) {
33+
for (const sub of schema.allOf) {
34+
const types = findTypes(sub as JsonObject);
35+
potentials = new Set([...potentials].filter(p => types.has(p)));
36+
}
37+
}
38+
39+
if (Array.isArray(schema.oneOf)) {
40+
let options = new Set<string>();
41+
for (const sub of schema.oneOf) {
42+
const types = findTypes(sub as JsonObject);
43+
options = new Set([...options, ...types]);
44+
}
45+
potentials = new Set([...potentials].filter(p => options.has(p)));
46+
}
47+
48+
if (Array.isArray(schema.anyOf)) {
49+
let options = new Set<string>();
50+
for (const sub of schema.anyOf) {
51+
const types = findTypes(sub as JsonObject);
52+
options = new Set([...options, ...types]);
53+
}
54+
potentials = new Set([...potentials].filter(p => options.has(p)));
55+
}
56+
57+
return potentials;
58+
}
1159

1260
export function addUndefinedDefaults(
13-
value: JsonValue | undefined,
61+
value: JsonValue,
1462
_pointer: JsonPointer,
1563
schema?: JsonObject,
16-
_root?: JsonObject | JsonArray,
1764
): JsonValue {
18-
if (value === undefined && schema) {
19-
if (schema.items || schema.type == 'array') {
20-
return [];
65+
if (!schema) {
66+
return value;
67+
}
68+
69+
const types = findTypes(schema);
70+
if (types.size === 0) {
71+
return value;
72+
}
73+
74+
let type;
75+
if (types.size === 1) {
76+
// only one potential type
77+
type = Array.from(types)[0];
78+
} else if (types.size === 2 && types.has('array') && types.has('object')) {
79+
// need to create one of them and array is simpler
80+
type = 'array';
81+
} else if (schema.properties && types.has('object')) {
82+
// assume object
83+
type = 'object';
84+
} else if (schema.items && types.has('array')) {
85+
// assume array
86+
type = 'array';
87+
} else {
88+
// anything else needs to be checked by the consumer anyway
89+
return value;
90+
}
91+
92+
if (type === 'array') {
93+
return value == undefined ? [] : value;
94+
}
95+
96+
if (type === 'object') {
97+
let newValue;
98+
if (value == undefined) {
99+
newValue = {} as JsonObject;
100+
} else if (JsonValue.isJsonObject(value)) {
101+
newValue = value;
102+
} else {
103+
return value;
21104
}
22-
if (schema.properties || schema.type == 'object') {
23-
const newValue: JsonObject = {};
24-
for (const propName of Object.getOwnPropertyNames(schema.properties || {})) {
25-
newValue[propName] = undefined as any; // tslint:disable-line:no-any
26-
}
27105

106+
if (!JsonValue.isJsonObject(schema.properties)) {
28107
return newValue;
29108
}
30-
} else if (schema
31-
&& typeof value == 'object' && value
32-
&& (schema.properties || schema.type == 'object')
33-
) {
34-
for (const propName of Object.getOwnPropertyNames(schema.properties || {})) {
35-
(value as JsonObject)[propName] = (propName in value)
36-
? (value as JsonObject)[propName]
37-
: undefined as any; // tslint:disable-line:no-any
109+
110+
for (const propName of Object.getOwnPropertyNames(schema.properties)) {
111+
if (propName in newValue) {
112+
continue;
113+
}
114+
115+
// TODO: Does not currently handle more complex schemas (oneOf/anyOf/etc.)
116+
const defaultValue = (schema.properties[propName] as JsonObject).default;
117+
118+
newValue[propName] = defaultValue;
38119
}
120+
121+
return newValue;
39122
}
40123

41-
return value as JsonValue;
124+
return value;
42125
}

0 commit comments

Comments
 (0)