fully-typed
Version:
Run time type validation, transformation, and error generator that works out of the box on primitives, objects, arrays, and nested objects. Also extensible for custom types.
165 lines (146 loc) • 4.73 kB
JavaScript
/**
* @license
* Copyright 2017 Brigham Young University
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
;
const FullyTyped = require('./fully-typed');
const util = require('./util');
module.exports = Typed;
/**
* Create a Typed instance.
* @param {object} config
* @returns {Typed}
* @constructor
*/
function Typed (config) {
const typed = this;
const hasDefault = config.hasOwnProperty('default');
// enum
if (config.hasOwnProperty('enum')) {
if (!Array.isArray(config.enum) || config.enum.length === 0) {
throw Error(util.propertyErrorMessage('enum', config.enum, 'Expected a non-empty array'));
}
const copy = [];
config.enum.forEach(function(v) {
if (copy.indexOf(v) === -1) copy.push(v);
});
config.enum = copy;
}
// transform
if (config.transform && typeof config.transform !== 'function') {
throw Error(util.propertyErrorMessage('transform', config.transform, 'Expected a function'));
}
// validate
if (config.validator && typeof config.validator !== 'function') {
throw Error(util.propertyErrorMessage('validator', config.validator, 'Expected a function'));
}
// define properties
Object.defineProperties(typed, {
default: {
/**
* @property
* @name Typed#default
* @type {function,*}
*/
value: config.default,
writable: false
},
enum: {
/**
* @property
* @name Typed#enum
* @readonly
* @type {function,*}
*/
value: config.enum,
writable: false
},
hasDefault: {
/**
* @property
* @name Typed#hasDefault
* @type {boolean}
*/
value: hasDefault,
writable: false
},
transform: {
/**
* @property
* @name Typed#transform
* @readonly
* @type {function}
*/
value: config.transform,
writable: false
},
type: {
/**
* @property
* @name Typed#type
* @readonly
* @type {string,function}
*/
value: config.type,
writable: false
},
validator: {
/**
* @property
* @name Typed#validator
* @readonly
* @type {function}
*/
value: config.validator,
writable: false
}
});
return typed;
}
/**
* Get details about any errors associated with the value provided.
* @param {*} value The value to check for errors.
* @param {string} [prefix=''] A string to add to the beginning of any errors.
* @returns {object} If an error then it's a plain object that has similar properties to the error, otherwise null.
*/
Typed.prototype.error = function(value, prefix) {
if (!prefix) prefix = '';
// validate the enum
if (this.enum && this.enum.indexOf(value) === -1) {
const expects = '. Expected one of: [' + this.enum.join(', ') + ']';
return prefix + util.valueErrorMessage(value, expects);
}
// run validate function
if (this.validator) {
const valid = this.validator(value);
const message = !valid || typeof valid === 'string'
? prefix + util.valueErrorMessage(value, !valid ? 'Validator did not pass.' : valid)
: '';
if (message) return message;
}
return null;
};
/**
* Validate a value and normalize it. Normalization takes the value through any transformations after validation.
* @param {*} value
*/
Typed.prototype.normalize = function(value) {
// if (this.hasDefault && typeof value === 'undefined') value = this.default; - default applied by schema prior to validation
return this.transform ? this.transform(value) : value;
};
Typed.register = {
aliases: ['typed', FullyTyped.Typed],
dependencies: []
};