Golar v0.5.1

Validation

The validation package checks structs against rules written in validate tags. Rules are parsed once per struct type and cached, so a request only pays for the checks themselves. It has no third-party dependencies.

type RegisterRequest struct {
    Name     string `json:"name" validate:"required,max=100"`
    Email    string `json:"email" validate:"required,email"`
    Password string `json:"password" validate:"required,min=8,confirmed"`
    PasswordConfirmation string `json:"password_confirmation"`
}

app.Post("/register", func(c *golar.Context) error {
    var req RegisterRequest
    if err := c.Validate(&req); err != nil {
        return err
    }
    // req is valid
})

c.Validate binds the body with c.Bind (JSON, URL-encoded or multipart, by Content-Type) and validates it. With the default error handler, a body that cannot be decoded returns 400 Invalid request body, an unsupported body type 415 and invalid input 422 with the messages keyed by field name:

{"errors": {"email": ["The email field must be a valid email address."]}}

Outside handlers, call validation.Validate(v) or validation.ValidateContext(ctx, v). They return nil, a *validation.Error whose Errors field holds the messages, or another error for an invalid tag or a failing database query.

Rules

Rule Passes when
required the value is present: not nil, not a blank string, not an empty slice or map, and not a zero number or false unless behind a pointer
email the string is a bare address such as ada@example.com
url the string is an absolute URL with a scheme and host
uuid the string is a UUID in 8-4-4-4-12 form
numeric the value is a number or a string that parses as one
string the value is a string, for any fields holding decoded JSON
integer the value is an integer, a whole float such as 3.0, or a string such as "12"
boolean the value is a bool, 0 or 1, or the string true, false, 1 or 0
alpha the string contains only letters, including non-ASCII ones such as ş
alpha_num the string contains only letters and digits
alpha_dash the string contains only letters, digits, - and _
starts_with=a b, ends_with=a b the string starts or ends with one of the space-separated values
date the value is a non-zero time.Time, or a string in RFC 3339 or 2006-01-02 form; date=02.01.2006 takes a Go layout
min=n, max=n the size is at least or at most n
len=n the size is exactly n
between=a b the size is between a and b, inclusive
in=a b c the value is one of the space-separated values
regex=pattern the string matches pattern; must be the last rule, so the pattern may contain commas
confirmed the field equals <json name>_confirmation or <Go name>Confirmation; confirmed=other names the field
unique=table.column no row has this value; unique=table uses the field name as the column
exists=table.column at least one row has this value

Size means characters for strings, the value for numbers, and the length for slices, arrays and maps. A string with the numeric or integer rule is sized by its value, so numeric,min=18 compares numbers.

As in Laravel, only required runs on missing or empty values: an optional email field may be left empty. Use a pointer, such as *int, to require a field that may legitimately be 0 or false.

Fields are named by their json tag, then their form tag, or their Go name without either. Nested structs are validated with dotted keys such as address.city, unless the nested struct itself failed a rule. Embedded structs share the outer keys, as in encoding/json. validate:"-" skips a field.

Arrays and maps

Structs inside slices, arrays and maps are validated element by element, with the index or map key in the error key:

type Order struct {
    Items  []Item   `json:"items" validate:"required,max=50"`
    Emails []string `json:"emails" validate:"max=3,each,required,email"`
}
type Item struct {
    SKU      string `json:"sku" validate:"required,alpha_dash"`
    Quantity int    `json:"quantity" validate:"required,min=1"`
}
{"errors": {
  "items.1.quantity": ["The quantity field is required."],
  "emails.0": ["The emails.0 field must be a valid email address."]
}}

Rules after each apply to every element; rules before it apply to the collection itself. When the collection fails its own rules, its elements are not checked. each must come before a regex rule.

Invalid tags, such as an unknown rule or min=abc, return an error the first time the type is validated.

Database rules

unique and exists run a COUNT(*) through the query builder and need a database on the validator:

validation.Default.DB = db // *database.DB or *database.Tx

unique does not ignore the current record. To check uniqueness on update, register a custom rule that adds the condition you need.

Messages

Messages use :attribute, the field name with underscores replaced by spaces, and :param, :min, :max and :values from the rule. Override them on the validator, from most to least specific:

v := validation.Default
v.Messages["email.unique"] = "This email is already registered."   // field.rule
v.Messages["min.string"] = "The :attribute needs :param characters." // rule.kind: string, numeric or array
v.Messages["required"] = ":attribute is required."                  // rule
v.Attributes["dob"] = "date of birth"                               // :attribute for a field key
v.Attributes["items.*.sku"] = "item SKU"                            // * matches any index

Messages and Attributes keys may use * for indexes, as in items.*.quantity or emails.*.email.

Locales

Messages are English by default. A Turkish catalogue (tr) is built in, and AddLocale adds or extends others:

validation.AddLocale("tr", map[string]string{
    "email.unique": "Bu e-posta zaten kayıtlı.",
}, map[string]string{
    "email": "e-posta", // :attribute in Turkish messages
})
validation.AddLocale("de", map[string]string{
    "required": "Das Feld :attribute ist erforderlich.",
}, nil)

Choose the locale per request. validation.LocaleMiddleware reads Accept-Language and picks the best registered locale; requests asking for English or an unknown language keep English. validation.WithLocale(ctx, "tr") sets one explicitly:

app.Use(validation.LocaleMiddleware)

A locale's messages are searched first, by the same keys as Messages; then Messages, the rule's registered message and the English defaults. Regional names fall back to their language: pt-BR uses pt when only that exists. Without the middleware or WithLocale, responses stay in English.

Form requests

c.Validate treats the struct as a form request. After binding, it calls the struct's Authorize and Prepare methods if it has them, then validates the tags and AfterValidate:

type UpdatePost struct {
    Title string `json:"title" validate:"required,max=120"`
    Start string `json:"start" validate:"required,date"`
    End   string `json:"end" validate:"required,date"`
}

// Authorize runs first; return httpcontext.ErrForbidden for 403.
func (r *UpdatePost) Authorize(c *golar.Context) error {
    if !canEdit(c.Context(), c.Param("id")) {
        return httpcontext.ErrForbidden
    }
    return nil
}

// Prepare normalizes input before the rules run.
func (r *UpdatePost) Prepare(c *golar.Context) error {
    r.Title = strings.TrimSpace(r.Title)
    return nil
}

// AfterValidate checks rules that span fields. It runs only when every tag
// rule passes; its errors are reported like tag errors.
func (r *UpdatePost) AfterValidate(ctx context.Context) (validation.Errors, error) {
    if r.End <= r.Start {
        return validation.Errors{"end": {"The end must be after the start."}}, nil
    }
    return nil, nil
}

The order is bind, Authorize, Prepare, tag rules, AfterValidate. AfterValidate also runs for validation.Validate(&v); pass a pointer when the method has a pointer receiver. Any error other than ErrForbidden from a hook is returned unchanged.

golar make:request StorePost writes app/requests/store_post_request.go with a StorePostRequest struct and empty Authorize and Prepare methods.

Custom rules

validation.Register("slug", "The :attribute may only contain lowercase letters, digits and dashes.",
    func(ctx context.Context, f validation.Field) (bool, error) {
        return slugPattern.MatchString(f.Value.String()), nil
    })

type Post struct {
    Slug string `json:"slug" validate:"required,slug"`
}

Field.Value is the dereferenced reflect.Value, Field.Param the text after =, and Field.Sibling(name) returns another field of the same struct. Returning an error aborts validation instead of reporting a failed field.

Configure messages, attributes, rules and the database before serving requests. validation.New() creates an independent validator.

Edit this page on GitHub