Golang參數校驗:go-playground/validator的缺點及替代品checker

Golang的參數校驗,大多數使用的是validator(gin框架使用的是validator v8/v9)。node

可是,validator的缺點是,將校驗的邏輯,以標籤(tag)的方式寫入結構體,這種方法具備很強的侵入性,而且校驗邏輯不容易閱讀。git

爲此,筆者寫了checker,做爲validator的替代品。checker能夠替代validator, 用於結構體或非結構體的參數校驗。github

使用例子: tag 與 Rule的比較

validator使用的tag,與checker的Rule的對應關係能夠參考README文檔markdown

使用checker校驗的例子能夠看這裏,分別有結構體中不一樣字段的大小比較校驗,Slice/Array/Map中元素的校驗等。app

自定義校驗規則

使用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"
}
複製代碼

打印出來的錯誤信息是:ide

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,不須要在結構體上添加校驗標籤,邏輯更加清晰。更多自定義規則的例子在這裏oop

定製錯誤信息

使用validator

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

checker在添加規則的時候,就能夠指定規則錯誤時,返回的提示promptthis

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/

相關文章
相關標籤/搜索