Better Validation Errors in Go Gin
Out-of-the-box errors provided by the validation library used by Go Gin aren’t that great.
Running the server
go run main.go
And sending a request yields us:
$ curl localhost:8080/car
"Key: 'Name' Error:Field validation for 'Name' failed on the 'required' tag"
Retrieving errors from Validator
Gin uses the go-playground/validator under the hood. When ShouldBind fails, the error returned is actually a map[string]validator.FieldError
which maps the field name to its error.
if err := c.ShouldBind(&query); err != nil {
for _, fieldErr := range err.(validator.ValidationErrors) {
c.JSON(http.StatusBadRequest, fieldErr)
return // exit on first error
}
}
Porting from validator.v8 to v9
If you just formatted your main.go file, you’ll notice the new import validator.v8
. This is (as of writing) the default validator for Gin. The latest version of validator however is v9
. To upgrade to v9, you need to create your own validator and assign it tobinding.validator
. By following the upgrade guide, you should end up with something like this:
Note: now with v9, the FieldError
struct has turned into an interface, which means we need to pass the error to fmt.Sprint
to get some output (don’t worry, it’s just temporary).
c.JSON(http.StatusBadRequest, fmt.Sprint(fieldErr))
Running
go run main.go v8_to_v9.go
And sending a request once again to localhost:8080/car
yields once again:
$ curl localhost:8080/car
"Key: 'Name' Error:Field validation for 'Name' failed on the 'required' tag"
Composing the validation message
The validator.FieldError
interface contains a number of functions that can be used to compose an error message, you can see them all here.
By wrapping the validator.FieldError
in our own fieldError
struct, we can implement the Stringer interface and get our own, custom validation message:
By initializing a fieldError
with a validator.FieldError
, we can now get some decent output from ShouldBind
:
c.JSON(http.StatusBadRequest, fieldError{fieldErr}.String())
Start the server and try once again:
go run main.go v8_to_v9.go fielderr.go
Output is now:
$ curl localhost:8080/car
"validation failed on field Name, condition: required"
Using the form tags as field names
As you could see in the error above, the Struct field name is used in the error message (returned by fieldErr.Field()
). To instead print the form
tag name (e.g. form:”name"
) we can add validator.RegisterTagNameFunc()
to the lazyinit
function that was added as part of the v8 -> v9 transition:
func (v *defaultValidator) lazyinit() {
v.once.Do(func() {
v.validate = validator.New()
v.validate.SetTagName("binding") // Print JSON name on validator.FieldError.Field()
v.validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("form"), ",", 2)[0] return name
})
})
}
Now, both the string
in the map[string]validator.FieldError
, and the validator.FieldError.Field()
function returns the JSON name from the tag, resulting in:
$ curl localhost:8080/car
"validation failed on field 'name', condition: required"$ curl localhost:8080/car?name=volvo
"validation failed on field 'color', condition: required"$ curl 'localhost:8080/car?name=volvo&color=red'
"validation failed on field 'color', condition: oneof { blue yellow }, actual: red"$ curl 'localhost:8080/car?name=volvo&color=blue'
{"status":"ok"}