Aaron’s Desk Chair Adventures

Code. Engineering Management. Fun.

Go Validation Tricks

As I’ve gotten more familiar with Go web programming, I’ve tried to solve the problem of form and payload validation a number of different ways. Coming from a Python background, I initially really liked the idea of using a validation method.

type Form struct{
    Field1 string `json:"field1"`
    Field2 int    `json:"field2"`
}

// we can pass in multiple arguments like db, or whatever else.
// do any validation necessary using the values in Form
func (f *Form) Validate(db *Database) error{
}

There are a few problems with this approach. The first problem is that this validate method is likely to return an error immediately rather than aggregate them all. The second problem is that this validation method is incredibly painful to mock when testing.

First Problem

Let’s take a stab at the first problem. In Go, errors are actually an interface. One way to implement an error interface and return all errors at the same time might be this Verror approach that husobee and I came up with:

import (
    "fmt"
    "strings"
)

func NewVerror() *Verrors {
    return &Verrors{
        Errors: make(map[string][]string),
    }
}

// keys represent the field
// and each field can have numerous errors
type Verrors struct {
    Errors map[string][]string
}

// also works with duplicate errors!
func (e *Verrors) AddError(field, err string) {
    if _, ok := e.Errors[field]; !ok {
        e.Errors[field] = make([]string, 0)
    }

    for _, ele := range e.Errors[field] {
        if ele == err {
            return
        }
    }
    e.Errors[field] = append(e.Errors[field], err)
}

// we can aggregate together functions that return verrors
// or multiple verrors from go routines, for example
func (e *Verrors) Merge(e2 *Verrors) {
    for key, values := range e2.Errors {
        for _, value := range values {
            e.AddError(key, value)
        }
    }
}

// if you want to return nil instead of an empty verror
// check if Valid return false first
func (e *Verrors) Valid() bool{
    return len(e.Errors) == 0
}

// satisfies the error interface
func (e *Verrors) Error() string {
    response := ""
    for k, v := range e.Errors {
        response += fmt.Sprintf("%s: [%s]", k, strings.Join(v, ","))
    }
    return response
}

Verrors are pretty flexible, you can even return them as JSON with some additional type checking. Keep in mind this doesn’t work too well if you’re returning XML.

Second Problem

Mocking in tests is a very large topic, and could be a blog post all on its own. In Go, the only way to mock a method is with an interface. There’s a more comprehensive example here, but the gist of it is:

type Form struct{
    Field1 string `json:"field1"`
    Field2 int    `json:"field2"`
}

func (f *Form) Validate(db *Database) error{
}

type FormValidater interface{
     Validate(db *Database) error
}

// we can pass in a mock here
// rarely this simple unfortunately
func ShowMock(f FormValidater){
    db := getDatabase()
    f.Validate(db)
}

If you’re unable to wrap your methods in an interface, for example when the method and its struct are both in the same scope, mocking these methods is pretty much impossible.

func Handler(w http.ResponseWriter, r *http.Request){
    db := getDatabase()
    c := getFromJSON(r)
    c.Validate(db)
}

Another option is using a function that takes the form object as the first argument instead of a method. With functions you can use: patch/restore.

type Form struct{
    Field1 string `json:"field1"`
    Field2 int    `json:"field2"`
}

var ValidateForm = func(f *Form, db *Database) error{
}

func ShowMock(){
    defer Patch(&ValidateForm, func(f *Form, db *Database) error{
        return nil
    }).Restore()
}

This has the unfortunate side-effect of modifying the global ValidateForm definition and is not thread-safe. For tests this is typically not an issue, but if you want to avoid patch/restore, another strategy is to make use of struct closure variables:

type Form struct {
    Field1 string `json:"field1"`
    Field2 int    `json:"field2"`
}

func ValidateForm(f *Form, db *Database) error {
}

func NewFormProcessor() *FormProcessor {
    return &FormProcessor{
        ValidateForm: ValidateForm,
    }
}

// ValidateForm is a closure
type FormProcessor struct {
    ValidateForm func(f *Form, db *Database) error
}

// an example of mocking ValidateForm that is
// thread-safe
func (f FormProcessor) ShowMock() {
    f.ValidateForm = func(f *Form, db *Database) error {
        return nil
    }
}

The controllers discussed here can make great use of an embedded FormProcessor (which can hold any number of validation functions). An example can be found here.

Conclusion

I’ve touched on a few strategies for tackling the validation problem, and I also highly encourage you to check out this validation library to supplement this information. I’ll probably discuss this more in the future as I inevitably revisit this myself again.