Golang的参数校验,大多数使用的是validator(gin框架使用的是validator v8/v9)。node
可是,validator
的缺点是,将校验的逻辑,以标签(tag)的方式写入结构体,这种方法具备很强的侵入性,而且校验逻辑不容易阅读。git
为此,笔者写了checker,做为validator的替代品。checker
能够替代validator
, 用于结构体或非结构体的参数校验。github
validator使用的tag,与checker的Rule的对应关系能够参考README文档。markdown
使用checker
校验的例子能够看这里,分别有结构体中不一样字段的大小比较校验,Slice
/Array
/Map
中元素的校验等。app
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"
}
复制代码
打印出来的错误信息是:ide
Key: 'MyStruct.String' Error:Field validation for 'String' failed on the 'is-awesome' tag
复制代码
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,不须要在结构体上添加校验标签,逻辑更加清晰。更多自定义规则的例子在这里。oop
validator
的定制错误信息比较复杂麻烦,很差理解,涉及到translator
的使用。看下面的官方例子ui
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
在添加规则的时候,就能够指定规则错误时,返回的提示prompt
。this
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)
复制代码
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分享的地方
个人知乎专栏: zhuanlan.zhihu.com/c_127546654…
个人博客:www.liangyaopei.com
Github Page: liangyaopei.github.io/