Idiomatic way to validate structs
To help anyone else that may be looking for another validation library I created the following https://github.com/bluesuncorp/validator
It addresses some issues that other plugins have not implemented yet that others in this thread had mentioned such as:
- Returning all validation errors
- multiple validations per field
- cross field validation ex. Start > End date
Inspired by several other projects including the accepted answer of go-validator/validator
Doing that way you will end up writing a lot of duplicate code for each of your model.
Using a library with tags comes with its own pros and cons. Sometimes is easy to start but down the road you hit the library limitations.
One possible approach is to create a "Validator" that its only responsibility is to keep track of the possible errors inside an object.
A very approximate stub of this idea:
http://play.golang.org/p/buBUzk5z6I
package main
import (
"fmt"
"time"
)
type Event struct {
Id int
UserId int
Start time.Time
End time.Time
Title string
Notes string
}
type Validator struct {
err error
}
func (v *Validator) MustBeGreaterThan(high, value int) bool {
if v.err != nil {
return false
}
if value <= high {
v.err = fmt.Errorf("Must be Greater than %d", high)
return false
}
return true
}
func (v *Validator) MustBeBefore(high, value time.Time) bool {
if v.err != nil {
return false
}
if value.After(high) {
v.err = fmt.Errorf("Must be Before than %v", high)
return false
}
return true
}
func (v *Validator) MustBeNotEmpty(value string) bool {
if v.err != nil {
return false
}
if value == "" {
v.err = fmt.Errorf("Must not be Empty")
return false
}
return true
}
func (v *Validator) IsValid() bool {
return v.err != nil
}
func (v *Validator) Error() string {
return v.err.Error()
}
func main() {
v := new(Validator)
e := new(Event)
v.MustBeGreaterThan(e.Id, 0)
v.MustBeGreaterThan(e.UserId, 0)
v.MustBeBefore(e.End, e.Start)
v.MustBeNotEmpty(e.Title)
if !v.IsValid() {
fmt.Println(v)
} else {
fmt.Println("Valid")
}
}
package main
import (
"fmt"
"time"
)
type Event struct {
Id int
UserId int
Start time.Time
End time.Time
Title string
Notes string
}
type Validator struct {
err error
}
func (v *Validator) MustBeGreaterThan(high, value int) bool {
if v.err != nil {
return false
}
if value <= high {
v.err = fmt.Errorf("Must be Greater than %d", high)
return false
}
return true
}
func (v *Validator) MustBeBefore(high, value time.Time) bool {
if v.err != nil {
return false
}
if value.After(high) {
v.err = fmt.Errorf("Must be Before than %v", high)
return false
}
return true
}
func (v *Validator) MustBeNotEmpty(value string) bool {
if v.err != nil {
return false
}
if value == "" {
v.err = fmt.Errorf("Must not be Empty")
return false
}
return true
}
func (v *Validator) IsValid() bool {
return v.err != nil
}
func (v *Validator) Error() string {
return v.err.Error()
}
func main() {
v := new(Validator)
e := new(Event)
v.MustBeGreaterThan(e.Id, 0)
v.MustBeGreaterThan(e.UserId, 0)
v.MustBeBefore(e.End, e.Start)
v.MustBeNotEmpty(e.Title)
if !v.IsValid() {
fmt.Println(v)
} else {
fmt.Println("Valid")
}
}
You can then create your Validate method and use the same code:
func (e *Event) IsValid() error {
v := new(Validator)
v.MustBeGreaterThan(e.Id, 0)
v.MustBeGreaterThan(e.UserId, 0)
v.MustBeBefore(e.End, e.Start)
v.MustBeNotEmpty(e.Title)
return v.IsValid()
}
I don't see any other way to do this quickly. But I found a go package which can help you with this: https://github.com/go-validator/validator
The README file gives this example:
type NewUserRequest struct {
Username string `validator:"min=3,max=40,regexp=^[a-zA-Z]$"`
Name string `validator:"nonzero"`
Age int `validator:"min=21"`
Password string `validator:"min=8"`
}
nur := NewUserRequest{Username: "something", Age: 20}
if valid, errs := validator.Validate(nur); !valid {
// values not valid, deal with errors here
}