UNPKG

validate

Version:

Validate object properties in javascript.

456 lines (376 loc) 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _dot = _interopRequireDefault(require("@eivifj/dot")); var _typecast = _interopRequireDefault(require("typecast")); var _property = _interopRequireDefault(require("./property")); var _messages = _interopRequireDefault(require("./messages")); var _validators = _interopRequireDefault(require("./validators")); var _error = _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 Schema defines the structure that objects should be validated against. * * @example * const post = new Schema({ * title: { * type: String, * required: true, * length: { min: 1, max: 255 } * }, * content: { * type: String, * required: true * }, * published: { * type: Date, * required: true * }, * keywords: [{ type: String }] * }) * * @example * const author = new Schema({ * name: { * type: String, * required: true * }, * email: { * type: String, * required: true * }, * posts: [post] * }) * * @param {Object} [obj] - schema definition * @param {Object} [opts] - options * @param {Boolean} [opts.typecast=false] - typecast values before validation * @param {Boolean} [opts.strip=true] - strip properties not defined in the schema * @param {Boolean} [opts.strict=false] - validation fails when object contains properties not defined in the schema */ var Schema = /*#__PURE__*/function () { function Schema() { var _this = this; var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; _classCallCheck(this, Schema); this.opts = opts; this.hooks = []; this.props = {}; this.messages = Object.assign({}, _messages["default"]); this.validators = Object.assign({}, _validators["default"]); this.typecasters = Object.assign({}, _typecast["default"]); Object.keys(obj).forEach(function (k) { return _this.path(k, obj[k]); }); } /** * Create or update `path` with given `rules`. * * @example * const schema = new Schema() * schema.path('name.first', { type: String }) * schema.path('name.last').type(String).required() * * @param {String} path - full path using dot-notation * @param {Object|Array|String|Schema|Property} [rules] - rules to apply * @return {Property} */ _createClass(Schema, [{ key: "path", value: function path(_path, rules) { var _this2 = this; var parts = _path.split('.'); var suffix = parts.pop(); var prefix = parts.join('.'); // Make sure full path is created if (prefix) { this.path(prefix); } // Array index placeholder if (suffix === '$') { this.path(prefix).type(Array); } // Nested schema if (rules instanceof Schema) { rules.hook(function (k, v) { return _this2.path((0, _utils.join)(k, _path), v); }); return this.path(_path, rules.props); } // Return early when given a `Property` if (rules instanceof _property["default"]) { this.props[_path] = rules; // Notify parents if mounted this.propagate(_path, rules); return rules; } var prop = this.props[_path] || new _property["default"](_path, this); this.props[_path] = prop; // Notify parents if mounted this.propagate(_path, prop); // No rules? if (!rules) return prop; // type shorthand // `{ name: String }` if (typeof rules == 'string' || typeof rules == 'function') { prop.type(rules); return prop; } // Allow arrays to be defined implicitly: // `{ keywords: [String] }` // `{ keyVal: [[String, Number]] }` if (Array.isArray(rules)) { prop.type(Array); if (rules.length === 1) { prop.each(rules[0]); } else { prop.elements(rules); } return prop; } var keys = Object.keys(rules); var nested = false; // Check for nested objects for (var _i = 0, _keys = keys; _i < _keys.length; _i++) { var key = _keys[_i]; if (typeof prop[key] == 'function') continue; prop.type(Object); nested = true; break; } keys.forEach(function (key) { var rule = rules[key]; if (nested) { return _this2.path((0, _utils.join)(key, _path), rule); } prop[key](rule); }); return prop; } /** * Typecast given `obj`. * * @param {Object} obj - the object to typecast * @return {Schema} * @private */ }, { key: "typecast", value: function typecast(obj) { var _loop = function _loop() { var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2), path = _Object$entries$_i[0], prop = _Object$entries$_i[1]; (0, _utils.enumerate)(path, obj, function (key, value) { if (value == null) return; var cast = prop.typecast(value); if (cast === value) return; _dot["default"].set(obj, key, cast); }); }; for (var _i2 = 0, _Object$entries = Object.entries(this.props); _i2 < _Object$entries.length; _i2++) { _loop(); } return this; } /** * Strip all keys not defined in the schema * * @param {Object} obj - the object to strip * @param {String} [prefix] * @return {Schema} * @private */ }, { key: "strip", value: function strip(obj) { var _this3 = this; (0, _utils.walk)(obj, function (path, prop) { if (_this3.props[prop]) return true; _dot["default"]["delete"](obj, path); return false; }); return this; } /** * Create errors for all properties that are not defined in the schema * * @param {Object} obj - the object to check * @return {Schema} * @private */ }, { key: "enforce", value: function enforce(obj) { var _this4 = this; var errors = []; (0, _utils.walk)(obj, function (path, prop) { if (_this4.props[prop]) return true; var error = new _error["default"](_messages["default"].illegal(path), path); errors.push(error); return false; }); return errors; } /** * Validate given `obj`. * * @example * const schema = new Schema({ name: { required: true }}) * const errors = schema.validate({}) * assert(errors.length == 1) * assert(errors[0].message == 'name is required') * assert(errors[0].path == 'name') * * @param {Object} obj - the object to validate * @param {Object} [opts] - options, see [Schema](#schema-1) * @return {Array} */ }, { key: "validate", value: function validate(obj) { var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; opts = Object.assign(this.opts, opts); var errors = []; if (opts.typecast) { this.typecast(obj); } if (opts.strict) { errors.push.apply(errors, _toConsumableArray(this.enforce(obj))); } if (opts.strip !== false) { this.strip(obj); } var _loop2 = function _loop2() { var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i3], 2), path = _Object$entries2$_i[0], prop = _Object$entries2$_i[1]; (0, _utils.enumerate)(path, obj, function (key, value) { var err = prop.validate(value, obj, key); if (err) errors.push(err); }); }; for (var _i3 = 0, _Object$entries2 = Object.entries(this.props); _i3 < _Object$entries2.length; _i3++) { _loop2(); } return errors; } /** * Assert that given `obj` is valid. * * @example * const schema = new Schema({ name: String }) * schema.assert({ name: 1 }) // Throws an error * * @param {Object} obj * @param {Object} [opts] */ }, { key: "assert", value: function assert(obj, opts) { var _this$validate = this.validate(obj, opts), _this$validate2 = _slicedToArray(_this$validate, 1), err = _this$validate2[0]; if (err) throw err; } /** * Override default error messages. * * @example * const hex = (val) => /^0x[0-9a-f]+$/.test(val) * schema.path('some.path').use({ hex }) * schema.message('hex', path => `${path} must be hexadecimal`) * * @example * schema.message({ hex: path => `${path} must be hexadecimal` }) * * @param {String|Object} name - name of the validator or an object with name-message pairs * @param {String|Function} [message] - the message or message generator to use * @return {Schema} */ }, { key: "message", value: function message(name, _message) { (0, _utils.assign)(name, _message, this.messages); return this; } /** * Override default validators. * * @example * schema.validator('required', val => val != null) * * @example * schema.validator({ required: val => val != null }) * * @param {String|Object} name - name of the validator or an object with name-function pairs * @param {Function} [fn] - the function to use * @return {Schema} */ }, { key: "validator", value: function validator(name, fn) { (0, _utils.assign)(name, fn, this.validators); return this; } /** * Override default typecasters. * * @example * schema.typecaster('SomeClass', val => new SomeClass(val)) * * @example * schema.typecaster({ SomeClass: val => new SomeClass(val) }) * * @param {String|Object} name - name of the validator or an object with name-function pairs * @param {Function} [fn] - the function to use * @return {Schema} */ }, { key: "typecaster", value: function typecaster(name, fn) { (0, _utils.assign)(name, fn, this.typecasters); return this; } /** * Accepts a function that is called whenever new props are added. * * @param {Function} fn - the function to call * @return {Schema} * @private */ }, { key: "hook", value: function hook(fn) { this.hooks.push(fn); return this; } /** * Notify all subscribers that a property has been added. * * @param {String} path - the path of the property * @param {Property} prop - the new property * @return {Schema} * @private */ }, { key: "propagate", value: function propagate(path, prop) { this.hooks.forEach(function (fn) { return fn(path, prop); }); return this; } }]); return Schema; }(); // Export ValidationError exports["default"] = Schema; Schema.ValidationError = _error["default"]; module.exports = exports.default;