Payload validation in Go with Validator

Read more articles on Go
Payload validation in Go with Validator

Subscribe

Get updated 1 - 2 times a month when new articles are published, no spam ever.

Getting Started #

In this article you are going to learn how to use the go-playground/validator package to validate data in your go application. validator uses struct tags to specify the validation constraints. This allows seamless integration to existing applications without the need of excess re-write of existing code.

Installing validator #

Create a new go project and using the go get command install the validator package as follows.

go get github.com/go-playground/validator/v10

At the time of writing, the current version of validator is v10 do check the GitHub Page to latest releases.

An example #

Let's take the example of signing up a user, we want email, password and an optional name.

type SignUpPayload struct {
	Email    string
	Password string
	Name     string
}

Following are the validation requirements we want for our fields.

  • Email is required and should be in email format.
  • Password is required, should be minimum 8 characters and should contain atleast once special character from the set of !@#?.
  • Name is optional, but if provided should be minimum 4 characters long.

Updating the example, adding struct tags.

type SignUpPayload struct {
	Email    string  `validate:"required,email"`
	Password string  `validate:"required,min=8,containsany=!@#?*"`
	Name     string `validate:"omitempty,min=4"`
}

A lot of these tags should be self explanatory, requried means fields cannot be empty, email checks for email format, min checks of minimum length, containsany checks for existence of atleast once specified character.

You should use omitempty when a field is optional, but when any value to the field is provided the validators will kick in.

The struct is ready, let's use the validator to actually validate an instance of this struct.

func main() {
	// creating a signup payload
	payload := SignUpPayload{
		Email:    "[email protected]",
		Password: "securepass#",
	}
	
    // get an instance of a validator
	v := validator.New()
	
    // call the `Struct` function passing in your payload
	err := v.Struct(payload)
	if err != nil {
		fmt.Println(err)
	}
}

The above payload conforms to the validation rules provided previously so err will be nil. Let's see an exampel that will fail.

func main() {
	payload := SignUpPayload{
		Email:    "test@testcom", // šŸ‘ˆ invalid email
		Password: "securepass", // šŸ‘ˆ missing required character
		Name:     "123", // šŸ‘ˆ min length is not 4
	}

	v := validator.New()

	err := v.Struct(payload)
	if err != nil {
		fmt.Println(err)
	}
}

Running this example will print the following in the console.

Key: 'SignUpPayload.Email' Error:Field validation for 'Email' failed on the 'email' tag
Key: 'SignUpPayload.Password' Error:Field validation for 'Password' failed on the 'containsany' tag
Key: 'SignUpPayload.Name' Error:Field validation for 'Name' failed on the 'min' tag

Error handling #

While checking if err is not nil is enough to know that validation failed, sometimes you may want to have more information from the error produced. The errors procuded by validator is of type validator.ValidationErrors, so you can cast the err to this type and obtain more information. Let me show you how.

func main() {
	payload := SignUpPayload{...}

	v := validator.New()

	err := v.Struct(payload)
    if err == nil {
    	return
    }

	// doing due diligence by checking cast was successful
	validationErr, ok := err.(validator.ValidationErrors)
	if !ok {
		fmt.Println(err)
		return
	}

	for _, vErr := range validationErr {
		fmt.Printf("'%s' has a value of '%v' which does not satisfy '%s'.\n", vErr.Field(), vErr.Value(), vErr.Tag())
	}
}

validator.ValidationErrors is actually a slice of validator.FieldError, so you can loop over all the errors.

The output of the above program will be:

'Email' has a value of 'test@testcom' which does not satisfy 'email'.
'Password' has a value of 'securepass' which does not satisfy 'containsany'.
'Name' has a value of '123' which does not satisfy 'min'.

Validating variables #

Validator provides the ability to validate individual variables using Var function. First argument is the value to validate and second are the validation tags (just like in struct validation).

func main() {
	var password = "securepass"

	v := validator.New()

	// šŸ‘‡
	err := v.Var(password, "required,min=8,containsany=!@#?*")
	if err != nil {
		fmt.Println(err)
	}
}

End #

Recommendation: A point to note when using validator in your application is to use the same instace of validator throughout the application i.e. v := validator.New(), use the same v across your application. The reason as specified in the soruce code of validator:

It caches information about your struct and validations, in essence only parsing your validation tags once per struct type. Using multiple instances neglects the benefit of caching.

Valdiator provides over 100 validation tags, checkout the documentation here.

Thank you for reading this article šŸ™šŸ» more content coming soon.

Subscribe

Get updated 1 - 2 times a month when new articles are published, no spam ever.

    TheDeveloperCafe Ā© 2022-2024