UNPKG

ydr-validator

Version:
738 lines (638 loc) 22.2 kB
/*! * 数据验证 * @author ydr.me * @create 2014-11-22 14:58 * @update 2014-12-16 20:22:36 * @update 2015年03月07日16:31:59 */ 'use strict'; var klass = require('ydr-util').class; var dato = require('ydr-util').dato; var typeis = require('ydr-util').typeis; var howdo = require('howdo'); var Emitter = require('events').EventEmitter; var types = 'string,number,email,url,boolean,array'.split(','); var noop = function () { // ignore }; var customRules = {}; var udf; var defaults = { // true: 返回单个错误对象 // false: 返回错误对象组成的数组 // 浏览器端,默认为 false // 服务器端,默认为 true isBreakOnInvalid: true }; var Validator = klass.create(function (options) { var the = this; // 规则列表,有顺序之分 the._ruleList = []; // 已经存在的验证规则 the._ruleNames = {}; the._customRules = {}; the.rules = {}; // 选项 the._options = dato.extend(true, {}, defaults, options); }, Emitter); /** * 注册自定义的静态验证规则 * @param options {Object} 规则配置 * @param fn {Function} 规则方法 * @param [isOverride=false] 是否覆盖已有规则 * * @example * // 添加一个检查后缀的自定义规则 * Validator.registerRule({ * name: 'suffix', * type: 'array' * }, function(suffix, val, next){ * var sf = (val.match(/\.[^.]*$/) || [''])[0]; * var boolean = suffix.indexOf(sf) > -1; * * next(boolean ? null : new Error(this.alias + '的文件后缀不正确'), val); * }); */ Validator.registerRule = function (options, fn, isOverride) { if (!customRules[options.name] || customRules[options.name] && isOverride) { customRules[options.name] = { name: options.name, type: options.type, fn: fn }; } }; /** * 注册自定义的实例验证规则 * @param options {Object} 规则配置 * @param fn {Function} 规则方法 * @param [isOverride=false] 是否覆盖已有规则 * * @example * // 添加一个检查后缀的自定义规则 * validator.registerRule({ * name: 'suffix', * type: 'array' * }, function(suffix, val, next){ * var sf = (val.match(/\.[^.]*$/) || [''])[0]; * var boolean = suffix.indexOf(sf) > -1; * * next(boolean ? null : new Error(this.alias + '的文件后缀不正确'), val); * }); */ Validator.fn.registerRule = function (options, fn, isOverride) { var the = this; if (!the._customRules[options.name] || the._customRules[options.name] && isOverride) { the._customRules[options.name] = { name: options.name, type: options.type, fn: fn }; } }; /** * 添加单个验证规则 * @param {Object} rule 验证规则对象 * @param {String} rule.name 数据字段名称【必须】 * @param {String} rule.type 数据类型【必须】 * @param {String} [rule.alias] 别称,否则在消息中字段名称以`name`输出 * @param {Function} [rule.onbefore] 验证前置:数据验证之前的处理回调 * @param {Boolean} [rule.exist=false] 验证前置:是否存在的时候才验证,默认false * @param {Boolean} [rule.trim=true] 验证前置:是否去除验证数据的左右空白,默认true * @param {Boolean} [rule.required] 验证规则:是否必填 * @param {Number} [rule.length] 验证规则:指定字符串数据的字符长度,仅当为字符串类型(string/email/url)有效 * @param {Number} [rule.minLength] 验证规则:指定字符串数据的最小字符长度,仅当为字符串类型(string/email/url)有效 * @param {Number} [rule.maxLength] 验证规则:指定字符串数据的最大字符长度,仅当为字符串类型(string/email/url)有效 * @param {Number} [rule.bytes] 验证规则:指定字符串数据的字节长度,仅当为字符串类型(string/email/url)有效 * @param {Number} [rule.minBytes] 验证规则:指定字符串数据的最小字节长度,仅当为字符串类型(string/email/url)有效 * @param {Number} [rule.maxBytes] 验证规则:指定字符串数据的最大字节长度,仅当为字符串类型(string/email/url)有效 * @param {Number} [rule.min] 验证规则:指定数字数据的最小值,仅当为数值类型(number)有效 * @param {Number} [rule.max] 验证规则:指定数字数据的最大值,仅当为数值类型(number)有效 * @param {RegExp} [rule.regexp] 验证规则:正则表达式,仅当为字符串类型(string/email/url)有效 * @param {*} [rule.equal] 验证规则:全等于指定值 * @param {Array} [rule.inArray] 验证规则:用数组指定范围值 * @param {Function} [rule.function] 验证规则:自定义验证函数,参数为`val`、`next`,可以是异步,最后执行`next(err);`即可 * @param {Function} [rule.onafter] 验证后置:数据验证之后的处理回调 * @param {Object} [rule.msg] 验证出错的消息 * @param {Boolean} [isOverride=false] 是否覆盖已经存在的验证规则,默认false * * @example * validator.pushRule({ * // 字段名称,必须,唯一性 * name: 'username', * // 数据类型,必须,为 string/email/url/number/boolean/array 之一 * type: 'string', * // 别称,当没有填写自定义错误消息时 * // 提示为 username不能为空 * // 当前设置别名之后 * // 提示为 用户名不能为空 * alias: '用户名', * * // 验证前置 * // val 当前字段值 * // data 所有数据 * onbefore: function(val, data){ * return val + 'abc'; * }, * exist: false, * trim: true, * * // 验证规则 * required: true, * length: 10, * minLength: 4, * maxLength: 12, * bytes: 10, * minBytes: 4, * maxBytes: 12, * min: 4, * max: 4, * regexp: /^[a-z]\w{3,11}$/, * equal: 'cloudcome', * inArray: ['cloudcome', 'yundanran'], * // val 当前字段值 * // [data 所有数据] 可选 * // next 执行下一步 * function: function(val, next){ * // 这里可以是异步的 * // 比如远程校验可以写在这里 * ajax.post('./validate.json', { * username: val * }).on('success', function(json){ * if(json.code>0){ * next(); * }else{ * next(new Error(json.msg)); * } * }).on('error', function(){ * next(new Error('网络连接错误,验证失败')); * }); * }, * * // 验证消息 * msg: { * required: '用户名不能为空', * length: '用户名长度必须为10' * // 未自定义的消息,以默认输出 * // 每个验证规则都必须配备一个消息体, * // 除了自定义的`function` * }, * * // 验证后置 * onafter: function(val){ * return val + 'abc'; * } * }); */ Validator.fn.pushRule = function (rule, isOverride) { var the = this; var pushFn = function (father) { if (typeis.function(rule.onbefore)) { father.onbefores.push(rule.onbefore); rule.onbefore = udf; } if (typeis.function(rule.onafter)) { father.onafters.push(rule.onafter); rule.onafter = udf; } if (typeis.function(rule.function)) { father.functions.push(rule.function); rule.function = udf; } }; // 是否存在 var isExist = !!the._ruleNames[rule.name]; if (!isExist || isOverride) { if (!rule.type || types.indexOf(rule.type) === -1) { throw '`rule.type` must be one of [' + types.join('/') + ']'; } if (!rule.msg) { rule.msg = {}; } if (!rule.alias) { rule.alias = rule.name; } rule.required = !!rule.required; if (rule.exist === udf) { rule.exist = false; } rule.exist = !!rule.exist; if (rule.trim === udf) { rule.trim = true; } rule.trim = !!rule.trim; // 存在并且覆盖 if (isExist && isOverride) { the._ruleList.forEach(function (existRule) { if (rule.name === existRule.name) { existRule.onbefores = existRule.onbefores || []; dato.extend(true, existRule, rule); the.rules[rule.name] = existRule; } }); } // 不存在 else if (!isExist) { the._ruleList.push(rule); the._ruleNames[rule.name] = true; the.rules[rule.name] = rule; // 函数数组 rule.onbefores = []; rule.onafters = []; rule.functions = []; pushFn(rule); } } // 存在不覆盖 else if (isExist && !isOverride) { pushFn(the.rules[rule.name]); } return the; }; /** * 执行所有数据验证 * @param {Object} data 数据 * @param {Function} callback 回调 * @arguments err 对象 */ Validator.fn.validateAll = function (data, callback) { var the = this; var isBreakOnInvalid = the._options.isBreakOnInvalid; var errList = []; data = typeis(data) === 'object' ? data : {}; callback = typeis(callback) === 'function' ? callback : noop; /** * 所有字段验证开始 * @event validateallstart */ the.emit('validateallstart'); howdo.each(the._ruleList, function (key, rule, next) { the._validateOne(rule, data, function (err) { if (!isBreakOnInvalid) { errList.push(err); } if (!err || !isBreakOnInvalid) { return next(); } next(err); }); }).follow(function (err) { var errs = null; if (isBreakOnInvalid) { return callback.call(the, err, data); } dato.each(the._ruleList, function (index, rule) { if (errList[index]) { errs = errs || {}; errs[rule.name] = errList[index]; } }); /** * 所有字段验证结束 * @event validateallend * @param errs {Array} 错误消息数组 * @param data {Object} 验证过滤后的数据 */ the.emit('validateallend', errs, data); callback.call(the, errs, data); }); }; /** * 只对当前一个数据验证 * @param {Object} data 数据 * @param {Function} callback 回调 * @arguments err 对象 */ Validator.fn.validateOne = function (data, callback) { var the = this; var findIndex = -1; var name = Object.keys(data)[0]; callback = typeis(callback) === 'function' ? callback : noop; if (name) { /** * 单个字段验证开始 * @event validateonestart * @param name {String} 字段名称 */ the.emit('validateonestart', name); dato.each(the._ruleList, function (index, rule) { if (rule.name === name) { findIndex = index; return false; } }); if (findIndex > -1) { the._validateOne(the._ruleList[findIndex], data, function (err, data) { /** * 单个字段验证结束 * @event validateoneend * @param name {String} 字段名称 * @param error {Error} 错误消息 * @param value {*} 字段值 */ the.emit('validateoneend', name, err, data[name]); callback.call(the, err, data[name], data); }); } else { /** * 单个字段验证结束 * @event validateoneend * @param name {String} 字段名称 * @param error {Error} 错误消息 * @param value {*} 字段值 */ the.emit('validateoneend', name, null, data[name]); callback.call(the, null, data[name], data); } } else { /** * 单个字段验证开始 * @event validateonestart * @param name {String} 字段名称 */ the.emit('validateonestart', name); /** * 单个字段验证结束 * @event validateoneend * @param name {String} 字段名称 * @param error {Error} 错误消息 * @param value {*} 字段值 */ the.emit('validateoneend', name, null, data[name]); callback.call(the, null, data[name], data); } }; /** * 单个数据验证 * @param rule * @param data * @param callback * @returns {*} * @private */ Validator.fn._validateOne = function (rule, data, callback) { var the = this; var val = data[rule.name]; var err; var type; var runCustom = function (next) { var runCustomRules = function (customRules) { return function (callback) { howdo.each(customRules, function (ruleName, ruleInfo, next) { var _rule = rule[ruleName]; if (typeis(_rule) !== ruleInfo.type) { return next(); } ruleInfo.fn.call(rule, _rule, val, function (err) { if (!err) { return next(); } err = rule.msg[ruleName] ? new Error(rule.msg[ruleName]) : err; next(err); }); }).follow(callback); }; }; howdo // 静态自定义验证规则 .task(runCustomRules(customRules)) // 实例自定义验证规则 .task(runCustomRules(the._customRules)) // 异步串行 .follow(next); }; var onover = function (err) { // onafter if (!err) { rule.onafters.forEach(function (fn) { data[rule.name] = fn.call(rule, val, data); }); } /** * 单个字段验证结束 * @event validateend * @param name {String} 字段名称 * @param data {Object} 验证过滤后的数据 */ the.emit('validateend', rule.name, data); // callback if (typeis(callback) === 'function') { callback.call(the, err, data); } }; /** * 单个字段验证开始 * @event validateend * @param name {String} 字段名称 */ the.emit('validatestart', rule.name); // onbefore rule.onbefores.forEach(function (fn) { data[rule.name] = val = fn.call(rule, val, data); }); type = typeis(val); // trim if (type === 'string' && rule.trim) { data[rule.name] = val = String(val).trim(); } // exist var info = _getDataInfo(val); // 是否存在 var isExist = !!info.stringLength || !!info.number || !!info.booleanLength; // 需要验证 = 存在就验证 && 存在 || 一直验证 var require2Exist2Validate = rule.exist && isExist || !rule.exist; if (require2Exist2Validate) { // required if (rule.required && type === 'string') { err = new Error(rule.msg.required || rule.alias + '不能为空'); if (!info.stringLength) { return onover(err); } } // type err = new Error(rule.msg.type || rule.alias + '数据类型必须为' + rule.type); switch (rule.type) { case 'string': case 'email': case 'url': if (type !== 'string') { return onover(err); } if (rule.type === 'email' && !typeis.email(val)) { return onover(err); } if (rule.type === 'url' && !typeis.url(val)) { return onover(err); } break; default: if (rule.type && type !== rule.type) { return onover(err); } } // length if (typeis.number(rule.length) && type === 'string') { err = new Error(rule.msg.length || rule.alias + '长度必须为' + rule.length + '字符'); if (info.stringLength !== rule.length) { return onover(err); } } // bytes if (typeis.number(rule.bytes) && type === 'string') { err = new Error(rule.msg.bytes || rule.alias + '长度必须为' + rule.bytes + '字节'); if (info.stringBytes !== rule.bytes) { return onover(err); } } // minLength if (typeis.number(rule.minLength) && type === 'string') { err = new Error(rule.msg.minLength || rule.alias + '长度不能少于' + rule.minLength + '字符'); if (info.stringLength < rule.minLength) { return onover(err); } } // minBytes if (typeis.number(rule.minBytes) && type === 'string') { err = new Error(rule.msg.minBytes || rule.alias + '长度不能少于' + rule.minBytes + '字节'); if (info.stringBytes < rule.minBytes) { return onover(err); } } // maxLength if (typeis.number(rule.maxLength) && type === 'string') { err = new Error(rule.msg.maxLength || rule.alias + '长度不能超过' + rule.maxLength + '字符'); if (info.stringLength > rule.maxLength) { return onover(err); } } // maxBytes if (typeis.number(rule.maxBytes) && type === 'string') { err = new Error(rule.msg.maxBytes || rule.alias + '长度不能超过' + rule.maxBytes + '字节'); if (info.stringBytes > rule.maxBytes) { return onover(err); } } // inArray if (typeis.array(rule.inArray)) { err = new Error(rule.msg.inArray || rule.alias + '必须是“' + rule.inArray.join('、') + '”其一'); if (rule.inArray.indexOf(val) === -1) { return onover(err); } } // min if (typeis.number(rule.min) && type === 'number') { err = new Error(rule.msg.min || rule.alias + '不能小于' + rule.min); if (val < rule.min) { return onover(err); } } // max if (typeis.number(rule.max) && type === 'number') { err = new Error(rule.msg.max || rule.alias + '不能大于' + rule.max); if (val > rule.max) { return onover(err); } } // regexp if (typeis.regexp(rule.regexp) && type === 'string') { err = new Error(rule.msg.regexp || rule.alias + '不符合规则'); if (!rule.regexp.test(val)) { return onover(err); } } // equal if (rule.equal !== undefined) { var msg = ''; switch (typeis(rule.equal)) { case 'string': case 'email': case 'url': msg = rule.equal + ''; break; case 'boolean': msg = rule.equal ? '真' : '假'; break; case 'number': msg = '数值' + rule.equal; break; } err = new Error(rule.msg.equal || rule.alias + '必须是' + rule.equal); if (!_isEqual(val, rule.equal)) { return onover(err); } } runCustom(function (err) { if (err) { return onover(err); } // function howdo.each(rule.functions, function (index, fn, next) { var fnLen = fn.length; if (fnLen === 3) { fn.call(window, val, data, next); } else if (fnLen === 2) { fn.call(window, val, next); } else { next(); } }).follow(onover); }); } else { onover(); } }; module.exports = Validator; /** * 获取数据信息 * @param {*} data * @returns {Object} * @private */ function _getDataInfo(data) { var type = typeis(data); var ret = {}; switch (type) { case 'string': ret.stringLength = data.length; ret.stringBytes = dato.bytes(data); break; case 'number': ret.number = data; break; // //case 'array': // ret.arrayLength = dato.length; // break; // //case 'object': // ret.objectLength = Object.keys(data).length; // break; // case 'boolean': ret.booleanLength = 1; break; } return ret; } /** * 判断两个数据是否相等 * @param data * @param equal * @returns {Boolean} * @private */ function _isEqual(data, equal) { var type = typeis(data); var compare; switch (type) { case 'string': case 'number': return data === equal; case 'array': case 'object': compare = dato.compare(data, equal); return compare ? !compare.different.length : false; case 'boolean': return data === equal; } }