Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(@ngtools/json-schema): add support for enums. #4082

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"
]
}