Search code examples
validationgogo-gin

How to get field with error from gin binding/validation?


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 ?


Solution

  • 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()
    }