validate
Version:
Validate object properties in javascript.
519 lines (453 loc) • 15.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _error2 = _interopRequireDefault(require("./error"));
var _utils = require("./utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
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); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
/**
* A property instance gets returned whenever you call `schema.path()`.
* Properties are also created internally when an object is passed to the Schema constructor.
*
* @param {String} name - the name of the property
* @param {Schema} schema - parent schema
*/
var Property = /*#__PURE__*/function () {
function Property(name, schema) {
_classCallCheck(this, Property);
this.name = name;
this.registry = {};
this._schema = schema;
this._type = null;
this.messages = {};
}
/**
* Registers messages.
*
* @example
* prop.message('something is wrong')
* prop.message({ required: 'thing is required.' })
*
* @param {Object|String} messages
* @return {Property}
*/
_createClass(Property, [{
key: "message",
value: function message(messages) {
if (typeof messages == 'string') {
messages = {
"default": messages
};
}
var entries = Object.entries(messages);
for (var _i = 0, _entries = entries; _i < _entries.length; _i++) {
var _entries$_i = _slicedToArray(_entries[_i], 2),
key = _entries$_i[0],
val = _entries$_i[1];
this.messages[key] = val;
}
return this;
}
/**
* Mount given `schema` on current path.
*
* @example
* const user = new Schema({ email: String })
* prop.schema(user)
*
* @param {Schema} schema - the schema to mount
* @return {Property}
*/
}, {
key: "schema",
value: function schema(_schema) {
this._schema.path(this.name, _schema);
return this;
}
/**
* Validate using named functions from the given object.
* Error messages can be defined by providing an object with
* named error messages/generators to `schema.message()`
*
* The message generator receives the value being validated,
* the object it belongs to and any additional arguments.
*
* @example
* const schema = new Schema()
* const prop = schema.path('some.path')
*
* schema.message({
* binary: (path, ctx) => `${path} must be binary.`,
* bits: (path, ctx, bits) => `${path} must be ${bits}-bit`
* })
*
* prop.use({
* binary: (val, ctx) => /^[01]+$/i.test(val),
* bits: [(val, ctx, bits) => val.length == bits, 32]
* })
*
* @param {Object} fns - object with named validation functions to call
* @return {Property}
*/
}, {
key: "use",
value: function use(fns) {
var _this = this;
Object.keys(fns).forEach(function (name) {
var arr = fns[name];
if (!Array.isArray(arr)) arr = [arr];
var fn = arr.shift();
_this._register(name, arr, fn);
});
return this;
}
/**
* Registers a validator that checks for presence.
*
* @example
* prop.required()
*
* @param {Boolean} [bool] - `true` if required, `false` otherwise
* @return {Property}
*/
}, {
key: "required",
value: function required() {
var bool = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
return this._register('required', [bool]);
}
/**
* Registers a validator that checks if a value is of a given `type`
*
* @example
* prop.type(String)
*
* @example
* prop.type('string')
*
* @param {String|Function} type - type to check for
* @return {Property}
*/
}, {
key: "type",
value: function type(_type) {
this._type = _type;
return this._register('type', [_type]);
}
/**
* Convenience method for setting type to `String`
*
* @example
* prop.string()
*
* @return {Property}
*/
}, {
key: "string",
value: function string() {
return this.type(String);
}
/**
* Convenience method for setting type to `Number`
*
* @example
* prop.number()
*
* @return {Property}
*/
}, {
key: "number",
value: function number() {
return this.type(Number);
}
/**
* Convenience method for setting type to `Array`
*
* @example
* prop.array()
*
* @return {Property}
*/
}, {
key: "array",
value: function array() {
return this.type(Array);
}
/**
* Convenience method for setting type to `Date`
*
* @example
* prop.date()
*
* @return {Property}
*/
}, {
key: "date",
value: function date() {
return this.type(Date);
}
/**
* Registers a validator that checks length.
*
* @example
* prop.length({ min: 8, max: 255 })
* prop.length(10)
*
* @param {Object|Number} rules - object with `.min` and `.max` properties or a number
* @param {Number} rules.min - minimum length
* @param {Number} rules.max - maximum length
* @return {Property}
*/
}, {
key: "length",
value: function length(rules) {
return this._register('length', [rules]);
}
/**
* Registers a validator that checks size.
*
* @example
* prop.size({ min: 8, max: 255 })
* prop.size(10)
*
* @param {Object|Number} rules - object with `.min` and `.max` properties or a number
* @param {Number} rules.min - minimum size
* @param {Number} rules.max - maximum size
* @return {Property}
*/
}, {
key: "size",
value: function size(rules) {
return this._register('size', [rules]);
}
/**
* Registers a validator for enums.
*
* @example
* prop.enum(['cat', 'dog'])
*
* @param {Array} rules - allowed values
* @return {Property}
*/
}, {
key: "enum",
value: function _enum(enums) {
return this._register('enum', [enums]);
}
/**
* Registers a validator that checks if a value matches given `regexp`.
*
* @example
* prop.match(/some\sregular\sexpression/)
*
* @param {RegExp} regexp - regular expression to match
* @return {Property}
*/
}, {
key: "match",
value: function match(regexp) {
return this._register('match', [regexp]);
}
/**
* Registers a validator that checks each value in an array against given `rules`.
*
* @example
* prop.each({ type: String })
* prop.each([{ type: Number }])
* prop.each({ things: [{ type: String }]})
* prop.each(schema)
*
* @param {Array|Object|Schema|Property} rules - rules to use
* @return {Property}
*/
}, {
key: "each",
value: function each(rules) {
this._schema.path((0, _utils.join)('$', this.name), rules);
return this;
}
/**
* Registers paths for array elements on the parent schema, with given array of rules.
*
* @example
* prop.elements([{ type: String }, { type: Number }])
*
* @param {Array} arr - array of rules to use
* @return {Property}
*/
}, {
key: "elements",
value: function elements(arr) {
var _this2 = this;
arr.forEach(function (rules, i) {
_this2._schema.path((0, _utils.join)(i, _this2.name), rules);
});
return this;
}
/**
* Registers all properties from the given object as nested properties
*
* @example
* prop.properties({
* name: String,
* email: String
* })
*
* @param {Object} props - properties with rules
* @return {Property}
*/
}, {
key: "properties",
value: function properties(props) {
for (var _i2 = 0, _Object$entries = Object.entries(props); _i2 < _Object$entries.length; _i2++) {
var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
prop = _Object$entries$_i[0],
rule = _Object$entries$_i[1];
this._schema.path((0, _utils.join)(prop, this.name), rule);
}
return this;
}
/**
* Proxy method for schema path. Makes chaining properties together easier.
*
* @example
* schema
* .path('name').type(String).required()
* .path('email').type(String).required()
*
*/
}, {
key: "path",
value: function path() {
var _this$_schema;
return (_this$_schema = this._schema).path.apply(_this$_schema, arguments);
}
/**
* Typecast given `value`
*
* @example
* prop.type(String)
* prop.typecast(123) // => '123'
*
* @param {Mixed} value - value to typecast
* @return {Mixed}
*/
}, {
key: "typecast",
value: function typecast(value) {
var schema = this._schema;
var type = this._type;
if (!type) return value;
if (typeof type == 'function') {
type = type.name;
}
var cast = schema.typecasters[type] || schema.typecasters[type.toLowerCase()];
if (typeof cast != 'function') {
throw new Error("Typecasting failed: No typecaster defined for ".concat(type, "."));
}
return cast(value);
}
/**
* Validate given `value`
*
* @example
* prop.type(Number)
* assert(prop.validate(2) == null)
* assert(prop.validate('hello world') instanceof Error)
*
* @param {Mixed} value - value to validate
* @param {Object} ctx - the object containing the value
* @param {String} [path] - path of the value being validated
* @return {ValidationError}
*/
}, {
key: "validate",
value: function validate(value, ctx) {
var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.name;
var types = Object.keys(this.registry);
for (var _i3 = 0, _types = types; _i3 < _types.length; _i3++) {
var type = _types[_i3];
var err = this._run(type, value, ctx, path);
if (err) return err;
}
return null;
}
/**
* Run validator of given `type`
*
* @param {String} type - type of validator
* @param {Mixed} value - value to validate
* @param {Object} ctx - the object containing the value
* @param {String} path - path of the value being validated
* @return {ValidationError}
* @private
*/
}, {
key: "_run",
value: function _run(type, value, ctx, path) {
if (!this.registry[type]) return;
var schema = this._schema;
var _this$registry$type = this.registry[type],
args = _this$registry$type.args,
fn = _this$registry$type.fn;
var validator = fn || schema.validators[type];
var valid = validator.apply(void 0, [value, ctx].concat(_toConsumableArray(args), [path]));
if (!valid) return this._error(type, ctx, args, path);
}
/**
* Register validator
*
* @param {String} type - type of validator
* @param {Array} args - argument to pass to validator
* @param {Function} [fn] - custom validation function to call
* @return {Property}
* @private
*/
}, {
key: "_register",
value: function _register(type, args, fn) {
this.registry[type] = {
args: args,
fn: fn
};
return this;
}
/**
* Create an error
*
* @param {String} type - type of validator
* @param {Object} ctx - the object containing the value
* @param {Array} args - arguments to pass
* @param {String} path - path of the value being validated
* @return {ValidationError}
* @private
*/
}, {
key: "_error",
value: function _error(type, ctx, args, path) {
var schema = this._schema;
var message = this.messages[type] || this.messages["default"] || schema.messages[type] || schema.messages["default"];
if (typeof message == 'function') {
message = message.apply(void 0, [path, ctx].concat(_toConsumableArray(args)));
}
return new _error2["default"](message, path);
}
}]);
return Property;
}();
exports["default"] = Property;
module.exports = exports.default;