【问题标题】:How to assert error type json.UnmarshalTypeError when caught by gin c.BindJSON被 gin c.BindJSON 捕获时如何断言错误类型 json.UnmarshalTypeError
【发布时间】:2021-01-24 10:38:06
【问题描述】:

我正在尝试使用 gin gonic 捕获绑定错误,它对于来自 go-playground/validator/v10 的所有验证错误都可以正常工作,但是在解组为正确的数据类型时我遇到了捕获错误的问题。

使用验证器标签(必需,...)时,结构字段验证不成功将返回 gin.ErrorTypeBind 错误类型

但如果我有一个结构

type Foo struct {
  ID int `json:"id"`
  Bar string `json:"bar"`
}

而且我试图传递的 json 格式错误(传递一个字符串而不是 id 的数字)

{
    "id":"string",
    "bar":"foofofofo"
}

它将失败并出现错误json: cannot unmarshal string into Go struct field Foo.id of type int

它仍然在我的处理程序中作为绑定错误捕获为gin.ErrorTypeBind,但由于我需要区分验证错误和解组错误,我遇到了问题。

我已经尝试过在验证错误上进行类型转换不适用于解组: e.Err.(validator.ValidationErrors)会恐慌

或者只是errors.Is,但这根本不会捕获错误

    if errors.Is(e.Err, &json.UnmarshalTypeError{}) {
        log.Println("Json binding error")
    } 

我这样做的目标是向用户返回格式正确的错误消息。它目前适用于所有验证逻辑,但我似乎无法使其适用于将不正确数据发送给我的 json 数据。

有什么想法吗?

编辑:

添加示例以重现:

package main

import (
    "encoding/json"
    "errors"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

type Foo struct {
    ID  int    `json:"id" binding:"required"`
    Bar string `json:"bar"`
}

func FooEndpoint(c *gin.Context) {
    var fooJSON Foo
    err := c.BindJSON(&fooJSON)
    if err != nil {
        // caught and answer in the error MW
        return
    }
    c.JSON(200, "test")
}

func main() {
    api := gin.Default()
    api.Use(ErrorMW())
    api.POST("/foo", FooEndpoint)
    api.Run(":5000")
}

func ErrorMW() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        if len(c.Errors) > 0 {
            for _, e := range c.Errors {
                switch e.Type {
                case gin.ErrorTypeBind:
                    log.Println(e.Err)
                    var jsonErr json.UnmarshalTypeError
                    if errors.Is(e.Err, &jsonErr) {
                        log.Println("Json binding error")
                    }
                    // if errors.As(e.Err, &jsonErr) {
                    //  log.Println("Json binding error")
                    // }
                    // in reality i'm making it panic.
                    // errs := e.Err.(validator.ValidationErrors)
                    errs, ok := e.Err.(validator.ValidationErrors)
                    if ok {
                        log.Println("error trying to cast validation type")
                    }
                    log.Println(errs)
                    status := http.StatusBadRequest
                    if c.Writer.Status() != http.StatusOK {
                        status = c.Writer.Status()
                    }
                    c.JSON(status, gin.H{"error": "error"})
                default:
                    log.Println("other error")
                }

            }
            if !c.Writer.Written() {
                c.JSON(http.StatusInternalServerError, gin.H{"Error": "internal error"})
            }
        }
    }
}

尝试发送带有正文的发布请求

{
  "id":"rwerewr",
   "bar":"string"
}

interface conversion: error is *json.UnmarshalTypeError, not validator.ValidationErrors

这会起作用:

{
  "id":1,
   "bar":"string"
}

这将(正确地)返回 Key: 'Foo.ID' Error:Field validation for 'ID' failed on the 'required' tag

{ “条”:“字符串” }

【问题讨论】:

    标签: json validation go error-handling go-gin


    【解决方案1】:

    errors.Is 查找完全匹配(在您的情况下,json.UnmarshalTypeError 的空实例)。请改用errors.As

    var jsonErr *json.UnmarshalTypeError
    if errors.As(e.Err, &jsonErr) {
        log.Println("Json binding error")
    }
    

    Playground example

    但是,正如@LeGEC 所指出的,这假设 gin 的错误符合标准 Unwrapper 接口,而他们并不这样做。

    【讨论】:

    • 用完整的复制案例提供建议会容易得多。
    • 我添加了一个例子
    • 在这种情况下,扩展 gin 或使用某种字符串比较错误消息会更好吗?
    • 尽可能避免字符串比较。在这种情况下,Gin 会公开您需要的信息,而不是通过标准渠道。 LeGEC 的回答很好地解释了如何做到这一点。
    • 打开一个针对杜松子酒的问题/公关以添加该功能可能也是值得的。我有点惊讶它还没有完成。
    【解决方案2】:

    [更新]:sice 版本1.7.0,它集成了this PR,现在可以在gin.Error 上使用errors.Is / errors.As

    所以你可以写:

    err := c.BindJson(&fooJson)
    
    if err != nil {
    var jsErr *json.UnmarshalTypeError
      if errors.As(err, &jsErr) {
        fmt.Println("the json is invalid")
      } else {
        fmt.Println("this is something else")
      }
    }
    

    [编辑]:嗯,它不会修复@Flimzy 的答案:查看the docsgin.Error 没有实现.Unwrap() 方法。

    您必须先将错误转换为gin.Error,然后检查ginErr.Err

    // you can probably work with straight conversion from interface to target type :
    if g, ok := err.(*gin.Error); ok {
        if _, ok := g.Err.(*json.UnmarshalTypeError); ok {
            log.Println("Json binding error")
        }
    }
    
    // or use errors.As() :
    var g *gin.Error
    if errors.As(err, &g) {
        var j *json.UnmarshalTypeError
        if errors.As(g.Err, &j) {
            log.Println("Json binding error")
        }
    }
    

    我的初步回答:

    (修复@Fllimzy 的答案)

    1. 使用errors.As
    2. 因为json.UnmarshalTypeError 是一个结构(不是接口),你必须明确区分json.UnmarshalTypeError*json.UnmarshalTypeError

    尝试运行:

    var jsonErr *json.UnmarshalTypeError  // emphasis on the '*'
    if errors.As(e.Err, &jsonErr) {
        log.Println("Json binding error")
    }
    

    这是errors.As() 行为方式的说明:
    https://play.golang.org/p/RVz6xop5k4u

    【讨论】:

    • FWIW : .Unwrap() 的实现已合并到 gin 的主分支(PR #2526)。不知道什么是明智的计划发布。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-14
    • 1970-01-01
    • 1970-01-01
    • 2016-10-03
    • 2020-04-19
    • 1970-01-01
    相关资源
    最近更新 更多