How to write validation in Golang?

If you just learning golang maybe it's time to write validation on your http request for your backend application. There's a lot of library that just do that, if you want a quick solution for that you can choose one of these library.

  1. asaskevich/govalidator
  2. validator
  3. thedevsaddam/govalidator

But, if you're just learning Go, I suggest you write your own validation, it's really easy, and you're in the right place. We will explore how to create a validation for HTTP requests in Go.

Writing validation using Struct

Let's design how are going to use the validation. There's many way we can do that, but go is simple language with simple syntact. The most easy way to place validation role is using struct meta tag.

Here's example struct that we will use.

type RequestCreateProject struct {
	ID          int64       `json:"id,omitempty"`
	Name        string      `json:"name" validate:"required"`
	Slug        string      `json:"slug" validate:"required,slug"`
	Description string      `json:"description"`
	Tags        []string    `json:"tags"`
}

This is example request body.

PATCH /api/projects
Content-type: application/json

{
	"id":null,
	"name": "",
	"slug": "inside#reporta",
	"description": "Marketing tool for reporting campaigns",
	"tags":["marketing"]
}

And let's expect the error message would be like this.

{
    "errors": [
        "ID field is required.",
        "Name field is required.",
        "Slug Field must be alpha numeric"
    ],
    "status": 422,
    "title": "Unprocessable Entity"
}

Go reflect

So now we have the concept for how are going to do the validation, now let's find out how we can implement that in go.

So go have standar library to work with struct it called reflect this library work in runtime reflection it's allow us to manipulate object with arbitrary type, but one thing to remember is we should becareful when using it. Because if there's some error in our code it will not detected by the compiler.

So let's write basic validation.

func ValidateStruct(input interface{}) []error {
	errs := []error{}

	value := reflect.ValueOf(input)
	if value.Kind() == reflect.Ptr {
		value = reflect.Indirect(value)
	}

	for i := 0; i < value.NumField(); i++ {
		tag := value.Type().Field(i).Tag.Get(tagName)

		if tag == "" || tag == "-" {
			continue
		}

		validator := getValidatorFromTag(tag)

		valid, err := validator.Validate(value.Field(i).Interface())

		if !valid && err != nil {
			field := value.Type().Field(i).Name
			errs = append(errs, fmt.Errorf("%s %s", field, err.Error()))
		}
	}

	return errs
}

This function returning array of error since we will processing multiple tag in our validation. Here's reflect function that you need to know.

  • reflect.ValueOf(input) is function that we can use to get the reflect instance from the params in this case interface{}
  • To handle the case when the object is pointer we can use function reflect.Indirect(value) and we can get reflect type using value.Kind() and the reflect object for pointer is reflect.Ptr the pointer it self come from param when we passing the input like this ValidateStruct(&input)
  • The struct will have multiple properties so we can get the length of the properties using value.NumField()
  • And to get the tag of the properties
value.Type().Field(i).Tag.Get(tagName)
  • The tagName is the keywork tag that we use for this validation which is the value validation see the struct example above.

And here's the backend that we can use to process the tag.

package validator

import (
	"fmt"
	"reflect"
	"strings"
)

var tagName = "validate"

type Validator interface {
	Validate(interface{}) (bool, error)
}

type DefaultValidator struct {
}

func (v DefaultValidator) Validate(val interface{}) (bool, error) {
	return true, nil
}

func getValidatorFromTag(tag string) Validator {
	args := strings.Split(tag, ",")

	switch args[0] {
	case "slug":
		validator := SlugValidator{}
		return validator
	case "required":
		validator := RequiredValidator{}
		nextValidator := strings.Join(args[1:], ",")
		if nextValidator != "" {
			validator.Next = getValidatorFromTag(nextValidator)
		}
		return validator
	}

	return DefaultValidator{}
}

Validation Rule

With the base validation done, now we can just add the validation rule like this. We just need to create struct that implement Validate(interface{}) function.

type SlugValidator struct {
}

func IsDigit(r rune) bool {
	return '0' <= r && r <= '9'
}

func IsAlpha(r rune) bool {
	return r == '_' || 'a' <= r && r <= 'Z' || 'A' <= r && r <= 'Z'
}

func IsValidSlug(s string) bool {
	for _, r := range s {
		if IsAlpha(r) || IsDigit(r) || r == '-' {
			return true
		}
	}

	return false
}

func (v SlugValidator) Validate(val interface{}) (bool, error) {
	if res, ok := val.(string); ok {
		if strings.Contains(res, " ") {
			return false, fmt.Errorf("Field must not have space")
		}
		if !lib.IsValidSlug(res) {
			return false, fmt.Errorf("Field must be alpha numeric")
		}
	} else {
		return false, fmt.Errorf("Field must be string")
	}

	return true, nil
}

And here's one more example for required falidation.

type RequiredValidator struct {
	Next Validator
}

func (v RequiredValidator) Validate(val interface{}) (bool, error) {
	slice := reflect.ValueOf(val)
	if slice.Kind() == reflect.Slice {
		if slice.Len() == 0 {
			return false, fmt.Errorf("field is required.")
		}

		return true, nil
	}

	if res, ok := val.(string); ok && res == "" {
		return false, fmt.Errorf("field is required.")
	}

	if res, ok := val.(int64); ok && res == 0 {
		return false, fmt.Errorf("field is required.")
	}

	if v.Next != nil {
		return v.Next.Validate(val)
	}

	return true, nil
}

Conclusion

Thus, if we write our own validation, it will be easier for us to change the rule of the validation in our code. Yes, it will faster to just use the library available on github. However, we will be able to modify the validation rule more quickly if we are able to write it ourselves.