From 70af34485722dec23e4097edc2b414d142edf2ec Mon Sep 17 00:00:00 2001 From: Dengshihua Date: Mon, 27 Jun 2022 10:24:02 +0800 Subject: [PATCH 1/2] style:Object.prototype.hasOwnProperty.call --- index.js | 1266 +++++++++++++++++++++++++++--------------------------- 1 file changed, 633 insertions(+), 633 deletions(-) diff --git a/index.js b/index.js index e1db09a..cfeb429 100644 --- a/index.js +++ b/index.js @@ -1,633 +1,633 @@ -'use strict'; - -var util = require('util'); - -/** - * Regexps - */ - -var DATE_TYPE_RE = /^\d{4}\-\d{2}\-\d{2}$/; -var DATETIME_TYPE_RE = /^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/; -var ID_RE = /^\d+$/; - -// http://www.regular-expressions.info/email.html -var EMAIL_RE = /^[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+(?:\.[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?$/i; - -var PASSWORD_RE = /^[\w\`\~\!\@\#\$\%\^\&\*\(\)\-\_\=\+\[\]\{\}\|\;\:\'\"\,\<\.\>\/\?]+$/; - -// https://gist.github.com/dperini/729294 -var URL_RE = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i; - -/** - * Parameter class - * @class Parameter - */ -class Parameter { - constructor(opts) { - opts = opts || {}; - - if (typeof opts.translate === 'function') { - this.translate = opts.translate; - } - - if (opts.validateRoot) this.validateRoot = true; - if (opts.convert) this.convert = true; - if (opts.widelyUndefined) this.widelyUndefined = true; - } - - t() { - var args = Array.prototype.slice.call(arguments); - if (typeof this.translate === 'function') { - return this.translate.apply(this, args); - } else { - return util.format.apply(util, args); - } - } - - /** - * validate - * - * @param {Object} rules - * @return {Object} obj - * @api public - */ - validate(rules, obj) { - if (typeof rules !== 'object') { - throw new TypeError('need object type rule'); - } - - if (this.validateRoot && (typeof obj !== 'object' || !obj)) { - return [{ - message: this.t('the validated value should be a object'), - code: this.t('invalid'), - field: undefined, - }]; - } - - var self = this; - - var errors = []; - - for (var key in rules) { - var rule = formatRule(rules[key]); - var value = obj[key]; - - if (typeof value === 'string' && rule.trim === true) { - value = obj[key] = value.trim(); - } - - // treat null / '' / NaN as undefined - let widelyUndefined = this.widelyUndefined; - if ('widelyUndefined' in rule) widelyUndefined = rule.widelyUndefined; - if (widelyUndefined && - (value === '' || value === null || Number.isNaN(value))) { - value = obj[key] = undefined; - } - - var has = value !== null && value !== undefined; - - if (!has) { - if (rule.required !== false) { - errors.push({ - message: this.t('required'), - field: key, - code: this.t('missing_field') - }); - } - // support default value - if ('default' in rule) { - obj[key] = rule.default; - } - continue; - } - - var checker = TYPE_MAP[rule.type]; - if (!checker) { - throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + - ', but the following type was passed: ' + rule.type); - } - - convert(rule, obj, key, this.convert); - var msg = checker.call(self, rule, obj[key], obj); - if (typeof msg === 'string') { - errors.push({ - message: msg, - code: this.t('invalid'), - field: key - }); - } - - if (Array.isArray(msg)) { - msg.forEach(function (e) { - var dot = rule.type === 'object' ? '.' : ''; - e.field = key + dot + e.field; - errors.push(e); - }); - } - } - - if (errors.length) { - return errors; - } - } -}; - -/** - * Module exports - * @type {Function} - */ -module.exports = Parameter; - -/** - * add custom rule to global rules list. - * - * @param {String} type - * @param {Function | RegExp} check - * @param {Boolean} [override] - override exists rule or not, default is true - * @param {String|Function} [convertType] - * @api public - */ -Parameter.prototype.addRule = Parameter.addRule = function addRule(type, check, override, convertType) { - if (!type) { - throw new TypeError('`type` required'); - } - - // addRule(type, check, convertType) - if (typeof override === 'string' || typeof override === 'function') { - convertType = override; - override = true; - } - - if (typeof override !== 'boolean') { - override = true; - } - - if (!override && TYPE_MAP[type]) { - throw new TypeError('rule `' + type + '` exists'); - } - - if (convertType) { - if (typeof convertType !== 'string' && typeof convertType !== 'function') { - throw new TypeError('convertType should be string or function'); - } - Parameter.CONVERT_MAP[type] = convertType; - } - - - if (typeof check === 'function') { - TYPE_MAP[type] = check; - return; - } - - if (check instanceof RegExp) { - TYPE_MAP[type] = function (rule, value) { - return checkString.call(this, {format: check}, value); - }; - return; - } - - throw new TypeError('check must be function or regexp'); -}; - -/** - * Simple type map - * @type {Object} - */ -var TYPE_MAP = Parameter.TYPE_MAP = { - number: checkNumber, - int: checkInt, - integer: checkInt, - string: checkString, - id: checkId, - date: checkDate, - dateTime: checkDateTime, - datetime: checkDateTime, - boolean: checkBoolean, - bool: checkBoolean, - array: checkArray, - object: checkObject, - enum: checkEnum, - email: checkEmail, - password: checkPassword, - url: checkUrl, -}; - -var CONVERT_MAP = Parameter.CONVERT_MAP = { - number: 'number', - int: 'int', - integer: 'int', - string: 'string', - id: 'string', - date: 'string', - dateTime: 'string', - datetime: 'string', - boolean: 'bool', - bool: 'bool', - email: 'string', - password: 'string', - url: 'string', -}; - -/** - * format a rule - * - resolve abbr - * - resolve `?` - * - resolve default convertType - * - * @param {Mixed} rule - * @return {Object} - * @api private - */ - -function formatRule(rule) { - rule = rule || {}; - if (typeof rule === 'string') { - rule = { type: rule }; - } else if (Array.isArray(rule)) { - rule = { type: 'enum', values: rule }; - } else if (rule instanceof RegExp) { - rule = { type: 'string', format: rule }; - } - - if (rule.type && rule.type[rule.type.length - 1] === '?') { - rule.type = rule.type.slice(0, -1); - rule.required = false; - } - - return rule; -} - -/** - * convert param to specific type - * @param {Object} rule - * @param {Object} obj - * @param {String} key - * @param {Boolean} defaultConvert - */ -function convert(rule, obj, key, defaultConvert) { - var convertType; - if (defaultConvert) convertType = CONVERT_MAP[rule.type]; - if (rule.convertType) convertType = rule.convertType; - if (!convertType) return; - - const value = obj[key]; - // convert type only work for primitive data - if (typeof value === 'object') return; - - // convertType support function - if (typeof convertType === 'function') { - obj[key] = convertType(value, obj); - return; - } - - switch (convertType) { - case 'int': - obj[key] = parseInt(value, 10); - break; - case 'string': - obj[key] = String(value); - break; - case 'number': - obj[key] = Number(obj[key]); - break; - case 'bool': - case 'boolean': - obj[key] = !!value; - break; - default: - // support convertType function added by addRule - if (typeof CONVERT_MAP[convertType] === 'function' ) { - obj[key] = CONVERT_MAP[rule.type](obj[key]); - } - } -} - -/** - * check interger - * { - * max: 100, - * min: 0 - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkInt(rule, value) { - if (typeof value !== 'number' || value % 1 !== 0) { - return this.t('should be an integer'); - } - - if (rule.hasOwnProperty('max') && value > rule.max) { - return this.t('should smaller than %s', rule.max); - } - - if (rule.hasOwnProperty('min') && value < rule.min) { - return this.t('should bigger than %s', rule.min); - } -} - -/** - * check number - * { - * max: 100, - * min: 0 - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkNumber(rule, value) { - if (typeof value !== 'number' || isNaN(value)) { - return this.t('should be a number'); - } - if (rule.hasOwnProperty('max') && value > rule.max) { - return this.t('should smaller than %s', rule.max); - } - if (rule.hasOwnProperty('min') && value < rule.min) { - return this.t('should bigger than %s', rule.min); - } -} - -/** - * check string - * { - * allowEmpty: true, // resolve default convertType to false, alias to empty) - * format: /^\d+$/, - * max: 100, - * min: 0, - * trim: false, - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkString(rule, value) { - if (typeof value !== 'string') { - return this.t('should be a string'); - } - - // if required === false, set allowEmpty to true by default - if (!rule.hasOwnProperty('allowEmpty') && rule.required === false) { - rule.allowEmpty = true; - } - - var allowEmpty = rule.hasOwnProperty('allowEmpty') - ? rule.allowEmpty - : rule.empty; - - if (!value) { - if (allowEmpty) return; - return this.t('should not be empty'); - } - - if (rule.hasOwnProperty('max') && value.length > rule.max) { - return this.t('length should smaller than %s', rule.max); - } - if (rule.hasOwnProperty('min') && value.length < rule.min) { - return this.t('length should bigger than %s', rule.min); - } - - if (rule.format && !rule.format.test(value)) { - return rule.message || this.t('should match %s', rule.format); - } -} - -/** - * check id format - * format: /^\d+/ - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkId(rule, value) { - return checkString.call(this, { format: ID_RE, allowEmpty: rule.allowEmpty }, value); -} - -/** - * check date format - * format: YYYY-MM-DD - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkDate(rule, value) { - return checkString.call(this, { format: DATE_TYPE_RE, allowEmpty: rule.allowEmpty }, value); -} - -/** - * check date time format - * format: YYYY-MM-DD HH:mm:ss - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkDateTime(rule, value) { - return checkString.call(this, { format: DATETIME_TYPE_RE, allowEmpty: rule.allowEmpty }, value); -} - -/** - * check boolean - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkBoolean(rule, value) { - if (typeof value !== 'boolean') { - return this.t('should be a boolean'); - } -} - -/** - * check enum - * { - * values: [0, 1, 2] - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkEnum(rule, value) { - if (!Array.isArray(rule.values)) { - throw new TypeError('check enum need array type values'); - } - if (rule.values.indexOf(value) === -1) { - return this.t('should be one of %s', rule.values.join(', ')); - } -} - -/** - * check email - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkEmail(rule, value) { - return checkString.call(this, { - format: EMAIL_RE, - message: rule.message || this.t('should be an email'), - allowEmpty: rule.allowEmpty, - }, value); -} - -/** - * check password - * @param {Object} rule - * @param {Object} value - * @param {Object} obj - * @return {Boolean} - * - * @api private - */ - -function checkPassword(rule, value, obj) { - if (!rule.min) { - rule.min = 6; - } - rule.format = PASSWORD_RE; - var error = checkString.call(this, rule, value); - if (error) { - return error; - } - if (rule.compare && obj[rule.compare] !== value) { - return this.t('should equal to %s', rule.compare); - } -} - -/** - * check url - * - * @param {Object} rule - * @param {Object} value - * @return {Boolean} - * @api private - */ - -function checkUrl(rule, value) { - return checkString.call(this, { - format: URL_RE, - message: rule.message || this.t('should be a url'), - allowEmpty: rule.allowEmpty - }, value); -} - -/** - * check object - * { - * rule: {} - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkObject(rule, value) { - if (typeof value !== 'object') { - return this.t('should be an object'); - } - - if (rule.rule) { - return this.validate(rule.rule, value); - } -} - -/** - * check array - * { - * type: 'array', - * itemType: 'string' - * rule: {type: 'string', allowEmpty: true} - * } - * - * { - * type: 'array'. - * itemType: 'object', - * rule: { - * name: 'string' - * } - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - -function checkArray(rule, value) { - if (!Array.isArray(value)) { - return this.t('should be an array'); - } - - if (rule.hasOwnProperty('max') && value.length > rule.max) { - return this.t('length should smaller than %s', rule.max); - } - if (rule.hasOwnProperty('min') && value.length < rule.min) { - return this.t('length should bigger than %s', rule.min); - } - - if (!rule.itemType) { - return; - } - - var self = this; - var checker = TYPE_MAP[rule.itemType]; - if (!checker) { - throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + - ', but the following type was passed: ' + rule.itemType); - } - - var errors = []; - var subRule = rule.itemType === 'object' - ? rule - : rule.rule || formatRule(rule.itemType); - - value.forEach(function (v, i) { - var index = '[' + i + ']'; - var errs = checker.call(self, subRule, v); - - if (typeof errs === 'string') { - errors.push({ - field: index, - message: errs, - code: self.t('invalid') - }); - } - if (Array.isArray(errs)) { - errors = errors.concat(errs.map(function (e) { - e.field = index + '.' + e.field; - e.message = e.message; - return e; - })); - } - }); - - return errors; -} + 'use strict' + + const util = require('util') + + /** + * Regexps + */ + + const DATE_TYPE_RE = /^\d{4}\-\d{2}\-\d{2}$/ + const DATETIME_TYPE_RE = /^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/ + const ID_RE = /^\d+$/ + + // http://www.regular-expressions.info/email.html + const EMAIL_RE = /^[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+(?:\.[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?$/i + + const PASSWORD_RE = /^[\w\`\~\!\@\#\$\%\^\&\*\(\)\-\_\=\+\[\]\{\}\|\;\:\'\"\,\<\.\>\/\?]+$/ + + // https://gist.github.com/dperini/729294 + const URL_RE = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i + + /** + * Parameter class + * @class Parameter + */ + class Parameter { + constructor (opts) { + opts = opts || {} + + if (typeof opts.translate === 'function') { + this.translate = opts.translate + } + + if (opts.validateRoot) this.validateRoot = true + if (opts.convert) this.convert = true + if (opts.widelyUndefined) this.widelyUndefined = true + } + + t () { + const args = Array.prototype.slice.call(arguments) + if (typeof this.translate === 'function') { + return this.translate.apply(this, args) + } else { + return util.format.apply(util, args) + } + } + + /** + * validate + * + * @param {Object} rules + * @return {Object} obj + * @api public + */ + validate (rules, obj) { + if (typeof rules !== 'object') { + throw new TypeError('need object type rule') + } + + if (this.validateRoot && (typeof obj !== 'object' || !obj)) { + return [{ + message: this.t('the validated value should be a object'), + code: this.t('invalid'), + field: undefined + }] + } + + const self = this + + const errors = [] + + for (const key in rules) { + const rule = formatRule(rules[key]) + let value = obj[key] + + if (typeof value === 'string' && rule.trim === true) { + value = obj[key] = value.trim() + } + + // treat null / '' / NaN as undefined + let widelyUndefined = this.widelyUndefined + if ('widelyUndefined' in rule) widelyUndefined = rule.widelyUndefined + if (widelyUndefined && + (value === '' || value === null || Number.isNaN(value))) { + value = obj[key] = undefined + } + + const has = value !== null && value !== undefined + + if (!has) { + if (rule.required !== false) { + errors.push({ + message: this.t('required'), + field: key, + code: this.t('missing_field') + }) + } + // support default value + if ('default' in rule) { + obj[key] = rule.default + } + continue + } + + const checker = TYPE_MAP[rule.type] + if (!checker) { + throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + + ', but the following type was passed: ' + rule.type) + } + + convert(rule, obj, key, this.convert) + const msg = checker.call(self, rule, obj[key], obj) + if (typeof msg === 'string') { + errors.push({ + message: msg, + code: this.t('invalid'), + field: key + }) + } + + if (Array.isArray(msg)) { + msg.forEach(function (e) { + const dot = rule.type === 'object' ? '.' : '' + e.field = key + dot + e.field + errors.push(e) + }) + } + } + + if (errors.length) { + return errors + } + } + }; + + /** + * Module exports + * @type {Function} + */ + module.exports = Parameter + + /** + * add custom rule to global rules list. + * + * @param {String} type + * @param {Function | RegExp} check + * @param {Boolean} [override] - override exists rule or not, default is true + * @param {String|Function} [convertType] + * @api public + */ + Parameter.prototype.addRule = Parameter.addRule = function addRule (type, check, override, convertType) { + if (!type) { + throw new TypeError('`type` required') + } + + // addRule(type, check, convertType) + if (typeof override === 'string' || typeof override === 'function') { + convertType = override + override = true + } + + if (typeof override !== 'boolean') { + override = true + } + + if (!override && TYPE_MAP[type]) { + throw new TypeError('rule `' + type + '` exists') + } + + if (convertType) { + if (typeof convertType !== 'string' && typeof convertType !== 'function') { + throw new TypeError('convertType should be string or function') + } + Parameter.CONVERT_MAP[type] = convertType + } + + if (typeof check === 'function') { + TYPE_MAP[type] = check + return + } + + if (check instanceof RegExp) { + TYPE_MAP[type] = function (rule, value) { + return checkString.call(this, { format: check }, value) + } + return + } + + throw new TypeError('check must be function or regexp') + } + + /** + * Simple type map + * @type {Object} + */ + const TYPE_MAP = Parameter.TYPE_MAP = { + number: checkNumber, + int: checkInt, + integer: checkInt, + string: checkString, + id: checkId, + date: checkDate, + dateTime: checkDateTime, + datetime: checkDateTime, + boolean: checkBoolean, + bool: checkBoolean, + array: checkArray, + object: checkObject, + enum: checkEnum, + email: checkEmail, + password: checkPassword, + url: checkUrl + } + + const CONVERT_MAP = Parameter.CONVERT_MAP = { + number: 'number', + int: 'int', + integer: 'int', + string: 'string', + id: 'string', + date: 'string', + dateTime: 'string', + datetime: 'string', + boolean: 'bool', + bool: 'bool', + email: 'string', + password: 'string', + url: 'string' + } + + /** + * format a rule + * - resolve abbr + * - resolve `?` + * - resolve default convertType + * + * @param {Mixed} rule + * @return {Object} + * @api private + */ + + function formatRule (rule) { + rule = rule || {} + if (typeof rule === 'string') { + rule = { type: rule } + } else if (Array.isArray(rule)) { + rule = { type: 'enum', values: rule } + } else if (rule instanceof RegExp) { + rule = { type: 'string', format: rule } + } + + if (rule.type && rule.type[rule.type.length - 1] === '?') { + rule.type = rule.type.slice(0, -1) + rule.required = false + } + + return rule + } + + /** + * convert param to specific type + * @param {Object} rule + * @param {Object} obj + * @param {String} key + * @param {Boolean} defaultConvert + */ + function convert (rule, obj, key, defaultConvert) { + let convertType + if (defaultConvert) convertType = CONVERT_MAP[rule.type] + if (rule.convertType) convertType = rule.convertType + if (!convertType) return + + const value = obj[key] + // convert type only work for primitive data + if (typeof value === 'object') return + + // convertType support function + if (typeof convertType === 'function') { + obj[key] = convertType(value, obj) + return + } + + switch (convertType) { + case 'int': + obj[key] = parseInt(value, 10) + break + case 'string': + obj[key] = String(value) + break + case 'number': + obj[key] = Number(obj[key]) + break + case 'bool': + case 'boolean': + obj[key] = !!value + break + default: + // support convertType function added by addRule + if (typeof CONVERT_MAP[convertType] === 'function') { + obj[key] = CONVERT_MAP[rule.type](obj[key]) + } + } + } + + /** + * check interger + * { + * max: 100, + * min: 0 + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkInt (rule, value) { + if (typeof value !== 'number' || value % 1 !== 0) { + return this.t('should be an integer') + } + + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value > rule.max) { + return this.t('should smaller than %s', rule.max) + } + + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value < rule.min) { + return this.t('should bigger than %s', rule.min) + } + } + + /** + * check number + * { + * max: 100, + * min: 0 + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkNumber (rule, value) { + if (typeof value !== 'number' || isNaN(value)) { + return this.t('should be a number') + } + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value > rule.max) { + return this.t('should smaller than %s', rule.max) + } + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value < rule.min) { + return this.t('should bigger than %s', rule.min) + } + } + + /** + * check string + * { + * allowEmpty: true, // resolve default convertType to false, alias to empty) + * format: /^\d+$/, + * max: 100, + * min: 0, + * trim: false, + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkString (rule, value) { + if (typeof value !== 'string') { + return this.t('should be a string') + } + + // if required === false, set allowEmpty to true by default + if (!Object.prototype.hasOwnProperty.call(rule, 'allowEmpty') && rule.required === false) { + rule.allowEmpty = true + } + + const allowEmpty = Object.prototype.hasOwnProperty.call(rule, 'allowEmpty') + ? rule.allowEmpty + : rule.empty + + if (!value) { + if (allowEmpty) return + return this.t('should not be empty') + } + + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value.length > rule.max) { + return this.t('length should smaller than %s', rule.max) + } + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value.length < rule.min) { + return this.t('length should bigger than %s', rule.min) + } + + if (rule.format && !rule.format.test(value)) { + return rule.message || this.t('should match %s', rule.format) + } + } + + /** + * check id format + * format: /^\d+/ + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkId (rule, value) { + return checkString.call(this, { format: ID_RE, allowEmpty: rule.allowEmpty }, value) + } + + /** + * check date format + * format: YYYY-MM-DD + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkDate (rule, value) { + return checkString.call(this, { format: DATE_TYPE_RE, allowEmpty: rule.allowEmpty }, value) + } + + /** + * check date time format + * format: YYYY-MM-DD HH:mm:ss + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkDateTime (rule, value) { + return checkString.call(this, { format: DATETIME_TYPE_RE, allowEmpty: rule.allowEmpty }, value) + } + + /** + * check boolean + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkBoolean (rule, value) { + if (typeof value !== 'boolean') { + return this.t('should be a boolean') + } + } + + /** + * check enum + * { + * values: [0, 1, 2] + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkEnum (rule, value) { + if (!Array.isArray(rule.values)) { + throw new TypeError('check enum need array type values') + } + if (rule.values.indexOf(value) === -1) { + return this.t('should be one of %s', rule.values.join(', ')) + } + } + + /** + * check email + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkEmail (rule, value) { + return checkString.call(this, { + format: EMAIL_RE, + message: rule.message || this.t('should be an email'), + allowEmpty: rule.allowEmpty + }, value) + } + + /** + * check password + * @param {Object} rule + * @param {Object} value + * @param {Object} obj + * @return {Boolean} + * + * @api private + */ + + function checkPassword (rule, value, obj) { + if (!rule.min) { + rule.min = 6 + } + rule.format = PASSWORD_RE + const error = checkString.call(this, rule, value) + if (error) { + return error + } + if (rule.compare && obj[rule.compare] !== value) { + return this.t('should equal to %s', rule.compare) + } + } + + /** + * check url + * + * @param {Object} rule + * @param {Object} value + * @return {Boolean} + * @api private + */ + + function checkUrl (rule, value) { + return checkString.call(this, { + format: URL_RE, + message: rule.message || this.t('should be a url'), + allowEmpty: rule.allowEmpty + }, value) + } + + /** + * check object + * { + * rule: {} + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkObject (rule, value) { + if (typeof value !== 'object') { + return this.t('should be an object') + } + + if (rule.rule) { + return this.validate(rule.rule, value) + } + } + + /** + * check array + * { + * type: 'array', + * itemType: 'string' + * rule: {type: 'string', allowEmpty: true} + * } + * + * { + * type: 'array'. + * itemType: 'object', + * rule: { + * name: 'string' + * } + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + + function checkArray (rule, value) { + if (!Array.isArray(value)) { + return this.t('should be an array') + } + + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value.length > rule.max) { + return this.t('length should smaller than %s', rule.max) + } + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value.length < rule.min) { + return this.t('length should bigger than %s', rule.min) + } + + if (!rule.itemType) { + return + } + + const self = this + const checker = TYPE_MAP[rule.itemType] + if (!checker) { + throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + + ', but the following type was passed: ' + rule.itemType) + } + + let errors = [] + const subRule = rule.itemType === 'object' + ? rule + : rule.rule || formatRule(rule.itemType) + + value.forEach(function (v, i) { + const index = '[' + i + ']' + const errs = checker.call(self, subRule, v) + + if (typeof errs === 'string') { + errors.push({ + field: index, + message: errs, + code: self.t('invalid') + }) + } + if (Array.isArray(errs)) { + errors = errors.concat(errs.map(function (e) { + e.field = index + '.' + e.field + e.message = e.message + return e + })) + } + }) + + return errors + } + \ No newline at end of file From 6ca90b3be6a2d487ade01472a632fc9d5de79a84 Mon Sep 17 00:00:00 2001 From: Dengshihua Date: Mon, 27 Jun 2022 10:50:48 +0800 Subject: [PATCH 2/2] Object.prototype.hasOwnProperty.call --- index.js | 1266 +++++++++++++++++++++++++++--------------------------- 1 file changed, 633 insertions(+), 633 deletions(-) diff --git a/index.js b/index.js index cfeb429..56981a7 100644 --- a/index.js +++ b/index.js @@ -1,633 +1,633 @@ - 'use strict' - - const util = require('util') - - /** - * Regexps - */ - - const DATE_TYPE_RE = /^\d{4}\-\d{2}\-\d{2}$/ - const DATETIME_TYPE_RE = /^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/ - const ID_RE = /^\d+$/ - - // http://www.regular-expressions.info/email.html - const EMAIL_RE = /^[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+(?:\.[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?$/i - - const PASSWORD_RE = /^[\w\`\~\!\@\#\$\%\^\&\*\(\)\-\_\=\+\[\]\{\}\|\;\:\'\"\,\<\.\>\/\?]+$/ - - // https://gist.github.com/dperini/729294 - const URL_RE = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i - - /** - * Parameter class - * @class Parameter - */ - class Parameter { - constructor (opts) { - opts = opts || {} - - if (typeof opts.translate === 'function') { - this.translate = opts.translate - } - - if (opts.validateRoot) this.validateRoot = true - if (opts.convert) this.convert = true - if (opts.widelyUndefined) this.widelyUndefined = true - } - - t () { - const args = Array.prototype.slice.call(arguments) - if (typeof this.translate === 'function') { - return this.translate.apply(this, args) - } else { - return util.format.apply(util, args) - } - } - - /** - * validate - * - * @param {Object} rules - * @return {Object} obj - * @api public - */ - validate (rules, obj) { - if (typeof rules !== 'object') { - throw new TypeError('need object type rule') - } - - if (this.validateRoot && (typeof obj !== 'object' || !obj)) { - return [{ - message: this.t('the validated value should be a object'), - code: this.t('invalid'), - field: undefined - }] - } - - const self = this - - const errors = [] - - for (const key in rules) { - const rule = formatRule(rules[key]) - let value = obj[key] - - if (typeof value === 'string' && rule.trim === true) { - value = obj[key] = value.trim() - } - - // treat null / '' / NaN as undefined - let widelyUndefined = this.widelyUndefined - if ('widelyUndefined' in rule) widelyUndefined = rule.widelyUndefined - if (widelyUndefined && - (value === '' || value === null || Number.isNaN(value))) { - value = obj[key] = undefined - } - - const has = value !== null && value !== undefined - - if (!has) { - if (rule.required !== false) { - errors.push({ - message: this.t('required'), - field: key, - code: this.t('missing_field') - }) - } - // support default value - if ('default' in rule) { - obj[key] = rule.default - } - continue - } - - const checker = TYPE_MAP[rule.type] - if (!checker) { - throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + - ', but the following type was passed: ' + rule.type) - } - - convert(rule, obj, key, this.convert) - const msg = checker.call(self, rule, obj[key], obj) - if (typeof msg === 'string') { - errors.push({ - message: msg, - code: this.t('invalid'), - field: key - }) - } - - if (Array.isArray(msg)) { - msg.forEach(function (e) { - const dot = rule.type === 'object' ? '.' : '' - e.field = key + dot + e.field - errors.push(e) - }) - } - } - - if (errors.length) { - return errors - } - } - }; - - /** - * Module exports - * @type {Function} - */ - module.exports = Parameter - - /** - * add custom rule to global rules list. - * - * @param {String} type - * @param {Function | RegExp} check - * @param {Boolean} [override] - override exists rule or not, default is true - * @param {String|Function} [convertType] - * @api public - */ - Parameter.prototype.addRule = Parameter.addRule = function addRule (type, check, override, convertType) { - if (!type) { - throw new TypeError('`type` required') - } - - // addRule(type, check, convertType) - if (typeof override === 'string' || typeof override === 'function') { - convertType = override - override = true - } - - if (typeof override !== 'boolean') { - override = true - } - - if (!override && TYPE_MAP[type]) { - throw new TypeError('rule `' + type + '` exists') - } - - if (convertType) { - if (typeof convertType !== 'string' && typeof convertType !== 'function') { - throw new TypeError('convertType should be string or function') - } - Parameter.CONVERT_MAP[type] = convertType - } - - if (typeof check === 'function') { - TYPE_MAP[type] = check - return - } - - if (check instanceof RegExp) { - TYPE_MAP[type] = function (rule, value) { - return checkString.call(this, { format: check }, value) - } - return - } - - throw new TypeError('check must be function or regexp') - } - - /** - * Simple type map - * @type {Object} - */ - const TYPE_MAP = Parameter.TYPE_MAP = { - number: checkNumber, - int: checkInt, - integer: checkInt, - string: checkString, - id: checkId, - date: checkDate, - dateTime: checkDateTime, - datetime: checkDateTime, - boolean: checkBoolean, - bool: checkBoolean, - array: checkArray, - object: checkObject, - enum: checkEnum, - email: checkEmail, - password: checkPassword, - url: checkUrl - } - - const CONVERT_MAP = Parameter.CONVERT_MAP = { - number: 'number', - int: 'int', - integer: 'int', - string: 'string', - id: 'string', - date: 'string', - dateTime: 'string', - datetime: 'string', - boolean: 'bool', - bool: 'bool', - email: 'string', - password: 'string', - url: 'string' - } - - /** - * format a rule - * - resolve abbr - * - resolve `?` - * - resolve default convertType - * - * @param {Mixed} rule - * @return {Object} - * @api private - */ - - function formatRule (rule) { - rule = rule || {} - if (typeof rule === 'string') { - rule = { type: rule } - } else if (Array.isArray(rule)) { - rule = { type: 'enum', values: rule } - } else if (rule instanceof RegExp) { - rule = { type: 'string', format: rule } - } - - if (rule.type && rule.type[rule.type.length - 1] === '?') { - rule.type = rule.type.slice(0, -1) - rule.required = false - } - - return rule - } - - /** - * convert param to specific type - * @param {Object} rule - * @param {Object} obj - * @param {String} key - * @param {Boolean} defaultConvert - */ - function convert (rule, obj, key, defaultConvert) { - let convertType - if (defaultConvert) convertType = CONVERT_MAP[rule.type] - if (rule.convertType) convertType = rule.convertType - if (!convertType) return - - const value = obj[key] - // convert type only work for primitive data - if (typeof value === 'object') return - - // convertType support function - if (typeof convertType === 'function') { - obj[key] = convertType(value, obj) - return - } - - switch (convertType) { - case 'int': - obj[key] = parseInt(value, 10) - break - case 'string': - obj[key] = String(value) - break - case 'number': - obj[key] = Number(obj[key]) - break - case 'bool': - case 'boolean': - obj[key] = !!value - break - default: - // support convertType function added by addRule - if (typeof CONVERT_MAP[convertType] === 'function') { - obj[key] = CONVERT_MAP[rule.type](obj[key]) - } - } - } - - /** - * check interger - * { - * max: 100, - * min: 0 - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkInt (rule, value) { - if (typeof value !== 'number' || value % 1 !== 0) { - return this.t('should be an integer') - } - - if (Object.prototype.hasOwnProperty.call(rule, 'max') && value > rule.max) { - return this.t('should smaller than %s', rule.max) - } - - if (Object.prototype.hasOwnProperty.call(rule, 'min') && value < rule.min) { - return this.t('should bigger than %s', rule.min) - } - } - - /** - * check number - * { - * max: 100, - * min: 0 - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkNumber (rule, value) { - if (typeof value !== 'number' || isNaN(value)) { - return this.t('should be a number') - } - if (Object.prototype.hasOwnProperty.call(rule, 'max') && value > rule.max) { - return this.t('should smaller than %s', rule.max) - } - if (Object.prototype.hasOwnProperty.call(rule, 'min') && value < rule.min) { - return this.t('should bigger than %s', rule.min) - } - } - - /** - * check string - * { - * allowEmpty: true, // resolve default convertType to false, alias to empty) - * format: /^\d+$/, - * max: 100, - * min: 0, - * trim: false, - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkString (rule, value) { - if (typeof value !== 'string') { - return this.t('should be a string') - } - - // if required === false, set allowEmpty to true by default - if (!Object.prototype.hasOwnProperty.call(rule, 'allowEmpty') && rule.required === false) { - rule.allowEmpty = true - } - - const allowEmpty = Object.prototype.hasOwnProperty.call(rule, 'allowEmpty') - ? rule.allowEmpty - : rule.empty - - if (!value) { - if (allowEmpty) return - return this.t('should not be empty') - } - - if (Object.prototype.hasOwnProperty.call(rule, 'max') && value.length > rule.max) { - return this.t('length should smaller than %s', rule.max) - } - if (Object.prototype.hasOwnProperty.call(rule, 'min') && value.length < rule.min) { - return this.t('length should bigger than %s', rule.min) - } - - if (rule.format && !rule.format.test(value)) { - return rule.message || this.t('should match %s', rule.format) - } - } - - /** - * check id format - * format: /^\d+/ - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkId (rule, value) { - return checkString.call(this, { format: ID_RE, allowEmpty: rule.allowEmpty }, value) - } - - /** - * check date format - * format: YYYY-MM-DD - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkDate (rule, value) { - return checkString.call(this, { format: DATE_TYPE_RE, allowEmpty: rule.allowEmpty }, value) - } - - /** - * check date time format - * format: YYYY-MM-DD HH:mm:ss - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkDateTime (rule, value) { - return checkString.call(this, { format: DATETIME_TYPE_RE, allowEmpty: rule.allowEmpty }, value) - } - - /** - * check boolean - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkBoolean (rule, value) { - if (typeof value !== 'boolean') { - return this.t('should be a boolean') - } - } - - /** - * check enum - * { - * values: [0, 1, 2] - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkEnum (rule, value) { - if (!Array.isArray(rule.values)) { - throw new TypeError('check enum need array type values') - } - if (rule.values.indexOf(value) === -1) { - return this.t('should be one of %s', rule.values.join(', ')) - } - } - - /** - * check email - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkEmail (rule, value) { - return checkString.call(this, { - format: EMAIL_RE, - message: rule.message || this.t('should be an email'), - allowEmpty: rule.allowEmpty - }, value) - } - - /** - * check password - * @param {Object} rule - * @param {Object} value - * @param {Object} obj - * @return {Boolean} - * - * @api private - */ - - function checkPassword (rule, value, obj) { - if (!rule.min) { - rule.min = 6 - } - rule.format = PASSWORD_RE - const error = checkString.call(this, rule, value) - if (error) { - return error - } - if (rule.compare && obj[rule.compare] !== value) { - return this.t('should equal to %s', rule.compare) - } - } - - /** - * check url - * - * @param {Object} rule - * @param {Object} value - * @return {Boolean} - * @api private - */ - - function checkUrl (rule, value) { - return checkString.call(this, { - format: URL_RE, - message: rule.message || this.t('should be a url'), - allowEmpty: rule.allowEmpty - }, value) - } - - /** - * check object - * { - * rule: {} - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkObject (rule, value) { - if (typeof value !== 'object') { - return this.t('should be an object') - } - - if (rule.rule) { - return this.validate(rule.rule, value) - } - } - - /** - * check array - * { - * type: 'array', - * itemType: 'string' - * rule: {type: 'string', allowEmpty: true} - * } - * - * { - * type: 'array'. - * itemType: 'object', - * rule: { - * name: 'string' - * } - * } - * - * @param {Object} rule - * @param {Mixed} value - * @return {Boolean} - * @api private - */ - - function checkArray (rule, value) { - if (!Array.isArray(value)) { - return this.t('should be an array') - } - - if (Object.prototype.hasOwnProperty.call(rule, 'max') && value.length > rule.max) { - return this.t('length should smaller than %s', rule.max) - } - if (Object.prototype.hasOwnProperty.call(rule, 'min') && value.length < rule.min) { - return this.t('length should bigger than %s', rule.min) - } - - if (!rule.itemType) { - return - } - - const self = this - const checker = TYPE_MAP[rule.itemType] - if (!checker) { - throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + - ', but the following type was passed: ' + rule.itemType) - } - - let errors = [] - const subRule = rule.itemType === 'object' - ? rule - : rule.rule || formatRule(rule.itemType) - - value.forEach(function (v, i) { - const index = '[' + i + ']' - const errs = checker.call(self, subRule, v) - - if (typeof errs === 'string') { - errors.push({ - field: index, - message: errs, - code: self.t('invalid') - }) - } - if (Array.isArray(errs)) { - errors = errors.concat(errs.map(function (e) { - e.field = index + '.' + e.field - e.message = e.message - return e - })) - } - }) - - return errors - } - \ No newline at end of file +'use strict'; + +var util = require('util'); + +/** + * Regexps + */ + +var DATE_TYPE_RE = /^\d{4}\-\d{2}\-\d{2}$/; +var DATETIME_TYPE_RE = /^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/; +var ID_RE = /^\d+$/; + +// http://www.regular-expressions.info/email.html +var EMAIL_RE = /^[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+(?:\.[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?$/i; + +var PASSWORD_RE = /^[\w\`\~\!\@\#\$\%\^\&\*\(\)\-\_\=\+\[\]\{\}\|\;\:\'\"\,\<\.\>\/\?]+$/; + +// https://gist.github.com/dperini/729294 +var URL_RE = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i; + +/** + * Parameter class + * @class Parameter + */ +class Parameter { + constructor(opts) { + opts = opts || {}; + + if (typeof opts.translate === 'function') { + this.translate = opts.translate; + } + + if (opts.validateRoot) this.validateRoot = true; + if (opts.convert) this.convert = true; + if (opts.widelyUndefined) this.widelyUndefined = true; + } + + t() { + var args = Array.prototype.slice.call(arguments); + if (typeof this.translate === 'function') { + return this.translate.apply(this, args); + } else { + return util.format.apply(util, args); + } + } + + /** + * validate + * + * @param {Object} rules + * @return {Object} obj + * @api public + */ + validate(rules, obj) { + if (typeof rules !== 'object') { + throw new TypeError('need object type rule'); + } + + if (this.validateRoot && (typeof obj !== 'object' || !obj)) { + return [{ + message: this.t('the validated value should be a object'), + code: this.t('invalid'), + field: undefined, + }]; + } + + var self = this; + + var errors = []; + + for (var key in rules) { + var rule = formatRule(rules[key]); + var value = obj[key]; + + if (typeof value === 'string' && rule.trim === true) { + value = obj[key] = value.trim(); + } + + // treat null / '' / NaN as undefined + let widelyUndefined = this.widelyUndefined; + if ('widelyUndefined' in rule) widelyUndefined = rule.widelyUndefined; + if (widelyUndefined && + (value === '' || value === null || Number.isNaN(value))) { + value = obj[key] = undefined; + } + + var has = value !== null && value !== undefined; + + if (!has) { + if (rule.required !== false) { + errors.push({ + message: this.t('required'), + field: key, + code: this.t('missing_field') + }); + } + // support default value + if ('default' in rule) { + obj[key] = rule.default; + } + continue; + } + + var checker = TYPE_MAP[rule.type]; + if (!checker) { + throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + + ', but the following type was passed: ' + rule.type); + } + + convert(rule, obj, key, this.convert); + var msg = checker.call(self, rule, obj[key], obj); + if (typeof msg === 'string') { + errors.push({ + message: msg, + code: this.t('invalid'), + field: key + }); + } + + if (Array.isArray(msg)) { + msg.forEach(function (e) { + var dot = rule.type === 'object' ? '.' : ''; + e.field = key + dot + e.field; + errors.push(e); + }); + } + } + + if (errors.length) { + return errors; + } + } +}; + +/** + * Module exports + * @type {Function} + */ +module.exports = Parameter; + +/** + * add custom rule to global rules list. + * + * @param {String} type + * @param {Function | RegExp} check + * @param {Boolean} [override] - override exists rule or not, default is true + * @param {String|Function} [convertType] + * @api public + */ +Parameter.prototype.addRule = Parameter.addRule = function addRule(type, check, override, convertType) { + if (!type) { + throw new TypeError('`type` required'); + } + + // addRule(type, check, convertType) + if (typeof override === 'string' || typeof override === 'function') { + convertType = override; + override = true; + } + + if (typeof override !== 'boolean') { + override = true; + } + + if (!override && TYPE_MAP[type]) { + throw new TypeError('rule `' + type + '` exists'); + } + + if (convertType) { + if (typeof convertType !== 'string' && typeof convertType !== 'function') { + throw new TypeError('convertType should be string or function'); + } + Parameter.CONVERT_MAP[type] = convertType; + } + + + if (typeof check === 'function') { + TYPE_MAP[type] = check; + return; + } + + if (check instanceof RegExp) { + TYPE_MAP[type] = function (rule, value) { + return checkString.call(this, {format: check}, value); + }; + return; + } + + throw new TypeError('check must be function or regexp'); +}; + +/** + * Simple type map + * @type {Object} + */ +var TYPE_MAP = Parameter.TYPE_MAP = { + number: checkNumber, + int: checkInt, + integer: checkInt, + string: checkString, + id: checkId, + date: checkDate, + dateTime: checkDateTime, + datetime: checkDateTime, + boolean: checkBoolean, + bool: checkBoolean, + array: checkArray, + object: checkObject, + enum: checkEnum, + email: checkEmail, + password: checkPassword, + url: checkUrl, +}; + +var CONVERT_MAP = Parameter.CONVERT_MAP = { + number: 'number', + int: 'int', + integer: 'int', + string: 'string', + id: 'string', + date: 'string', + dateTime: 'string', + datetime: 'string', + boolean: 'bool', + bool: 'bool', + email: 'string', + password: 'string', + url: 'string', +}; + +/** + * format a rule + * - resolve abbr + * - resolve `?` + * - resolve default convertType + * + * @param {Mixed} rule + * @return {Object} + * @api private + */ + +function formatRule(rule) { + rule = rule || {}; + if (typeof rule === 'string') { + rule = { type: rule }; + } else if (Array.isArray(rule)) { + rule = { type: 'enum', values: rule }; + } else if (rule instanceof RegExp) { + rule = { type: 'string', format: rule }; + } + + if (rule.type && rule.type[rule.type.length - 1] === '?') { + rule.type = rule.type.slice(0, -1); + rule.required = false; + } + + return rule; +} + +/** + * convert param to specific type + * @param {Object} rule + * @param {Object} obj + * @param {String} key + * @param {Boolean} defaultConvert + */ +function convert(rule, obj, key, defaultConvert) { + var convertType; + if (defaultConvert) convertType = CONVERT_MAP[rule.type]; + if (rule.convertType) convertType = rule.convertType; + if (!convertType) return; + + const value = obj[key]; + // convert type only work for primitive data + if (typeof value === 'object') return; + + // convertType support function + if (typeof convertType === 'function') { + obj[key] = convertType(value, obj); + return; + } + + switch (convertType) { + case 'int': + obj[key] = parseInt(value, 10); + break; + case 'string': + obj[key] = String(value); + break; + case 'number': + obj[key] = Number(obj[key]); + break; + case 'bool': + case 'boolean': + obj[key] = !!value; + break; + default: + // support convertType function added by addRule + if (typeof CONVERT_MAP[convertType] === 'function' ) { + obj[key] = CONVERT_MAP[rule.type](obj[key]); + } + } +} + +/** + * check interger + * { + * max: 100, + * min: 0 + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkInt(rule, value) { + if (typeof value !== 'number' || value % 1 !== 0) { + return this.t('should be an integer'); + } + + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value > rule.max) { + return this.t('should smaller than %s', rule.max); + } + + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value < rule.min) { + return this.t('should bigger than %s', rule.min); + } +} + +/** + * check number + * { + * max: 100, + * min: 0 + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkNumber(rule, value) { + if (typeof value !== 'number' || isNaN(value)) { + return this.t('should be a number'); + } + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value > rule.max) { + return this.t('should smaller than %s', rule.max); + } + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value < rule.min) { + return this.t('should bigger than %s', rule.min); + } +} + +/** + * check string + * { + * allowEmpty: true, // resolve default convertType to false, alias to empty) + * format: /^\d+$/, + * max: 100, + * min: 0, + * trim: false, + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkString(rule, value) { + if (typeof value !== 'string') { + return this.t('should be a string'); + } + + // if required === false, set allowEmpty to true by default + if (!Object.prototype.hasOwnProperty.call(rule, 'allowEmpty') && rule.required === false) { + rule.allowEmpty = true; + } + + var allowEmpty = Object.prototype.hasOwnProperty.call(rule, 'allowEmpty') + ? rule.allowEmpty + : rule.empty; + + if (!value) { + if (allowEmpty) return; + return this.t('should not be empty'); + } + + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value.length > rule.max) { + return this.t('length should smaller than %s', rule.max); + } + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value.length < rule.min) { + return this.t('length should bigger than %s', rule.min); + } + + if (rule.format && !rule.format.test(value)) { + return rule.message || this.t('should match %s', rule.format); + } +} + +/** + * check id format + * format: /^\d+/ + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkId(rule, value) { + return checkString.call(this, { format: ID_RE, allowEmpty: rule.allowEmpty }, value); +} + +/** + * check date format + * format: YYYY-MM-DD + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkDate(rule, value) { + return checkString.call(this, { format: DATE_TYPE_RE, allowEmpty: rule.allowEmpty }, value); +} + +/** + * check date time format + * format: YYYY-MM-DD HH:mm:ss + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkDateTime(rule, value) { + return checkString.call(this, { format: DATETIME_TYPE_RE, allowEmpty: rule.allowEmpty }, value); +} + +/** + * check boolean + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkBoolean(rule, value) { + if (typeof value !== 'boolean') { + return this.t('should be a boolean'); + } +} + +/** + * check enum + * { + * values: [0, 1, 2] + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkEnum(rule, value) { + if (!Array.isArray(rule.values)) { + throw new TypeError('check enum need array type values'); + } + if (rule.values.indexOf(value) === -1) { + return this.t('should be one of %s', rule.values.join(', ')); + } +} + +/** + * check email + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkEmail(rule, value) { + return checkString.call(this, { + format: EMAIL_RE, + message: rule.message || this.t('should be an email'), + allowEmpty: rule.allowEmpty, + }, value); +} + +/** + * check password + * @param {Object} rule + * @param {Object} value + * @param {Object} obj + * @return {Boolean} + * + * @api private + */ + +function checkPassword(rule, value, obj) { + if (!rule.min) { + rule.min = 6; + } + rule.format = PASSWORD_RE; + var error = checkString.call(this, rule, value); + if (error) { + return error; + } + if (rule.compare && obj[rule.compare] !== value) { + return this.t('should equal to %s', rule.compare); + } +} + +/** + * check url + * + * @param {Object} rule + * @param {Object} value + * @return {Boolean} + * @api private + */ + +function checkUrl(rule, value) { + return checkString.call(this, { + format: URL_RE, + message: rule.message || this.t('should be a url'), + allowEmpty: rule.allowEmpty + }, value); +} + +/** + * check object + * { + * rule: {} + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkObject(rule, value) { + if (typeof value !== 'object') { + return this.t('should be an object'); + } + + if (rule.rule) { + return this.validate(rule.rule, value); + } +} + +/** + * check array + * { + * type: 'array', + * itemType: 'string' + * rule: {type: 'string', allowEmpty: true} + * } + * + * { + * type: 'array'. + * itemType: 'object', + * rule: { + * name: 'string' + * } + * } + * + * @param {Object} rule + * @param {Mixed} value + * @return {Boolean} + * @api private + */ + +function checkArray(rule, value) { + if (!Array.isArray(value)) { + return this.t('should be an array'); + } + + if (Object.prototype.hasOwnProperty.call(rule, 'max') && value.length > rule.max) { + return this.t('length should smaller than %s', rule.max); + } + if (Object.prototype.hasOwnProperty.call(rule, 'min') && value.length < rule.min) { + return this.t('length should bigger than %s', rule.min); + } + + if (!rule.itemType) { + return; + } + + var self = this; + var checker = TYPE_MAP[rule.itemType]; + if (!checker) { + throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + + ', but the following type was passed: ' + rule.itemType); + } + + var errors = []; + var subRule = rule.itemType === 'object' + ? rule + : rule.rule || formatRule(rule.itemType); + + value.forEach(function (v, i) { + var index = '[' + i + ']'; + var errs = checker.call(self, subRule, v); + + if (typeof errs === 'string') { + errors.push({ + field: index, + message: errs, + code: self.t('invalid') + }); + } + if (Array.isArray(errs)) { + errors = errors.concat(errs.map(function (e) { + e.field = index + '.' + e.field; + e.message = e.message; + return e; + })); + } + }); + + return errors; +}