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

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

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分享的地方

我的知乎专栏: zhuanlan.zhihu.com/c_127546654…

我的博客:www.liangyaopei.com

Github Page: liangyaopei.github.io/

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16105.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注