Chinese processing of parameter verification error information

Posted by tsukushi on Mon, 08 Jun 2020 18:55:59 +0200

As we explained in the previous section, gin can use the ShouldBind method to bind parameters to a structure, but we don't cover how parameter checking works. In this section, we describe how parameter checking and checking fail and then convert to Chinese to return to the front end.

1. Data Validation

Let's start with a simple example:

  1. Create a new test_under the requests directory in the root directoryRequest.go
package requests

//Test Request Structure This structure defines the parameters and validation rules for the request
type TestRequest struct {
    Username string `form:"username" binding:"required"`
}
  1. Create a new one in the root api directoryTest.goController, define test controller
package api

import (
    "cn.sockstack/gin_demo/requests"
    "github.com/gin-gonic/gin"
    "net/http"
)

func Test(c *gin.Context)  {
    //Instantiate a TestRequest structure for receiving parameters
    testStruct := requests.TestRequest{}

    //Receive Request Parameters
    err := c.ShouldBind(&testStruct)

    //Determines whether the parameter check passes or not, and if it does not, returns the error to the front end
    if err != nil {
        c.JSON(http.StatusOK, gin.H{"error": err.Error()})
        return
    }

    //Check passes, returning request parameters
    c.JSON(http.StatusOK, gin.H{"params": testStruct})
}
  1. Define/test routes under the root directory routers, create new onesInit.goandTest.goFile initialization routing.

test.go

package routers

import (
    "cn.sockstack/gin_demo/api"
    "github.com/gin-gonic/gin"
)

func test(r *gin.Engine)  {
    //Define/test routing
    r.GET("/test", api.Test)
}

init.go

package routers

import "github.com/gin-gonic/gin"

func Init(r *gin.Engine)  {
    //Register test route
    test(r)
}
  1. stayMain.goRegister Routes in
package main
// Import gin package
import (
    "cn.sockstack/gin_demo/pkg/config"
    "cn.sockstack/gin_demo/routers"
    "fmt"
    "github.com/gin-gonic/gin"
)

// Entry function
func main() {
    // Initialize an http service object
    r := gin.Default()

    //Register Routes
    routers.Init(r)

    r.Run(fmt.Sprintf("%s:%d", config.Server.Address, config.Server.Port)) // Listen and start the service at 0.0.0.0:8081
}
  1. Run and accessLocalhost:8081/test, error occurs without parameters
{
    "error": "Key: 'TestRequest.Username' Error:Field validation for 'Username' failed on the 'required' tag"
}
  1. Run and accessLocalhost:8081/test?Username=sockstackReturns the parameters of the response.
{
    "params": {
        "Username": "sockstack"
    }
}

The example above already implements parameter checking and accepts parameters, but the hint returned when the check fails is in English. Let's show how to convert the error into Chinese.

2. Check parameter failures prompt automatic translation

Looking at the code, we found that gin's default validator uses the validator package, and looking at the documentation found that validator can translate English errors into Chinese.

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))
        }
    }
}

So let's transform gin's verification tips.

  1. New under requests directoryInit.gofile
package requests

import (
   "github.com/gin-gonic/gin/binding"
   "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"
)

var (
   uni      *ut.UniversalTranslator
   validate *validator.Validate
   trans ut.Translator
)

func init() {
   //Register Translator
   zh := zh.New()
   uni = ut.New(zh, zh)

   trans, _ = uni.GetTranslator("zh")
   
   //Get gin's verifier
   validate := binding.Validator.Engine().(*validator.Validate)
   //Register Translator
   zh_translations.RegisterDefaultTranslations(validate, trans)
}

//Translate Translation Error Information
func Translate(err error) map[string][]string {
   var result = make(map[string][]string)

   errors := err.(validator.ValidationErrors)

   for _, err := range errors{
      result[err.Field()] = append(result[err.Field()], err.Translate(trans))
   }
   return result
}
  1. Modify Controller
package api

import (
    "cn.sockstack/gin_demo/requests"
    "github.com/gin-gonic/gin"
    "net/http"
)

func Test(c *gin.Context)  {
    //Instantiate a TestRequest structure for receiving parameters
    testStruct := requests.TestRequest{}

    //Receive Request Parameters
    err := c.ShouldBind(&testStruct)

    //Determines whether the parameter check passes or not, and if it does not, returns the error to the front end
    if err != nil {
        c.JSON(http.StatusOK, gin.H{"error": requests.Translate(err)})
        return
    }

    //Check passes, returning request parameters
    c.JSON(http.StatusOK, gin.H{"params": testStruct})
}
  1. Run and accessLocalhost:8081/test, error without parameters, but error message has been translated into Chinese
{
    "error": {
        "Username": [
            "Username Is a required field"
        ]
    }
}

Source gin from entry to practice More great articles, please follow my blog SOCKSTACK To share my work experience.

Topics: Go github JSON