Parameter verification—The gin framework uses the built-in validator

1. Description of special symbols before marking

Comma (,): separate multiple verification tags. Note that there cannot be spaces between commas

Horizontal dash (-): This field is not verified and skipped

Vertical bar (|): Use multiple verification tags, but only one of them needs to be satisfied.

required: indicates that the field must be set and cannot be used as a default value

omitempty: If a field is not set, ignore it

omitempty,xxx=xxx # Optional, if it exists, continue to verify the rule xxx=xxx backwards. If it does not exist, xxx=xxx will not take effect, but if there is a verification rule before omitempty, the previous verification rule will still be Effective, such as gte=-1,omitempty,len=3, the gte=-1 rule will always take effect, and len=3 will only take effect when the value is not 0.

2. Verification scope

1: Verification range: slices, arrays, maps, and strings, verify their length; numerical values, verify the size range

lte: less than or equal to the parameter value, validate: "lte=3" (less than or equal to 3)

gte: greater than or equal to the parameter value, validate: "lte=120,gte=0" (greater than or equal to 0 and less than or equal to 120)

lt: less than the parameter value, validate: "lt=3" (less than 3)

gt: greater than the parameter value, validate: "lt=120,gt=0" (greater than 0 and less than 120)

len: equal to parameter value, validate: "len=2"

max: maximum value, less than or equal to the parameter value, validate: "max=20" (less than or equal to 20)

min: minimum value, greater than or equal to the parameter value, validate: "min=2,max=20" (greater than or equal to 2 and less than or equal to 20)

ne: not equal to, validate: "ne=2" (not equal to 2)

oneof: can only be one of the listed values. These values must be numeric values or strings, separated by spaces. If there are spaces in the string, surround the string with single quotes, validate: "oneof=red green"

2: String verification

contains: Contains parameter substring, validate: "contains=tom" (the string value of the field contains tom)

excludes: Contains parameter substrings, validate: "excludes=tom" (the string value of the field does not include tom)

startswith: prefixed with parameter substring, validate: "startswith=golang"

endswith: suffixed with parameter substring, validate: "startswith=world"

 
max=10 # The maximum length is 10

min=10 # The minimum length is 10

gt=10 # The length is greater than 10

lt=10 # The length is less than 10

gte=10 # The length is greater than or equal to 10

let=10 # The length is less than or equal to 10

 
eq=aaa #The value is aaa

ne=aaa # The value cannot be aaa

oneof=a b c # Enumeration, can only be a, b or c

len=3 #The character length is 3

3: Field verification

eqcsfield: Verification across different structure fields, for example, struct filed1 and struct filed2 are equal

necsfield: fields are not equal across different structures

eqfield: The fields of the same structure are verified to be equal. The most common one is to enter the password twice for verification.

【

 Password string `json:"password" binding:"required"`
    RePassword string `json:"re_password" binding:"required,eqfield=Password"`
】

nefield: Field verification of the same structure is not equal

gtefield: greater than or equal to the same structure field, validate: "gtefiled=Field2"

ltefield: less than or equal to the same structure field

 A level internal check

qfield=AAA #Equal to field AAA value
nefield=AAA # is not equal to the field AAA value
gtfield=AAA # Value greater than field AAA
gtefield=AAA # Greater than or equal to the value of field AAA
ltfield=AAA # Value less than field AAA
ltefield=AAA # Less than or equal to the value of the AAA field
Array check dive

Domains []string `binding:"gt=0,dive,required,min=1,max=100"`

Check content: [] string length must be greater than 0, and the string length of the element in the array must be between 1-100

 

Dive Map Verification

ReqHeaders map[string]string `binding:"dive,keys,min=1,max=100,endkeys,required,min=1,max=100"`

Keys and endkeys are used here to mark the verification range of key values, starting from keys and ending with endkeys

Verification content: The whole verification is not done. The key value length must be between 1-100, and the value value length must also be between 1-100.

 

structonly

When a structure defines validation rules, but in some places, these validation rules are not required to take effect, you can use the structonly tag, and the validation rules inside the structure with this tag will no longer take effect. as follows:


type Timeout struct {
    Connect int `json:"connect" binding:"required"`
    Read int `json:"read" binding:"required"`
    Send int `json:"send" binding:"required"`
}

type MyStruct struct {
    Name string `json:"name" binding:"required"`
    Timeout Timeout `json:"timeout" binding:"structonly"`
}
Each field of the Timeout structure defines verification content, but I used the structonly tag in the Timeout field of MyStruct, so the verification content defined in Timeout will no longer take effect.

4: Network verification

ip: Whether the field value contains a valid IP address, validate: "ip"

ipv4: Whether the field value contains a valid ipv4 address, validate: "ipv4"

ipv6: Whether the field value contains a valid ipv6 address, validate: "ipv6"

uri: Whether the field value contains a valid uri, validate: "uri"

url: Whether the field value contains a valid uri, validate: "url"

5: Date verification

Date string `json:"date" binding:"required,datetime=2006-01-02,checkDate"`
Among them, datetime=2006-01-02 is a built-in tag used to verify whether date parameters meet the specified format requirements.
If the date parameter passed in does not meet the format of 2006-01-02, the following error will be prompted:

 

example


