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
的校验标签。
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 package mainimport ( "fmt" "github.com/go-playground/validator/v10" ) type MyStruct struct { String string `validate:"is-awesome"` } var validate *validator.Validatefunc 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) } } func ValidateMyVal (fl validator.FieldLevel) bool { return fl.Field().String() == "awesome" }
打印出来的错误信息是:
1 Key : 'MyStruct.String' Error :Field validation for 'String' failed on the 'is-awesome' tag
使用checker 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 33 34 35 36 package mainimport ( "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
的使用。看下面的官方例子
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 33 34 35 36 37 38 39 40 41 42 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 () { en := en.New() uni = ut.New(en, en) 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 ) }, func (ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("required" , fe.Field()) return t }) .... }
使用checker checker
在添加规则的时候,就可以指定规则错误时,返回的提示prompt
。
1 2 3 4 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就是规则校验的日志,指出某个字段不符合某条规则。
1 2 ch.Add(rule, "value is not awesome" ) isValid, prompt, errMsg := ch.Check(s)
checker易做,validator难做 validator
主要的缺点是,把校验规则以标签的形式写在结构体字段上,这用很强的侵入性,并且不易于阅读校验逻辑。
更改第三方包结构体的校验规则 假设由一个第三方包的结构体Param
。Param
以及有了校验规则:18<=age<=80
。
1 2 3 4 5 package thrid_partytype Param struct { Age `validate:"min=18,max=80"` }
如果想在自己的代码包下,将min改为20,这个时候validator
将无法添加校验规则,因为在自己的包下,不能更改third_party
下Param
的校验标签。
1 2 3 4 5 package mainfunc validate (p thrid_party.Param) (isValid bool ) { .... }
而使用checker
,只需要改为:
1 2 rule := checker.NewRangeRuleInt("Age" , 20 , 80 ) checker.Add(rule, "invlaid age" )
因为checker
的校验规则与结构体解耦,因此,修改校验规则非常简单。
自引用的结构体校验 假设需要校验链表的长度,完整的例子在这里
1 2 3 4 type list struct { Name *string Next *list `validate:"nonzero"` }
要校验链表的长度,要求前几个节点的Next
不为空,validator
不能做到,因为自引用的结构体,同样的标签适用于相同的字段。
如果使用checker
,
1 2 3 4 5 6 7 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/