Description

There is no way to handle validation error If struct field has type "int" and user sends string value.

How to reproduce

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
    "net/http"
        "reflect"
)

func main() {
    g := gin.Default()
    g.GET("/articles", func(c *gin.Context) {
        type Paginator struct {
            Page int `form:"page" binding:"required,min=1"`
        }
        var pag Paginator
        if err := c.ShouldBindQuery(&pag); err != nil {
            errs := err.(validator.ValidationErrors)
            respErrors := make(map[string]interface{})
            for _, v := range errs {
                field, _ := reflect.TypeOf(&pag).Elem().FieldByName(v.Field())
                fieldName, _ := field.Tag.Lookup("form")
                respErrors[fieldName] = v.Tag()
            }
            c.JSON(http.StatusBadRequest, gin.H{"errors": respErrors})
            return
        }
        c.String(200, "Articles list is here")
    })
    g.Run(":9000")
}

Expectations

For example: /articles?page=1 is correct but /articles?page=stringishere throws panic. How to resolve this situation and return correct error message {"errors":"page":"Number expected"}?

Environment

  • go version: go1.12.7 linux/amd64
  • gin version (or commit ref): 1.6.2
  • operating system: Linux Mint

Comment From: canercidam

@dimuska139 The panic comes from type assertion line:

errs := err.(validator.ValidationErrors)
interface conversion: error is *strconv.NumError, not validator.ValidationErrors

You probably need to do something like:

errs, ok := err.(validator.ValidationErrors)
if !ok {
    c.String(400, "Handle differently")
    return
}

or this, which is even better handling IMO:

switch err.(type) {
case validator.ValidationErrors:
    respErrors := make(map[string]interface{})
    for _, v := range err.(validator.ValidationErrors) {
        field, _ := reflect.TypeOf(&pag).Elem().FieldByName(v.Field())
        fieldName, _ := field.Tag.Lookup("form")
        respErrors[fieldName] = v.Tag()
    }
    c.JSON(http.StatusBadRequest, gin.H{"errors": respErrors})

case *strconv.NumError:
    c.String(400, "Handle differently")
}

Comment From: dimuska139

@canercidam yes, you are right. But this method doesn't allow to know which field has error. Because of strconv.NumError has no information about validation.

Comment From: canercidam

@dimuska139 True. I understand the problem better now.

The number conversion error comes from mapForm call inside Bind() and thus it's different than the error from validate(obj). I think the solution to your problem is setting the field empty, nil or zero in these cases by changing the overall logic in this file (and not returning these errors). Then I guess validate(obj) will fail with field errors.

It should also be possible to use a different query binding in ShouldBindWith(). I guess it is hard to prefer over the other solution but you might find something that works better for your case.

Comment From: crodwell

+1, same frustrating problem with gin not returning which field is at fault in the bind error

Comment From: ijingjingyang

+1 , Binding.Bind method should return framework customize error like validator.ValidationErrors when binding error

Comment From: nikzanda

+1, same problem...

Comment From: maxatome

Quickly done, this patch seems to work https://github.com/maxatome/gin/commit/6186ee22b451ee2100328101cb1590dcec02da2b I don't have time to do more test, but if someone wants to test further, feel free :)

To test use replace directive in go.mod:

replace github.com/gin-gonic/gin => github.com/maxatome/gin v0.0.0-20220113224038-6186ee22b451

Comment From: dimuska139

I think the only option is to validate JSON string (not structure) using jsonschema. But then the method ShouldBindQuery (and similar) will not be used.