Home > database >  Return custom error message from struct tag validation
Return custom error message from struct tag validation

Time:11-23

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.

  • Related