type SignUpParam struct {
   Age uint8 `json:"age" binding:"gte=1,lte=130"`
   Name string `json:"name" binding:"required"`
   Email string `json:"email" binding:"required,email"`
   Password string `json:"password" binding:"required,max=13,min=1"`
   RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

Three: Translation verification error message

The validator library itself supports internationalization, and the automatic translation of verification error prompts can be achieved with the help of the corresponding language pack. The examples below are translated into Chinese.

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/locales/en"
    "github.com/go-playground/locales/zh"
    ut "github.com/go-playground/universal-translator"
    "github.com/go-playground/validator/v10"
    enTranslations "github.com/go-playground/validator/v10/translations/en"
    zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)

//Define a global translator T
var transut.Translator

// InitTrans initializes the translator
func InitTrans(locale string) (err error) {
    // Modify the Validator engine properties in the gin framework to achieve customization
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

        zhT := zh.New() // Chinese translator
        enT := en.New() // English translator

        // The first parameter is the fallback locale
        //The following parameters are the locale that should be supported (multiple supported)
        // uni := ut.New(zhT, zhT) is also possible
        uni := ut.New(enT, zhT, enT)

        // locale usually depends on the 'Accept-Language' http request header
        var OK bool
        // You can also use uni.FindTranslator(...) to pass in multiple locales for search
        trans, ok = uni.GetTranslator(locale)
        if !ok {
            return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
        }

        //Register translator
        switch locale {
        case "en":
            err = enTranslations.RegisterDefaultTranslations(v, trans)
        case "zh":
            err = zhTranslations.RegisterDefaultTranslations(v, trans)
        default:
            err = enTranslations.RegisterDefaultTranslations(v, trans)
        }
        return
    }
    return
}

type SignUpParam struct {
    Age uint8 `json:"age" binding:"gte=1,lte=130"`
    Name string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required"`
    RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}

func main() {
    if err := InitTrans("zh"); err != nil {
        fmt.Printf("init trans failed, err:%v\
", err)
        return
    }

    r := gin.Default()

    r.POST("/signup", func(c *gin.Context) {
        var uSignUpParam
        if err := c.ShouldBind( & amp;u); err != nil {
            // Get errors of type validator.ValidationErrors
            errs, ok := err.(validator.ValidationErrors)
            if !ok {
                // Non-validator.ValidationErrors type errors are returned directly
                c.JSON(http.StatusOK, gin.H{
                    "msg": err.Error(),
                })
                return
            }
            // Validator.ValidationErrors type errors are translated
            c.JSON(http.StatusOK, gin.H{
                "msg":errs.Translate(trans),
            })
            return
        }
        // Save specific business logic code such as warehousing...

        c.JSON(http.StatusOK, "success")
    })

    _ = r.Run(":8999")
}

Customize the field name of the error message
The above error message seems to be OK, but it still makes little sense. First of all, the field in the error message is not the field used in the request. For example: RePassword is the field name in the structure defined by our backend, and the field used in the request is the re_password field. How to use a custom name for the field in the error message, such as the value specified by jsontag?

Just add a custom method to get the json tag when initializing the translator as shown below.

//InitTrans initializes the translator
func InitTrans(locale string) (err error) {
    // Modify the Validator engine properties in the gin framework to achieve customization
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

        //Register a custom method to get json tag
        v.RegisterTagNameFunc(func(fld reflect.StructField) string {
            name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
            if name == "-" {
                return ""
            }
            return name
        })

        zhT := zh.New() // Chinese translator
        enT := en.New() // English translator

        // The first parameter is the fallback locale
        //The following parameters are the locale that should be supported (multiple supported)
        // uni := ut.New(zhT, zhT) is also possible
        uni := ut.New(enT, zhT, enT)

        //...
}
{"msg":{"SignUpParam.email":"email must be a valid email address",
"SignUpParam.password":"password is a required field",
"SignUpParam.re_password":"re_password is a required field"}
}

You can see that the error message now uses the name of the jsontag setting in our structure.

But there is still a slight flaw, that is, the final error message center still has the structure name defined by our backend – SignUpParam. This name actually does not need to be returned to the front end with the error message, and the front end does not need this value. We need to find a way to get rid of it.

Here, refer to the method provided by https://github.com/go-playground/validator/issues/633#issuecomment-654382345 to define a custom method that removes the prefix of the structure name.

func removeTopStruct(fields map[string]string) map[string]string {
    res := map[string]string{}
    for field, err := range fields {
        res[field[strings.Index(field, ".") + 1:]] = err
    }
    return res
}

We can use the above function in the code to process the translated errors:

if err := c.ShouldBind( & amp;u); err != nil {
    // Get errors of type validator.ValidationErrors
    errs, ok := err.(validator.ValidationErrors)
    if !ok {
        // Non-validator.ValidationErrors type errors are returned directly
        c.JSON(http.StatusOK, gin.H{
            "msg": err.Error(),
        })
        return
    }
    // Validator.ValidationErrors type errors are translated
    // And use the removeTopStruct function to remove the structure name identifier in the field name
    c.JSON(http.StatusOK, gin.H{
        "msg": removeTopStruct(errs.Translate(trans)),
    })
    return
}

Take a look at the final effect:

{"msg":{"email":"email must be a valid email address",
"password":"password is a required field","re_password":"re_password is a required field"}}