banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

定制Gin的Validator錯誤消息及響應結構

title: 定制 Gin 的 Validator 錯誤消息及響應結構
date: 2021-11-14 19:02:00
toc: true
category:

  • Golang
  • Gin
    tags:
  • Golang
  • golang
  • Go
  • go
  • Gin
  • gin
  • Validator
  • validator
  • 功能
  • 校驗
  • 定制
  • 實現
  • 響應
  • 結構
  • 錯誤
  • 消息
  • 本地化
  • 翻譯

前言#

在使用 Golang 的 Validator (校驗) 功能時,會發現很多錯誤的提示信息都是英文的。例如

{  
    "code": -1,  
    "msg": {  
        "SingUpForm.Age": "Key: 'SingUpForm.Age' Error:Field validation for 'Age' failed on the 'gte' tag",  
        "SingUpForm.Email": "Key: 'SingUpForm.Email' Error:Field validation for 'Email' failed on the 'required' tag",  
        "SingUpForm.Name": "Key: 'SingUpForm.Name' Error:Field validation for 'Name' failed on the 'required' tag",  
        "SingUpForm.Password": "Key: 'SingUpForm.Password' Error:Field validation for 'Password' failed on the 'required' tag",  
        "SingUpForm.RePassword": "Key: 'SingUpForm.RePassword' Error:Field validation for 'RePassword' failed on the 'required' tag"  
    }  
}  

這樣會造成接口調用者的閱讀困難。
翻閱官方文檔後發現翻譯的工作已經做過了,可以直接將錯誤信息修改為指定語言。

但需要注意的是,目前 go-playground/validator 只支持了 13 種語言,具體參閱官方列出的目錄:validator/translations at master · go-playground/validator

Snipaste20211114190832.png

錯誤消息本地化#

直接上代碼,邏輯很清晰。每次出現錯誤信息通過 err.(validator.ValidationErrors) 轉換得到異常對象 validator.ValidationErrors,調用其 func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations {} 方法即可。

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"  

    en_translations "github.com/go-playground/validator/v10/translations/en"  
    zh_translations "github.com/go-playground/validator/v10/translations/zh"  
)  

// 翻譯校驗信息  
func main() {  
    if err := InitTranslate("zh"); err != nil {  
        fmt.Println("初始化翻譯器出錯")  
        return  
    }  

    r := gin.Default()  

    r.POST("/", func(c *gin.Context) {  
        var singUpForm SingUpForm  
        if err := c.ShouldBind(&singUpForm); nil != err {  
            // 出現錯誤  
            errors, ok := err.(validator.ValidationErrors)  
            if !ok {  
                c.JSON(http.StatusOK, gin.H{  
                    "code": -1,  
                    "msg":  fmt.Errorf("error getting translation of error message, %s", err.Error()),  
                })  
                return  
            }  

            c.JSON(http.StatusBadRequest, gin.H{  
                "code": -1,  
                "msg":  errors.Translate(translator),  
            })  

            return  
        }  

        c.JSON(http.StatusOK, gin.H{  
            "code": 0,  
            "msg":  "登錄成功",  
        })  
    })  

    // 監聽並在 0.0.0.0:8080 上啟動服務  
    r.Run(":8080")  
}  

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

// use a single instance , it caches struct info  
var (  
    uni        *ut.UniversalTranslator  
    validate   *validator.Validate  
    translator ut.Translator  
)  

func InitTranslate(locale string) (err error) {  
    // 修改 gin 框架中的 validator 引擎屬性,實現翻譯效果  
    engine, ok := binding.Validator.Engine().(*validator.Validate)  
    if !ok {  
        fmt.Println("獲取 gin 框架的 validator 出錯")  
    }  

    zhT := zh.New()  
    enT := en.New()  

    uni := ut.New(enT, zhT, enT)  

    translator, ok = uni.GetTranslator(locale)  
    if !ok {  
        return fmt.Errorf("uni.GetTranslator(%s)", locale)  
    }  

    switch locale {  
    case "en":  
        en_translations.RegisterDefaultTranslations(engine, translator)  
    case "zh":  
        zh_translations.RegisterDefaultTranslations(engine, translator)  
    }  

    return  
}  

修改後的效果為:

