@ngtools/json-schema
Version:
Schema validating and reading for configurations, similar to Angular CLI config.
430 lines • 15 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const error_1 = require("./error");
class InvalidSchema extends error_1.JsonSchemaErrorBase {
}
exports.InvalidSchema = InvalidSchema;
class InvalidValueError extends error_1.JsonSchemaErrorBase {
}
exports.InvalidValueError = InvalidValueError;
class MissingImplementationError extends error_1.JsonSchemaErrorBase {
}
exports.MissingImplementationError = MissingImplementationError;
class SettingReadOnlyPropertyError extends error_1.JsonSchemaErrorBase {
}
exports.SettingReadOnlyPropertyError = SettingReadOnlyPropertyError;
class InvalidUpdateValue extends error_1.JsonSchemaErrorBase {
}
exports.InvalidUpdateValue = InvalidUpdateValue;
/**
* Holds all the information, including the value, of a node in the schema tree.
*/
class SchemaTreeNode {
constructor(nodeMetaData) {
this._defined = false;
this._dirty = false;
this._schema = nodeMetaData.schema;
this._name = nodeMetaData.name;
this._value = nodeMetaData.value;
this._forward = nodeMetaData.forward;
this._parent = nodeMetaData.parent;
}
dispose() {
this._parent = null;
this._schema = null;
this._value = null;
if (this._forward) {
this._forward.dispose();
}
this._forward = null;
}
get defined() { return this._defined; }
get dirty() { return this._dirty; }
set dirty(v) {
if (v) {
this._defined = true;
this._dirty = true;
if (this._parent) {
this._parent.dirty = true;
}
}
}
get value() { return this.get(); }
get name() { return this._name; }
get readOnly() { return this._schema['readOnly']; }
get frozen() { return true; }
get description() {
return 'description' in this._schema ? this._schema['description'] : null;
}
get required() {
if (!this._parent) {
return false;
}
return this._parent.isChildRequired(this.name);
}
isChildRequired(_name) { return false; }
get parent() { return this._parent; }
get children() { return null; }
get items() { return null; }
get itemPrototype() { return null; }
set(_v, _init = false, _force = false) {
if (!this.readOnly) {
throw new MissingImplementationError();
}
throw new SettingReadOnlyPropertyError();
}
isCompatible(_v) { return false; }
static _defineProperty(proto, treeNode) {
if (treeNode.readOnly) {
Object.defineProperty(proto, treeNode.name, {
enumerable: true,
get: () => treeNode.get()
});
}
else {
Object.defineProperty(proto, treeNode.name, {
enumerable: true,
get: () => treeNode.get(),
set: (v) => treeNode.set(v)
});
}
}
}
exports.SchemaTreeNode = SchemaTreeNode;
/** Base Class used for Non-Leaves TreeNode. Meaning they can have children. */
class NonLeafSchemaTreeNode extends SchemaTreeNode {
dispose() {
for (const key of Object.keys(this.children || {})) {
this.children[key].dispose();
}
for (let item of this.items || []) {
item.dispose();
}
super.dispose();
}
get() {
if (this.defined) {
return this._value;
}
else {
return undefined;
}
}
destroy() {
this._defined = false;
this._value = null;
}
// Helper function to create a child based on its schema.
_createChildProperty(name, value, forward, schema, define = true) {
const type = ('oneOf' in schema) ? 'oneOf' :
('enum' in schema) ? 'enum' : schema['type'];
let Klass = null;
switch (type) {
case 'object':
Klass = ObjectSchemaTreeNode;
break;
case 'array':
Klass = ArraySchemaTreeNode;
break;
case 'string':
Klass = StringSchemaTreeNode;
break;
case 'boolean':
Klass = BooleanSchemaTreeNode;
break;
case 'number':
Klass = NumberSchemaTreeNode;
break;
case 'integer':
Klass = IntegerSchemaTreeNode;
break;
case 'enum':
Klass = EnumSchemaTreeNode;
break;
case 'oneOf':
Klass = OneOfSchemaTreeNode;
break;
default:
throw new InvalidSchema('Type ' + type + ' not understood by SchemaClassFactory.');
}
const metaData = new Klass({ parent: this, forward, value, schema, name });
if (define) {
SchemaTreeNode._defineProperty(this._value, metaData);
}
return metaData;
}
}
exports.NonLeafSchemaTreeNode = NonLeafSchemaTreeNode;
class OneOfSchemaTreeNode extends NonLeafSchemaTreeNode {
constructor(metaData) {
super(metaData);
let { value, forward, schema } = metaData;
this._typesPrototype = schema['oneOf'].map((schema) => {
return this._createChildProperty('', '', forward, schema, false);
});
this._currentTypeHolder = null;
this._set(value, true, false);
}
_set(v, init, force) {
if (!init && this.readOnly && !force) {
throw new SettingReadOnlyPropertyError();
}
// Find the first type prototype that is compatible with the
let proto = null;
for (let i = 0; i < this._typesPrototype.length; i++) {
const p = this._typesPrototype[i];
if (p.isCompatible(v)) {
proto = p;
break;
}
}
if (proto == null) {
return;
}
if (!init) {
this.dirty = true;
}
this._currentTypeHolder = proto;
this._currentTypeHolder.set(v, false, true);
}
set(v, _init = false, force = false) {
return this._set(v, false, force);
}
get() {
return this._currentTypeHolder ? this._currentTypeHolder.get() : null;
}
get defaultValue() {
return null;
}
get defined() { return this._currentTypeHolder ? this._currentTypeHolder.defined : false; }
get items() { return this._typesPrototype; }
get type() { return 'oneOf'; }
get tsType() { return null; }
serialize(serializer) { serializer.outputOneOf(this); }
}
exports.OneOfSchemaTreeNode = OneOfSchemaTreeNode;
/** A Schema Tree Node that represents an object. */
class ObjectSchemaTreeNode extends NonLeafSchemaTreeNode {
constructor(metaData) {
super(metaData);
this._frozen = false;
this._set(metaData.value, true, false);
}
_set(value, init, force) {
if (!init && this.readOnly && !force) {
throw new SettingReadOnlyPropertyError();
}
const schema = this._schema;
const forward = this._forward;
this._defined = !!value;
this._children = Object.create(null);
this._value = Object.create(null);
this._dirty = this._dirty || !init;
if (schema['properties']) {
for (const name of Object.keys(schema['properties'])) {
const propertySchema = schema['properties'][name];
this._children[name] = this._createChildProperty(name, value ? value[name] : undefined, forward ? forward.children[name] : null, propertySchema);
}
}
else if (!schema['additionalProperties']) {
throw new InvalidSchema('Schema does not have a properties, but doesnt allow for '
+ 'additional properties.');
}
if (!schema['additionalProperties']) {
this._frozen = true;
Object.freeze(this._value);
Object.freeze(this._children);
}
else if (value) {
// Set other properties which don't have a schema.
for (const key of Object.keys(value)) {
if (!this._children[key]) {
this._value[key] = value[key];
}
}
}
}
set(v, force = false) {
return this._set(v, false, force);
}
get frozen() { return this._frozen; }
get children() { return this._children; }
get type() { return 'object'; }
get tsType() { return Object; }
get defaultValue() { return null; }
isCompatible(v) { return typeof v == 'object' && v !== null; }
isChildRequired(name) {
if (this._schema['required']) {
return this._schema['required'].indexOf(name) != -1;
}
return false;
}
serialize(serializer) { serializer.object(this); }
}
exports.ObjectSchemaTreeNode = ObjectSchemaTreeNode;
/** A Schema Tree Node that represents an array. */
class ArraySchemaTreeNode extends NonLeafSchemaTreeNode {
constructor(metaData) {
super(metaData);
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('', undefined, null, metaData.schema['items'], false);
}
_set(value, init, _force) {
const schema = this._schema;
const forward = this._forward;
this._value = Object.create(null);
this._dirty = this._dirty || !init;
if (value) {
this._defined = true;
}
else {
this._defined = false;
value = [];
}
this._items = [];
this._value = [];
for (let index = 0; index < value.length; index++) {
this._items[index] = this._createChildProperty('' + index, value && value[index], forward && forward.items[index], schema['items']);
}
}
set(v, init = false, force = false) {
return this._set(v, init, force);
}
isCompatible(v) { return Array.isArray(v); }
get type() { return 'array'; }
get tsType() { return Array; }
get items() { return this._items; }
get itemPrototype() { return this._itemPrototype; }
get defaultValue() { return null; }
serialize(serializer) { serializer.array(this); }
}
exports.ArraySchemaTreeNode = ArraySchemaTreeNode;
/**
* The root class of the tree node. Receives a prototype that will be filled with the
* properties of the Schema root.
*/
class RootSchemaTreeNode extends ObjectSchemaTreeNode {
constructor(proto, metaData) {
super(metaData);
for (const key of Object.keys(this._children)) {
if (this._children[key]) {
SchemaTreeNode._defineProperty(proto, this._children[key]);
}
}
}
}
exports.RootSchemaTreeNode = RootSchemaTreeNode;
/** A leaf in the schema tree. Must contain a single primitive value. */
class LeafSchemaTreeNode extends SchemaTreeNode {
constructor(metaData) {
super(metaData);
this._defined = metaData.value !== undefined;
if ('default' in metaData.schema) {
this._default = this.convert(metaData.schema['default']);
}
}
get() {
if (!this.defined && this._forward) {
return this._forward.get();
}
if (!this.defined) {
return 'default' in this._schema ? this._default : undefined;
}
return this._value === undefined
? undefined
: (this._value === null ? null : this.convert(this._value));
}
set(v, init = false, force = false) {
if (this.readOnly && !force) {
throw new SettingReadOnlyPropertyError();
}
let convertedValue = this.convert(v);
if (convertedValue === null || convertedValue === undefined) {
if (this.required) {
throw new InvalidValueError(`Invalid value "${v}" on a required field.`);
}
}
this.dirty = !init;
this._value = convertedValue;
}
destroy() {
this._defined = false;
this._value = null;
}
get defaultValue() {
return this.hasDefault ? this._default : null;
}
get hasDefault() {
return 'default' in this._schema;
}
serialize(serializer) {
serializer.outputValue(this);
}
}
exports.LeafSchemaTreeNode = LeafSchemaTreeNode;
/** Basic primitives for JSON Schema. */
class StringSchemaTreeNode extends LeafSchemaTreeNode {
serialize(serializer) { serializer.outputString(this); }
isCompatible(v) { return typeof v == 'string' || v instanceof String; }
convert(v) { return v === undefined ? undefined : '' + v; }
get type() { return 'string'; }
get tsType() { return String; }
}
class EnumSchemaTreeNode extends LeafSchemaTreeNode {
constructor(metaData) {
super(metaData);
if (!Array.isArray(metaData.schema['enum'])) {
throw new InvalidSchema();
}
if (this.hasDefault && !this._isInEnum(this._default)) {
throw new InvalidSchema();
}
this.set(metaData.value, true, true);
}
_isInEnum(value) {
return this._schema['enum'].some((v) => v === value);
}
get items() { return this._schema['enum']; }
set(value, init = false, force = false) {
if (!(value === undefined || this._isInEnum(value))) {
throw new InvalidUpdateValue('Invalid value can only be one of these: ' + this.items);
}
super.set(value, init, force);
}
isCompatible(v) {
return this._isInEnum(v);
}
convert(v) {
if (v === undefined) {
return undefined;
}
if (!this._isInEnum(v)) {
return undefined;
}
return v;
}
get type() {
return this._schema['type'] || 'any';
}
get tsType() { return null; }
serialize(serializer) { serializer.outputEnum(this); }
}
class BooleanSchemaTreeNode extends LeafSchemaTreeNode {
serialize(serializer) { serializer.outputBoolean(this); }
isCompatible(v) { return typeof v == 'boolean' || v instanceof Boolean; }
convert(v) { return v === undefined ? undefined : !!v; }
get type() { return 'boolean'; }
get tsType() { return Boolean; }
}
class NumberSchemaTreeNode extends LeafSchemaTreeNode {
serialize(serializer) { serializer.outputNumber(this); }
isCompatible(v) { return typeof v == 'number' || v instanceof Number; }
convert(v) { return v === undefined ? undefined : +v; }
get type() { return 'number'; }
get tsType() { return Number; }
}
class IntegerSchemaTreeNode extends NumberSchemaTreeNode {
convert(v) { return v === undefined ? undefined : Math.floor(+v); }
}
//# sourceMappingURL=/users/hansl/sources/angular-cli/src/schema-tree.js.map