Table of contents
1.
Introduction
2.
Retrieving errors from Validator
3.
Porting Errors from the validator.v8 to v9
4.
Composing the validation messages
5.
Frequently Asked Questions 
5.1.
What are the data types used in Golang?  
5.2.
What are Goroutines? 
5.3.
Can we declare multiple variables at once in Go? 
5.4.
Which keyword is used to import the packages in Go language? 
5.5.
What is the GOPATH environment variable in go programming?
6.
Conclusion
Last Updated: Mar 27, 2024
Medium

Better Validation Errors in Go Gin

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

As we all know, Go is a famous programming language and Gin is a high-performance HTTP web framework written in Golang (Go). Gin allows you to build web applications and microservices in Go. It contains a set of commonly used functionalities (e.g., routing, middleware support, rendering, etc.) that reduce boilerplate code and make it simpler to build web applications.

intro image

In this blog, we will learn about the better validation errors in Go Gin. We will see how we can retrieve errors from Validator and port from Validator v8 to v9, and in the end, we will see how we can compose a validation message.

Retrieving errors from Validator

As we know that the errors provided by the validation library used by Go Gin are not that great. Gin internally uses httprouter, but it also uses go-playground/validator to validate incoming requests. The validator uses a struct tag to determine what to check and how to validate the struct field. 
 

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/codingninjas", func(c *gin.Context) {
        var query struct {
            Ninja string `form:"ninja" json:"ninja" binding:"required"`
            Code  string `form:"code" json:"code" binding:"required"`
        }

        if err := c.ShouldBind(&query); err != nil {
            c.JSON(http.StatusBadRequest, err.Error())
            return
        }

        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })

    r.Run()
}

 

After sending a request to http://localhost:8080/codingninjas,

Output:

output

Porting Errors from the validator.v8 to v9

If you have formatted your main.go file, you must have noticed the new import of validator.v8, which is the old version of the Validator. But this is the default validator of Gin. However, the latest version of Gin is v9. If we want to upgrade to validator.v9, we must create our Validator and assign it to binding.validator
 

1. First create a main.go file,

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
)

func main() {
    binding.Validator = new(defaultValidator)

    r := gin.Default()

    r.GET("/codingninjas", func(c *gin.Context) {
        var query struct {
            Ninja string `form:"ninja" json:"ninja" binding:"required"`
        }

        if err := c.ShouldBind(&query); err != nil {
            c.JSON(http.StatusBadRequest, err.Error())
            return
        }

        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })

    r.Run()
}

 

2. Now make another file named v8_to_v9.go, now copy this code to this file,

package main

import (
    "reflect"
    "sync"

    "github.com/gin-gonic/gin/binding"
    "gopkg.in/go-playground/validator.v9"
)

type defaultValidator struct {
    once     sync.Once
    validate *validator.Validate
}

var _ binding.StructValidator = &defaultValidator{}

func (v *defaultValidator) ValidateStruct(obj interface{}) error {

    if kindOfData(obj) == reflect.Struct {

        v.lazyinit()

        if err := v.validate.Struct(obj); err != nil {
            return error(err)
        }
    }

    return nil
}

func (v *defaultValidator) Engine() interface{} {
    v.lazyinit()
    return v.validate
}

func (v *defaultValidator) lazyinit() {
    v.once.Do(func() {
        v.validate = validator.New()
        v.validate.SetTagName("binding")

        // add any custom validations etc. here
    })
}

func kindOfData(data interface{}) reflect.Kind {

    value := reflect.ValueOf(data)
    valueType := value.Kind()

    if valueType == reflect.Ptr {
        valueType = value.Elem().Kind()
    }
    return valueType
}

 

3. After this to run just type

 go run main.go v8_to_v9.go 

 

After sending a request to http://localhost:8080/codingninjas

Output:

output

Composing the validation messages

The validator.feldError interface contains a number of functions that can be used to compose any error message if we wrap the validator.FieldError in our own fieldError struct, then we can get our own custom validation message:

1. Make a new file named  fielderror.go and copy this code in that file

package main

import (
    "fmt"
    "strings"

    "gopkg.in/go-playground/validator.v9"
)

type fieldError struct {
    err validator.FieldError
}

func (q fieldError) String() string {
    var sb strings.Builder

    sb.WriteString("validation failed on the field '" + q.err.Field() + "', condition: " + q.err.ActualTag())

    if q.err.Param() != "" {
        sb.WriteString(" { " + q.err.Param() + " }")
    }

    if q.err.Value() != nil && q.err.Value() != "" {
        sb.WriteString(fmt.Sprintf(", actual: %v", q.err.Value()))
    }

    return sb.String()
}

 

2. Now update the main.go file with this code:

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "gopkg.in/go-playground/validator.v9"
)

func main() {
    binding.Validator = new(defaultValidator)

    r := gin.Default()

    r.GET("/codingninjas", func(c *gin.Context) {
        var query struct {
            Ninja string `form:"ninja" json:"ninja" binding:"required"`
        }

        if err := c.ShouldBind(&query); err != nil {
            for _, fieldErr := range err.(validator.ValidationErrors) {
                c.JSON(http.StatusBadRequest, fmt.Sprint(fieldErr))
                return // exit on the first error
            }
        }

        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })

    r.Run()
}

 

3. After the above step, run the following command in the terminal 

go run main.go v8_to_v9.go fielderror.go

 

Output:

output

Frequently Asked Questions 

What are the data types used in Golang?  

Method, Boolean, Numeric, String, Array, Slice, Struct, Pointer, Function, Interface, Map, Channel

What are Goroutines? 

Goroutines are procedures or functions that operate parallel to other procedures or components. Goroutines can be compared to thin threads. When compared to the cost of a line, a goroutine is extremely inexpensive. Go applications frequently have thousands of Goroutines running at once. 

Can we declare multiple variables at once in Go? 

Yes, we can declare multiple variables at once in Go.

var v1, v2, ... type

Which keyword is used to import the packages in Go language? 

The import keyword is used to import packages into our Go language program, as well as to import packages into other packages.

What is the GOPATH environment variable in go programming?

The GOPATH environment variable basically specifies the location of the workspace. You must have to set this environment variable while developing Go code.

Conclusion

In this blog, we have learned about the better validation errors in Go Gin. We have seen how we can retrieve errors from Validator and port from Validator v8 to v9, and in the end, we saw how we could compose a validation message.

Refer to our guided paths on Coding Ninjas Studio to learn more about DSA, Competitive Programming, JavaScript, System Design, etc. Enroll in our courses and refer to the mock test and problems available; look at the Top 150 Interview Puzzles interview experiences, and interview bundle for placement preparations. Read our blogs on aptitudecompetitive programminginterview questionsIT certifications, and data structures and algorithms for the best practice.

Live masterclass