{  
    "code": -1,  
    "msg": {  
        "SingUpForm.Age": "Age必須大於或等於1",  
        "SingUpForm.Email": "Email為必填字段",  
        "SingUpForm.Name": "Name為必填字段",  
        "SingUpForm.Password": "Password為必填字段",  
        "SingUpForm.RePassword": "RePassword為必填字段"  
    }  
}  

響應內容結構屬性名稱首字母小寫#

原來的響應內容:

{  
    "code": -1,  
    "msg": {  
        "SingUpForm.Age": "Age必須大於或等於1",  
        "SingUpForm.Email": "Email為必填字段",  
        "SingUpForm.Name": "Name為必填字段",  
        "SingUpForm.Password": "Password為必填字段",  
        "SingUpForm.RePassword": "RePassword為必填字段"  
    }  
}  

修改後的內容為:

{  
    "code": -1,  
    "msg": {  
        "SingUpForm.age": "age必須大於或等於1",  
        "SingUpForm.email": "email為必填字段",  
        "SingUpForm.name": "name為必填字段",  
        "SingUpForm.password": "password為必填字段",  
        "SingUpForm.rePassword": "rePassword為必填字段"  
    }  
}  

完整代碼為:

package main  

import (  
    "fmt"  
    "net/http"  
    "reflect"  
    "strings"  

    "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"  

    en_translations "github.com/go-playground/validator/v10/translations/en"  
    zh_translations "github.com/go-playground/validator/v10/translations/zh"  
)  

// 翻譯校驗信息  
func main() {  
    if err := InitTranslate("zh"); err != nil {  
        fmt.Println("初始化翻譯器出錯")  
        return  
    }  

    r := gin.Default()  

    r.POST("/", func(c *gin.Context) {  
        var singUpForm SingUpForm  
        if err := c.ShouldBind(&singUpForm); nil != err {  
            // 出現錯誤  
            errors, ok := err.(validator.ValidationErrors)  
            if !ok {  
                c.JSON(http.StatusOK, gin.H{  
                    "code": -1,  
                    "msg":  fmt.Errorf("error getting translation of error message, %s", err.Error()),  
                })  
                return  
            }  

            c.JSON(http.StatusBadRequest, gin.H{  
                "code": -1,  
                "msg":  errors.Translate(translator),  
            })  

            return  
        }  

        c.JSON(http.StatusOK, gin.H{  
            "code": 0,  
            "msg":  "登錄成功",  
        })  
    })  

    // 監聽並在 0.0.0.0:8080 上啟動服務  
    r.Run(":8080")  
}  

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

// use a single instance , it caches struct info  
var (  
    uni        *ut.UniversalTranslator  
    validate   *validator.Validate  
    translator ut.Translator  
)  

func InitTranslate(locale string) (err error) {  
    // 修改 gin 框架中的 validator 引擎屬性,實現翻譯效果  
    engine, ok := binding.Validator.Engine().(*validator.Validate)  
    if !ok {  
        fmt.Println("獲取 gin 框架的 validator 出錯")  
    }  

    // 實現響應內容 tag 首字母小寫  
    engine.RegisterTagNameFunc(func(field reflect.StructField) string {  
        // 如 RePassword string `json:"rePassword" binding:"required,eqfield=Password"`  
        // 那麼 jsonTag 就是 rePassword  
        jsonTag := field.Tag.Get("json")  
        // 分割,例如 strings.SplitN("a,b,c", ",", 2) 那麼就是 [a b,c],strings.SplitN("a,b,c,d", ",", 2) 那麼就是 [a b c,d]  
        splitN := strings.SplitN(jsonTag, ",", 2)  
        name := splitN[0]  
        if name == "-" {  
            return ""  
        }  
        return name  
    })  

    zhT := zh.New()  
    enT := en.New()  

    uni := ut.New(enT, zhT, enT)  

    translator, ok = uni.GetTranslator(locale)  
    if !ok {  
        return fmt.Errorf("uni.GetTranslator(%s)", locale)  
    }  

    switch locale {  
    case "en":  
        en_translations.RegisterDefaultTranslations(engine, translator)  
    case "zh":  
        zh_translations.RegisterDefaultTranslations(engine, translator)  
    }  

    return  
}  

// 移除響應的 Struct 名稱,例如 `SingUpForm.age` 會變成 `age`  
func removeTopStruct(fields map[string]string) map[string]string {  
    resp := make(map[string]string)  
    for key, value := range fields {  
        resp[key[strings.Index(key, ".")+1:]] = value  
    }  
    return resp  
}  

移除響應內容中的構造體#

原來的響應內容:

{  
    "code": -1,  
    "msg": {  
        "SingUpForm.Age": "Age必須大於或等於1",  
        "SingUpForm.Email": "Email為必填字段",  
        "SingUpForm.Name": "Name為必填字段",  
        "SingUpForm.Password": "Password為必填字段",  
        "SingUpForm.RePassword": "RePassword為必填字段"  
    }  
}  

修改後的內容為:

{  
    "code": -1,  
    "msg": {  
        "age": "age必須大於或等於1",  
        "email": "email為必填字段",  
        "name": "name為必填字段",  
        "password": "password為必填字段",  
        "rePassword": "rePassword為必填字段"  
    }  
}  

完整代碼為:

package main  

import (  
    "fmt"  
    "net/http"  
    "reflect"  
    "strings"  

    "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"  

    en_translations "github.com/go-playground/validator/v10/translations/en"  
    zh_translations "github.com/go-playground/validator/v10/translations/zh"  
)  

// 翻譯校驗信息  
func main() {  
    if err := InitTranslate("zh"); err != nil {  
        fmt.Println("初始化翻譯器出錯")  
        return  
    }  

    r := gin.Default()  

    r.POST("/", func(c *gin.Context) {  
        var singUpForm SingUpForm  
        if err := c.ShouldBind(&singUpForm); nil != err {  
            // 出現錯誤  
            errors, ok := err.(validator.ValidationErrors)  
            if !ok {  
                c.JSON(http.StatusOK, gin.H{  
                    "code": -1,  
                    "msg":  fmt.Errorf("error getting translation of error message, %s", err.Error()),  
                })  
                return  
            }  

            c.JSON(http.StatusBadRequest, gin.H{  
                "code": -1,  
                "msg":  removeTopStruct(errors.Translate(translator)),  
            })  

            return  
        }  

        c.JSON(http.StatusOK, gin.H{  
            "code": 0,  
            "msg":  "登錄成功",  
        })  
    })  

    // 監聽並在 0.0.0.0:8080 上啟動服務  
    r.Run(":8080")  
}  

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

// use a single instance , it caches struct info  
var (  
    uni        *ut.UniversalTranslator  
    validate   *validator.Validate  
    translator ut.Translator  
)  

func InitTranslate(locale string) (err error) {  
    // 修改 gin 框架中的 validator 引擎屬性,實現翻譯效果  
    engine, ok := binding.Validator.Engine().(*validator.Validate)  
    if !ok {  
        fmt.Println("獲取 gin 框架的 validator 出錯")  
    }  

    // 實現響應內容 tag 首字母小寫  
    engine.RegisterTagNameFunc(func(field reflect.StructField) string {  
        // 如 RePassword string `json:"rePassword" binding:"required,eqfield=Password"`  
        // 那麼 jsonTag 就是 rePassword  
        jsonTag := field.Tag.Get("json")  
        // 分割,例如 strings.SplitN("a,b,c", ",", 2) 那麼就是 [a b,c],strings.SplitN("a,b,c,d", ",", 2) 那麼就是 [a b c,d]  
        splitN := strings.SplitN(jsonTag, ",", 2)  
        name := splitN[0]  
        if name == "-" {  
            return ""  
        }  
        return name  
    })  

    zhT := zh.New()  
    enT := en.New()  

    uni := ut.New(enT, zhT, enT)  

    translator, ok = uni.GetTranslator(locale)  
    if !ok {  
        return fmt.Errorf("uni.GetTranslator(%s)", locale)  
    }  

    switch locale {  
    case "en":  
        en_translations.RegisterDefaultTranslations(engine, translator)  
    case "zh":  
        zh_translations.RegisterDefaultTranslations(engine, translator)  
    }  

    return  
}  

// 移除響應的 Struct 名稱,例如 `SingUpForm.age` 會變成 `age`  
func removeTopStruct(fields map[string]string) map[string]string {  
    resp := make(map[string]string)  
    for key, value := range fields {  
        resp[key[strings.Index(key, ".")+1:]] = value  
    }  
    return resp  
}  

參考資料#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。