官方地址https://github.com/go-playground/validator
开发文档:https://godoc.org/github.com/go-playground/validator
中文开发参考文档:https://s0godoc0org.icopy.site/gopkg.in/go-playground/validator.v9
开发参考用例:https://github.com/go-playground/validator/tree/master/_examples
HelloWorld
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
)
// User contains user information
type User struct {
FirstName string `validate:"required"`
LastName string `validate:"required"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
}
// Address houses a users address information
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
}
// use a single instance of Validate, it caches struct info
var validate *validator.Validate
func main() {
validate = validator.New()
validateStruct()
validateVariable()
}
func validateStruct() {
address := &Address{
Street: "Eavesdown Docks",
Planet: "Persphone",
Phone: "none",
}
user := &User{
FirstName: "Badger",
LastName: "Smith",
Age: 135,
Email: "Badger.Smith@gmail.com",
FavouriteColor: "#000-",
Addresses: []*Address{address},
}
// returns nil or ValidationErrors ( []FieldError )
err := validate.Struct(user)
if err != nil {
// this check is only needed when your code could produce
// an invalid value for validation such as interface with nil
// value most including myself do not usually have code like this.
if _, ok := err.(*validator.InvalidValidationError); ok {
fmt.Println(err)
return
}
for _, err := range err.(validator.ValidationErrors) {
fmt.Println(err.Namespace())
fmt.Println(err.Field())
fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
fmt.Println(err.StructField()) // by passing alt name to ReportError like below
fmt.Println(err.Tag())
fmt.Println(err.ActualTag())
fmt.Println(err.Kind())
fmt.Println(err.Type())
fmt.Println(err.Value())
fmt.Println(err.Param())
}
return
}
// save user to database
}
func validateVariable() {
myEmail := "joeybloggs.gmail.com"
errs := validate.Var(myEmail, "required,email")
if errs != nil {
fmt.Println(errs.Error()) // output: Key: "" Error:Field validation for "" failed on the "email" tag
return
}
}
打印结果
GOROOT=/usr/local/Cellar/go/1.13.4/libexec #gosetup
GOPATH=/Users/baxiang/go #gosetup
/usr/local/Cellar/go/1.13.4/libexec/bin/go build -o /private/var/folders/61/2qtr44g130x_pp0xn3ym8qv80000gn/T/___go_build_github_com_baxiang_go_note_validator github.com/baxiang/go-note/validator #gosetup
/private/var/folders/61/2qtr44g130x_pp0xn3ym8qv80000gn/T/___go_build_github_com_baxiang_go_note_validator #gosetup
User.Age
Age
User.Age
Age
lte
lte
uint8
uint8
135
130
User.FavouriteColor
FavouriteColor
User.FavouriteColor
FavouriteColor
iscolor
hexcolor|rgb|rgba|hsl|hsla
string
string
#000-
User.Addresses[0].City
City
User.Addresses[0].City
City
required
required
string
string
Key: '' Error:Field validation for '' failed on the 'email' tag
Process finished with exit code 0
其中一个区别是v8版本没有指定默认的TagName,需要在每次声明validator实例的时候配置;在v9中设置了默认的TagName就是validate。我们的gin框架在使用的时候设置的TagName为”binding”。
validator中给的例子代码
var validate *validator.Validate
config := &validator.Config{TagName: "validate"}
validate = validator.New(config)
gin中设置的TagName,文件gin/binding/default_validator.go
func (v *defaultValidator) lazyinit() {
v.once.Do(func() {
config := &validator.Config{TagName: "binding"}
v.validate = validator.New(config)
})
}
这也就是为啥gin框架里面example中的定义的struct都是binding开头
其他区别例如实例化不同等
type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
常用标签字段
validator使用的struct的tag有以下这么多,其中有一些是通过正则表达式实现类型判断的,例如email、rgb、uuid等;有一些是通过第三方库实现的,例如ipv4、cidr等。
// validator/baked_in.go
bakedInValidators = map[string]Func{
"required": hasValue,
"isdefault": isDefault,
"len": hasLengthOf,
"min": hasMinOf,
"max": hasMaxOf,
"eq": isEq,
"ne": isNe,
"lt": isLt,
"lte": isLte,
"gt": isGt,
"gte": isGte,
"eqfield": isEqField,
"eqcsfield": isEqCrossStructField,
"necsfield": isNeCrossStructField,
"gtcsfield": isGtCrossStructField,
"gtecsfield": isGteCrossStructField,
"ltcsfield": isLtCrossStructField,
"ltecsfield": isLteCrossStructField,
"nefield": isNeField,
"gtefield": isGteField,
"gtfield": isGtField,
"ltefield": isLteField,
"ltfield": isLtField,
"alpha": isAlpha,
"alphanum": isAlphanum,
"alphaunicode": isAlphaUnicode,
"alphanumunicode": isAlphanumUnicode,
"numeric": isNumeric,
"number": isNumber,
"hexadecimal": isHexadecimal,
"hexcolor": isHEXColor,
"rgb": isRGB,
"rgba": isRGBA,
"hsl": isHSL,
"hsla": isHSLA,
"email": isEmail,
"url": isURL,
"uri": isURI,
"file": isFile,
"base64": isBase64,
"base64url": isBase64URL,
"contains": contains,
"containsany": containsAny,
"containsrune": containsRune,
"excludes": excludes,
"excludesall": excludesAll,
"excludesrune": excludesRune,
"isbn": isISBN,
"isbn10": isISBN10,
"isbn13": isISBN13,
"eth_addr": isEthereumAddress,
"btc_addr": isBitcoinAddress,
"btc_addr_bech32": isBitcoinBech32Address,
"uuid": isUUID,
"uuid3": isUUID3,
"uuid4": isUUID4,
"uuid5": isUUID5,
"ascii": isASCII,
"printascii": isPrintableASCII,
"multibyte": hasMultiByteCharacter,
"datauri": isDataURI,
"latitude": isLatitude,
"longitude": isLongitude,
"ssn": isSSN,
"ipv4": isIPv4,
"ipv6": isIPv6,
"ip": isIP,
"cidrv4": isCIDRv4,
"cidrv6": isCIDRv6,
"cidr": isCIDR,
"tcp4_addr": isTCP4AddrResolvable,
"tcp6_addr": isTCP6AddrResolvable,
"tcp_addr": isTCPAddrResolvable,
"udp4_addr": isUDP4AddrResolvable,
"udp6_addr": isUDP6AddrResolvable,
"udp_addr": isUDPAddrResolvable,
"ip4_addr": isIP4AddrResolvable,
"ip6_addr": isIP6AddrResolvable,
"ip_addr": isIPAddrResolvable,
"unix_addr": isUnixAddrResolvable,
"mac": isMAC,
"hostname": isHostnameRFC952, // RFC 952
"hostname_rfc1123": isHostnameRFC1123, // RFC 1123
"fqdn": isFQDN,
"unique": isUnique,
"oneof": isOneOf,
"html": isHTML,
"html_encoded": isHTMLEncoded,
"url_encoded": isURLEncoded,
}
required表示字段必须有值,并且不为默认值,例如bool默认值为false、string默认值为””、int默认值为0。如果有些字段是可填的,并且需要满足某些规则的,那么需要使用omitempty
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
)
func main() {
validate := validator.New()
var a =0
err := validate.Var(a,"required")
if err != nil {
fmt.Println(err)
}
var b bool
err = validate.Var(b,"required")
if err != nil {
fmt.Println(err)
}
var s string =""
err = validate.Var(s,"required")
if err != nil {
fmt.Println(err)
}
}
执行结果
Key: '' Error:Field validation for '' failed on the 'required' tag
Key: '' Error:Field validation for '' failed on the 'required' tag
Key: '' Error:Field validation for '' failed on the 'required' tag
dive
tag中出现的dive的使用,dive一般用在slice、array、map、嵌套的struct验证中,作为分隔符表示进入里面一层的验证规则
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
)
type Container struct {
Array []string `validate:"required,gt=0,dive,required"`
Map map[string]string `validate:"required,gt=0,dive,keys,max=10,endkeys,required,max=100"`
}
func main() {
validate := validator.New()
var empty Container
if err := validate.Struct(empty);err!=nil{
fmt.Println(err.Error())
fmt.Println()
}
vContainer := Container{Array:[]string{""},Map:map[string]string{"test > than 10": ""}}
if err := validate.Struct(vContainer);err!=nil{
fmt.Println(err.Error())
}
}
打印结果如下
Key: 'Container.Array' Error:Field validation for 'Array' failed on the 'required' tag
Key: 'Container.Map' Error:Field validation for 'Map' failed on the 'required' tag
Key: 'Container.Array[0]' Error:Field validation for 'Array[0]' failed on the 'required' tag
Key: 'Container.Map[test > than 10]' Error:Field validation for 'Map[test > than 10]' failed on the 'max' tag
Key: 'Container.Map[test > than 10]' Error:Field validation for 'Map[test > than 10]' failed on the 'required' tag
slice使用gt=0代表field.len()>0,所以[]string{}符合required规则,但是不符合gt=0规则
dive代表进入里面一层的验证 例如 a={“b”:”c”}中 dive之前的required代表a是必填,大于0,
dive之后出现keys与endkeys之间代表验证map的keys的tag值:max=10,也就是string长度不大于10
后面的自然而然的是验证value了,required 必填并且stirng最大长度是100
关系比较
"eqfield": isEqField,
"eqcsfield": isEqCrossStructField,
"necsfield": isNeCrossStructField,
"gtcsfield": isGtCrossStructField,
"gtecsfield": isGteCrossStructField,
"ltcsfield": isLtCrossStructField,
"ltecsfield": isLteCrossStructField,
"nefield": isNeField,
"gtefield": isGteField,
"gtfield": isGtField,
"ltefield": isLteField,
"ltfield": isLtField
自定义tag
package main
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
"strings"
)
type MyStruct struct {
String string `validate:"pre-we"`
}
func main() {
validate := validator.New()
validate.RegisterValidation("pre-we", ValidateMyVal)
s := MyStruct{String: "wechat"}
if err := validate.Struct(s);err != nil {
fmt.Printf("Err(s):\n%+v\n", err)
}
s.String = "not wechat"
if err := validate.Struct(s);err != nil {
fmt.Println(err)
}
}
func ValidateMyVal(fl validator.FieldLevel) bool {
return strings.HasPrefix(fl.Field().String(),"we")
}
打印结果
Key: 'MyStruct.String' Error:Field validation for 'String' failed on the 'pre-we' tag
自定义结构体
以下例子在验证FirstName与LastName时候不能为空
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// User contains user information
type User struct {
FirstName string `json:"fname"`
LastName string `json:"lname"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
FavouriteColor string `validate:"hexcolor|rgb|rgba"`
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
}
// Address houses a users address information
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
}
// use a single instance of Validate, it caches struct info
var validate *validator.Validate
func main() {
validate = validator.New()
// register validation for 'User'
// NOTE: only have to register a non-pointer type for 'User', validator
// interanlly dereferences during it's type checks.
validate.RegisterStructValidation(UserStructLevelValidation, User{})
// build 'User' info, normally posted data etc...
address := &Address{
Street: "Eavesdown Docks",
Planet: "Persphone",
Phone: "none",
City: "Unknown",
}
user := &User{
FirstName: "",
LastName: "",
Age: 45,
Email: "Badger.Smith@gmail.com",
FavouriteColor: "#000",
Addresses: []*Address{address},
}
// returns InvalidValidationError for bad validation input, nil or ValidationErrors ( []FieldError )
err := validate.Struct(user)
if err != nil {
// this check is only needed when your code could produce
// an invalid value for validation such as interface with nil
// value most including myself do not usually have code like this.
if _, ok := err.(*validator.InvalidValidationError); ok {
fmt.Println(err)
return
}
for _, err := range err.(validator.ValidationErrors) {
fmt.Println(err.Namespace())
fmt.Println(err.Field())
fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
fmt.Println(err.StructField()) // by passing alt name to ReportError like below
fmt.Println(err.Tag())
fmt.Println(err.ActualTag())
fmt.Println(err.Kind())
fmt.Println(err.Type())
fmt.Println(err.Value())
fmt.Println(err.Param())
fmt.Println()
}
// from here you can create your own error messages in whatever language you wish
return
}
// save user to database
}
// UserStructLevelValidation contains custom struct level validations that don't always
// make sense at the field validation level. For Example this function validates that either
// FirstName or LastName exist; could have done that with a custom field validation but then
// would have had to add it to both fields duplicating the logic + overhead, this way it's
// only validated once.
//
// NOTE: you may ask why wouldn't I just do this outside of validator, because doing this way
// hooks right into validator and you can combine with validation tags and still have a
// common error output format.
func UserStructLevelValidation(sl validator.StructLevel) {
user := sl.Current().Interface().(User)
if len(user.FirstName) == 0 && len(user.LastName) == 0 {
sl.ReportError(user.FirstName, "FirstName", "fname", "fnameorlname", "")
sl.ReportError(user.LastName, "LastName", "lname", "fnameorlname", "")
}
// plus can do more, even with different tag than "fnameorlname"
}
国际化处理
package main
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"
)
// User contains user information
type User struct {
FirstName string `validate:"required"`
LastName string `validate:"required"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
FavouriteColor string `validate:"iscolor"` // alias for 'hexcolor|rgb|rgba|hsl|hsla'
Addresses []*Address `validate:"required,dive,required"` // a person can have a home and cottage...
}
// Address houses a users address information
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required"`
}
// use a single instance , it caches struct info
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)
translateAll(trans)
translateIndividual(trans)
translateOverride(trans) // yep you can specify your own in whatever locale you want!
}
func translateAll(trans ut.Translator) {
type User struct {
Username string `validate:"required"`
Tagline string `validate:"required,lt=10"`
Tagline2 string `validate:"required,gt=1"`
}
user := User{
Username: "Joeybloggs",
Tagline: "This tagline is way too long.",
Tagline2: "1",
}
err := validate.Struct(user)
if err != nil {
// translate all error at once
errs := err.(validator.ValidationErrors)
// returns a map with key = namespace & value = translated error
// NOTICE: 2 errors are returned and you'll see something surprising
// translations are i18n aware!!!!
// eg. '10 characters' vs '1 character'
fmt.Println(errs.Translate(trans))
}
}
func translateIndividual(trans ut.Translator) {
type User struct {
Username string `validate:"required"`
}
var user User
err := validate.Struct(user)
if err != nil {
errs := err.(validator.ValidationErrors)
for _, e := range errs {
// can translate each error one at a time.
fmt.Println(e.Translate(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
})
type User struct {
Username string `validate:"required"`
}
var user User
err := validate.Struct(user)
if err != nil {
errs := err.(validator.ValidationErrors)
for _, e := range errs {
// can translate each error one at a time.
fmt.Println(e.Translate(trans))
}
}
}
Gin 中v8升级
package main
import (
"reflect"
"sync"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
type defaultValidator struct {
once sync.Once
validate *validator.Validate
}
var _ binding.StructValidator = &defaultValidator{}
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
if kindOfData(obj) == reflect.Struct {
v.lazyinit()
if err := v.validate.Struct(obj); err != nil {
return error(err)
}
}
return nil
}
func (v *defaultValidator) Engine() interface{} {
v.lazyinit()
return v.validate
}
func (v *defaultValidator) lazyinit() {
v.once.Do(func() {
v.validate = validator.New()
v.validate.SetTagName("binding")
// add any custom validations etc. here
})
}
func kindOfData(data interface{}) reflect.Kind {
value := reflect.ValueOf(data)
valueType := value.Kind()
if valueType == reflect.Ptr {
valueType = value.Elem().Kind()
}
return valueType
}
在main 函数中调用
package main
import "github.com/gin-gonic/gin/binding"
func main() {
binding.Validator = new(defaultValidator)
// regular gin logic
}
参考
https://www.cnblogs.com/zhzhlong/p/10033234.html
https://frankhitman.github.io/zh-CN/gin-validator/