-
Notifications
You must be signed in to change notification settings - Fork 8k
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
Custom Binding Error Message #430
Comments
+1 |
Assuming your using the default validator, v5, used by default in gin. The return error value for Bind is actually the type https://godoc.org/gopkg.in/bluesuncorp/validator.v5#StructErrors so you should be able to typecast the error to that, run the Flatten() to better represent the field errors and then you'll end up with a map[string]*FieldError which you can range over and create your own error message. See FieldError https://godoc.org/gopkg.in/bluesuncorp/validator.v5#FieldError err := c.Bind(&form)
// note should check the error type before assertion
errs := err.(*StructErrors)
for _, fldErr := range errs.Flatten() {
// make your own error messages using fldErr which is a *FieldError
} *Note the validator is currently at v8 which works slightly different but will still return the map of errors, hopefully it will be updated soon as v8 is much simpler and powerful, see #393 |
@joeybloggs That is what I'm currently doing.. would like a way to set custom error messages.. doing it that way just seems redundant and would rather just not use |
Let me add my 2 cents here 😄 I have an error handling middleware that handles all the parsing for me. Gin allows you to set different types of errors, which makes error handling a breeze. But you need to parse the bind errors 'manually' to get nice responses out. All of it can be wrapped in 3 stages:
You can see the all three in action below, but what's most important here is the package middleware
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/kardianos/service"
"github.com/stvp/rollbar"
"gopkg.in/bluesuncorp/validator.v5"
"net/http"
)
var (
ErrorInternalError = errors.New("Woops! Something went wrong :(")
)
func ValidationErrorToText(e *validator.FieldError) string {
switch e.Tag {
case "required":
return fmt.Sprintf("%s is required", e.Field)
case "max":
return fmt.Sprintf("%s cannot be longer than %s", e.Field, e.Param)
case "min":
return fmt.Sprintf("%s must be longer than %s", e.Field, e.Param)
case "email":
return fmt.Sprintf("Invalid email format")
case "len":
return fmt.Sprintf("%s must be %s characters long", e.Field, e.Param)
}
return fmt.Sprintf("%s is not valid", e.Field)
}
// This method collects all errors and submits them to Rollbar
func Errors(env, token string, logger service.Logger) gin.HandlerFunc {
rollbar.Environment = env
rollbar.Token = token
return func(c *gin.Context) {
c.Next()
// Only run if there are some errors to handle
if len(c.Errors) > 0 {
for _, e := range c.Errors {
// Find out what type of error it is
switch e.Type {
case gin.ErrorTypePublic:
// Only output public errors if nothing has been written yet
if !c.Writer.Written() {
c.JSON(c.Writer.Status(), gin.H{"Error": e.Error()})
}
case gin.ErrorTypeBind:
errs := e.Err.(*validator.StructErrors)
list := make(map[string]string)
for field, err := range errs.Errors {
list[field] = ValidationErrorToText(err)
}
// Make sure we maintain the preset response status
status := http.StatusBadRequest
if c.Writer.Status() != http.StatusOK {
status = c.Writer.Status()
}
c.JSON(status, gin.H{"Errors": list})
default:
// Log all other errors
rollbar.RequestError(rollbar.ERR, c.Request, e.Err)
if logger != nil {
logger.Error(e.Err)
}
}
}
// If there was no public or bind error, display default 500 message
if !c.Writer.Written() {
c.JSON(http.StatusInternalServerError, gin.H{"Error": ErrorInternalError.Error()})
}
}
}
} If you use something like this together with the binding middleware, your handlers never need to think about errors. Handler is only executed if the form passed all validations and in case of any errors the above middleware takes care of everything! r.POST("/login", gin.Bind(LoginStruct{}), LoginHandler)
(...)
func LoginHandler(c *gin.Context) {
var player *PlayerStruct
login := c.MustGet(gin.BindKey).(*LoginStruct)
} Hope it helps a little 😄 |
I can't get the Errors map:
Also is it:
or
Tried for an hour to get something other than.
I'd like to stick with c.Bind and stay within GIN. Other than use the validation lib directly. Thanks |
It's definitely http://gopkg.in/go-playground/validator.v8 It was very recently updated from v5 to v8 perhaps you just need to ensure the libs are updated? And the return value in v5 used to be StructError but now is ValidationErrors which is a flattened and much easier to parse map of errors. |
Thanks for getting back so quickly! Spinning my wheels and I figured it out. 👍 |
just for everyones information as of validator v9.1.0 custom validation errors are possible and are i18n and l10n aware using universal-translator and locales click here for instructions to upgrade gin to validator v9 |
Oh I changed the examples folder to _examples a while ago to avoid pulling in any external example dependencies, if any, when using go get, just modify the URL and the example is still there |
Working link for future reference - https://github.com/go-playground/validator/tree/v9/_examples/gin-upgrading-overriding |
since @sudo-suhas has added a new function @joeybloggs do you have any solutions? |
@Kaijun Have you tried vendoring your dependencies? You could use One possible way to resolve this would be to export the validator instance itself. That way I can call |
@sudo-suhas thanks, that should work for me |
@javierprovecho What do you suggest? Shall I make a PR to remove |
just my 2 cents, but it can be solved one of two ways:
Too keep Gin configurable I would expose |
I know this is old but I took liberty and try to little modify the code of @nazwa in accordance with "gopkg.in/go-playground/validator.v8" and also to get errors a little bit more readable package middleware
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/stvp/rollbar"
"gopkg.in/go-playground/validator.v8"
"net/http"
"strings"
"unicode"
"unicode/utf8"
)
var (
ErrorInternalError = errors.New("whoops something went wrong")
)
func UcFirst(str string) string {
for i, v := range str {
return string(unicode.ToUpper(v)) + str[i+1:]
}
return ""
}
func LcFirst(str string) string {
return strings.ToLower(str)
}
func Split(src string) string {
// don't split invalid utf8
if !utf8.ValidString(src) {
return src
}
var entries []string
var runes [][]rune
lastClass := 0
class := 0
// split into fields based on class of unicode character
for _, r := range src {
switch true {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
for index, word := range entries {
if index == 0 {
entries[index] = UcFirst(word)
} else {
entries[index] = LcFirst(word)
}
}
justString := strings.Join(entries," ")
return justString
}
func ValidationErrorToText(e *validator.FieldError) string {
word := Split(e.Field)
switch e.Tag {
case "required":
return fmt.Sprintf("%s is required", word)
case "max":
return fmt.Sprintf("%s cannot be longer than %s", word, e.Param)
case "min":
return fmt.Sprintf("%s must be longer than %s", word, e.Param)
case "email":
return fmt.Sprintf("Invalid email format")
case "len":
return fmt.Sprintf("%s must be %s characters long", word, e.Param)
}
return fmt.Sprintf("%s is not valid", word)
}
// This method collects all errors and submits them to Rollbar
func Errors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// Only run if there are some errors to handle
if len(c.Errors) > 0 {
for _, e := range c.Errors {
// Find out what type of error it is
switch e.Type {
case gin.ErrorTypePublic:
// Only output public errors if nothing has been written yet
if !c.Writer.Written() {
c.JSON(c.Writer.Status(), gin.H{"Error": e.Error()})
}
case gin.ErrorTypeBind:
errs := e.Err.(validator.ValidationErrors)
list := make(map[string]string)
for _,err := range errs {
list[err.Field] = ValidationErrorToText(err)
}
// Make sure we maintain the preset response status
status := http.StatusBadRequest
if c.Writer.Status() != http.StatusOK {
status = c.Writer.Status()
}
c.JSON(status, gin.H{"Errors": list})
default:
// Log all other errors
rollbar.RequestError(rollbar.ERR, c.Request, e.Err)
}
}
// If there was no public or bind error, display default 500 message
if !c.Writer.Written() {
c.JSON(http.StatusInternalServerError, gin.H{"Error": ErrorInternalError.Error()})
}
}
}
} P.S @nazwa thanx for your solution really appreciate it! |
middleware, written by @nazwa and modified by @roshanr83 is working perfectly fine, Only things I am missing here is the field's JSON tag. any way to get json tag in error messages? |
@nazwa @roshanr83 Can we set the content-type to |
I'vent tried it yet but I think you can access field's JSON tag in one of field of struct validator.FieldError. |
Try this on your controller method:
|
|
@gobeam So, now, how can I implement the handler? I'm currently unable to catch errors, I'm trying with |
@gobeam @ivan-avalos is this what you're looking for?
That way gin handles your binding automatically, and all errors are processed before your handler is even hit. This way you have a guarantee of a valid params object inside your handler. |
@surahmans did @gobeam solution for getting |
What is the current way to create custom validation error messages? The one's listed above are not working |
Hi I suggest to try the third part package ShyGinErrors first, you can define the validate rule with customize error message key in the data model. // error message key value
var requestErrorMessage = map[string]string{
"error_invalid_email": "please input a valid email",
"error_invalid_username": "username must be alphanumric, with length 6-32",
"error_invalid_password": "password length 6-32",
}
// specific
type RegisterForm struct {
Email string `json:"email" binding:"required,email" msg:"error_invalid_email"`
Username string `json:"username" binding:"required,alphanum,gte=6,lte=32" msg:"error_invalid_username"`
Password string `json:"password" binding:"required,gte=6,lte=32" msg:"error_invalid_password"`
} then, we can initialize the ShyGinError and use it to parse the err return by gin.BindJson() ge = NewShyGinErrors(requestErrorMessage)
req := model.RegisterForm{}
if err := reqCtx.Gin().BindJSON(&req); err != nil {
// get key value error messages: { "username":"username must be alphanumric, with length 6-32"}
errors := ge.ListAllErrors(req, err)
// error handling
} |
Hi you can custom error message multiple language. may be solution for me type LoginUser struct {
Email string `json:"email"`
Password string `json:"password"`
} handler.go lang := c.GetHeader("Accept-Language")
var req models.LoginUser
err := c.ShouldBindJSON(&req)
validation := req.Validation(lang)
if len(validation) > 0 {
errorMessage := gin.H{"errors": validation}
respon := helpers.ResponseApi("Failed Register", http.StatusBadRequest, "Failed", errorMessage)
c.JSON(http.StatusBadRequest, respon)
return
} validation.go func (v LoginUser) Validation(lang string) []string {
message := []string{}
if lang == "id" {
if len(v.Email) < 1 {
message = append(message, "invalid email :")
}
if len(v.Email) < 1 {
message = append(message, "invalid email :")
}
} else if lang == "en" {
if len(v.Email) < 1 {
message = append(message, "invalid email :")
}
if len(v.Email) < 1 {
message = append(message, "invalid email :")
}
}
return message
} |
Okey, let me drop another stone into the well. I upgraded the code of @gobeam,@nazwa for the v10 validator. package middleware
import (
"errors"
"fmt"
"net/http"
"strings"
"unicode"
"unicode/utf8"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/stvp/rollbar"
)
var (
ErrorInternalError = errors.New("whoops something went wrong")
)
func UcFirst(str string) string {
for i, v := range str {
return string(unicode.ToUpper(v)) + str[i+1:]
}
return ""
}
func LcFirst(str string) string {
return strings.ToLower(str)
}
func Split(src string) string {
// don't split invalid utf8
if !utf8.ValidString(src) {
return src
}
var entries []string
var runes [][]rune
lastClass := 0
class := 0
// split into fields based on class of unicode character
for _, r := range src {
switch true {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
for index, word := range entries {
if index == 0 {
entries[index] = UcFirst(word)
} else {
entries[index] = LcFirst(word)
}
}
justString := strings.Join(entries, " ")
return justString
}
func ValidationErrorToText(e validator.FieldError) string {
word := Split(e.Field())
switch e.Tag() {
case "required":
return fmt.Sprintf("%s is required", word)
case "max":
return fmt.Sprintf("%s cannot be longer than %s", word, e.Param())
case "min":
return fmt.Sprintf("%s must be longer than %s", word, e.Param())
case "email":
return fmt.Sprintf("Invalid email format")
case "len":
return fmt.Sprintf("%s must be %s characters long", word, e.Param())
}
return fmt.Sprintf("%s is not valid", word)
}
// This method collects all errors and submits them to Rollbar
func Errors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// Only run if there are some errors to handle
if len(c.Errors) > 0 {
for _, e := range c.Errors {
// Find out what type of error it is
switch e.Type {
case gin.ErrorTypePublic:
// Only output public errors if nothing has been written yet
if !c.Writer.Written() {
c.JSON(c.Writer.Status(), gin.H{"Error": e.Error()})
}
case gin.ErrorTypeBind:
errs := e.Err.(validator.ValidationErrors)
list := make(map[string]string)
for _, err := range errs {
list[err.Field()] = ValidationErrorToText(err)
}
// Make sure we maintain the preset response status
status := http.StatusBadRequest
if c.Writer.Status() != http.StatusOK {
status = c.Writer.Status()
}
c.JSON(status, gin.H{"Errors": list})
default:
// Log all other errors
rollbar.RequestError(rollbar.ERR, c.Request, e.Err)
}
}
// If there was no public or bind error, display default 500 message
if !c.Writer.Written() {
c.JSON(http.StatusInternalServerError, gin.H{"Error": ErrorInternalError.Error()})
}
}
}
} |
@ndasim Hi, thank you for your solution. But I could not solve the problem which is that response content type is text instead of json. |
@ismailbayram I assume it is because you are using |
Thank you for your reply, but I've figured out myself like below if err := ctx.ShouldBindJSON(&yourStruct); err != nil {
ctx.Error(err).SetType(gin.ErrorTypeBind)
return
} |
After calling
c.Bind(&form)
, is it possible to provide a custom validation error message per field rather than the generic "Field validation for '...' failed on the '...' tag"The text was updated successfully, but these errors were encountered: