分享一个javascript validator,请大家拍砖。
发布于 3 个月前 作者 renjunqing 2003 次浏览 来自 问答

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又有几分神似。

  1. 不是一个完全的JSON语法,因为其中包含了JavaScript函数,所以校验规则是一个Javascript对象。
  2. 规则对象与被校验数据对象结构基本保持一致,不一致的点在规则对象中包含了描述符

校验规则

分两种情况

  1. 描述性校验,即被校验的数据节点是否符合描述符的含义,因描述符都是一个对象的key,所以包含描述符的对象,称为描述对象
  2. 相等性校验,如果规则节点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)

交换等价性,在“向上结合,深度优先”的基本原则下,以下两个规则rule1rule2描述是等价的,从代码复杂程度上看,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种类型的描述符,分别是 loadCheckOperatorsloadLogicOperatorsloadHelpOperators。使用姿势如下:

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%左右,基本覆盖了所有情况。

202004ll01165407.jpg

篇后语

我对结构化校验规则的看法:先说优点,结构化更清晰,这表现在编写时的思路和阅读时的理解难度。缺点:受限于结构,有些极端逻辑编写并不方便且代码会累赘。

回到顶部