metaschema
Advanced tools
Comparing version 0.0.27 to 0.0.28
@@ -7,3 +7,3 @@ #!/usr/bin/env node | ||
const common = require('metarhia-common'); | ||
const common = require('@metarhia/common'); | ||
@@ -10,0 +10,0 @@ const metaschema = require('..'); |
@@ -275,7 +275,21 @@ 'use strict'; // Field decorators | ||
var _History = | ||
var _Table = | ||
/*#__PURE__*/ | ||
function (_Decorator7) { | ||
_inherits(History, _Decorator7); | ||
_inherits(Table, _Decorator7); | ||
function Table() { | ||
_classCallCheck(this, Table); | ||
return _possibleConstructorReturn(this, _getPrototypeOf(Table).apply(this, arguments)); | ||
} | ||
return Table; | ||
}(Decorator); | ||
var _History = | ||
/*#__PURE__*/ | ||
function (_Decorator8) { | ||
_inherits(History, _Decorator8); | ||
function History() { | ||
@@ -293,4 +307,4 @@ _classCallCheck(this, History); | ||
/*#__PURE__*/ | ||
function (_Decorator8) { | ||
_inherits(View, _Decorator8); | ||
function (_Decorator9) { | ||
_inherits(View, _Decorator9); | ||
@@ -308,4 +322,4 @@ function View() { | ||
/*#__PURE__*/ | ||
function (_Decorator9) { | ||
_inherits(Memory, _Decorator9); | ||
function (_Decorator10) { | ||
_inherits(Memory, _Decorator10); | ||
@@ -323,4 +337,4 @@ function Memory() { | ||
/*#__PURE__*/ | ||
function (_Decorator10) { | ||
_inherits(Form, _Decorator10); | ||
function (_Decorator11) { | ||
_inherits(Form, _Decorator11); | ||
@@ -338,4 +352,4 @@ function Form() { | ||
/*#__PURE__*/ | ||
function (_Decorator11) { | ||
_inherits(Arguments, _Decorator11); | ||
function (_Decorator12) { | ||
_inherits(Arguments, _Decorator12); | ||
@@ -369,2 +383,5 @@ function Arguments() { | ||
}, | ||
Table: function Table(def) { | ||
return new _Table(def); | ||
}, | ||
History: function History(def) { | ||
@@ -371,0 +388,0 @@ return new _History(def); |
@@ -11,3 +11,3 @@ 'use strict'; | ||
var common = require('metarhia-common'); | ||
var common = require('@metarhia/common'); | ||
@@ -14,0 +14,0 @@ var last = common.last; |
@@ -29,3 +29,3 @@ 'use strict'; | ||
var validationErrorSerializers = { | ||
var schemaValidationErrorSerializers = { | ||
linkToLog: function linkToLog(error) { | ||
@@ -45,13 +45,13 @@ return 'Forbidden reference to a \'Log\' category ' + "from ".concat(error.source, ".").concat(error.property); | ||
var ValidationError = | ||
var SchemaValidationError = | ||
/*#__PURE__*/ | ||
function (_Error) { | ||
_inherits(ValidationError, _Error); | ||
_inherits(SchemaValidationError, _Error); | ||
function ValidationError(type, source, property, info) { | ||
function SchemaValidationError(type, source, property, info) { | ||
var _this; | ||
_classCallCheck(this, ValidationError); | ||
_classCallCheck(this, SchemaValidationError); | ||
_this = _possibleConstructorReturn(this, _getPrototypeOf(ValidationError).call(this)); | ||
_this = _possibleConstructorReturn(this, _getPrototypeOf(SchemaValidationError).call(this)); | ||
_this.type = type; | ||
@@ -64,2 +64,69 @@ _this.source = source; | ||
_createClass(SchemaValidationError, [{ | ||
key: "toString", | ||
value: function toString() { | ||
return schemaValidationErrorSerializers[this.type](this); | ||
} | ||
}]); | ||
return SchemaValidationError; | ||
}(_wrapNativeSuper(Error)); | ||
var wrap = function wrap(value) { | ||
return Array.isArray(value) ? value.map(wrap).join(', ') : "'".concat(value, "'"); | ||
}; | ||
var validationErrorSerializers = { | ||
unresolvedProperty: function unresolvedProperty(error) { | ||
return "Unresolved property '".concat(error.property, "'"); | ||
}, | ||
missingProperty: function missingProperty(error) { | ||
return "Missing property '".concat(error.property, "'"); | ||
}, | ||
emptyValue: function emptyValue(error) { | ||
return "Empty value in required property '".concat(error.property, "'"); | ||
}, | ||
validation: function validation(error) { | ||
return "Failed to validate rule '".concat(error.property, "'"); | ||
}, | ||
propValidation: function propValidation(error) { | ||
return "Failed to validate property '".concat(error.property, "'"); | ||
}, | ||
immutable: function immutable(error) { | ||
return "Mutation of read-only property '".concat(error.property, "'"); | ||
}, | ||
invalidType: function invalidType(error) { | ||
return "Invalid type of property '".concat(error.property, "', ") + "expected: '".concat(error.info.expected, "', actual: '").concat(error.info.actual, "'"); | ||
}, | ||
invalidClass: function invalidClass(error) { | ||
return "Invalid class of property '".concat(error.property, "', ") + "expected: ".concat(wrap(error.info.expected), ", actual: '").concat(error.info.actual, "'"); | ||
}, | ||
domainValidation: function domainValidation(error) { | ||
return "Failed to validate rule '".concat(error.info, "' on property '").concat(error.property, "'"); | ||
}, | ||
enum: function _enum(error) { | ||
return "Invalid value of a enum in a property '".concat(error.property, "' ") + "allowed: ".concat(wrap(error.info.expected), ", actual: '").concat(error.info.actual, "'"); | ||
}, | ||
undefinedEntity: function undefinedEntity(error) { | ||
return "Undefined ".concat(error.info, " '").concat(error.property, "'"); | ||
} | ||
}; | ||
var ValidationError = | ||
/*#__PURE__*/ | ||
function (_Error2) { | ||
_inherits(ValidationError, _Error2); | ||
function ValidationError(type, property, info) { | ||
var _this2; | ||
_classCallCheck(this, ValidationError); | ||
_this2 = _possibleConstructorReturn(this, _getPrototypeOf(ValidationError).call(this)); | ||
_this2.type = type; | ||
_this2.property = property; | ||
_this2.info = info; | ||
return _this2; | ||
} | ||
_createClass(ValidationError, [{ | ||
@@ -77,13 +144,13 @@ key: "toString", | ||
/*#__PURE__*/ | ||
function (_Error2) { | ||
_inherits(MetaschemaError, _Error2); | ||
function (_Error3) { | ||
_inherits(MetaschemaError, _Error3); | ||
function MetaschemaError(errors) { | ||
var _this2; | ||
var _this3; | ||
_classCallCheck(this, MetaschemaError); | ||
_this2 = _possibleConstructorReturn(this, _getPrototypeOf(MetaschemaError).call(this)); | ||
_this2.errors = errors; | ||
return _this2; | ||
_this3 = _possibleConstructorReturn(this, _getPrototypeOf(MetaschemaError).call(this)); | ||
_this3.errors = errors; | ||
return _this3; | ||
} | ||
@@ -104,4 +171,5 @@ | ||
module.exports = { | ||
SchemaValidationError: SchemaValidationError, | ||
ValidationError: ValidationError, | ||
MetaschemaError: MetaschemaError | ||
}; |
@@ -50,2 +50,3 @@ 'use strict'; | ||
// schema - <Object> parsed schema | ||
// source - <string> schema source | ||
@@ -70,3 +71,3 @@ | ||
callback(null, name, exports); | ||
callback(null, name, exports, source); | ||
}); | ||
@@ -79,3 +80,3 @@ }; | ||
metasync.each(files, function (filepath, callback) { | ||
loadSchema(filepath, api, function (err, name, schema) { | ||
loadSchema(filepath, api, function (err, name, schema, source) { | ||
if (err) { | ||
@@ -86,3 +87,3 @@ callback(err); | ||
schemas.push([name, schema]); | ||
schemas.push([name, schema, source]); | ||
callback(null); | ||
@@ -89,0 +90,0 @@ }); |
'use strict'; | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } | ||
@@ -13,8 +19,2 @@ | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } | ||
@@ -28,9 +28,27 @@ | ||
var _require = require('./schema-errors'), | ||
ValidationError = _require.ValidationError, | ||
MetaschemaError = _require.MetaschemaError; | ||
var _require = require('@metarhia/common'), | ||
iter = _require.iter; | ||
var _require2 = require('./schema-errors'), | ||
SchemaValidationError = _require2.SchemaValidationError, | ||
ValidationError = _require2.ValidationError, | ||
MetaschemaError = _require2.MetaschemaError; | ||
var DOMAINS_NAME = 'domains'; | ||
var addSchema = Symbol('addSchema'); | ||
var factorify = Symbol('factorify'); // Extracts category decorator type, any undecorated category | ||
var factorify = Symbol('factorify'); | ||
var initActions = Symbol('initActions'); | ||
var validate = Symbol('validate'); // Extracts schema decorator | ||
// schema - <Object> | ||
// Returns: <string> | ||
var extractDecorator = function extractDecorator(schema) { | ||
var className = schema.constructor.name; | ||
if (className !== 'Function') { | ||
return className; | ||
} | ||
return Object.getPrototypeOf(schema).name || 'Function'; | ||
}; // Extracts category decorator type, any undecorated category | ||
// will be treated as Local. | ||
@@ -40,4 +58,5 @@ // category - <Object> | ||
var getCategoryType = function getCategoryType(category) { | ||
var type = category.constructor.name; | ||
var type = extractDecorator(category); | ||
return type === 'Object' ? 'Local' : type; | ||
@@ -50,3 +69,3 @@ }; // Verifies that there could be link from source category to destination | ||
// propertyName - <string> | ||
// Returns: <ValidationError> | <null> information about error or null | ||
// Returns: <SchemaValidationError> | <null> information about error or null | ||
// if link is valid | ||
@@ -60,7 +79,7 @@ | ||
if (destinationType === 'Log') { | ||
return new ValidationError('linkToLog', sourceName, propertyName); | ||
return new SchemaValidationError('linkToLog', sourceName, propertyName); | ||
} | ||
if (destinationType === 'Local' && sourceType !== 'Local') { | ||
return new ValidationError('illegalLinkToLocal', sourceName, propertyName, { | ||
return new SchemaValidationError('illegalLinkToLocal', sourceName, propertyName, { | ||
destination: destinationName | ||
@@ -99,3 +118,3 @@ }); | ||
} else { | ||
errors.push(new ValidationError('unresolvedDomain', schemaName, fieldName, { | ||
errors.push(new SchemaValidationError('unresolvedDomain', schemaName, fieldName, { | ||
domain: field.domain | ||
@@ -117,3 +136,3 @@ })); | ||
} else { | ||
errors.push(new ValidationError('unresolvedCategory', schemaName, fieldName, { | ||
errors.push(new SchemaValidationError('unresolvedCategory', schemaName, fieldName, { | ||
category: field.category | ||
@@ -143,2 +162,154 @@ })); | ||
var domainValidators = { | ||
string: function string(domain, prop, value) { | ||
var errors = []; | ||
if (domain.min !== undefined && value.length < domain.min) { | ||
errors.push(new ValidationError('domainValidation', prop, 'min')); | ||
} | ||
if (domain.length !== undefined && value.length > domain.length) { | ||
errors.push(new ValidationError('domainValidation', prop, 'length')); | ||
} | ||
return errors; | ||
}, | ||
number: function number(domain, prop, value) { | ||
var errors = []; // The condition is inverted because of possible NaN | ||
if (domain.min !== undefined && !(value >= domain.min)) { | ||
errors.push(new ValidationError('domainValidation', prop, 'min')); | ||
} // The condition is inverted because of possible NaN | ||
if (domain.max !== undefined && !(value <= domain.max)) { | ||
errors.push(new ValidationError('domainValidation', prop, 'max')); | ||
} | ||
if (domain.subtype === 'int' && !Number.isInteger(value)) { | ||
errors.push(new ValidationError('domainValidation', prop, 'subtype')); | ||
} | ||
return errors; | ||
}, | ||
object: function object(domain, prop, value) { | ||
var errors = []; | ||
var valueClass = value.constructor.name; | ||
if (domain.class !== valueClass) { | ||
errors.push(new ValidationError('invalidClass', prop, { | ||
expected: domain.class, | ||
actual: valueClass | ||
})); | ||
return errors; | ||
} | ||
if (domain.length !== undefined && (value.length === undefined || value.length > domain.length)) { | ||
errors.push(new ValidationError('domainValidation', prop, 'length')); | ||
} | ||
return errors; | ||
}, | ||
bigint: function bigint() { | ||
return []; | ||
}, | ||
boolean: function boolean() { | ||
return []; | ||
}, | ||
function: function _function() { | ||
return []; | ||
}, | ||
symbol: function symbol() { | ||
return []; | ||
} | ||
}; // Validates value against a domain | ||
// value - <any> | ||
// path - <string> | ||
// domain - <Object> | ||
// Returns: <ValidationError[]> | ||
var validateDomain = function validateDomain(value, path, domain) { | ||
var errors = []; | ||
var domainType = extractDecorator(domain); | ||
if (domain.type) { | ||
var type = _typeof(value); | ||
if (type !== domain.type) { | ||
errors.push(new ValidationError('invalidType', path, { | ||
expected: domain.type, | ||
actual: type | ||
})); | ||
return errors; | ||
} | ||
var validator = domainValidators[type]; | ||
errors.push.apply(errors, _toConsumableArray(validator(domain, path, value))); | ||
} | ||
if (domainType === 'Enum') { | ||
if (!domain.values.includes(value)) { | ||
errors.push(new ValidationError('enum', path, { | ||
expected: domain.values, | ||
actual: value | ||
})); | ||
} | ||
} | ||
if (domainType === 'Flags') { | ||
var valueClass = value.constructor.name; | ||
if (valueClass !== 'Uint64' && valueClass !== 'FlagsClass') { | ||
errors.push(new ValidationError('invalidClass', path, { | ||
expected: ['Uint64', 'FlagsClass'], | ||
actual: valueClass | ||
})); | ||
} | ||
} | ||
if (domain.check && !domain.check(value)) { | ||
errors.push(new ValidationError('domainValidation', path, 'check')); | ||
} | ||
return errors; | ||
}; | ||
var validateLink = function validateLink(value, path, definition, ms, patch) { | ||
var errors = []; | ||
var category = definition.definition; | ||
var type = extractDecorator(definition); | ||
if (type === 'Include') { | ||
return ms[validate](category, value, patch, "".concat(path, ".")); | ||
} | ||
var checkLink = function checkLink(value, path) { | ||
var valueClass = value.constructor.name; | ||
if (valueClass !== 'Uint64' && valueClass !== 'String') { | ||
errors.push(new ValidationError('invalidClass', path, { | ||
expected: ['Uint64', 'String'], | ||
actual: valueClass | ||
})); | ||
} | ||
}; | ||
if (type === 'Many') { | ||
if (!Array.isArray(value)) { | ||
errors.push(new ValidationError('invalidType', "".concat(path), { | ||
expected: 'Array', | ||
actual: _typeof(value) | ||
})); | ||
} else { | ||
value.forEach(function (val, index) { | ||
return checkLink(val, "".concat(path, "[").concat(index, "]")); | ||
}); | ||
} | ||
} else { | ||
checkLink(value, path); | ||
} | ||
return errors; | ||
}; | ||
var Metaschema = | ||
@@ -153,2 +324,4 @@ /*#__PURE__*/ | ||
this.forms = new Map(); | ||
this.actions = new Map(); | ||
this.sources = []; | ||
} // Internal add schema, only processes the `schema` | ||
@@ -160,7 +333,9 @@ // but doesn't do anything else (i.e. linking) | ||
key: addSchema, | ||
value: function value(name, schema) { | ||
value: function value(name, schema, source) { | ||
this.sources.push(source); | ||
if (name === DOMAINS_NAME) { | ||
for (var _name in schema) { | ||
var domain = schema[_name]; | ||
var decorator = domain.constructor.name; | ||
var decorator = extractDecorator(domain); | ||
@@ -175,3 +350,3 @@ if (decorator === 'Flags' && domain.enum) { | ||
} else { | ||
var _decorator = schema.constructor.name; | ||
var _decorator = extractDecorator(schema); | ||
@@ -201,3 +376,3 @@ if (_decorator === 'Form') { | ||
if (typeof def === 'string') name = def;else name = def.category || def.domain; | ||
var decorator = def.constructor.name; | ||
var decorator = extractDecorator(def); | ||
var category = this.categories.get(name); | ||
@@ -314,23 +489,19 @@ | ||
return factory; | ||
} // Validate instance against a category | ||
// category - <string> category name | ||
// instance - <any> instance to validate | ||
// result - <Object> in a form { valid: true, errors: [] } | ||
// Returns: <Object> result | ||
} // Validate object against a schema | ||
// schema - <Object>, schema to validate against | ||
// object - <Object>, object to validate | ||
// patch - <boolean>, flag to determine if the object contains patch or | ||
// value, default: `false` | ||
// path - <string>, path to an object, for nested objects, default: `''` | ||
// Returns: <ValidationError[]> | ||
}, { | ||
key: "validate", | ||
value: function validate(category, instance) { | ||
key: validate, | ||
value: function value(schema, object) { | ||
var patch = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; | ||
var path = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; | ||
var errors = []; | ||
var record = this.categories.get(category); | ||
if (!record) { | ||
errors.push("Category ".concat(category, " not found")); | ||
return new MetaschemaError(errors); | ||
} | ||
var definition = record.definition; | ||
var defKeys = new Set(Object.keys(definition)); | ||
var insKeys = new Set(Object.keys(instance)); | ||
var keys = new Set(_toConsumableArray(defKeys).concat(_toConsumableArray(insKeys))); | ||
var schemaProps = new Set(Object.keys(schema)); | ||
var objectProps = new Set(Object.keys(object)); | ||
var props = new Set(_toConsumableArray(schemaProps).concat(_toConsumableArray(objectProps))); | ||
var _iteratorNormalCompletion3 = true; | ||
@@ -341,31 +512,40 @@ var _didIteratorError3 = false; | ||
try { | ||
for (var _iterator3 = keys[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { | ||
var key = _step3.value; | ||
var defKey = defKeys.has(key); | ||
var insKey = insKeys.has(key); | ||
for (var _iterator3 = props[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { | ||
var prop = _step3.value; | ||
var isSchemaProp = schemaProps.has(prop); | ||
var isObjectProp = objectProps.has(prop); | ||
if (!defKey && insKey) { | ||
errors.push("Field ".concat(key, " not defined")); | ||
if (isObjectProp && !isSchemaProp) { | ||
errors.push(new ValidationError('unresolvedProperty', "".concat(path).concat(prop))); | ||
continue; | ||
} | ||
var def = definition[key]; | ||
var val = instance[key]; | ||
var definition = schema[prop]; | ||
var type = _typeof(val); | ||
if (extractDecorator(definition) === 'Validate' && !patch) { | ||
if (!definition(object)) { | ||
errors.push(new ValidationError('validation', "".concat(path).concat(prop))); | ||
} | ||
var domain = this.domains.get(def.domain); | ||
continue; | ||
} | ||
if (!insKey && defKey) { | ||
if (typeof def === 'function') { | ||
var decorator = def.prototype.constructor.name; | ||
if (definition.readOnly && patch) { | ||
errors.push(new ValidationError('immutable', "".concat(path).concat(prop))); | ||
continue; | ||
} | ||
if (decorator === 'Validate' && !def(instance)) { | ||
errors.push('Validation failed'); | ||
continue; | ||
} | ||
if (!isObjectProp) { | ||
if (definition.required && !patch) { | ||
errors.push(new ValidationError('missingProperty', "".concat(path).concat(prop))); | ||
} | ||
if (def.required) { | ||
errors.push("Field ".concat(key, " not found")); | ||
continue; | ||
} | ||
var value = object[prop]; | ||
if (value === undefined || value === null) { | ||
if (definition.required) { | ||
errors.push(new ValidationError('emptyValue', "".concat(path).concat(prop))); | ||
} | ||
@@ -376,5 +556,11 @@ | ||
if (domain && type !== domain.type) { | ||
errors.push("Field ".concat(key, ":").concat(val, " expected to be ").concat(domain.type)); | ||
if (definition.domain) { | ||
errors.push.apply(errors, _toConsumableArray(validateDomain(value, "".concat(path).concat(prop), definition.definition))); | ||
} else if (definition.category) { | ||
errors.push.apply(errors, _toConsumableArray(validateLink(value, "".concat(path).concat(prop), definition, this, patch))); | ||
} | ||
if (definition.validate && !definition.validate(value)) { | ||
errors.push(new ValidationError('propValidation', "".concat(path).concat(prop))); | ||
} | ||
} | ||
@@ -396,6 +582,43 @@ } catch (err) { | ||
return errors; | ||
} // Validate instance against a category | ||
// categoryName - <string>, category name | ||
// instance - <any>, instance to validate | ||
// patch - <boolean>, flag to determine if the object contains patch or | ||
// value, default: `false` | ||
// path - <string>, path to an object, for nested objects, default: `''` | ||
// Returns: <MetaschemaError> | <null> | ||
}, { | ||
key: "validateCategory", | ||
value: function validateCategory(categoryName, instance) { | ||
var patch = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; | ||
var path = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : ''; | ||
var category = this.categories.get(categoryName); | ||
if (!category) { | ||
return new MetaschemaError([new ValidationError('undefinedEntity', categoryName, 'category')]); | ||
} | ||
var errors = this[validate](category.definition, instance, patch, "".concat(path).concat(categoryName, ".")); | ||
return errors.length === 0 ? null : new MetaschemaError(errors); | ||
} // Validate instance against a category | ||
// formName - <string>, form name | ||
// args - <Object>, form arguments | ||
// Returns: <MetaschemaError> | <null> | ||
}, { | ||
key: "validateForm", | ||
value: function validateForm(formName, args) { | ||
var form = this.forms.get(formName); | ||
if (!form) { | ||
return new MetaschemaError([new ValidationError('undefinedEntity', formName, 'form')]); | ||
} | ||
var errors = this[validate](form, args, false, "".concat(formName, ".")); | ||
return errors.length === 0 ? null : new MetaschemaError(errors); | ||
} // Validate fields of an instance against a category | ||
// category - <string> category name | ||
// instance - <any> instance to validate | ||
// category - <string>, category name | ||
// instance - <any>, instance to validate | ||
// Returns: <MetaschemaError> | <null> | ||
@@ -410,3 +633,3 @@ | ||
var val = instance[key]; | ||
var err = this.validate(category, val); | ||
var err = this.validateCategory(category, val, false, "".concat(key, ".")); | ||
if (err) errors.push.apply(errors, _toConsumableArray(err.errors)); | ||
@@ -428,2 +651,50 @@ } | ||
} | ||
}, { | ||
key: initActions, | ||
value: function value() { | ||
var _this3 = this; | ||
var _iteratorNormalCompletion4 = true; | ||
var _didIteratorError4 = false; | ||
var _iteratorError4 = undefined; | ||
try { | ||
var _loop = function _loop() { | ||
var _step4$value = _slicedToArray(_step4.value, 2), | ||
categoryName = _step4$value[0], | ||
category = _step4$value[1].definition; | ||
var actions = iter(Object.keys(category)).filter(function (key) { | ||
return extractDecorator(category[key]) === 'Action'; | ||
}).map(function (key) { | ||
var action = { | ||
categoryName: categoryName, | ||
name: key, | ||
definition: category[key], | ||
form: _this3.forms.get("".concat(categoryName, ".").concat(key)) | ||
}; | ||
return [key, action]; | ||
}).collectTo(Map); | ||
_this3.actions.set(categoryName, actions); | ||
}; | ||
for (var _iterator4 = this.categories[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { | ||
_loop(); | ||
} | ||
} catch (err) { | ||
_didIteratorError4 = true; | ||
_iteratorError4 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion4 && _iterator4.return != null) { | ||
_iterator4.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError4) { | ||
throw _iteratorError4; | ||
} | ||
} | ||
} | ||
} | ||
}]); | ||
@@ -433,4 +704,5 @@ | ||
}(); // Creates Metaschema instance | ||
// schemas - <Iterable> schemas in form [name, schema] (e.g. Map) | ||
// Returns: <Metaschema> | ||
// schemas - <Iterable> schemas in form [name, schema, source] | ||
// (the 'source' is optional) | ||
// Returns: [<MetaschemaError>, <Metaschema>] | ||
@@ -440,25 +712,26 @@ | ||
var ms = new Metaschema(); | ||
var _iteratorNormalCompletion4 = true; | ||
var _didIteratorError4 = false; | ||
var _iteratorError4 = undefined; | ||
var _iteratorNormalCompletion5 = true; | ||
var _didIteratorError5 = false; | ||
var _iteratorError5 = undefined; | ||
try { | ||
for (var _iterator4 = schemas[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { | ||
var _step4$value = _slicedToArray(_step4.value, 2), | ||
name = _step4$value[0], | ||
schema = _step4$value[1]; | ||
for (var _iterator5 = schemas[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { | ||
var _step5$value = _slicedToArray(_step5.value, 3), | ||
name = _step5$value[0], | ||
schema = _step5$value[1], | ||
source = _step5$value[2]; | ||
ms[addSchema](name, schema); | ||
ms[addSchema](name, schema, source); | ||
} | ||
} catch (err) { | ||
_didIteratorError4 = true; | ||
_iteratorError4 = err; | ||
_didIteratorError5 = true; | ||
_iteratorError5 = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion4 && _iterator4.return != null) { | ||
_iterator4.return(); | ||
if (!_iteratorNormalCompletion5 && _iterator5.return != null) { | ||
_iterator5.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError4) { | ||
throw _iteratorError4; | ||
if (_didIteratorError5) { | ||
throw _iteratorError5; | ||
} | ||
@@ -468,2 +741,3 @@ } | ||
ms[initActions](); | ||
return [linkSchemas(schemas, ms.categories, ms.domains), ms]; | ||
@@ -474,3 +748,4 @@ }; | ||
create: create, | ||
addSchema: addSchema | ||
addSchema: addSchema, | ||
extractDecorator: extractDecorator | ||
}; |
'use strict'; | ||
var _require = require('metarhia-common'), | ||
var _require = require('@metarhia/common'), | ||
merge = _require.merge; | ||
@@ -5,0 +5,0 @@ |
@@ -12,3 +12,4 @@ 'use strict'; | ||
var _require = require('./lib/schema'), | ||
create = _require.create; | ||
create = _require.create, | ||
extractDecorator = _require.extractDecorator; | ||
@@ -20,3 +21,4 @@ var schemaFs = require('./lib/schema-fs-loader'); | ||
create: create, | ||
decorators: decorators.all | ||
decorators: decorators.all, | ||
extractDecorator: extractDecorator | ||
}); |
@@ -81,2 +81,3 @@ 'use strict'; | ||
class Local extends Decorator {} | ||
class Table extends Decorator {} | ||
class History extends Decorator {} | ||
@@ -99,2 +100,3 @@ | ||
Local: def => new Local(def), | ||
Table: def => new Table(def), | ||
History: def => new History(def), | ||
@@ -101,0 +103,0 @@ View: def => new View(def), |
'use strict'; | ||
const common = require('metarhia-common'); | ||
const common = require('@metarhia/common'); | ||
const { last } = common; | ||
@@ -5,0 +5,0 @@ |
'use strict'; | ||
const validationErrorSerializers = { | ||
const schemaValidationErrorSerializers = { | ||
linkToLog: error => 'Forbidden reference to a \'Log\' category ' + | ||
@@ -14,3 +14,3 @@ `from ${error.source}.${error.property}`, | ||
class ValidationError extends Error { | ||
class SchemaValidationError extends Error { | ||
constructor(type, source, property, info) { | ||
@@ -25,2 +25,39 @@ super(); | ||
toString() { | ||
return schemaValidationErrorSerializers[this.type](this); | ||
} | ||
} | ||
const wrap = value => (Array.isArray(value) ? | ||
value.map(wrap).join(', ') : `'${value}'`); | ||
const validationErrorSerializers = { | ||
unresolvedProperty: error => `Unresolved property '${error.property}'`, | ||
missingProperty: error => `Missing property '${error.property}'`, | ||
emptyValue: error => `Empty value in required property '${error.property}'`, | ||
validation: error => `Failed to validate rule '${error.property}'`, | ||
propValidation: error => `Failed to validate property '${error.property}'`, | ||
immutable: error => `Mutation of read-only property '${error.property}'`, | ||
invalidType: error => | ||
`Invalid type of property '${error.property}', ` + | ||
`expected: '${error.info.expected}', actual: '${error.info.actual}'`, | ||
invalidClass: error => | ||
`Invalid class of property '${error.property}', ` + | ||
`expected: ${wrap(error.info.expected)}, actual: '${error.info.actual}'`, | ||
domainValidation: error => | ||
`Failed to validate rule '${error.info}' on property '${error.property}'`, | ||
enum: error => | ||
`Invalid value of a enum in a property '${error.property}' ` + | ||
`allowed: ${wrap(error.info.expected)}, actual: '${error.info.actual}'`, | ||
undefinedEntity: error => `Undefined ${error.info} '${error.property}'`, | ||
}; | ||
class ValidationError extends Error { | ||
constructor(type, property, info) { | ||
super(); | ||
this.type = type; | ||
this.property = property; | ||
this.info = info; | ||
} | ||
toString() { | ||
return validationErrorSerializers[this.type](this); | ||
@@ -42,4 +79,5 @@ } | ||
module.exports = { | ||
SchemaValidationError, | ||
ValidationError, | ||
MetaschemaError, | ||
}; |
@@ -36,2 +36,3 @@ 'use strict'; | ||
// schema - <Object> parsed schema | ||
// source - <string> schema source | ||
const loadSchema = (filepath, api, callback) => { | ||
@@ -51,3 +52,3 @@ fs.readFile(filepath, (err, source) => { | ||
} | ||
callback(null, name, exports); | ||
callback(null, name, exports, source); | ||
}); | ||
@@ -60,3 +61,3 @@ }; | ||
metasync.each(files, (filepath, callback) => { | ||
loadSchema(filepath, api, (err, name, schema) => { | ||
loadSchema(filepath, api, (err, name, schema, source) => { | ||
if (err) { | ||
@@ -66,3 +67,3 @@ callback(err); | ||
} | ||
schemas.push([name, schema]); | ||
schemas.push([name, schema, source]); | ||
callback(null); | ||
@@ -69,0 +70,0 @@ }); |
'use strict'; | ||
const { ValidationError, MetaschemaError } = require('./schema-errors'); | ||
const { iter } = require('@metarhia/common'); | ||
const { | ||
SchemaValidationError, | ||
ValidationError, | ||
MetaschemaError, | ||
} = require('./schema-errors'); | ||
const DOMAINS_NAME = 'domains'; | ||
const addSchema = Symbol('addSchema'); | ||
const factorify = Symbol('factorify'); | ||
const initActions = Symbol('initActions'); | ||
const validate = Symbol('validate'); | ||
// Extracts schema decorator | ||
// schema - <Object> | ||
// Returns: <string> | ||
const extractDecorator = schema => { | ||
const className = schema.constructor.name; | ||
if (className !== 'Function') { | ||
return className; | ||
} | ||
return Object.getPrototypeOf(schema).name || 'Function'; | ||
}; | ||
// Extracts category decorator type, any undecorated category | ||
@@ -14,3 +34,3 @@ // will be treated as Local. | ||
const getCategoryType = category => { | ||
const type = category.constructor.name; | ||
const type = extractDecorator(category); | ||
return type === 'Object' ? 'Local' : type; | ||
@@ -25,3 +45,3 @@ }; | ||
// propertyName - <string> | ||
// Returns: <ValidationError> | <null> information about error or null | ||
// Returns: <SchemaValidationError> | <null> information about error or null | ||
// if link is valid | ||
@@ -39,7 +59,7 @@ const verifyLink = ( | ||
if (destinationType === 'Log') { | ||
return new ValidationError('linkToLog', sourceName, propertyName); | ||
return new SchemaValidationError('linkToLog', sourceName, propertyName); | ||
} | ||
if (destinationType === 'Local' && sourceType !== 'Local') { | ||
return new ValidationError( | ||
return new SchemaValidationError( | ||
'illegalLinkToLocal', | ||
@@ -70,3 +90,3 @@ sourceName, | ||
} else { | ||
errors.push(new ValidationError( | ||
errors.push(new SchemaValidationError( | ||
'unresolvedDomain', | ||
@@ -91,3 +111,3 @@ schemaName, | ||
} else { | ||
errors.push(new ValidationError( | ||
errors.push(new SchemaValidationError( | ||
'unresolvedCategory', | ||
@@ -105,2 +125,153 @@ schemaName, | ||
const domainValidators = { | ||
string: (domain, prop, value) => { | ||
const errors = []; | ||
if (domain.min !== undefined && value.length < domain.min) { | ||
errors.push(new ValidationError('domainValidation', prop, 'min')); | ||
} | ||
if (domain.length !== undefined && value.length > domain.length) { | ||
errors.push(new ValidationError('domainValidation', prop, 'length')); | ||
} | ||
return errors; | ||
}, | ||
number: (domain, prop, value) => { | ||
const errors = []; | ||
// The condition is inverted because of possible NaN | ||
if (domain.min !== undefined && !(value >= domain.min)) { | ||
errors.push(new ValidationError('domainValidation', prop, 'min')); | ||
} | ||
// The condition is inverted because of possible NaN | ||
if (domain.max !== undefined && !(value <= domain.max)) { | ||
errors.push(new ValidationError('domainValidation', prop, 'max')); | ||
} | ||
if (domain.subtype === 'int' && !Number.isInteger(value)) { | ||
errors.push(new ValidationError('domainValidation', prop, 'subtype')); | ||
} | ||
return errors; | ||
}, | ||
object: (domain, prop, value) => { | ||
const errors = []; | ||
const valueClass = value.constructor.name; | ||
if (domain.class !== valueClass) { | ||
errors.push( | ||
new ValidationError( | ||
'invalidClass', | ||
prop, | ||
{ expected: domain.class, actual: valueClass } | ||
) | ||
); | ||
return errors; | ||
} | ||
if (domain.length !== undefined && | ||
(value.length === undefined || value.length > domain.length) | ||
) { | ||
errors.push(new ValidationError('domainValidation', prop, 'length')); | ||
} | ||
return errors; | ||
}, | ||
bigint: () => [], | ||
boolean: () => [], | ||
function: () => [], | ||
symbol: () => [], | ||
}; | ||
// Validates value against a domain | ||
// value - <any> | ||
// path - <string> | ||
// domain - <Object> | ||
// Returns: <ValidationError[]> | ||
const validateDomain = (value, path, domain) => { | ||
const errors = []; | ||
const domainType = extractDecorator(domain); | ||
if (domain.type) { | ||
const type = typeof value; | ||
if (type !== domain.type) { | ||
errors.push(new ValidationError( | ||
'invalidType', | ||
path, | ||
{ expected: domain.type, actual: type } | ||
)); | ||
return errors; | ||
} | ||
const validator = domainValidators[type]; | ||
errors.push(...validator(domain, path, value)); | ||
} | ||
if (domainType === 'Enum') { | ||
if (!domain.values.includes(value)) { | ||
errors.push( | ||
new ValidationError( | ||
'enum', | ||
path, | ||
{ expected: domain.values, actual: value } | ||
) | ||
); | ||
} | ||
} | ||
if (domainType === 'Flags') { | ||
const valueClass = value.constructor.name; | ||
if (valueClass !== 'Uint64' && valueClass !== 'FlagsClass') { | ||
errors.push( | ||
new ValidationError( | ||
'invalidClass', | ||
path, | ||
{ expected: ['Uint64', 'FlagsClass'], actual: valueClass } | ||
) | ||
); | ||
} | ||
} | ||
if (domain.check && !domain.check(value)) { | ||
errors.push(new ValidationError('domainValidation', path, 'check')); | ||
} | ||
return errors; | ||
}; | ||
const validateLink = (value, path, definition, ms, patch) => { | ||
const errors = []; | ||
const category = definition.definition; | ||
const type = extractDecorator(definition); | ||
if (type === 'Include') { | ||
return ms[validate](category, value, patch, `${path}.`); | ||
} | ||
const checkLink = (value, path) => { | ||
const valueClass = value.constructor.name; | ||
if (valueClass !== 'Uint64' && valueClass !== 'String') { | ||
errors.push( | ||
new ValidationError( | ||
'invalidClass', | ||
path, | ||
{ expected: ['Uint64', 'String'], actual: valueClass } | ||
) | ||
); | ||
} | ||
}; | ||
if (type === 'Many') { | ||
if (!Array.isArray(value)) { | ||
errors.push( | ||
new ValidationError( | ||
'invalidType', | ||
`${path}`, | ||
{ expected: 'Array', actual: typeof value } | ||
) | ||
); | ||
} else { | ||
value.forEach((val, index) => checkLink(val, `${path}[${index}]`)); | ||
} | ||
} else { | ||
checkLink(value, path); | ||
} | ||
return errors; | ||
}; | ||
class Metaschema { | ||
@@ -111,2 +282,4 @@ constructor() { | ||
this.forms = new Map(); | ||
this.actions = new Map(); | ||
this.sources = []; | ||
} | ||
@@ -116,7 +289,8 @@ | ||
// but doesn't do anything else (i.e. linking) | ||
[addSchema](name, schema) { | ||
[addSchema](name, schema, source) { | ||
this.sources.push(source); | ||
if (name === DOMAINS_NAME) { | ||
for (const name in schema) { | ||
const domain = schema[name]; | ||
const decorator = domain.constructor.name; | ||
const decorator = extractDecorator(domain); | ||
if (decorator === 'Flags' && domain.enum) { | ||
@@ -129,3 +303,3 @@ const enumDomain = schema[domain.enum]; | ||
} else { | ||
const decorator = schema.constructor.name; | ||
const decorator = extractDecorator(schema); | ||
if (decorator === 'Form') { | ||
@@ -149,3 +323,3 @@ this.forms.set(name, schema); | ||
const decorator = def.constructor.name; | ||
const decorator = extractDecorator(def); | ||
const category = this.categories.get(name); | ||
@@ -217,52 +391,116 @@ if (category && category.factory) { | ||
// Validate instance against a category | ||
// category - <string> category name | ||
// instance - <any> instance to validate | ||
// result - <Object> in a form { valid: true, errors: [] } | ||
// Returns: <Object> result | ||
validate(category, instance) { | ||
// Validate object against a schema | ||
// schema - <Object>, schema to validate against | ||
// object - <Object>, object to validate | ||
// patch - <boolean>, flag to determine if the object contains patch or | ||
// value, default: `false` | ||
// path - <string>, path to an object, for nested objects, default: `''` | ||
// Returns: <ValidationError[]> | ||
[validate](schema, object, patch = false, path = '') { | ||
const errors = []; | ||
const record = this.categories.get(category); | ||
if (!record) { | ||
errors.push(`Category ${category} not found`); | ||
return new MetaschemaError(errors); | ||
} | ||
const { definition } = record; | ||
const defKeys = new Set(Object.keys(definition)); | ||
const insKeys = new Set(Object.keys(instance)); | ||
const keys = new Set([...defKeys, ...insKeys]); | ||
for (const key of keys) { | ||
const defKey = defKeys.has(key); | ||
const insKey = insKeys.has(key); | ||
if (!defKey && insKey) { | ||
errors.push(`Field ${key} not defined`); | ||
const schemaProps = new Set(Object.keys(schema)); | ||
const objectProps = new Set(Object.keys(object)); | ||
const props = new Set([...schemaProps, ...objectProps]); | ||
for (const prop of props) { | ||
const isSchemaProp = schemaProps.has(prop); | ||
const isObjectProp = objectProps.has(prop); | ||
if (isObjectProp && !isSchemaProp) { | ||
errors.push( | ||
new ValidationError('unresolvedProperty', `${path}${prop}`) | ||
); | ||
continue; | ||
} | ||
const def = definition[key]; | ||
const val = instance[key]; | ||
const type = typeof val; | ||
const domain = this.domains.get(def.domain); | ||
if (!insKey && defKey) { | ||
if (typeof def === 'function') { | ||
const decorator = def.prototype.constructor.name; | ||
if (decorator === 'Validate' && !def(instance)) { | ||
errors.push('Validation failed'); | ||
continue; | ||
} | ||
const definition = schema[prop]; | ||
if (extractDecorator(definition) === 'Validate' && !patch) { | ||
if (!definition(object)) { | ||
errors.push(new ValidationError('validation', `${path}${prop}`)); | ||
} | ||
if (def.required) { | ||
errors.push(`Field ${key} not found`); | ||
continue; | ||
} | ||
if (definition.readOnly && patch) { | ||
errors.push(new ValidationError('immutable', `${path}${prop}`)); | ||
continue; | ||
} | ||
if (!isObjectProp) { | ||
if (definition.required && !patch) { | ||
errors.push(new ValidationError('missingProperty', `${path}${prop}`)); | ||
} | ||
continue; | ||
} | ||
if (domain && type !== domain.type) { | ||
errors.push(`Field ${key}:${val} expected to be ${domain.type}`); | ||
const value = object[prop]; | ||
if (value === undefined || value === null) { | ||
if (definition.required) { | ||
errors.push(new ValidationError('emptyValue', `${path}${prop}`)); | ||
} | ||
continue; | ||
} | ||
if (definition.domain) { | ||
errors.push( | ||
...validateDomain(value, `${path}${prop}`, definition.definition) | ||
); | ||
} else if (definition.category) { | ||
errors.push( | ||
...validateLink(value, `${path}${prop}`, definition, this, patch) | ||
); | ||
} | ||
if (definition.validate && !definition.validate(value)) { | ||
errors.push(new ValidationError('propValidation', `${path}${prop}`)); | ||
} | ||
} | ||
return errors; | ||
} | ||
// Validate instance against a category | ||
// categoryName - <string>, category name | ||
// instance - <any>, instance to validate | ||
// patch - <boolean>, flag to determine if the object contains patch or | ||
// value, default: `false` | ||
// path - <string>, path to an object, for nested objects, default: `''` | ||
// Returns: <MetaschemaError> | <null> | ||
validateCategory(categoryName, instance, patch = false, path = '') { | ||
const category = this.categories.get(categoryName); | ||
if (!category) { | ||
return new MetaschemaError( | ||
[new ValidationError('undefinedEntity', categoryName, 'category')] | ||
); | ||
} | ||
const errors = this[validate]( | ||
category.definition, | ||
instance, | ||
patch, | ||
`${path}${categoryName}.` | ||
); | ||
return errors.length === 0 ? null : new MetaschemaError(errors); | ||
} | ||
// Validate instance against a category | ||
// formName - <string>, form name | ||
// args - <Object>, form arguments | ||
// Returns: <MetaschemaError> | <null> | ||
validateForm(formName, args) { | ||
const form = this.forms.get(formName); | ||
if (!form) { | ||
return new MetaschemaError([ | ||
new ValidationError('undefinedEntity', formName, 'form'), | ||
]); | ||
} | ||
const errors = this[validate](form, args, false, `${formName}.`); | ||
return errors.length === 0 ? null : new MetaschemaError(errors); | ||
} | ||
// Validate fields of an instance against a category | ||
// category - <string> category name | ||
// instance - <any> instance to validate | ||
// category - <string>, category name | ||
// instance - <any>, instance to validate | ||
// Returns: <MetaschemaError> | <null> | ||
@@ -273,3 +511,3 @@ validateFields(category, instance) { | ||
const val = instance[key]; | ||
const err = this.validate(category, val); | ||
const err = this.validateCategory(category, val, false, `${key}.`); | ||
if (err) errors.push(...err.errors); | ||
@@ -283,12 +521,33 @@ } | ||
} | ||
[initActions]() { | ||
for (const [categoryName, { definition: category }] of this.categories) { | ||
const actions = iter(Object.keys(category)) | ||
.filter(key => extractDecorator(category[key]) === 'Action') | ||
.map(key => { | ||
const action = { | ||
categoryName, | ||
name: key, | ||
definition: category[key], | ||
form: this.forms.get(`${categoryName}.${key}`), | ||
}; | ||
return [key, action]; | ||
}).collectTo(Map); | ||
this.actions.set(categoryName, actions); | ||
} | ||
} | ||
} | ||
// Creates Metaschema instance | ||
// schemas - <Iterable> schemas in form [name, schema] (e.g. Map) | ||
// Returns: <Metaschema> | ||
// schemas - <Iterable> schemas in form [name, schema, source] | ||
// (the 'source' is optional) | ||
// Returns: [<MetaschemaError>, <Metaschema>] | ||
const create = schemas => { | ||
const ms = new Metaschema(); | ||
for (const [name, schema] of schemas) { | ||
ms[addSchema](name, schema); | ||
for (const [name, schema, source] of schemas) { | ||
ms[addSchema](name, schema, source); | ||
} | ||
ms[initActions](); | ||
return [linkSchemas(schemas, ms.categories, ms.domains), ms]; | ||
@@ -300,2 +559,3 @@ }; | ||
addSchema, | ||
extractDecorator, | ||
}; |
'use strict'; | ||
const { merge } = require('metarhia-common'); | ||
const { merge } = require('@metarhia/common'); | ||
@@ -5,0 +5,0 @@ const SCALAR_TYPES = ['string', 'number', 'boolean', 'undefined']; |
@@ -7,3 +7,3 @@ 'use strict'; | ||
const decorators = require('./lib/decorators'); | ||
const { create } = require('./lib/schema'); | ||
const { create, extractDecorator } = require('./lib/schema'); | ||
const schemaFs = require('./lib/schema-fs-loader'); | ||
@@ -20,3 +20,4 @@ | ||
decorators: decorators.all, | ||
extractDecorator, | ||
} | ||
); |
{ | ||
"name": "metaschema", | ||
"version": "0.0.27", | ||
"version": "0.0.28", | ||
"author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>", | ||
@@ -42,14 +42,14 @@ "description": "Metadata Schema and Interface Definition Language (IDL)", | ||
"dependencies": { | ||
"metarhia-common": "^0.0.37", | ||
"metasync": "^0.3.30" | ||
"@metarhia/common": "^1.0.0", | ||
"metasync": "^0.3.31" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.1.5", | ||
"@babel/core": "^7.1.5", | ||
"@babel/preset-env": "^7.1.5", | ||
"eslint": "^5.8.0", | ||
"@babel/core": "^7.1.6", | ||
"@babel/preset-env": "^7.1.6", | ||
"eslint": "^5.9.0", | ||
"eslint-config-metarhia": "^6.0.0", | ||
"eslint-plugin-import": "^2.14.0", | ||
"metatests": "^0.2.3" | ||
"metatests": "^0.3.0" | ||
} | ||
} |
'use strict'; | ||
const { test } = require('metatests'); | ||
const { duplicate } = require('metarhia-common'); | ||
const { duplicate } = require('@metarhia/common'); | ||
const metaschema = require('..'); | ||
@@ -6,0 +6,0 @@ |
@@ -5,72 +5,304 @@ 'use strict'; | ||
const metatests = require('metatests'); | ||
const { Uint64 } = require('@metarhia/common'); | ||
const metaschema = require('..'); | ||
const { addSchema } = require('../lib/schema'); | ||
const { ValidationError, MetaschemaError } = require('../lib/schema-errors'); | ||
const getDefinition = (ms, name) => ms.categories.get(name).definition; | ||
const validateTest = metatests.test('validate'); | ||
const metaschemaPath = path.join(__dirname, '..', 'schemas', 'metaschema'); | ||
metaschema.fs.loadAndCreate(metaschemaPath, null, (err, ms) => { | ||
const schemaPath = path.join(__dirname, 'schemas', 'validate'); | ||
metaschema.fs.loadAndCreate([metaschemaPath, schemaPath], null, (err, ms) => { | ||
validateTest.error(err); | ||
ms[addSchema]('Schema1', { Name: { domain: 'Nomen', required: false } }); | ||
ms[addSchema]('Schema2', { Name: { domain: 'Nomen', required: true } }); | ||
ms[addSchema]('Schema3', { Name: { domain: 'Unknown' } }); | ||
if (err) { | ||
validateTest.end(); | ||
return; | ||
} | ||
metatests.case('Metaschema / validate', { | ||
validate: ms.validate.bind(ms), | ||
validateFields: ms.validateFields.bind(ms), | ||
}, { | ||
'validate': [ | ||
[ | ||
'Schema1', { Name: 'Marcus Aurelius' }, | ||
null, | ||
], [ | ||
'Schema1', {}, | ||
null, | ||
], [ | ||
'Schema1', { City: 'Kiev' }, | ||
{ errors: ['Field City not defined'] }, | ||
], [ | ||
'Schema1', { FirstName: 'Marcus', Surname: 'Aurelius' }, | ||
{ | ||
errors: [ | ||
'Field FirstName not defined', | ||
'Field Surname not defined', | ||
], | ||
}, | ||
], [ | ||
'Schema1', {}, | ||
null, | ||
], [ | ||
'Schema2', {}, | ||
{ | ||
errors: [ | ||
'Field Name not found', | ||
], | ||
}, | ||
metatests.case( | ||
'Metaschema / validate', | ||
{ | ||
validateCategory: ms.validateCategory.bind(ms), | ||
validateForm: ms.validateForm.bind(ms), | ||
validateFields: ms.validateFields.bind(ms), | ||
}, | ||
{ | ||
validateCategory: [ | ||
['Schema1', { Name: 'Marcus Aurelius', Ratio: 0.5 }, null], | ||
['Schema1', {}, null], | ||
['Schema1', { Name: undefined }, null], | ||
['Schema1', { Name: null }, null], | ||
[ | ||
'Schema1', | ||
{ City: 'Kiev' }, | ||
new MetaschemaError([ | ||
new ValidationError('unresolvedProperty', 'Schema1.City'), | ||
]), | ||
], | ||
[ | ||
'Schema1', | ||
{ FirstName: 'Marcus', Surname: 'Aurelius' }, | ||
new MetaschemaError([ | ||
new ValidationError('unresolvedProperty', 'Schema1.FirstName'), | ||
new ValidationError('unresolvedProperty', 'Schema1.Surname'), | ||
]), | ||
], | ||
[ | ||
'Schema2', | ||
{}, | ||
new MetaschemaError([ | ||
new ValidationError('missingProperty', 'Schema2.Name'), | ||
]), | ||
], | ||
['Schema2', {}, true, null], | ||
[ | ||
'Schema2', | ||
{ Name: null }, | ||
new MetaschemaError([ | ||
new ValidationError('emptyValue', 'Schema2.Name'), | ||
]), | ||
], | ||
[ | ||
'Schema2', | ||
{ Name: undefined }, | ||
new MetaschemaError([ | ||
new ValidationError('emptyValue', 'Schema2.Name'), | ||
]), | ||
], | ||
[ | ||
'SchemaForDomainRules', | ||
{ Number: 2, String: '1'.repeat(20) }, | ||
new MetaschemaError([ | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.Number', | ||
'min' | ||
), | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.String', | ||
'length' | ||
), | ||
]), | ||
], | ||
[ | ||
'SchemaForDomainRules', | ||
{ Number: NaN, String: '1234' }, | ||
new MetaschemaError([ | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.Number', | ||
'min' | ||
), | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.Number', | ||
'max' | ||
), | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.Number', | ||
'subtype' | ||
), | ||
new ValidationError( | ||
'propValidation', | ||
'SchemaForDomainRules.Number' | ||
), | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.String', | ||
'min' | ||
), | ||
]), | ||
], | ||
[ | ||
'SchemaForDomainRules', | ||
{ Number: 20, String: '1'.repeat(5), UintArray: new Uint32Array(5) }, | ||
null, | ||
], | ||
[ | ||
'SchemaForDomainRules', | ||
{ Number: 5.5, UintArray: new Uint32Array(20) }, | ||
new MetaschemaError([ | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.Number', | ||
'min' | ||
), | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.Number', | ||
'subtype' | ||
), | ||
new ValidationError( | ||
'propValidation', | ||
'SchemaForDomainRules.Number' | ||
), | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.UintArray', | ||
'length' | ||
), | ||
]), | ||
], | ||
[ | ||
'SchemaForDomainRules', | ||
{ Number: 200, CheckSum: new Number() }, | ||
new MetaschemaError([ | ||
new ValidationError( | ||
'domainValidation', | ||
'SchemaForDomainRules.Number', | ||
'max' | ||
), | ||
new ValidationError( | ||
'invalidClass', | ||
'SchemaForDomainRules.CheckSum', | ||
{ | ||
expected: 'Uint8Array', | ||
actual: 'Number', | ||
} | ||
), | ||
]), | ||
], | ||
['SchemaEnum', { Enum: 'One' }, null], | ||
[ | ||
'SchemaEnum', | ||
{ Enum: '__Invalid__' }, | ||
new MetaschemaError([ | ||
new ValidationError('enum', 'SchemaEnum.Enum', { | ||
expected: ['One', 'Two'], | ||
actual: '__Invalid__', | ||
}), | ||
]), | ||
], | ||
['SchemaFlags', { Flags: new Uint64(2) }, null], | ||
['SchemaEnum', { Enum: 'One' }, null], | ||
[ | ||
'SchemaEnum', | ||
{ Enum: '__Invalid__' }, | ||
new MetaschemaError([ | ||
new ValidationError('enum', 'SchemaEnum.Enum', { | ||
expected: ['One', 'Two'], | ||
actual: '__Invalid__', | ||
}), | ||
]), | ||
], | ||
['SchemaFlags', { Flags: new Uint64(2) }, null], | ||
[ | ||
'SchemaFlags', | ||
{ Flags: 'One' }, | ||
new MetaschemaError([ | ||
new ValidationError('invalidClass', 'SchemaFlags.Flags', { | ||
expected: ['Uint64', 'FlagsClass'], | ||
actual: 'String', | ||
}), | ||
]), | ||
], | ||
['SchemaWithLink', { Plain: new Uint64(2) }, null], | ||
['SchemaWithLink', { Plain: '2' }, null], | ||
[ | ||
'SchemaWithLink', | ||
{ Plain: {} }, | ||
new MetaschemaError([ | ||
new ValidationError('invalidClass', 'SchemaWithLink.Plain', { | ||
expected: ['Uint64', 'String'], | ||
actual: 'Object', | ||
}), | ||
]), | ||
], | ||
['SchemaWithLink', { Many: [new Uint64(2)] }, null], | ||
[ | ||
'SchemaWithLink', | ||
{ Many: [{}] }, | ||
new MetaschemaError([ | ||
new ValidationError('invalidClass', 'SchemaWithLink.Many[0]', { | ||
expected: ['Uint64', 'String'], | ||
actual: 'Object', | ||
}), | ||
]), | ||
], | ||
[ | ||
'SchemaWithLink', | ||
{ Many: 42 }, | ||
new MetaschemaError([ | ||
new ValidationError('invalidType', 'SchemaWithLink.Many', { | ||
expected: 'Array', | ||
actual: 'number', | ||
}), | ||
]), | ||
], | ||
['SchemaWithLink', { Include: { Name: 'Marcus' } }, null], | ||
[ | ||
'SchemaWithLink', | ||
{ Include: { Name: 42 } }, | ||
new MetaschemaError([ | ||
new ValidationError('invalidType', 'SchemaWithLink.Include.Name', { | ||
expected: 'string', | ||
actual: 'number', | ||
}), | ||
]), | ||
], | ||
[ | ||
'__InvalidCategory__', | ||
{ Name: 21 }, | ||
new MetaschemaError([ | ||
new ValidationError( | ||
'undefinedEntity', | ||
'__InvalidCategory__', | ||
'category' | ||
), | ||
]), | ||
], | ||
], | ||
], | ||
'validateFields': [ | ||
[ | ||
'StructureField', getDefinition(ms, 'Schema1'), | ||
null, | ||
], [ | ||
'StructureField', getDefinition(ms, 'StructureField'), | ||
{ | ||
errors: [ | ||
'Field definition not defined', | ||
'Field definition not defined', | ||
'Field definition not defined', | ||
'Field definition not defined', | ||
'Field definition not defined', | ||
'Validation failed', | ||
], | ||
}, | ||
validateFields: [ | ||
['StructureField', { prop: { domain: 'Nomen' } }, null], | ||
[ | ||
'StructureField', | ||
{ | ||
valid: { domain: 'Nomen' }, | ||
invalid: { domain: 'Nomen', definition: 42 }, | ||
failsCheck: { required: true }, | ||
}, | ||
new MetaschemaError([ | ||
new ValidationError( | ||
'unresolvedProperty', | ||
'invalid.StructureField.definition' | ||
), | ||
new ValidationError( | ||
'validation', | ||
'failsCheck.StructureField.Check' | ||
), | ||
]), | ||
], | ||
], | ||
], | ||
}); | ||
validateForm: [ | ||
['Form', { Name: '12' }, null], | ||
[ | ||
'Form', | ||
{ Name: 12 }, | ||
new MetaschemaError([ | ||
new ValidationError('invalidType', 'Form.Name', { | ||
expected: 'string', | ||
actual: 'number', | ||
}), | ||
]), | ||
], | ||
[ | ||
'__InvalidForm__', | ||
{ Name: 21 }, | ||
new MetaschemaError([ | ||
new ValidationError( | ||
'undefinedEntity', | ||
'__InvalidForm__', | ||
'form' | ||
), | ||
]), | ||
], | ||
], | ||
} | ||
); | ||
validateTest.end(); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
120460
84
3051
0
+ Added@metarhia/common@^1.0.0
+ Added@metarhia/common@1.5.0(transitive)
- Removedmetarhia-common@^0.0.37
- Removedmetarhia-common@0.0.37(transitive)
Updatedmetasync@^0.3.31