validate

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package validate checks structs against rules written in `validate` struct tags and reports one message per field.

type Register struct {
	Name     string `json:"name"     validate:"required|max:100"`
	Email    string `json:"email"    validate:"required|email"`
	Password string `json:"password" validate:"required|min:12|confirmed"`
	PasswordConfirmation string `json:"password_confirmation"`
	Role     string `json:"role"     validate:"in:reader,editor"`
	Terms    bool   `json:"terms"    validate:"accepted"`
}

err := validate.Struct(ctx, &in) // nil, *validate.Errors or a rule's error

Handlers built with web.H validate their input automatically after binding, and a failure becomes a 422 response listing the messages.

Tags

The syntax is Laravel's: rules are separated by "|", parameters follow ":" and are separated by commas: `validate:"required|between:3,20|in:a,b,c"`. Rules run in order and stop at the first failure, so each field gets at most one message. `validate:"-"` skips a field and its nested fields.

Rules other than the "required" family and "accepted" skip empty fields, so optional fields only need rules for their format. Nil pointers, blank strings, empty slices and maps, and zero structs (such as a zero time.Time) are empty. Numbers and booleans are never empty, since a zero may be deliberate; use a pointer (*int, *bool) to tell "not sent" from zero.

Keys and labels

Errors are keyed by the name clients use: the json tag, else the form, query, path or header tag, else the Go field name. Nested structs, slices and maps of structs are validated too, with keys like "address.city" and "items.0.name". Messages use a label derived from the key ("first_name" becomes "first name"); set a `label` tag to change it.

Messages

Messages are in the language of the context (package i18n): the catalogs' validation.<rule> messages, English by default, in the style of "The email field must be a valid email address." Labels come from validation.attributes.<key> when a catalog has it. A struct can replace messages with a ValidationMessages method returning templates (or catalog keys, which are translated) keyed by "key.rule" or "rule":

func (Register) ValidationMessages() map[string]string {
	return map[string]string{
		"email.required": "We need your email to send the receipt.",
		"min":            "{label} is too short (at least {0}).",
	}
}

Templates may use {label}, the rule's parameters {0}, {1}, … and {list} (all parameters joined with ", "). A parameter the catalog has under validation.values.<parameter> is translated ("now").

Custom rules

Register adds a named rule, usually from an init function:

validate.Register("slug", "The {label} field must be a slug.",
	func(ctx context.Context, f validate.Field) (bool, error) {
		s, _ := f.Value.(string)
		return slugRE.MatchString(s), nil
	})

Checks that need a database or several fields belong in a Validate(ctx) method on the input type (see web.Validator), which runs after the tag rules pass, or in the handler. Report problems with Fail or an Errors built by hand.

Performance

A struct type's tags are parsed once into a Plan and cached; tag mistakes (unknown rules, bad parameters, rules on the wrong field type, references to missing fields) are reported then, which for web.H means at startup. Validating walks the plan with reflection but never parses tags; with the built-in rules, a valid struct is checked without allocating (maps of structs and file rules excepted).

Fields are resolved like encoding/json resolves them: embedded structs are flattened, shadowed fields are ignored, and a field inside a nil embedded pointer counts as empty.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Fail

func Fail(field, message string) error

Fail returns an error with one message for field, for checks done in code:

if taken {
	return nil, validate.Fail("email", "This email address is already registered.")
}

func Register

func Register(name, message string, rule Rule)

Register adds a named rule usable in validate tags, for example "slug" or "starts_with_any:a,b". message is the default message; it may use {label}, and {0}, {1}, … or {list} for the parameters. A catalog's validation.<name> message replaces it (package i18n), and an empty message means validation.custom: "The {label} field is invalid.".

Like sql.Register, Register is meant to be called from an init function, so rules exist before routes compile their validation plans. It panics if the name is not made of letters, digits and underscores, is already registered, or is a built-in rule.

func Struct

func Struct(ctx context.Context, v any) error

Struct validates v, a struct or a pointer to one, using its validate tags. It returns nil, an *Errors listing the failures, or the error of a custom rule that could not run. The plan for v's type is compiled on first use and cached; tag mistakes are returned as errors.

