Go Validate
Table of Contents
While working with golang in my various projects, I realized that there were a lack of validation tools for golang. I decided to make a validation function in golang from scratch to learn more about golang and to make a tool that I can use in my future projects.
Introduction #
What is validation? #
Validation is the process of checking if the input is valid. For example, if we want to check if a user’s email is valid, we can check if the email contains an @
symbol. If it does not, we can say that the email is invalid. (Please do not use this in your projects as it is not a good way to check if an email is valid).
Goals of the project #
The goals of the project are as follows:
- Make validation syntax easier to read.
- Able to support compounding of different validation functions.
- Able to insert custom error messages.
Architecture #
Installing the package #
Prerequisites #
- Go 1.16 or higher
Install command #
To install the package, you can use the following command:
go get github.com/Jh123x/go-validate
Demo #
Imagine I am working on a test suite for my server. I want to see if it is working correctly. Here are the constraints for the response.
type Response struct {
Code int // Must be non-zero.
Message string // Must be non-empty.
Extras map[string]any // Must be non-nil.
Optional string // Optional
SetIfOptSet string // Set if Optional is set, empty otherwise.
}
Normally, if I want to check it, I will have to do something like this:
func validate(resp Response) error {
if resp.Code == 0 {
return fmt.Errorf("Code cannot be 0")
}
if len(resp.Message) == 0 {
return fmt.Errorf("Message cannot be empty")
}
if resp.Extras == nil {
return fmt.Errorf("Extras cannot be nil")
}
if len(resp.Optional) > 0 && len(resp.SetIfOptSet) == 0 {
return fmt.Errorf("Optional cannot be empty")
}
return nil
}
Notice that if there were more conditions, I will have to add more of the if
statements to check for them. This will make the code very long and hard to read.
By using this wrapper, I can do the following instead.
func validate(resp Response) error {
return NewValidator().WithOptions(
options.IsNotEmpty(resp.Code),
options.IsNotEmpty(resp.Message),
options.WithRequire(func() bool { return resp.Extras != nil }, errTest),
options.Or(
options.And(
options.IsEmpty(resp.Optional),
options.IsEmpty(resp.SetIfOptSet),
),
options.And(
options.IsNotEmpty(resp.Optional),
options.IsNotEmpty(resp.SetIfOptSet),
),
),
).Validate()
}
The results at each step is also cached. At each step of the validation we can save the result to a variable and use it later.
cond1 = NewValidator().WithOptions(
options.IsNotEmpty(resp.Code),
options.IsNotEmpty(resp.Message),
)
//EG: Check optional is non-empty if not empty
split1 = cond1.WithOptions(
options.IsNotEmpty(resp.Optional),
).validate()
//EG: Check if is empty in this case
split2 = cond1.WithOptions(
options.IsEmpty(resp.Optional),
).validate()
Trade-offs #
What we lose:
0.56
to0.84
times slower than the raw if statements
What we gain:
- Easier to read code
- Easier to add new conditions