Skip to content

Commit 1b8fabf

Browse files
committed
feat(@angular-devkit/core): add basic support for oneOf/anyOf to addUndefinedDefaults transformer
1 parent 0c112e5 commit 1b8fabf

File tree

3 files changed

+275
-104
lines changed

3 files changed

+275
-104
lines changed

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

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -386,101 +386,6 @@ describe('CoreSchemaRegistry', () => {
386386
expect(result.data).toBe(data);
387387
});
388388

389-
it('adds undefined properties', done => {
390-
const registry = new CoreSchemaRegistry();
391-
registry.addPostTransform(addUndefinedDefaults);
392-
const data: any = {}; // tslint:disable-line:no-any
393-
394-
registry
395-
.compile({
396-
properties: {
397-
bool: { type: 'boolean' },
398-
str: { type: 'string', default: 'someString' },
399-
obj: {
400-
properties: {
401-
num: { type: 'number' },
402-
other: { type: 'number', default: 0 },
403-
},
404-
},
405-
objAllOk: {
406-
allOf: [
407-
{ type: 'object' },
408-
],
409-
},
410-
objAllBad: {
411-
allOf: [
412-
{ type: 'object' },
413-
{ type: 'number' },
414-
],
415-
},
416-
objOne: {
417-
oneOf: [
418-
{ type: 'object' },
419-
],
420-
},
421-
objNotOk: {
422-
not: { not: { type: 'object' } },
423-
},
424-
objNotBad: {
425-
type: 'object',
426-
not: { type: 'object' },
427-
},
428-
},
429-
})
430-
.pipe(
431-
mergeMap(validator => validator(data)),
432-
map(result => {
433-
expect(result.success).toBe(true);
434-
expect(data.bool).toBeUndefined();
435-
expect(data.str).toBe('someString');
436-
expect(data.obj.num).toBeUndefined();
437-
expect(data.obj.other).toBe(0);
438-
expect(data.objAllOk).toEqual({});
439-
expect(data.objOne).toEqual({});
440-
expect(data.objAllBad).toBeUndefined();
441-
expect(data.objNotOk).toEqual({});
442-
expect(data.objNotBad).toBeUndefined();
443-
}),
444-
)
445-
.toPromise().then(done, done.fail);
446-
});
447-
448-
it('adds defaults to undefined properties', done => {
449-
const registry = new CoreSchemaRegistry();
450-
registry.addPostTransform(addUndefinedDefaults);
451-
// tslint:disable-line:no-any
452-
const data: any = {
453-
bool: undefined,
454-
str: undefined,
455-
obj: {
456-
num: undefined,
457-
},
458-
};
459-
460-
registry
461-
.compile({
462-
properties: {
463-
bool: { type: 'boolean', default: true },
464-
str: { type: 'string', default: 'someString' },
465-
obj: {
466-
properties: {
467-
num: { type: 'number', default: 0 },
468-
},
469-
},
470-
},
471-
})
472-
.pipe(
473-
mergeMap(validator => validator(data)),
474-
map(result => {
475-
expect(result.success).toBe(true);
476-
expect(data.bool).toBe(true);
477-
expect(data.str).toBe('someString');
478-
expect(data.obj.num).toBe(0);
479-
}),
480-
)
481-
.toPromise().then(done, done.fail);
482-
});
483-
484389
it('adds deprecated options usage', done => {
485390
const registry = new CoreSchemaRegistry();
486391
const deprecatedMessages: string[] = [];

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

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
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 { JsonObject, JsonValue, isJsonObject } from '../interface';
8+
import { JsonObject, JsonValue, isJsonArray, isJsonObject } from '../interface';
99
import { JsonPointer } from './interface';
1010
import { JsonSchema } from './schema';
1111
import { getTypesOfSchema } from './utility';
@@ -15,10 +15,7 @@ export function addUndefinedDefaults(
1515
_pointer: JsonPointer,
1616
schema?: JsonSchema,
1717
): JsonValue {
18-
if (schema === true || schema === false) {
19-
return value;
20-
}
21-
if (schema === undefined) {
18+
if (typeof schema === 'boolean' || schema === undefined) {
2219
return value;
2320
}
2421

@@ -64,14 +61,37 @@ export function addUndefinedDefaults(
6461
}
6562

6663
for (const [propName, schemaObject] of Object.entries(schema.properties)) {
67-
if (newValue[propName] !== undefined || propName === '$schema') {
64+
if (propName === '$schema' || !isJsonObject(schemaObject)) {
6865
continue;
6966
}
7067

71-
// TODO: Does not currently handle more complex schemas (oneOf/anyOf/etc.)
72-
const defaultValue = (schemaObject as JsonObject).default;
68+
const value = newValue[propName];
69+
if (value === undefined) {
70+
newValue[propName] = schemaObject.default;
71+
} else if (isJsonObject(value)) {
72+
// Basic support for oneOf and anyOf.
73+
const propertySchemas = schemaObject.oneOf || schemaObject.anyOf;
74+
const allProperties = Object.keys(value);
75+
// Locate a schema which declares all the properties that the object contains.
76+
const adjustedSchema = isJsonArray(propertySchemas) && propertySchemas.find(s => {
77+
if (!isJsonObject(s)) {
78+
return false;
79+
}
80+
81+
const schemaType = getTypesOfSchema(s);
82+
if (schemaType.size === 1 && schemaType.has('object') && isJsonObject(s.properties)) {
83+
const properties = Object.keys(s.properties);
7384

74-
newValue[propName] = defaultValue;
85+
return allProperties.every(key => properties.includes(key));
86+
}
87+
88+
return false;
89+
});
90+
91+
if (adjustedSchema && isJsonObject(adjustedSchema)) {
92+
newValue[propName] = addUndefinedDefaults(value, _pointer, adjustedSchema);
93+
}
94+
}
7595
}
7696

7797
return newValue;

0 commit comments

Comments
 (0)