Example
// SPDX-License-Identifier: Apache-2.0

package main

import (
	"context"
	"errors"
	"fmt"

	"anetos.dev/anetos/validate"
)

type SignUp struct {
	Email    string `json:"email" validate:"required|email"`
	Password string `json:"password" validate:"required|min:12|confirmed"`
	Confirm  string `json:"password_confirmation"`
	Age      *int   `json:"age" validate:"min:18"` // optional: nil passes
}

func main() {
	err := validate.Struct(context.Background(), SignUp{Email: "ada@", Password: "short", Confirm: "shrt"})
	if errs, ok := errors.AsType[*validate.Errors](err); ok {
		for _, key := range errs.Keys() {
			fmt.Printf("%s: %s\n", key, errs.Get(key))
		}
	}
}
Output:
email: The email field must be a valid email address.
password: The password field must be at least 12 characters.

Types

type Errors

type Errors struct {
	// contains filtered or unexported fields
}

Errors collects validation failures, at most one message per field. Keys are the field keys used in requests ("email", "address.city", "items.0.name"), so clients can show each message next to its input.

*Errors implements the web package's status and field-error interfaces: returned from a handler, it becomes a 422 Unprocessable Entity response listing the messages. The methods that read accept a nil *Errors.

func (*Errors) Add

func (e *Errors) Add(field, message string)

Add records message for field unless the field already has one.

func (*Errors) Err

func (e *Errors) Err() error

Err returns a copy of e as an error, or nil if it has no messages. Later calls to Add on e don't change the returned error. Use it to return an *Errors you built by hand:

var errs validate.Errors
if taken {
	errs.Add("email", "This email address is already registered.")
}
return errs.Err()

func (*Errors) Error

func (e *Errors) Error() string

Error lists the messages in order.

func (*Errors) FieldErrors

func (e *Errors) FieldErrors() map[string]string

FieldErrors returns a copy of the messages keyed by field.

func (*Errors) Get

func (e *Errors) Get(field string) string

Get returns the message for field, or "".

func (*Errors) HTTPStatus

func (e *Errors) HTTPStatus() int

HTTPStatus returns 422 Unprocessable Entity.

func (*Errors) Has

func (e *Errors) Has(field string) bool

Has reports whether field has a message.

func (*Errors) Keys

func (e *Errors) Keys() []string

Keys returns the fields with messages, in the order they were added.

func (*Errors) Len

func (e *Errors) Len() int

Len returns the number of fields with messages.

func (*Errors) MarshalJSON

func (e *Errors) MarshalJSON() ([]byte, error)

MarshalJSON encodes the messages as an object keyed by field, in order.

type Field

type Field struct {
	Key    string   // request key, e.g. "address.city"
	Label  string   // human-readable name used in messages
	Value  any      // the field's value, with pointers dereferenced (nil for a nil pointer)
	Params []string // rule parameters from the tag, e.g. ["3"] for "slug_max:3"
	Parent any      // the struct that contains the field
}

Field describes the field a custom Rule is checking.

type Plan

type Plan struct {
	// contains filtered or unexported fields
}

Plan is the compiled validation for one struct type. Compile it once (Struct and web.H cache plans) and reuse it; a Plan is safe for concurrent use.

func Compile

func Compile(t reflect.Type) (*Plan, error)

Compile builds (or returns the cached) plan for struct type t. It reports unknown rules, bad parameters, rules that don't apply to a field's type, references to missing fields and unknown message keys.

func MustCompile

func MustCompile(t reflect.Type) *Plan

MustCompile is like Compile but panics on error. Use it at startup.

func (*Plan) Empty

func (p *Plan) Empty() bool

Empty reports whether the plan has no rules at all, including in nested structs.

func (*Plan) Validate

func (p *Plan) Validate(ctx context.Context, v any) error

Validate checks v, which must be the plan's struct type or a non-nil pointer to it. See Struct.

type Rule

type Rule func(ctx context.Context, f Field) (bool, error)

Rule is a custom validation rule. It reports whether f passes. A non-nil error means the check itself failed (for example a database was unreachable) and aborts validation with that error.

Custom rules are skipped when the field is empty and not required, like the built-in rules.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL