I'm trying to catch binding errors with gin gonic and it's working fine for all validation errors from go-playground/validator/v10 but i'm having an issue catching errors when unmarshalling into the proper data type.
Unsuccessful validation of a struct field will return a gin.ErrorTypeBind Type of error when using validator tags ( required, ...)
but if i have a struct
type Foo struct {
ID int `json:"id"`
Bar string `json:"bar"`
}
And the json i'm trying to pass is of a wrong format (passing a string instead of a number for id )
{
"id":"string",
"bar":"foofofofo"
}
It will fail with an error json: cannot unmarshal string into Go struct field Foo.id of type int
It is still caught as a gin.ErrorTypeBind in my handler as an error in binding but as i need to differentiate between validation error and unmarshalling error i'm having issues.
I have tried Type casting on validaton error doesn't work for unmarshalling :
e.Err.(validator.ValidationErrors) will panic
or just errors.Is but this will not catch the error at all
if errors.Is(e.Err, &json.UnmarshalTypeError{}) {
log.Println("Json binding error")
}
My goal in doing so is to return properly formatted error message to the user. It's currently working well for all the validation logic but i can't seem to make it work for json data where incorrect data would be sent to me.
any ideas?
edit :
adding example to reproduce :
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"})
}
}
}
}
trying sending a post request with a body
{
"id":"rwerewr",
"bar":"string"
}
interface conversion: error is *json.UnmarshalTypeError, not validator.ValidationErrors
this will work :
{
"id":1,
"bar":"string"
}
and this will (rightfully ) return Key: 'Foo.ID' Error:Field validation for 'ID' failed on the 'required' tag
{ "bar":"string" }
errors.Is looks for an exact match (in your case, an empty instance of json.UnmarshalTypeError). Use errors.As instead:
var jsonErr *json.UnmarshalTypeError
if errors.As(e.Err, &jsonErr) {
log.Println("Json binding error")
}
Playground example
However, as @LeGEC has pointed out, this assumes that gin's errors conform to the standard Unwrapper interface, which they don't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With