The task all web developers actively avoid... form validation! Whether you're implementing it on the client or server, it somehow always manages to be a painful experience. Though I can't make the pain go away, I can make it sting a little less by using this as an opportunity to demonstrate how you can use the builder pattern to construct your own powerful form validation tools. If you've spent any amount of time doing web development in Go and working with third party libraries, you've probably encountered the builder pattern already. For a classless language like Go, you can apply the builder pattern to ease constructing and initializing complex types.

The Builder Pattern

Let's take a brief look at some simple examples of the builder pattern before diving into more concrete form validation code. Think of the pattern as a series of chain-able setters. There's two common forms you'll see this pattern take in Go, the first is where these methods are embedded within the same type you're initializing.

type Complex struct {
	field1 []string
	field2 bool
	// ... many more fields
}

func (c *Complex) Field1(value string) *Complex {
	c.field1 = append(c.field1, value)
        return c
}

func (c *Complex) Field2(value bool) *Complex {
	c.field2 = value
        return c
}

func New() *Complex {
	// initialize slices, maps, etc.
	return &Complex{
		field1: make([]string, 0),
	}
}

// usage
myComplex := New().Field1("foo").Field2(true)

The other variation is where there's an explicit builder type, which serves as a factory for producing a separate type.

type ComplexBuilder struct {
	field1 []string
	field2 bool
	// ... many more fields, mirroring Complex
}

func (cb *ComplexBuilder) Field1(value string) *ComplexBuilder {
	cb.field1 = append(cb.field1, value)
	return cb
}

func (cb *ComplexBuilder) Field2(value bool) *ComplexBuilder {
	cb.field2 = value
	return cb
}

func (cb *ComplexBuilder) Build() *Complex {
	return &Complex{
		field1: cb.field1,
		field2: cb.field2,
		// ... and so on, for every field
	}
}

func NewBuilder() *ComplexBuilder {
	// initialize slices, maps, etc.
	return &ComplexBuilder{
		field1: make([]string, 0),
	}
}

// usage
myComplexBuilder := NewBuilder().Field1("foo")
complex1 := myComplexBuilder.Build()
// continue with more configuration
myComplexBuilder.Field2(true)
complex2 := myComplexBuilder.Build()

The nice thing about this approach is you can re-use the builder to continue producing initialized types, even making incremental modifications between calls to the Build method.

Custom Form Types

Several custom named types appear throughout these examples which make the code more concise, legible, and enforce explicit interactions with the methods of Form and FormBuilder. The ability to create unique named types to capture specific unnamed literals is a powerful feature of Go, and something I find myself leveraging quite often. Here's a quick look at those types with some comments explaining their usage.

// FormValues is a map of form field names to corresponding values.
// It is only available on a Form when all validation passes.
type FormValues map[string]interface{}

// FormValidator is a function that will run only if all field validation passes.
// It receives the FormValues, providing an opportunity to validate multiple values
// together, and return a boolean indicating whether the form as a whole is valid.
type FormValidator func(formValues *FormValues) bool

// FormatterFunc is a function which parses raw form input before it is unmarshalled into a Go type.
type FormatterFunc func(string) string

// LoaderFunc takes the formatted value for a field and unmarshels it into a Go type.
// It may return an error if there is a problem converting the string to the appropriate type.
type LoaderFunc func(string) (interface{}, error)

// ValidatorFunc takes loaded values and performs arbitrary validation.
// It may return an error if the value doesn't pass validation.
type ValidatorFunc func(interface{}) error

The Form Type

Field validation is orchestrated by the Form type which implements the builder pattern.

type Form struct {
	Fields     map[string]*Field
	FieldNames []string
	Errors     map[string]error
	Values     FormValues
	validator  FormValidator
}

// WithField adds the Field produced by the FieldBuilder to the Form under the given name.
func (f *Form) WithField(name string, fb *FieldBuilder) *Form {
	field := fb.Build()
	f.Fields[name] = field
	f.FieldNames = append(f.FieldNames, name)
	return f
}

// WithValidator adds the FormValidator to the form.
func (f *Form) WithValidator(validator FormValidator) *Form {
	f.validator = validator
	return f
}

The WithField method receives FieldBuilder instances along with the name of the field as it will appear in the raw form values pulled from Request.Form. Should you require a form-level validator, WithValidator accepts a custom FormValidator that will only be ran if all fields pass validation.

Building a Form Field

The FieldBuilder serves as the builder for producing Field instances. If you hadn't picked up on it from the custom named types listed earlier, fields do three things in this minimal example: clean and format raw input, convert raw input to appropriate Go types, and validate the given value. One nice side effect to using a FieldBuilder is that most fields on the Field type can be left private so as to enforce the usage of the FieldBuilder, avoiding potential errors. Let's take a quick peek at the Field and FieldBuilder types.

type Field struct {
	Name       string
	formatters []FormatterFunc
	loader     LoaderFunc
	validators []ValidatorFunc
	required   bool
	empty      interface{}
}

type FieldBuilder struct {
	formatters []FormatterFunc
	loader     LoaderFunc
	validators []ValidatorFunc
	required   bool
	empty      interface{}
}

func (fb *FieldBuilder) WithFormatters(formatters ...FormatterFunc) *FieldBuilder {
	fb.formatters = append(fb.formatters, formatters...)
	return fb
}

func (fb *FieldBuilder) WithValidators(validators ...ValidatorFunc) *FieldBuilder {
	fb.validators = append(fb.validators, validators...)
	return fb
}

func (fb *FieldBuilder) Loader(loader LoaderFunc) *FieldBuilder {
	fb.loader = loader
	return fb
}

func (fb *FieldBuilder) Required() *FieldBuilder {
	fb.required = true
	return fb
}

It's common for a builder to mirror the same fields as the type its constructing. As I mentioned previously, explicit builder types are useful when there's a likelihood one would want to produce more than one instance from the same builder. The builder method for the FieldBuilder does nothing more than construct a Field with the same values as itself.

func (fb *FieldBuilder) Build() *Field {
	return &Field{
		formatters: fb.formatters,
		loader:     fb.loader,
		validators: fb.validators,
		required:   fb.required,
		empty:      fb.empty,
	}
}

A Working Form

Constructing a Form to serve our validation needs is pretty straight forward.

func newRegistrationForm() *Form {
        var form = forms.New()

	// username
	form.WithField("Username", new(FieldBuilder).
		Required().
		Loader(StringLoader).
		WithValidators(ReValidator(`^\w{5,}$`, "Username must be at least 3 characters.")))

	// password
	form.WithField("Password", new(FieldBuilder).
		Required().
		Loader(StringLoader).
		WithValidators(ReValidator(`^\w{10,}$`, "Password must be at least 10 characters.")))

	return form
}

Here I'm defining two fields, Username and Password. Both fields are required, use the StringLoader, and have some simple validation requirements expressed through an ReValidator. You can find the source for the loader and validator types (along with others) on github by following the link at the end of this post.

Usage of this form in an http.HandlerFunc might look something like this:

func registrationHandler(w http.ResponseWriter, r *http.Request) {
	form := newRegistrationForm()
	if err := r.ParseForm(); err != nil {
		// handle form parse error
	}
	
	if form.valid(r.Form) {
		// handle successful form validation
		// by accessing values from form.Values
	} else {
		// handle invalid form by accessing
		// errors from form.Errors
	}
}

Conclusion

Hopefully this served as a useful introduction to the builder pattern, and maybe even left you with some ideas for handling validation in future applications. Check out the source code for this blog post on github where you can find other validation examples.

comments powered by Disqus