The golang data verifies the implementation of validator

  • 2020-11-20 06:08:18
  • OfStack

preface

In THE application of web, we often encounter data validation problems, and the common validation methods are quite tedious. Here is a package validator that is used more.

The principle of

Write the validation rule in struct pair field tag, and then get tag of struct through reflection (reflect) to realize data validation.

The installation


go get github.com/go-playground/validator/v10

The sample


package main

import (
 "fmt"
 "github.com/go-playground/validator/v10"
)

type Users struct {
 Phone string `form:"phone" json:"phone" validate:"required"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {

 users := &Users{
 Phone:  "1326654487",
 Passwd:  "123",
 Code:   "123456",
 }
 validate := validator.New()
 err := validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err)//Key: 'Users.Passwd' Error:Field validation for 'Passwd' failed on the 'min' tag
 return
 }
 }
 return
}

The validation rules

required: required email: Verify that the string is email format; Example: "email" url: This will verify that the string value contains a valid URL; Example: "url" max: Maximum length of string; Example: "max = 20" min: Minimum length of string; Example: "min = 6" excludesall: Cannot contain special characters; Example: "excludesall=0x2C" // Note that this is expressed in base 106. len: The character length must be equal to n, or the len value of array, slice, map is n, that is, the number of items contained; Example: "len = 6" eq: The number is equal to n, or the len value of array, slice, map is n, that is, the number of items contained; Example: "eq = 6" ne: the number is not equal to n, or the len value of array, slice, map is not equal to n, that is, the number of items is not n, which is opposite to eq; Example: "ne = 6" gt: the number is greater than n, or the len value of array, slice, map is greater than n, that is, the number of items contained is greater than n; Example: "gt = 6" gte: the number is greater than or equal to n, or the len value of array, slice, map is greater than or equal to n, i.e. the number of items contained is greater than or equal to n; Example: "gte = 6" lt: the number is less than n, or the len value of array, slice and map is less than n, that is, the number of items contained is less than n; Example: "lt = 6" lte: the number is less than or equal to n, or the len value of array, slice, map is less than or equal to n, i.e. the number of items contained is less than or equal to n; Example: "lte = 6"

Cross-field validation

If you want to implement a similar scenario, such as comparing the input password and confirm whether the password is 1

eqfield=Field: must equal the value of Field; nefield=Field: must not be equal to the value of Field; gtfield=Field: must be greater than Field; gtefield=Field: must be greater than or equal to Field; ltfield=Field: Must be less than the value of Field; ltefield=Field: must be less than or equal to Field; eqcsfield= ES110en.ES111en: must be equal to the value of Field in struct Other; necsfield=Other.Field: must not equal the value of Field in struct Other; gtcsfield= ES122en. Field: Must be greater than the value of struct Other; gtecsfield=Other.Field: must be greater than or equal to the value of Field in struct Other; ltcsfield= ES134en. Field: Must be less than the value of Field in struct Other; ltecsfield=Other.Field: must be less than or equal to the value of Field in struct Other;

The sample


type UserReg struct {
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Repasswd string `form:"repasswd" json:"repasswd" validate:"required,max=20,min=6,eqfield=Passwd"`
}

The example verifies that the Passwd, Repasswd values are equal. If you would like to understand more types, please reference documentation https: / / godoc org/gopkg in/go - playground/validator. v10

Custom validation types

Example:


package main

import (
 "fmt"
 "github.com/go-playground/validator/v10"
)

type Users struct {
 Name string `form:"name" json:"name" validate:"required,CustomValidationErrors"`// Contains custom functions 
 Age uint8 `form:"age" json:"age" validate:"required,gt=18"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {

 users := &Users{
 Name:  "admin",
 Age:  12,
 Passwd:  "123",
 Code:   "123456",
 }
 validate := validator.New()
 // Register custom functions 
 _=validate.RegisterValidation("CustomValidationErrors", CustomValidationErrors)
 err := validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err)//Key: 'Users.Name' Error:Field validation for 'Name' failed on the 'CustomValidationErrors' tag
 return
 }
 }
 return
}

func CustomValidationErrors(fl validator.FieldLevel) bool {
return fl.Field().String() != "admin"
}

