Skip to content

Commit

Permalink
feat(@ngtools/json-schema): add support for enums.
Browse files Browse the repository at this point in the history
  • Loading branch information
hansl authored and MRHarrison committed Feb 9, 2017
1 parent 8dbd9be commit 19dfe74
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 23 deletions.
59 changes: 41 additions & 18 deletions packages/@ngtools/json-schema/src/schema-tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,54 @@ import {join} from 'path';
import {RootSchemaTreeNode} from './schema-tree';


describe('SchemaTreeNode', () => {
describe('@ngtools/json-schema', () => {

});
describe('OneOfSchemaTreeNode', () => {
const schemaJsonFilePath = join(__dirname, '../tests/schema1.json');
const schemaJson = JSON.parse(readFileSync(schemaJsonFilePath, 'utf-8'));
const valueJsonFilePath = join(__dirname, '../tests/value1-1.json');
const valueJson = JSON.parse(readFileSync(valueJsonFilePath, 'utf-8'));


describe('OneOfSchemaTreeNode', () => {
const schemaJsonFilePath = join(__dirname, '../tests/schema1.json');
const schemaJson = JSON.parse(readFileSync(schemaJsonFilePath, 'utf-8'));
const valueJsonFilePath = join(__dirname, '../tests/value1-1.json');
const valueJson = JSON.parse(readFileSync(valueJsonFilePath, 'utf-8'));
it('works', () => {
const proto: any = Object.create(null);
new RootSchemaTreeNode(proto, {
value: valueJson,
schema: schemaJson
});

expect(proto.oneOfKey2 instanceof Array).toBe(true);
expect(proto.oneOfKey2.length).toBe(2);

it('works', () => {
const proto: any = Object.create(null);
new RootSchemaTreeNode(proto, {
value: valueJson,
schema: schemaJson
// Set it to a string, which is valid.
proto.oneOfKey2 = 'hello';
expect(proto.oneOfKey2 instanceof Array).toBe(false);
});
});


describe('EnumSchemaTreeNode', () => {
const schemaJsonFilePath = join(__dirname, '../tests/schema2.json');
const schemaJson = JSON.parse(readFileSync(schemaJsonFilePath, 'utf-8'));
const valueJsonFilePath = join(__dirname, '../tests/value2-1.json');
const valueJson = JSON.parse(readFileSync(valueJsonFilePath, 'utf-8'));


expect(proto.oneOfKey2 instanceof Array).toBe(true);
expect(proto.oneOfKey2.length).toBe(2);
it('works', () => {
const proto: any = Object.create(null);
new RootSchemaTreeNode(proto, {
value: valueJson,
schema: schemaJson
});

// Set it to a string, which is valid.
proto.oneOfKey2 = 'hello';
expect(proto.oneOfKey2 instanceof Array).toBe(false);
expect(proto.a instanceof Array).toBe(true);
expect(proto.a).toEqual([null, 'v1', null, 'v3']);

// Set it to a string, which is valid.
proto.a[0] = 'v2';
proto.a[1] = 'INVALID';
expect(proto.a).toEqual(['v2', null, null, 'v3']);
});
});
});

});
53 changes: 48 additions & 5 deletions packages/@ngtools/json-schema/src/schema-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {SchemaNode, TypeScriptType} from './node';


export class InvalidSchema extends JsonSchemaErrorBase {}
export class InvalidValueError extends JsonSchemaErrorBase {}
export class MissingImplementationError extends JsonSchemaErrorBase {}
export class SettingReadOnlyPropertyError extends JsonSchemaErrorBase {}

Expand Down Expand Up @@ -151,8 +152,9 @@ export abstract class NonLeafSchemaTreeNode<T> extends SchemaTreeNode<T> {
// Helper function to create a child based on its schema.
protected _createChildProperty<T>(name: string, value: T, forward: SchemaTreeNode<T>,
schema: Schema, define = true): SchemaTreeNode<T> {

let type: string = schema['oneOf'] ? 'oneOf' : schema['type'];
const type: string =
('oneOf' in schema) ? 'oneOf' :
('enum' in schema) ? 'enum' : schema['type'];
let Klass: { new (arg: TreeNodeConstructorArgument<any>): SchemaTreeNode<any> } = null;

switch (type) {
Expand All @@ -163,6 +165,7 @@ export abstract class NonLeafSchemaTreeNode<T> extends SchemaTreeNode<T> {
case 'number': Klass = NumberSchemaTreeNode; break;
case 'integer': Klass = IntegerSchemaTreeNode; break;

case 'enum': Klass = EnumSchemaTreeNode; break;
case 'oneOf': Klass = OneOfSchemaTreeNode; break;

default:
Expand Down Expand Up @@ -327,7 +330,8 @@ export class ArraySchemaTreeNode extends NonLeafSchemaTreeNode<Array<any>> {
this._set(metaData.value, true, false);

// Keep the item's schema as a schema node. This is important to keep type information.
this._itemPrototype = this._createChildProperty('', null, null, metaData.schema['items']);
this._itemPrototype = this._createChildProperty(
'', null, null, metaData.schema['items'], false);
}

_set(value: any, init: boolean, force: boolean) {
Expand Down Expand Up @@ -397,7 +401,7 @@ export abstract class LeafSchemaTreeNode<T> extends SchemaTreeNode<T> {
super(metaData);
this._defined = !(metaData.value === undefined || metaData.value === null);
if ('default' in metaData.schema) {
this._default = metaData.schema['default'];
this._default = this.convert(metaData.schema['default']);
}
}

Expand All @@ -415,8 +419,15 @@ export abstract class LeafSchemaTreeNode<T> extends SchemaTreeNode<T> {
throw new SettingReadOnlyPropertyError();
}

let convertedValue: T | null = this.convert(v);
if (convertedValue === null || convertedValue === undefined) {
if (this.required) {
throw new InvalidValueError(`Invalid value "${v}" on a required field.`);
}
}

this.dirty = true;
this._value = this.convert(v);
this._value = convertedValue;
}

destroy() {
Expand Down Expand Up @@ -448,6 +459,38 @@ class StringSchemaTreeNode extends LeafSchemaTreeNode<string> {
}


class EnumSchemaTreeNode extends StringSchemaTreeNode {
private _enumValues: string[];

constructor(metaData: TreeNodeConstructorArgument<string>) {
super(metaData);

if (!Array.isArray(metaData.schema['enum'])) {
throw new InvalidSchema();
}
this._enumValues = [].concat(metaData.schema['enum']);
this.set(metaData.value, true);
}

protected _isInEnum(value: string) {
return this._enumValues.some(v => v === value);
}

isCompatible(v: any) {
return (typeof v == 'string' || v instanceof String) && this._isInEnum('' + v);
}
convert(v: any) {
if (v === undefined) {
return undefined;
}
if (v === null || !this._isInEnum('' + v)) {
return null;
}
return '' + v;
}
}


class BooleanSchemaTreeNode extends LeafSchemaTreeNode<boolean> {
serialize(serializer: Serializer) { serializer.outputBoolean(this); }

Expand Down
13 changes: 13 additions & 0 deletions packages/@ngtools/json-schema/tests/schema2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "JsonSchema",
"type": "object",
"properties": {
"a": {
"type": "array",
"items": {
"enum": [ "v1", "v2", "v3" ]
}
}
}
}
8 changes: 8 additions & 0 deletions packages/@ngtools/json-schema/tests/value2-1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"a": [
"INVALID",
"v1",
"INVALID",
"v3"
]
}

0 comments on commit 19dfe74

Please sign in to comment.