Documentation
¶
Overview ¶
Package form binds and validates HTML form submissions into Go structs.
Bind is the headline API: it parses an *http.Request body, decodes the form values into a destination struct (using "form" struct tags via gorilla/schema), runs declarative validation (using "validate" struct tags via go-playground/validator), and optionally runs bespoke cross-field rules via the Validator interface. User-facing problems (a non-numeric value in a numeric field, a missing required field, a malformed email, and so on) are reported as an Errors value keyed by form field name. Only genuinely unprocessable requests (a body that exceeds the size limit, an unparseable content type) are reported as a returned error, which a handler should translate into an HTTP 400 response.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Errors ¶
Errors maps an HTML form field name to its human-readable validation messages. The keys are the form field names (the value of the struct's "form" tag), which makes Errors convenient to consume directly from HTML templates when re-rendering a form with inline error messages.
The zero value of Errors is not usable for writing; construct one with make(Errors) or rely on Bind to allocate it. Read methods (Get, Has, Any) are safe to call on a nil Errors.
func Bind ¶
Bind parses, decodes, and validates the form submission in r into dst.
dst must be a non-nil pointer to a struct. Its fields are populated from the request's POST form values using "form" struct tags, then validated using "validate" struct tags. If dst (or the struct it points to) implements Validator, its Validate method is invoked and the resulting errors are merged in.
Bind distinguishes two failure modes:
- User errors (a wrong type for a field, a failed validation rule) are returned in the Errors result keyed by form field name. In this case the returned error is nil. Callers test Errors.Any to decide whether to re-render the form.
- Unprocessable requests (a body that exceeds the configured size limit, a malformed or unparseable body) are reported via the returned error. In this case the Errors result is nil. Handlers should respond with HTTP 400 (Bad Request).
Callers should check the returned error first and respond 400 if it is non-nil; only then consult Errors.Any to decide whether to re-render. The Errors result is safe to use even when nil or empty (its read methods are nil-safe), so the two checks must not be collapsed: a non-nil error with a nil Errors would make a bare Errors.Any check silently skip the 400.
Only "form"-tagged fields are populated, but every such field is settable by the client. Bind to a struct that contains only user-supplied inputs; never bind directly to a domain or persistence model, or a client could set fields (an ID, an ownership or role flag) by posting extra form keys.
Example ¶
ExampleBind demonstrates binding a form submission, then printing the per-field validation errors in a deterministic order.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"sort"
"strings"
"github.com/mikehelmick/go-bananas/form"
)
// Signup is a destination struct with form and validate tags.
type Signup struct {
Name string `form:"name" validate:"required"`
Email string `form:"email" validate:"required,email"`
Age int `form:"age" validate:"gte=18"`
}
// ExampleBind demonstrates binding a form submission, then printing the
// per-field validation errors in a deterministic order.
func main() {
// Build a request with a missing name, a malformed email, and an
// under-age value.
values := url.Values{}
values.Set("email", "nope")
values.Set("age", "12")
r := httptest.NewRequest(http.MethodPost, "/signup", strings.NewReader(values.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
var dst Signup
errs, err := form.Bind(r, &dst)
if err != nil {
// An unprocessable request (oversized body, malformed content type).
fmt.Println("bad request:", err)
return
}
if !errs.Any() {
fmt.Println("ok")
return
}
// Sort field names for stable output.
fields := make([]string, 0, len(errs))
for field := range errs {
fields = append(fields, field)
}
sort.Strings(fields)
for _, field := range fields {
for _, msg := range errs.Get(field) {
fmt.Printf("%s: %s\n", field, msg)
}
}
}
Output: age: is too small email: must be a valid email address name: is required
func (Errors) Add ¶
Add appends msg to the list of messages associated with field. It must be called on an initialized (non-nil) Errors; Bind always passes an initialized map, and a Validator implementation should construct one with make(Errors).
func (Errors) Any ¶
Any reports whether any field has at least one message recorded. It is the canonical way to ask "did validation fail?" after a call to Bind.
func (Errors) Get ¶
Get returns the messages recorded for field, or nil if there are none. The nil return is intentional: ranging over a nil slice in a template (or in Go) is a no-op, so callers can write {{range .Errors.Get "email"}} without a guard.
type Option ¶
type Option func(*options)
Option configures the behavior of Bind.
func WithMaxBodyBytes ¶
WithMaxBodyBytes sets the maximum number of bytes Bind will read from the request body. A request whose body exceeds this limit is treated as unprocessable and causes Bind to return a non-nil error. The default is 10 MiB. A non-positive value is ignored and the default is retained.
The limit is only enforced if nothing has already read the body: net/http's ParseForm is idempotent, so if an earlier middleware parsed the form (for example a CSRF middleware that reads a posted token), that read used the standard library's own limit and Bind's cap no longer applies. To bound body size reliably regardless of middleware ordering, wrap the request earlier in the chain with http.MaxBytesHandler.
func WithMessages ¶
WithMessages overrides the human-readable messages used for one or more validation tags. The keys are validator tag names (for example "required" or "email"); the special key "default" overrides the fallback message. Tags not present in m fall back to the built-in defaults.
type Validator ¶
type Validator interface {
Validate() Errors
}
Validator is implemented by destination structs that need cross-field or otherwise bespoke validation rules that the "validate" struct tags cannot express cleanly. It is optional: Bind only invokes Validate when the destination (or the value it points to) implements the interface.
Validate returns an Errors value keyed by form field name. The returned errors are merged over (appended to) any errors already produced by tag validation.