5

Golang参数校验:go-playground/validator的缺点及替代品checker

 2 years ago
source link: https://liangyaopei.github.io/2020/12/13/golang-validator-checker/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Golang的参数校验,大多数使用的是validator(gin框架使用的是validator v8/v9)。但是,validator的缺点是,将校验的逻辑,以标签(tag)的方式写入结构体,这种方法具有很强的侵入性,并且校验逻辑不容易阅读。
为此,笔者写了checker,作为validator的替代品。checker可以替代validator, 用于结构体或非结构体的参数校验。

使用例子: tag 与 Rule的比较

validator使用的tag,与checker的Rule的对应关系可以参考README文档

使用checker校验的例子可以看这里,分别有结构体中不同字段的大小比较校验,Slice/Array/Map中元素的校验等。

自定义校验规则

使用validator

validator的自定义校验规则用起来麻烦,看下面的官方例子,自定义了一个is-awesome的校验标签。

package main

import (
"fmt"

"github.com/go-playground/validator/v10"
)

// MyStruct ..
type MyStruct struct {
String string `validate:"is-awesome"`
}

// use a single instance of Validate, it caches struct info
var validate *validator.Validate

func main() {
validate = validator.New()
validate.RegisterValidation("is-awesome", ValidateMyVal)

s := MyStruct{String: "not awesome"}
err := validate.Struct(s)
if err != nil {
fmt.Printf("%v", err)
}
}

// ValidateMyVal implements validator.Func
func ValidateMyVal(fl validator.FieldLevel) bool {
return fl.Field().String() == "awesome"
}

打印出来的错误信息是:

Key: 'MyStruct.String' Error:Field validation for 'String' failed on the 'is-awesome' tag

使用checker

package main

import (
"fmt"

"github.com/liangyaopei/checker"
)

type MyStruct struct {
String string
}

type isAwesomeRule struct {
FieldExpr string
}

func (r isAwesomeRule) Check(param interface{}) (bool, string) {
exprValue, _ := checker.FetchFieldInStruct(param, r.FieldExpr)
exprStr := exprValue.(string)
if exprStr != "awesome" {
return false, fmt.Sprintf("'%s' has worng value", r.FieldExpr)
}
return true, ""
}

func main() {
s := MyStruct{String: "not awesome"}
ch := checker.NewChecker()
rule := isAwesomeRule{FieldExpr: "String"}
ch.Add(rule, "value is not awesome")

isValid, prompt, errMsg := ch.Check(s)
if !isValid {
fmt.Printf("prompt:%s,errMsg:%s", prompt, errMsg)
}
}

使用checker,不需要在结构体上添加校验标签,逻辑更加清晰。更多自定义规则的例子在这里

定制错误信息

使用validator

validator的定制错误信息比较复杂麻烦,不好理解,涉及到translator的使用。看下面的官方例子

import (
"fmt"

"github.com/go-playground/locales/en"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
en_translations "github.com/go-playground/validator/v10/translations/en"
)

var (
uni *ut.UniversalTranslator
validate *validator.Validate
)

func main() {

// NOTE: ommitting allot of error checking for brevity

en := en.New()
uni = ut.New(en, en)

// this is usually know or extracted from http 'Accept-Language' header
// also see uni.FindTranslator(...)
trans, _ := uni.GetTranslator("en")

validate = validator.New()
en_translations.RegisterDefaultTranslations(validate, trans)

translateOverride(trans)
}

func translateOverride(trans ut.Translator) {

validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("required", fe.Field())

return t
})
....
}

使用checker

checker在添加规则的时候,就可以指定规则错误时,返回的提示prompt

func (c *ruleChecker) Add(rule Rule, prompt string) {
c.rules = append(c.rules, rule)
c.prompts = append(c.prompts, prompt)
}

上文的例子中,value is not awesome就是返回的错误提示,用起来非常简单,易于理解。
errMsg就是规则校验的日志,指出某个字段不符合某条规则。

ch.Add(rule, "value is not awesome")
isValid, prompt, errMsg := ch.Check(s)

checker易做,validator难做

validator主要的缺点是,把校验规则以标签的形式写在结构体字段上,这用很强的侵入性,并且不易于阅读校验逻辑。

更改第三方包结构体的校验规则

假设由一个第三方包的结构体ParamParam以及有了校验规则:18<=age<=80

package thrid_party

type Param struct{
Age `validate:"min=18,max=80"`
}

如果想在自己的代码包下,将min改为20,这个时候validator将无法添加校验规则,因为在自己的包下,不能更改third_partyParam的校验标签。

package main

func validate(p thrid_party.Param)(isValid bool){
....
}

而使用checker,只需要改为:

rule := checker.NewRangeRuleInt("Age", 20, 80)
checker.Add(rule, "invlaid age")

因为checker的校验规则与结构体解耦,因此,修改校验规则非常简单。

自引用的结构体校验

假设需要校验链表的长度,完整的例子在这里

type list struct {
Name *string
Next *list `validate:"nonzero"`
}

要校验链表的长度,要求前几个节点的Next不为空,validator不能做到,因为自引用的结构体,同样的标签适用于相同的字段。

如果使用checker

name := "list"
node1 := list{Name: &name, Next: nil}
lists := list{Name: &name, Next: &node1}

listChecker := checker.NewChecker()
nameRule := checker.NewLengthRule("Next.Name", 1, 20)
listChecker.Add(nameRule, "invalid info name")

通过Next.Name可以指定链表的长度。

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

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

我的博客:www.liangyaopei.com

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK