axetroy / blog

:open_book:基于Github API 的动态博客
https://axetroy.xyz
216 stars 36 forks source link

Struct@2, 优雅的,高度自定义的数据校验库 #149

Open axetroy opened 6 years ago

axetroy commented 6 years ago

Why

社区中有很多数据校验库,各功能不一。有些库有你想要的功能,有些库又没有。有写库又喜欢定义自己的模板,然后这些模板改着改着就成了 Magic String(魔符)

例如这样的模板:

validate(data, 'size:4')
validate(data, 'size:2-4')
validate(data, 'lt:20 || gt: 60 && is_prime')
...

现在是大前端时代,讲究的是 All in Javascript。也符合 React 的哲学。另一个套路 Javascript in HTML(Template) 是不友好的(AngularJs 我用了一年多)

目前比较满意的应该是这一款: superstruct

但是对我而言,它还是不顺手,如何校验多个条件? 如何做无限嵌套,包括数组/对象嵌套。

先上一个结构体,你们感受一下

深度嵌套的对象

// 如何检验这个数据, 虽然实际中,谁这么定义数据结构,会被打死
const data1 = {
  name: "axetroy",
  age: 18,
  country: {
    name: "China",
    province: {
      name: "You Guess",
      city: {
        name: "The capital of the province",
        street: {
          name: "suburbs",
          number: {
            code: 100031,
            owner: "Axetroy",
            family: [
              { name: "daddy" },
              { name: "mom" },
              { name: "brother" },
              { name: "sister" }
            ]
          }
        }
      }
    }
  }
};

所以才写一个自己顺手的东西。

结构体

先来一个简单的类型校验,只有名字和年龄

const { Struct, type } = require("@axetroy/struct");

const struct = Struct({
  name: type.string,
  age: type.int
});

它还可以这么写, 自己自定义一个 func 作为校验

const struct = Struct({
  name: type.string,
  age: type.func(function(input) {
    return parseInt(input) === input;
  })
});

甚至几个条件去校验一个字段, 带着参数

const struct = Struct({
  name: type.string,
  age: type.int.gte(18).lte(50) // 需要 >=18 && <=50
});

然后你还能定义某个字段的错误信息

const struct = Struct({
  name: type.string,
  age: type.int.gte(18).lte(50).msg("保险仅限于18-50周岁之间")
});

然后还支持数组

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [type.string]
});

同样支持数组里面嵌套对象

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [
    {
      name: type.string,
      age: type.int
    }
  ]
});

还可以自定义函数

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [
    function(element) {
      if (typeof element.name !== "string") {
        return false;
      } else if (typeof element.age !== "number") {
        return false;
      }
      return true;
    }
  ]
});

如果你还想组合多个校验器,还可以这么用

const struct = Struct({
  name: type.string,
  age: type.int,
  friends: [
    type.func(function(element) {
      if (typeof element.name !== "string") {
        return false;
      } else if (typeof element.age !== "number") {
        return false;
      }
      return true;
    }).isAdult
  ]
});

内置的校验器基本够平时校验使用

如果这些都没有你想要的,那么你还可以自定义

Struct.define("email", function(input) {
  return /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/.test(
    input
  );
});

// 然后这么使用
const struct = Struct({
  name: type.string,
  age: type.int,
  email: type.email
});

如果想定义一个带参数的校验器

// 发现了吗,校验器名字,跟上面的区别就是多了括号()
Struct.define("prefixWith()", function(prefix) {
  return function(input) {
    return input.indexOf(prefix) === 0;
  };
});

const struct = Struct({
  name: type.string.prefixWith("[A]"), // 名字必须是字符串,并且以[A]开头
  age: type.int
});

好了,最后回归到我们开头说的,深度嵌套的对象,怎么去解析

const struct = Struct({
  name: type.string,
  age: type.int,
  country: {
    name: type.string,
    province: {
      name: type.string,
      city: {
        name: type.string,
        street: {
          name: type.string,
          number: {
            code: type.int,
            owner: type.string,
            family: [
              {
                name: type.string
              }
            ]
          }
        }
      }
    }
  }
});

但是实际当中,我们可能不会这么定义这么一个庞大的数据结构。

而是分成细化的结构体。

比如一篇文章:

const User = Struct({
  name: type.string,
  age: type.int
});

const Article = Struct({
  title: type.string,
  content: type.string,
  author: User // 作者嵌套ser
});

在比如一个项目的信息:

const User = Struct({
  name: type.string,
  age: type.int
});

const Project = Struct({
  name: type.string,
  contributors: [User] // 数组的元素为User结构体
});

前面都说了定义结构体,那么输出呢? 依旧从最简单的入手

const struct = Struct({
  name: type.string,
  age: type.int
});

const err = struct.validate(data);

console.log(err);

struct.validate是执行校验工作。

如果校验过程中,有某个字段,不符合校验器的要求。

那么校验终止,并且返回这个错误。

如果校验成功,那么返回undefined

校验失败返回的错误为自定义错误TypeError,继承自Error

TypeError有一下属性:

{ Error
    at Object.<anonymous> (/home/axetroy/gpm/github.com/axetroy/struct/src/error.js:19:23)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Module.require (module.js:579:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/home/axetroy/gpm/github.com/axetroy/struct/src/type.js:2:19)
    at Module._compile (module.js:635:30)
  validator: 'int',
  path: [ 'author', 'age' ],
  value: '18',
  detail: 'Can not pass the validator "int" with value "18" in path "age"',
  message: 'Can not pass the validator "int" with value "18" in path "age"' }

最后

总结一下这个库的特点:

数据校验库层出不穷,关键也不看是不是很强大。

关键是要自己用得顺手。

代码覆盖率 100%,大量测试通过。

目前已运用到实际的生产环境中。

应用在哪里?

库没有使用 es6 语法, 也没有使用 Babel 编译, 没有使用 Webpack 打包.

但是理论上是支持浏览器和 Node 的.

浏览器需要使用打包工具引入.

最后上项目地址: struct

ipengyo commented 6 years ago

点赞支持!!!!