Random walk to my blog

my blog for sharing my knowledge,experience and viewpoint

0%

checker v2 高效灵活的Golang参数校验

前情回顾

checker v1文章中,讲到了:

  • 原生的参数校验方法冗长
  • 使用tag的校验方法可读性差,容易出错,性能不高(需要使用反射读取每一个字段的标签,解析标签内容,生成校验逻辑),与结构体强耦合
  • checker的校验方法清晰简洁,与结构体解耦。

回顾之前的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Req.Email需要符合电子邮箱的格式
type Req struct {
Info typeInfo
Email string
}

type typeStr string
// Req.Info.Type = "range",typeInfo.Type的长度为2,元素都是格式符合"2006-01-02"
// Req.Info.Type = "last",typeInfo.Type的长度为1,元素是正整数,Granularity只能是day/week/month之一
type typeInfo struct {
Type typeStr
Range []string
Unit string
Granularity string
}

checker的校验可以写成下面的代码,只需15行代码就可以把复杂的校验逻辑清楚表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rule := And(
Email("Email"),
Field("Info",
Or(
And(
EqStr("Type", "range"),
Length("Range", 2, 2),
Array("Range", Time("", "2006-01-02")),
),
And(
EqStr("Type", "last"),
InStr("Granularity", "day", "week", "month"),
Number("Unit"),
),
),
),
)

这里的rule变量,构成了一个规则树。
rule_tree

规则树的构成灵活,上面的rule变量可以改写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rule := And(
Email("Email"),
Or(
And(
EqStr("Info.Type", "range"),
Length("Info.Range", 2, 2),
Array("Info.Range", Time("", "2006-01-02")),
),
And(
EqStr("Info.Type", "last"),
InStr("Info.Granularity", "day", "week", "month"),
Number("Info.Unit"),
),
),
)

构成了另一个规则树:
rule_tree2

尽管规则树不一样,但是树的叶子节点的fieldExpr是一样的(这可以缓存字段),校验逻辑也是一样的。

v2的更新

更新分为三部分:

  • 详细的错误日志,定位出错字段
  • 自定义的错误提示
  • 字段缓存

错误日志

下面的列子,要校验结构体的二维数组的值,范围在[1,5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"

"github.com/liangyaopei/checker"
)

func main() {
type ArrayStruct struct {
Inner struct {
Array [][]int
}
}

arr := [][]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}

st := ArrayStruct{}
st.Inner.Array = arr

rule := checker.Array("Inner.Array", checker.Array(
"", checker.RangeInt("", 1, 5)))
validator := checker.NewChecker()
validator.Add(rule, "worng ArrayStruct")

_, _, errMsg := validator.Check(st)
fmt.Printf("errMsg:%v", errMsg)
}

程序输出的错误日志详细,用于定位出错的字段。

1
errMsg:[RangeInt]:Inner.Array[1][2] should be between 1 and 5,actual is 6

自定义的错误提示

将前情回顾中例子的rule改成

1
2
3
4
5
6
7
8
9
10
11
12
13
rule := checker.And(
checker.Email("Email").Prompt("Wrong email format") // [1],
checker.And(
checker.EqStr("Info.Type", "range"),
checker.Length("Info.Range", 2, 2).Prompt("Range's length should be 2") // [2],
checker.Array("Info.Range", checker.Time("", "2006-01-02")).
Prompt("Range's element should be time format") // [3],
),
)

validator := checker.NewChecker()
validator.Add(rule, "wrong parameter") // [4]
isValid, prompt, _ := validator.Check(item)

RulePrompt方法用来自定义错误提示。当规则没有通过时,会优先返回规则自己的prompt(代码的[1]/[2]/[3]),如果规则没有自己的prompt, 就会返回添加规则时的prompt(代码中的[4])。

字段缓存

从上面的规则树图示,可以看到,如果具有相同的字段表达式的叶子节点需要被多次校验, 可以将这个叶子节点的表达式的值缓存下来,减少反射调用的开销。

我的公众号:lyp分享的地方

我的知乎专栏: https://zhuanlan.zhihu.com/c_1275466546035740672

我的博客:www.liangyaopei.com

Github Page: https://liangyaopei.github.io/