Better Validation Errors in Go Gin

Sebastian Nyberg
2 min readSep 10, 2019

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"}

--

--