Data validation validator in golang

Data validation validator in golang

Preface

Data validation problems are often encountered in web applications. Common validation methods are more cumbersome. Here is a more-used package validator .

principle

Write the verification rules in the struct pair field tag, and then obtain the struct tag through reflection ( reflect ) to achieve data verification.

installation

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

Example

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
}

Validation rules

  • required: required
  • email: The verification string is in email format; for example: "email"
  • url: This will verify that the string value contains a valid URL; example: "url"
  • max: the maximum length of the string; for example: "max=20"
  • min: the minimum length of the string; for example: "min=6"
  • excludesall: Special characters cannot be included; for example: "excludesall=0x2C"//Note that it is expressed in hexadecimal.
  • len: The character length must be equal to n, or the len value of an array, slice, or map is n, which is the number of items included; for example: "len=6"
  • eq: The number is equal to n, or the len value of an array, slice, or map is n, which is the number of items included; for example: "eq=6"
  • ne: The number is not equal to n, or the len value of the array, slice, or map is not equal to n, that is, the number of items included is not n, which is the opposite of eq; for example: "ne=6"
  • gt: The number is greater than n, or the len value of the array, slice, or map is greater than n, that is, the number of items contained is greater than n; for example: "gt=6"
  • gte: The number is greater than or equal to n, or the len value of an array, slice, or map is greater than or equal to n, that is, the number of items contained is greater than or equal to n; for example: "gte=6"
  • lt: The number is less than n, or the len value of the array, slice, or map is less than n, that is, the number of items contained is less than n; for example: "lt=6"
  • lte: The number is less than or equal to n, or the len value of the array, slice, and map is less than or equal to n, that is, the number of items contained is less than or equal to n; for example: "lte=6"

Cross-field validation

If you want to implement similar scenarios such as comparing the input password and confirming whether the password is consistent, etc.

  • eqfield=Field: Must be equal to the value of Field;
  • nefield=Field: Must not be equal to the value of Field;
  • gtfield=Field: Must be greater than the value of Field;
  • gtefield=Field: Must be greater than or equal to the value of Field;
  • ltfield=Field: Must be less than the value of Field;
  • ltefield=Field: Must be less than or equal to the value of Field;
  • eqcsfield=Other.Field: Must be equal to the value of Field in struct Other;
  • necsfield=Other.Field: Must not be equal to the value of Field in struct Other;
  • gtcsfield=Other.Field: Must be greater than the value of Field in struct Other;
  • gtecsfield=Other.Field: Must be greater than or equal to the value of Field in struct Other;
  • ltcsfield=Other.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;

Example

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 Passwd and Repasswd are equal. If you want to know more types, please refer to the document https://godoc.org/gopkg.in/go...

Custom authentication type

Example:

package main

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

type Users struct {
    Name string `form:"name" json:"name" validate:"required,CustomValidationErrors"`//Include 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 a custom function
    _=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"
}

Translate the error message to Chinese

Through the above example, we can see that the default error message of validator is similar to the following

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

Obviously this is not what we want. What if we want to translate into Chinese or other languages? A good solution is provided on go-playground .

Install the two packages you need first

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

carried out:

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()
   //Verifier register 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))//Age must be greater than 18
            return
        }
    }

    return
}

Output:

Age must be greater than 18

So far we found that most of the error messages have been translated into Chinese, but the field name (Age) is still not translated. In order to translate the field name into Chinese, we checked some information, https://www.jianshu.com/p/51b.. . ,

I did it without success (there may be omissions), and finally looked at the source code, at https://github.com/go-playgro... , 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
//})

In fact, the principle is to register a function and use the Chinese name added in the struct tag as an alternate name.

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:"Username"`
    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()
   //Register a function, get the custom label in the struct tag as the field name
    validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
        name:=fld.Tag.Get("label")
        return name
    })
   //Register 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))//Age must be greater than 18
            return
        }
    }

    return
}

Output result:

Must be older than 18

gin built-in validator

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

The following only provides an example of binding ShouldindWith, if you want to know more methods, please enter here .

Example

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:"output 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 translator
        _= zh_translations.RegisterDefaultTranslations(v, trans)
       //Register a custom function
        _=v.RegisterValidation("bookabledate", bookableDate)

       //Register a function, get the custom label in the struct tag as the field name
        v.RegisterTagNameFunc(func(fld reflect.StructField) string {
            name:=fld.Tag.Get("label")
            return name
        })
       //Register the translation according to the provided mark
        v.RegisterTranslation("bookabledate", trans, func(ut ut.Translator) error {
            return ut.Add("bookabledate", "{0} cannot be earlier than the current time or {1} format is wrong!", 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, execute the following command

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

result:

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

Looking at the above results, we find that the translation is still not perfect. For example, if there is gtfield in the rule, the field (CheckIn) is not translated. Therefore, adding label through struct does not fundamentally solve the problem of field translation. In order to get the desired result, the error message needs to be processed separately and then output.

First define the translation library

var BookingTrans =map[string]string{"CheckIn":"input time","CheckOut":"output time"}

Redefine the translation function

func TransTagName(libTans,err interface{}) interface{} {
    switch err.(type) {
    case validator.ValidationErrorsTranslations:
        var errs map[string]string
        errs = make(map[string]string,0)
        for k,v:=range err.(validator.ValidationErrorsTranslations){
            for key,value:=range libTans.(map[string]string) {
                v=strings.Replace(v,key,value,-1)
            }
            errs[k] = v
        }
        return errs
    case string:
        var errs string
        for key,value:=range libTans.(map[string]string) {
            errs=strings.Replace(errs,key,value,-1)
        }
        return errs
    default:
        return err
    }
}

Place the original translation error message

errs.Translate(trans)

change into

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

result

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

summary:

  1. Gin already supports the latest v10 of validator .
  2. The validator data validation order struct field from top to bottom, single field rule (binding: "gt=0,lt=2`), first left and then right.

reference:

https://github.com/go-playgro...

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

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

https://segmentfault.com/a/11...

Reference: https://cloud.tencent.com/developer/article/1706237 golang data validation validator-cloud + community-Tencent Cloud