DEV Community

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

Posted on β€’ Edited on

11

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...

Sentry image

See why 4M developers consider Sentry, β€œnot bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

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.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay