3

I have this code:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

type TestForm struct {
    Age  int    `form:"age" binding:"required"`
    Name string `form:"name" binding:"required"`
}

func home(c *gin.Context) {
    c.HTML(200, "index.html", nil)
}

func homePost(c *gin.Context) {
    var f TestForm
    err := c.Bind(&f)
    c.String(200, fmt.Sprintf("%v : %v", err, f))
}

func main() {
    r := gin.Default()
    r.LoadHTMLGlob("templates/*")
    r.Use(gin.Recovery())
    r.GET("/", home)
    r.POST("/", homePost)
    r.Run()
}

and templates/index.html :

<!doctype html>
<html>
<head></head>
<body>
<h1>hello</h1>
<form action="/" method="POST">
<input type="text" name="name">
<input type="text" name="age"> 
<input type="submit">

</form>
</body>
</html>

When the fields fail binding/validation, I would like to iterate over all the fields in order to render an error in HTML on the matching field.

If I input test as name leave age blank, the response is this:

Key: 'TestForm.Age' Error:Field validation for 'Age' failed on the 'required' tag : {0 asd}

I have found the err returned from err := c.Bind(&f) to be the type validator.ValidationErrors which allows me to do exactly this, so far all is good.

However if I input test as name and abc as age` (or eve just input a number with a space after it), the response is this:

strconv.ParseInt: parsing "abc": invalid syntax : {0 }

So, just a generic error that somewhere something failed to parse an integer.

Now I have no way of knowing which field failed, nor do I get any info about other fields failing validation. Is there any way to have gin tell me that the age field failed in this case ?

binary01
  • 1,728
  • 2
  • 13
  • 27
  • 1
    That's how `Bind` works, the nice errors come from Validation which happends only after binding finished successfully. If `Bind` cannot set a value to a field (the string `"abc"` to an `int` field for example) then it returns an error without ever invoking the validation. Validation can happen only when the fields are already set by binding... One workaround is to use `ShouldBind` instead of `Bind`, the `ShouldBindXxx` group of methods returns the error to you instead of just responding with `400` like `Bind` does. – mkopriva Aug 19 '18 at 19:33
  • ... but you'll have to do some manual work, you'll have to turn the `ParseInt` error into something you can use in the html template. How you do that is up to you. – mkopriva Aug 19 '18 at 19:34
  • Ok, are there other frameworks that provides a more friendly binding and validation support, so it's easier to inform users about which field they messed up - without doing it all on the frontend ? – binary01 Aug 19 '18 at 19:41
  • I don't know, maybe there are but I don't know any so you'll have to research that yourself. – mkopriva Aug 19 '18 at 20:06
  • @binary01 Did you manage to find a solution to this problem? – gogofan Jun 19 '19 at 16:28

1 Answers1

0

I've tried and failed to do this using the built in playground validation. There just wasn't any easy way to access the actual field name that's exposed to the frontend.

Because of this, I went with a custom validation approach. It's a lot more readable (frontend + backend), specially when you have custom validators which are really ugly to setup with playground validation. You just have to write them once and then reuse validators by embedding structs basically.

I detailed an example of how I use them here: More elegant way of validate body in go-gin

Basically, I always use models (query models, body models, etc.) for reasons I detailed in that post. This allows me to easily add a Validate() method to them:

type User struct {
    UserId            string                     `form:"user_id"`
    Name              string                     `form:"name"`
}

func (user *User) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}

Example of reuse:

type OtherUser struct {
    User       // embedded struct here
    HasOther  bool  `form:"has_other"`
}

func (other *OtherUser) Validate() errors.RestError {
    // extra validation goes here for example
    return other.User.Validate()
}
Rami Awar
  • 663
  • 7
  • 28