https://github.com/renjunqing/data-police
简单示例
被校验的数据如下:
var data = {
a: 1
}
校验规则a是一个大于0且小于20的整数
,则校验描述如下:
var rule = {
a: {
$type: 'Number',
$gt: 0, // 大于0
$lt: 20, // 小于20
$factor: 1, // 是1的整数倍
$message: 'a是一个大于0且小于20的整数' // 校验不通过后的报错提示
}
}
则校验代码如下:
import Validator from 'data-police'
const ruleValidator = new Validator(rule)
ruleValidator.check(data).then(d => {
console.log(d)
}).catch(eMsg => {
console.log(eMsg)
})
安装
npm install data-police --save
环境配置
- 安装包为
ES6
源码,业务项目需要根据自己的环境进行编译。 - 提供了对
TypeScript
的支持,内含.d.ts
声明文件,使用者无需重复编写。
简单介绍
data-police 在语法设计上参考了mongodb的查询语法,恰巧与 JSON Schema又有几分神似。
- 不是一个完全的JSON语法,因为其中包含了JavaScript函数,所以校验规则是一个Javascript对象。
- 规则对象与被校验数据对象结构基本保持一致,不一致的点在规则对象中包含了
描述符
。
校验规则
分两种情况
- 描述性校验,即被校验的数据节点是否符合描述符的含义,因描述符都是一个对象的key,所以包含描述符的对象,称为
描述对象
。 - 相等性校验,如果规则节点key不是描述符,则做相等性比对。
描述符
描述符都是对象的key,且以$
开头
逻辑符
$and
: 需要满足所有规则,如$and: [4, 5, 6]
表示这个节点的值要同时等于 4/5/6(☠️,显然不可能,仅为了介绍)。
$or
: 需要满足所有规则,如$or: [4, 5, 6]
表示这个节点的值等于4或等于5或等于6。
$if
: 满足条件才做此项校验,如$if: [2, 3]
表示如果这个节点值等于2,则校验它是否等于3(☠️,能感觉到你要崩溃)。
逻辑运算优先级:“向上结合,深度优先”是基本原则。举例说明:
{
$gt: {
$or: [
{
$or: [3, 4]
},
5
]
}
}
如上代码示例:规则表述应是,(大于3或大于4)或大于5
,而非大于((3或4)或5)
。
交换等价性,在“向上结合,深度优先”的基本原则下,以下两个规则rule1
和rule2
描述是等价的,从代码复杂程度上看,rule1
更简单些 。
const rule1 = {
$gt: {
$or: [3, 5]
}
}
const rule2 = {
$or: [
{
$gt: 3,
},
{
$gt: 5
}
]
}
校验符
$type
: 校验数据类型,使用Object.prototype.toString.call(data).slice(8, -1)
得到的类型值。
$value
: 值等于。
$fn
: 自定义函数。
数字类
$gt
: 小于。$gt: 4
,校验值是否小于4。
$gte
: 小于等于。
$lt
: 大于。
$lte
: 大于等于。
$factor
: 因数,可以是任意数字。$factor: 0.1
表示该值是否是0.1的整数倍,可用于校验保留有效位数
。
字符串类
$isEmail
: true/false。
$isTel
: true/false。
$isUrl
: true/false。
$isID
: true/false,身份证号。
$pattern
: 正则校验字符串。
$len
: 字符串/数组的长度。
辅助符
$message
: 用户校验未通过后的提示语。
$unique
: 数组元素唯一校验标志,用户数组的每个元素都是用一样的校验规则。
$proxy
: 代理子节点,用于指定路径校验模式下,由上层节点代理后端节点报错(暂未实现)。
添加自定义描述符
data-police允许用户根据自己的业务特点添加自定义的描述符,Validator
类提供了3个静态方法用来添加以上3种类型的描述符,分别是 loadCheckOperators
、loadLogicOperators
、loadHelpOperators
。使用姿势如下:
import Validator from 'data-police'
Validator.loadCheckOperators({
$isUpper(rule, dataValue) {
return /^\W+$/.test(dataValue)
},
// 与系统描述符重名,将会覆盖系统描述符。同时,自定义描述符也可以覆盖,后来居上。
$gt(rule, dataValue) {
return dataValue < rule
}
})
描述符解析器是一个函数,入参如示例中所示,第一个是规则值,第二个是被校验数据值。
在自定义描述符的命名规则上,建议以$
开头,与系统描述符保持一致,但并不会做强制要求。
调用时机,loadxxxxOperators
方法可以重复多次调用,但针对某一描述符,必须在含有该描述符的规则实例化之前调用,否则会认为这只是一个普通key,而非描述符。
捷径
对于结构层级比较深的数据,提供使用路径的方式直达叶子节点,举例:
// 数据
{
a: {
a: {
a: {
a: 1
}
}
}
}
// 正常规则,为了一个叶子节点,嵌套的令人发指
{
a: {
a: {
a: {
a: 1 // 相等性校验,是否等于1
}
}
}
}
// 规则捷径
{
'.a.a.a.a': 1 // 捷径key以.开头,作为捷径的标志,所以要求被校验的数据中key不可以以.开头
}
指定路径校验
举个表单数据校验的场景,表单一般后多个字段,既要能校验指定字段(填写中),又要能够校验整个表单(提交前),后者无需多言,针对前者提供了指定路径校验的方式。被校验的数据支持两种情况,举例如下:
- 数据与规则是完全对应的
const rule = {
a: 1,
b: 2
}
const data = {
a: 1,
b: 2
}
// 数据与规则是对应的结构,校验过程是,根据路径找到对应的规则和对应的数据值进行比对。
validator.check(data, '.a', 'root')
- 数据是该路径下的值
const rule = {
a: 1,
b: 2
}
const data = 1
// 数据即是在这个路径下的值,校验过程是,根据路径找到规则,与传入的值直接比对。
validator.check(data, '.a', 'branch')
对规则的描述
规则校验符不仅可以用来校验“被校验的数据”,而且可以校验“校验规则”。举例如下:
const rule = {
$len: {
$gt: 4
}
}
含义是:字符串的长度小于4
,$gt
被用来校验$len
了。
并不是所有的校验规则都可以被校验符校验的,但所有可能被校验的规则都可以被校验,这句话有点拗口。举例说明:
// 含义,大于的数据类型是Number,这句话本身就读不通,所以$gt不可以被$type校验
{
$gt: {
$type: 'Number'
}
}
// 含义,数据类型中要包含Num字符串,前文提到过数据类型中新增了一个特殊类型NumStr,那下边这个规则即可表示,数字和数字字符串。当然,也有其他表示方式。
{
$type: {
$pattern: /Num/
}
}
那么,怎么区分哪些校验符可以被校验,哪些不可以呢?除了可能被校验的规则都可以被校验这条感性原则外,还有一条理性原则:可以得出固定值的校验规则都可以被校验,比如5
的数据类型是固定值,那么$type
就可以被校验,而5
大于几就没有固定值,可以大于3可以大于2等等,$gt
不能被校验。在感性原则与理性原则相冲突的情况下,都不会得到期望的校验结果。
约束模式
约束模式指的是,规则与被校验数据之间的字段对应关系,即数据是否可以比规则多或者少字段,举例如下:
const rule = {
a: 1,
b: 1
}
// 数据比规则少字段
const data1 = {
a: 1
}
// 数据比规则多字段
const data2 = {
a: 1,
b: 1,
c: 1
}
默认情况下,多或少字段,都会被忽略,即只校验相互匹配的字段,而通过校验。如果你的业务场景对这方面有特殊要求,可以通过传入check
方法的第二个参数来设置。more: false
会报错非法字段路径
,less: false
会根据规则设置的$message
信息报错。
rule.check(data, {
more: false,
less: false
})
健壮性
单元测试覆盖率90%
左右,基本覆盖了所有情况。
篇后语
我对结构化校验规则的看法:先说优点,结构化更清晰,这表现在编写时的思路和阅读时的理解难度。缺点:受限于结构,有些极端逻辑编写并不方便且代码会累赘。