DEV Community

Cover image for Go struct validation : the idiomatic way
Pankhudi Bhonsle
Pankhudi Bhonsle

Posted on • Edited on

Go struct validation : the idiomatic way

Validations, in general, are one of the most common problems across all programming languages.
Before I start explaining how to do it idiomatically in golang... Let me briefly explain why I dived into it in the first place.

While working on a project with tonnes of micro-services,
there was a need to maintain build time configurations for each of them.

This meant validating those configurations as well. Otherwise the micro-services would have been at the risk of runtime failures! 🤕

So let's say we have the below configuration in config.json

{
  "server_url": "https://some-server-url",
  "app_port": 8080
}
Enter fullscreen mode Exit fullscreen mode

This can then be encapsulated in a golang struct:

type Config struct {
    ServerUrl string `json:"server_url"`
    AppPort   int    `json:"app_port"`
}
Enter fullscreen mode Exit fullscreen mode

We would load the config.json to the struct:

import (
    "encoding/json"
    "errors"
    "fmt"
    "os"      
)

func LoadConfig() (*Config, error) {
    configFile, err := os.Open("configuration/config.json")
    if err != nil {
        log.Fatal("Could not open config file : ", err.Error())
    }

    decoder := json.NewDecoder(configFile)
    config := Config{}

    decodeErr := decoder.Decode(&config)
    if decodeErr != nil {
        log.Fatal("Could not decode config file : ", decodeErr.Error())
    }

    if !Validate(config) {
        return nil, errors.New("Invalid config !")
    }
    return &config, nil
}
Enter fullscreen mode Exit fullscreen mode

Now, let's say we want to validate:

  1. Server URL and port are non-empty
  2. The value of port is numeric and is between 8080 and 8085

Now the straight-forward way would be :

Write if-else conditional code to validate each of them. Something like:

func Validate(config Config) bool {
    if config.ServerUrl == "" {
        return false
    }
    if config.AppPort == 0 {
        return false
    }
    if config.AppPort >= 8080 || config.AppPort <= 8085 {
        return false
    }
    return true
}
Enter fullscreen mode Exit fullscreen mode

This can get pretty messy with more such fields and validations. Yikes! 😵‍💫

There's got to be a better way (Otherwise this blog wouldn't exist ! )

A cleaner, idiomatic way to do this is to use struct validations

type Config struct {
    ServerUrl string `json:"server_url" validate:"required"`
    AppPort   int    `json:"app_port" validate:"required,numeric,gte=8080,lte=8085"`
}    
Enter fullscreen mode Exit fullscreen mode

Declarative and closer to the struct definition. Isn't that beautiful!

Now our validate function would look something like this:


func Validate(config Config) bool {
    validate := validator.New()
    err := validate.Struct(config)
    if err != nil {
        fmt.Println("Invalid config !", err.Error())
        return false
    }
    return true
}
Enter fullscreen mode Exit fullscreen mode

Furthermore, we can have more meaningful errors to point out failing validations:

import "github.com/go-playground/validator/v10"

func Validate(config Config) bool {
    validate := validator.New()
    err := validate.Struct(config)
    if err != nil {
        fmt.Println("Invalid config !")
        for _, validationErr := range err.(validator.ValidationErrors) {
            fmt.Println(validationErr.StructNamespace() + " violated " + validationErr.Tag() + " validation.")
        }
        return false
    }
    return true
}    
Enter fullscreen mode Exit fullscreen mode

The final gist would look something like this:
https://github.com/PankhudiB/go-config-validation/blob/main/main.go

Head over to the awesome validator library for more such tags...

"But Pankhudi, what if I need to add validations of my own?" 🤔 - you ask.

To this, I answer head over to the next blog in the series...

Top comments (3)

Collapse
 
reacthunter0324 profile image
React Hunter

Great!

Collapse
 
shaswatprabhat profile image
Shaswat Prabhat

Very nicely explained.
I assume we can use similar methods for other kinds of validations like yaml?

Collapse
 
pankhudib profile image
Pankhudi Bhonsle

Yes, definitely...the validator library is agnostic of how you store the data. It acts on top of a struct.