Translation error message in Chinese

As you can see from the above example, the default error message for validator is similar to the following

[

Key: 'Users.Name' Error:Field validation for 'Name' failed on the 'CustomValidationErrors' tag

]

Obviously that's not what we want to do if we want to translate it into Chinese or any other language? go-playground provides a good solution.

First install the two packages you need

https://github.com/go-playground/locales
https://github.com/go-playground/universal-translator

Perform:


go get github.com/go-playground/universal-translator
go get github.com/go-playground/locales

Example:


package main

import (
 "fmt"
 "github.com/go-playground/locales/zh"
 ut "github.com/go-playground/universal-translator"
 "github.com/go-playground/validator/v10"
 zh_translations "github.com/go-playground/validator/v10/translations/zh"
)

type Users struct {
 Name string `form:"name" json:"name" validate:"required"`
 Age uint8 `form:"age" json:"age" validate:"required,gt=18"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {
 users := &Users{
 Name:  "admin",
 Age:  12,
 Passwd:  "123",
 Code:   "123456",
 }
 uni := ut.New(zh.New())
 trans, _ := uni.GetTranslator("zh")
 validate := validator.New()
 // Validators register translators 
 err := zh_translations.RegisterDefaultTranslations(validate, trans)
 if err!=nil {
 fmt.Println(err)
 }
 err = validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err.Translate(trans))//Age Must be greater than 18
 return
 }
 }

 return
}

Output:

[

Age must be greater than 18

]

So far we found that most of the error message has been translated into Chinese, but the field name (Age) or without translation, in order to bring the field name translated into Chinese, and checked some information, https: / / www ofstack. com article / 197866. htm > .

Do not succeed (there may be omission), or finally looked at the 1 under the source code, in < https: / / github com/go - playground/validator/blob/master/validator_instance go, line 137


// RegisterTagNameFunc registers a function to get alternate names for StructFields.
//
// eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names:
//
// validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
//  name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
//  if name == "-" {
//   return ""
//  }
//  return name
// })

The idea is to register a function that takes the Chinese name added to struct tag as an alternate.


package main

import (
 "fmt"
 "github.com/go-playground/locales/zh"
 ut "github.com/go-playground/universal-translator"
 "github.com/go-playground/validator/v10"
 zh_translations "github.com/go-playground/validator/v10/translations/zh"
 "reflect"
)

type Users struct {
 Name string `form:"name" json:"name" validate:"required" label:" The user name "`
 Age uint8 `form:"age" json:"age" validate:"required,gt=18" label:" age "`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {
 users := &Users{
 Name:  "admin",
 Age:  12,
 Passwd:  "123",
 Code:   "123456",
 }
 uni := ut.New(zh.New())
 trans, _ := uni.GetTranslator("zh")
 validate := validator.New()
 // registered 1 A function, get struct tag It's custom label As the field name 
 validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
 name:=fld.Tag.Get("label")
 return name
 })
 // Register the translator 
 err := zh_translations.RegisterDefaultTranslations(validate, trans)
 if err!=nil {
 fmt.Println(err)
 }
 err = validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err.Translate(trans))// Must be older than 18
 return
 }
 }

 return
}

Output results:

[

Must be older than 18

]

The validator is built into the gin

gin already supports go-playground/validator/v10 for verification. See the full documentation on label usage here.

Only 1 example of binding ShouldBindWith is provided below; for more information, go here.

The sample


package main

import (
 "fmt"
 "github.com/go-playground/locales/zh"
 ut "github.com/go-playground/universal-translator"
 "github.com/go-playground/validator/v10"
 "net/http"
 "reflect"
 "strings"
 "time"

 "github.com/gin-gonic/gin"
 "github.com/gin-gonic/gin/binding"
 zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
var trans ut.Translator
// Booking contains binded and validated data.
type Booking struct {
 CheckIn time.Time `form:"check_in" json:"check_in" binding:"required,bookabledate" time_format:"2006-01-02" label:" Input time "`
 CheckOut time.Time `form:"check_out" json:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02" label:" The output of time "`
}

var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
 date, ok := fl.Field().Interface().(time.Time)
 if ok {
 today := time.Now()
 if today.After(date) {
 return false
 }
 }
 return true
}

