Golang参数校验:go-playground/validator的缺点及替代品checker
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
主要的缺点是,把校验规则以标签的形式写在结构体字段上,这用很强的侵入性,并且不易于阅读校验逻辑。
更改第三方包结构体的校验规则
假设由一个第三方包的结构体Param
。Param
以及有了校验规则:18<=age<=80
。
package thrid_party
type Param struct{
Age `validate:"min=18,max=80"`
}
如果想在自己的代码包下,将min改为20,这个时候validator
将无法添加校验规则,因为在自己的包下,不能更改third_party
下Param
的校验标签。
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/
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK