The Wayback Machine - https://web.archive.org/web/20200630223957/https://github.com/gin-gonic/gin/issues/2334
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation issue #2334

Open
dimuska139 opened this issue Apr 21, 2020 · 4 comments
Open

Validation issue #2334

dimuska139 opened this issue Apr 21, 2020 · 4 comments

Comments

@dimuska139
Copy link

@dimuska139 dimuska139 commented Apr 21, 2020

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
@canercidam
Copy link

@canercidam canercidam commented Apr 22, 2020

@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")
}
@dimuska139
Copy link
Author

@dimuska139 dimuska139 commented Apr 22, 2020

@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.

@canercidam
Copy link

@canercidam canercidam commented Apr 22, 2020

@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.

@dimuska139
Copy link
Author

@dimuska139 dimuska139 commented Apr 22, 2020

@canercidam I've fixed it by using thedevsaddam/govalidator before ShouldBindQuery but I think that it's not a good solution.

rules := govalidator.MapData{
    "page":      []string{"required", "numeric", "numeric_between:1,"},
    "page_size": []string{"required", "numeric", "numeric_between:1,100"},
}

type Paginator struct {
    Page     int `form:"page"`
    PageSize int `form:"page_size"`
}

var pag Paginator

opts := govalidator.Options{
    Request: c.Request,
    Data:    &pag,
    Rules:   rules,
}

v := govalidator.New(opts)
errs := v.Validate()
if len(errs) != 0 {
    c.JSON(http.StatusOK, gin.H{
        "errors": errs,
        "meta":   nil,
        "data":   nil,
    })
    return
}
c.ShouldBindQuery(&pag)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
2 participants
You can’t perform that action at this time.