DEV Community

Pankhudi Bhonsle
Pankhudi Bhonsle

Posted on

Go custom struct validation - the idiomatic way

In the previous blog, we had gone over struct validations in golang using validator library. Continuing on, in this blog, we'll cover custom validations.

Let's say for the below struct:

type Config struct {
    ServerUrl string
    AppPort   int
}
Enter fullscreen mode Exit fullscreen mode

We could validate our struct with validations provided by the library like:

import (
    "fmt"
    "github.com/go-playground/validator"
    "strings"
)

func Validate(config Config) bool {
    validate := validator.New()
    validationErr := validate.Struct(config)
    if validationErr != nil {
        return false    
    }
    return true
}
Enter fullscreen mode Exit fullscreen mode

Now onto the topic of custom validations.

Say, we want to check whether ServerUrl field is an HTTPS URL
The straight forward way would be to introduce a function:

func customValidator(config Config) bool {
    value := config.ServerUrl
    if !strings.HasPrefix(value, "https://") {
        return false
    }
    return true
}
Enter fullscreen mode Exit fullscreen mode

and remember to invoke it in our Validate() func 💡

Or we could do it the idiomatic way and leverage validator library ❤️

The custom validation function would then look like:

import (
    "github.com/go-playground/validator"
    "strings"
)

func CustomValidation(fl validator.FieldLevel) bool {
    value := fl.Field().String()
    if !strings.HasPrefix(value, "https://") {
        return false
    }
    return true
}
Enter fullscreen mode Exit fullscreen mode

"Why this weird function signature?" 🤔 you ask - hold on to that question for a short while...

The next step is to register our shiny 🌟 new rule with the validator library.

import (
    "fmt"
    "github.com/go-playground/validator"
    "strings"
)

func Validate(config Config) bool {
    validate := validator.New()

    // Registering the new rule "is_https" ...
    err := validate.RegisterValidation("is_https", CustomValidation)
    if err != nil {
        fmt.Println("Error registering custom validation :", err.Error())
    }

    validationErr := validate.Struct(config)
    if validationErr != nil {
        return false    
    }
    return true
}
Enter fullscreen mode Exit fullscreen mode

Now that our rule is registered let us attach the tag to our struct's field:

type Config struct {
    // Careful, the tag name should be identical to the one registered
    ServerUrl string `validate:"is_https"`
    AppPort   int 
}
Enter fullscreen mode Exit fullscreen mode

And Voilà! 🎉 we are done!

Now, back to the question of this weird signature of CustomValidation() function -

Observe validate.RegisterValidation() takes in:

  1. A tag of how you want to refer the validation in your struct.
  2. A validator.Func - essentially a function of type: func(fl FieldLevel) bool (reference source code)

Even validations at struct level - ones that involve multiple fields of the same struct - are easily achievable with similar function signature: func(fl StructLevel) bool

This is how the final code looks like: https://github.com/PankhudiB/go-config-validation/blob/main/configuration/custom_validation.go

Happy Coding! 👩‍💻

Top comments (0)