I'm using Go 1.17 with Gin and I want to implement a struct validation before sending the data to my database. I took the example from Gin documentation.
In the struct we can declare different tags to validate a field like this:
type User struct {
FirstName string `json:"first_name" binding:"required"`
LastName string `json:"last_name" binding:"required"`
Age uint8 `json:"age" binding:"gte=0,lte=130"`
Email string `json:"email" binding:"required,email"`
FavouriteColor string `json:"favourite_color" binding:"iscolor"`
}
And in the handler I can grab the error like this:
var u User
if err := c.ShouldBindWith(&u, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "Good Job"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
The error message will be:
{
"error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'required' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'required' tag\nKey: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FavouriteColor' Error:Field validation for 'FavouriteColor' failed on the 'iscolor' tag"
}
The error messages are too verbose how it's possible to returns a better error to the user? I'd like to model the json response like:
{
"errors": [
"first_name": "This field is required",
"last_name": "This field is required",
"age": "This field is required",
"email": "Invalid email"
]
}
CodePudding user response:
Gin gonic uses the package github.com/go-playground/validator/v10
to perform binding validation. If the validation fails, the error returned is a validator.ValidationErrors
.
This is not mentioned explicitly but here in Model binding and validation it states:
Gin uses go-playground/validator/v10 for validation. Check the full docs on tags usage here.
That links to the go-playground/validator/v10
documentation, where you find the paragraph Validation Functions Return Type error.
You can use the standard errors
package to check if the error is that, unwrap it, and access the single fields, which are validator.FieldError
. From that, you can construct whatever error message you want.
Given an error model like this:
type ApiError struct {
Field string
Msg string
}
You can do this:
var u User
err := c.BindQuery(&u);
if err != nil {
var ve validator.ValidationErrors
if errors.As(err, &ve) {
out := make([]ApiError, len(ve))
for i, fe := range ve {
out[i] = ApiError{fe.Field(), msgForTag(fe.Tag())}
}
c.JSON(http.StatusBadRequest, gin.H{"errors": out})
}
return
}
with a helper function to output a custom error message for your validation rules:
func msgForTag(tag string) string {
switch tag {
case "required":
return "This field is required"
case "email":
return "Invalid email"
}
return ""
}
In my test, this outputs:
{
"errors": [
{
"Field": "Number",
"Msg": "This field is required"
}
]
}
PS: To have a json output with dynamic keys, you can use map[string]string
instead of a fixed struct model.