func main() {
 route := gin.Default()
 uni := ut.New(zh.New())
 trans, _ = uni.GetTranslator("zh")

 if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
 // Register the translator 
 _= zh_translations.RegisterDefaultTranslations(v, trans)
 // Register custom functions 
 _=v.RegisterValidation("bookabledate", bookableDate)

 // registered 1 A function, get struct tag It's custom label As the field name 
 v.RegisterTagNameFunc(func(fld reflect.StructField) string {
 name:=fld.Tag.Get("label")
 return name
 })
 // Register translations according to the tags provided 
 v.RegisterTranslation("bookabledate", trans, func(ut ut.Translator) error {
 return ut.Add("bookabledate", "{0} Not earlier than the current time or {1} Invalid format !", true)
 }, func(ut ut.Translator, fe validator.FieldError) string {
 t, _ := ut.T("bookabledate", fe.Field(), fe.Field())
 return t
 })

 }
 route.GET("/bookable", getBookable)
 route.Run(":8085")
}

func getBookable(c *gin.Context) {
 var b Booking
 if err := c.ShouldBindWith(&b, binding.Query); err == nil {
 c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
 } else {
 errs := err.(validator.ValidationErrors)

 fmt.Println(errs.Translate(trans))
 //for _, e := range errs {
 // // can translate each error one at a time.
 // fmt.Println(e.Translate(trans))
 //}
 c.JSON(http.StatusBadRequest, gin.H{"error": errs.Translate(trans)})
 }
}

Run the program and execute the following command


$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-16"

Results:

[

{"error":{"Booking. Input time ":" Input time cannot be earlier than the current time or input time format error!" ,"Booking. Output time ":" Output time must be greater than CheckIn"}}

]

Looking at the above results, we find that the translation is still not perfect. For example, in the case of gtfield in the rules, the field (CheckIn) is not translated. Therefore, adding label to struct does not fundamentally solve the field translation problem. To get the desired result, you need to process the error message separately and then output it.

Define the translation library first


package main

import (
 "fmt"
 "github.com/go-playground/validator/v10"
)

type Users struct {
 Phone string `form:"phone" json:"phone" validate:"required"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {

 users := &Users{
 Phone:  "1326654487",
 Passwd:  "123",
 Code:   "123456",
 }
 validate := validator.New()
 err := validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err)//Key: 'Users.Passwd' Error:Field validation for 'Passwd' failed on the 'min' tag
 return
 }
 }
 return
}
0

Redefine the translation function


package main

import (
 "fmt"
 "github.com/go-playground/validator/v10"
)

type Users struct {
 Phone string `form:"phone" json:"phone" validate:"required"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {

 users := &Users{
 Phone:  "1326654487",
 Passwd:  "123",
 Code:   "123456",
 }
 validate := validator.New()
 err := validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err)//Key: 'Users.Passwd' Error:Field validation for 'Passwd' failed on the 'min' tag
 return
 }
 }
 return
}
1

Where the original error message was translated


package main

import (
 "fmt"
 "github.com/go-playground/validator/v10"
)

type Users struct {
 Phone string `form:"phone" json:"phone" validate:"required"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {

 users := &Users{
 Phone:  "1326654487",
 Passwd:  "123",
 Code:   "123456",
 }
 validate := validator.New()
 err := validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err)//Key: 'Users.Passwd' Error:Field validation for 'Passwd' failed on the 'min' tag
 return
 }
 }
 return
}
2

Modified to


msg:=TransTagName(BookingTrans,errs.Translate(trans))
fmt.Println(msg)

The results of

[

{"error":{"Booking. Input time ":" Input time cannot be earlier than the current time or input time format error!" ,"Booking. Output time ":" Output time must be greater than input time "}}

]

Summary:

1.gin has supported validator's latest v10.

2.validator data validation order struct field from top to bottom, single field rule (binding:"gt=0,lt=2 '), first left, then right.

Reference:

https://github.com/go-playground/validator

https://github.com/gin-gonic/gin

https://gitissue.com/issues/5d06a73965d56f73569b825f

https://segmentfault.com/a/1190000022527284


Related articles: