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.