Golang的参数校验,大多数使用的是validator(gin框架使用的是validator v8/v9)。但是,validator
的缺点是,将校验的逻辑,以标签(tag)的方式写入结构体,这种方法具有很强的侵入性,并且校验逻辑不容易阅读。
为此,笔者写了checker,作为validator的替代品。checker
可以替代validator
, 用于结构体或非结构体的参数校验。
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 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" } |
打印出来的错误信息是:
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 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
的使用。看下面的官方例子
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() { // 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
。
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) |
validator
主要的缺点是,把校验规则以标签的形式写在结构体字段上,这用很强的侵入性,并且不易于阅读校验逻辑。
假设由一个第三方包的结构体Param
。Param
以及有了校验规则:18<=age<=80
。
1 2 3 4 5 | package thrid_party type Param struct{ Age `validate:"min=18,max=80"` } |
如果想在自己的代码包下,将min改为20,这个时候validator
将无法添加校验规则,因为在自己的包下,不能更改third_party
下Param
的校验标签。
1 2 3 4 5 | package main func 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分享的地方
我的知乎专栏: lyp's blog - 知乎
我的博客:www.liangyaopei.com
Github Page: Random walk to my blog