DEV Community

Pankhudi Bhonsle
Pankhudi Bhonsle

Posted on

13

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! 👩‍💻

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (1)

Collapse
 
mehrdadanvar profile image
Mdecode

wonderful. I was tying to write a FHIR server in go which also should validate a bunch of resources sent over to the server and inform of the client what might be wrong with the structure of a resource. the "is_http" part was nice :-)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

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

Okay