validator

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 29 Imported by: 0

README

validator

Go Version License Build Status Go Report Card Go Reference

Validation for structs, maps, JSON documents, form values and single values with a boolean rule expression DSL, typed field access, atomic binding into structs, filters and localized messages. The root module depends only on the standard library and requires Go 1.27.

type User struct {
	Email string   `validate:"required && email"`
	Age   int      `validate:"required && gte:18"`
	Tags  []string `validate:"dive && alpha"`
}

vd := validator.MustStruct(User{Email: "a@b.com", Age: 20, Tags: []string{"go"}})
if err := vd.Validate(ctx); err != nil {
	if errs, ok := validator.AsErrors(err); ok {
		fmt.Println(errs.All()) // field -> rule -> message
	} else {
		log.Printf("validation could not complete: %v", err) // *RuleError, cancellation
	}
}

🚀 Getting Started

go get github.com/libtnb/validator
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

type Signup struct {
	Email    string `validate:"required && email"`
	Password string `validate:"required && min:8"`
	Confirm  string `validate:"required && same:Password"`
}

func main() {
	vd, err := validator.Struct(Signup{Email: "a@b.com", Password: "supersecret", Confirm: "different"})
	if err != nil {
		panic(err) // not a struct, or a tag that does not compile
	}
	if err := vd.Validate(context.Background()); err != nil {
		fmt.Println(err) // Confirm: The Confirm and Password must match.
	}
}

✨ Features

Inputs

Every constructor returns a *Validation; input errors (ErrInvalidInput) and rule-configuration errors (*RulesError wrapping ErrInvalidRules) are returned immediately, and nothing is evaluated until Validate. The Must* forms panic instead, for static, known-valid configuration.

Constructor Input Notes
Struct(data) Struct value or pointer Rules come from validate tags. A nil pointer validates as the zero value; nested structs use dotted names; embedded structs promote their fields; validate:"-" excludes a subtree from validation but keeps it bindable.
Valid(ctx, data) Struct value or pointer Verdict only: stops at the first failing field, never applies filters, allocates nothing for built-in rules.
Map(data, rules) Any Go map Non-string keys are rendered with conv.ToString; dotted rule names reach into nested maps.
JSON(data, rules) One JSON object ([]byte or string) Integers keep their precision (int64/uint64); duplicate names, invalid UTF-8, trailing data and non-object roots are rejected.
Values(data, rules) url.Values A key with one value maps to that string; a repeated key (tags=a&tags=b) maps to a []string, so it validates with dive and binds into slice fields.
Value(value, rule) One value Validated under the field name value.

The package-level functions use a shared Validator with the built-in rules (Default()). Construct your own with New(options...) when rules, filters, messages or other options differ; a Validator is immutable and safe to share, and it caches compiled expressions, struct plans and rules-map plans.

vd := validator.MustMap(
	map[string]any{"name": "alice", "role": "root"},
	map[string]string{"name": "required && alpha", "role": "required && in:admin,user"},
)
_ = vd.Validate(ctx)
fmt.Println(vd.Errors().OneFor("role")) // The selected role is invalid.
Expression DSL
required && (email || regex:"^[a-z]+@example\\.com$")
  • Operators: ! binds tightest, then &&, then ||; parentheses group. &/| must be doubled, so a | inside a regex is never read as OR.
  • Arguments follow : and are comma-separated (between:3,20). Double quotes and backslashes protect commas, operators and parentheses. regex and not_regex take one raw argument (commas literal).
  • dive splits a collection field's expression into rules for the collection and rules for each element: required && min:1 && dive && alpha. It must be joined by && on both sides, a trailing dive is an error, and a second dive inside the element rules is not supported. Element failures are reported as field[i] (slices, arrays) or field[key] (maps).
  • sometimes on the top-level && chain skips the field when it is absent (missing key, nil pointer), which suits PATCH inputs. It is rejected under ||, ! or dive.

Most rules pass empty values (IsEmpty semantics: nil, "", 0, false, empty collections, nil pointers, zero time.Time), so presence is the job of required, filled and notblank. required checks presence only; WithStrictRequired() makes required and the required_* family reject zero values too.

Built-in rules

Rules() returns the catalog (91 rules) and Filters() the 10 filters.

Presence

Rule Passes when
required The value is present (non-nil); with WithStrictRequired also non-zero.
filled Present and not empty, regardless of options.
notblank The string form has a non-whitespace character.
sometimes Always; marks the field as skippable when absent.

Strings

Rule Passes when
alpha, alphanum, ascii Every rune is a letter / letter or digit / ASCII.
lowercase, uppercase The string equals its lower/upper-cased form.
contains:s, excludes:s The string contains / does not contain s.
startswith:s, endswith:s The string has prefix / suffix s.

Numbers and sizes

Numbers compare by value, strings and []byte by rune count, other collections by length. A numeric or number assertion in the same expression switches string comparison to numeric value, so numeric && gte:18 bounds the number a form field holds. Integers compare exactly (no float rounding above 2^53).

Rule Passes when
min:n, max:n Size or value is >= n / <= n.
between:lo,hi Size or value is within [lo, hi].
gt:n, gte:n, lt:n, lte:n Strict / inclusive comparisons.
len:n, size:n Size or value equals n.
digits:n The string form is exactly n ASCII digits.
numeric A number, or a decimal string (sign, digits, optional fraction).
number An integer, an integral float, or an integer string.
boolean A bool, 0/1, or a token accepted by conv.ParseBool (true, yes, on, ...).

Formats

Format rules apply to the string form of the value and are backed by the is package, which is usable on its own.

Rule Passes when
email RFC 5322 style address with a dotted domain (syntax only).
url, uri Absolute URL with scheme and host / absolute URI with a scheme.
uuid, ulid, jwt, semver The respective textual form (no signature or version checks).
ip, ipv4, ipv6, cidr, cidrv4, cidrv6, mac, hostname, fqdn, port, e164 Network identifiers; port is an integer in 1..65535.
json, base64, hexcolor, latitude, longitude, timezone Well-formed JSON / padded base64 / #rgb[a], #rrggbb[aa] / decimal coordinates / IANA zone name.
luhn, credit_card Luhn checksum / 12-19 digits passing Luhn, separators ignored.
date, datetime[:layout] A time.Time or a string parsing with conv.DefaultTimeLayouts; datetime may fix one Go layout.
regex:pattern, not_regex:pattern Go regexp match / non-match; an invalid pattern is a configuration error at construction.

Comparison

Values compare by canonical string first, then numerically, so ne:5.0 matches the float 5 and eq:0.10 matches float32(0.1).

Rule Passes when
in:a,b,..., not_in:a,b,... The value is / is not one of the arguments.
in_ci:a,b,... Case-insensitive membership.
eq:v, ne:v Equal / not equal to v.
eq_ignore_case:v, ne_ignore_case:v Case-insensitive string equality / inequality.

Cross-field

Arguments name sibling fields. A bare name resolves among the field's siblings first (the same nested struct or map object), then from the root; dotted names are absolute; dive elements use their container's siblings. Missing sibling arguments are rejected at construction because they would make the conditional rules pass vacuously.

Rule Passes when
required_if:F,v1,v2..., required_unless:F,v1... Present when F equals (does not equal) one of the values.
required_with:F1,F2..., required_without:F1... Present when any listed field is present (absent).
required_with_all:F1..., required_without_all:F1... Present when all listed fields are present (all absent).
excluded_if:F,v..., excluded_unless:F,v..., excluded_with:F..., excluded_without:F... Empty under the mirrored conditions.
same:F, eqfield:F The string form equals F's.
different:F, nefield:F The string form differs from F's.
gtfield:F, gtefield:F, ltfield:F, ltefield:F Numeric or time.Time comparison against F.
confirmed Equals the <name>_confirmation sibling.

Time

Rule Passes when
after:D, after_or_equal:D, before:D, before_or_equal:D Chronological comparison. D that parses as a date is always the literal bound (so input keys cannot shadow a hard-coded cutoff); otherwise it names a sibling field.

Files

The value is a multipart.FileHeader or *multipart.FileHeader.

Rule Passes when
ext:jpg,png The file name (or a plain string value) has one of the extensions, case-insensitively.
mimetypes:image/*,application/pdf The sniffed content type (first 512 bytes, never the client header) matches; type/* matches a family.
filemin:512kb, filemax:10mb Size bounds; suffixes b, kb, mb, gb, tb are 1024-based.

Collections

Rule Passes when
unique A slice or array has no duplicate elements (a map no duplicate values); 1 and "1" stay distinct.
Filters

Filters transform a value before its rules run and before ValidateAs binds it. Chains are |-separated with optional : arguments; they run only on present, non-nil values, never on a field with dive rules, and a failing filter is reported as a field failure rather than silently using the raw value.

Filter Effect
trim, ltrim, rtrim Trim whitespace, or the cutset given as argument (trim:xy).
lower, upper, title Case folding; title also collapses whitespace runs.
int, float, bool, string Convert through the conv package; overflow and unparsable input fail.
vd := validator.MustMap(
	map[string]any{"Email": "  Alice@Example.com "},
	map[string]string{"Email": "required && email"},
)
_ = vd.AddFilters("Email", "trim|lower")

var form struct{ Email string }
_ = vd.ValidateAs(ctx, &form) // form.Email == "alice@example.com"
Binding

ValidateAs(ctx, &dst) validates and then writes the filtered input into dst; Bind(&dst) writes the raw input without validating. Both are atomic: on any conversion failure the destination is unchanged and a *BindError (wrapping ErrBindConversion) names the field. Strings convert to numbers, booleans and time.Time, []any to typed slices, nested objects to structs; integer overflow and unparsable strings are errors, never zero values. Untagged fields bind too. T must be a non-pointer struct type (ErrBindTarget otherwise).

Results and errors

Validate returns nil on success, *Errors when fields failed, and *RuleError (wrapping ErrRuleEvaluation) when a rule could not complete; the two may be joined. Err() and Fails() read the same result later.

if errs, ok := validator.AsErrors(err); ok {
	errs.One()            // first message
	errs.OneFor("Email")  // first message for a field
	errs.Messages("Email") // rule -> message
	errs.All()            // field -> rule -> message
	errs.Items()          // []FieldError with resolved messages
	errs.Has("Email")
}
Messages, translations and attributes

Templates use {field} for the display name, {0}, {1}, ... for arguments and {N+} for the arguments from N on, comma-joined.

  • WithMessages(map): keys are "field.rule" or "rule"; the specific key wins.
  • WithTranslation(map): same keys, lower precedence. The translations package ships Es, Ja, Ko, Ru, ZhHans and ZhHant.
  • WithAttributes(map): display names substituted for {field}.
  • WithTranslator(fn): dynamic lookup by rule after the maps miss.
  • WithTransformFunc(fn): post-processes every resolved message.
  • Validation.AddMessages(map): overrides for one validation, applied on read.
v := validator.MustNew(
	validator.WithTranslation(translations.ZhHans()),
	validator.WithAttributes(map[string]string{"Email": "邮箱"}),
)
Custom rules and filters
v, err := validator.New(
	validator.WithRuleFunc("even", func(f *validator.Field) bool {
		n, ok := f.Value[int]()
		return ok && n%2 == 0
	}, "The {field} must be even."),
	validator.WithStringRule("slug", func(s string, args ...string) bool {
		return !strings.ContainsAny(s, " /")
	}, "The {field} must be a slug."),
)
  • WithRules(...) registers Rule implementations: Signature(), Passes(*Field) bool, Message(). Passes must be deterministic and safe for concurrent use, and must not retain the pooled *Field.
  • WithFallibleRules(...) registers FallibleRule implementations whose Validate(*Field) (bool, error) can fail. false, nil is a field failure; a non-nil error becomes a *RuleError and is never recorded as a field failure. Use f.Context() to bound I/O.
  • WithFilters(...) registers Filter implementations.
  • A rule may also implement CheckArgs(args []string) error to reject bad arguments at construction, and IsRawArg() bool to receive one raw argument.
  • Field gives Reflect(), Value[T](), Attrs(), Name(), Root[T](), Context(), SiblingValue(name) and Sibling[T](name).

Signatures must be identifiers (letters, digits, underscores; not starting with a digit) and dive is reserved; a duplicate signature is ErrDuplicateRule or ErrDuplicateFilter at New.

Introspection

Check[T]() compiles every tag reachable from T and reports each invalid one as a *RulesError, so a typo fails at start-up rather than on the first request. Describe[T]() returns the flattened rules per field (FieldRules{Name, Index, Rules, Element, Exact}) for schema generators; CheckType and DescribeType take a reflect.Type.

if err := validator.Check[Signup](); err != nil {
	log.Fatal(err)
}
Options
Option Effect
WithTagName(name) Struct tag to read (default validate).
WithTagNameFunc(fn) Derive field names, e.g. from json tags.
WithStrictRequired() required also rejects zero values.
WithPrivateFieldValidation() Include unexported fields.
WithoutBuiltinRules() Start from an empty registry.
WithParallel(minFields) Evaluate fields concurrently (up to GOMAXPROCS goroutines) once a validation has at least minFields rule-bearing fields; rules and filters must then be safe for concurrent use.
Contrib modules

Separate Go modules under contrib/, versioned against a published root release:

  • github.com/libtnb/validator/contrib/openapi generates OpenAPI 3.1 documents from request/response types and validate tags.

    g := openapi.MustNew("users", "1.0.0")
    err := g.Add[CreateUser](
    	http.MethodPost,
    	"/users",
    	openapi.WithResponse[User](http.StatusCreated),
    	openapi.WithResponse[Problem](http.StatusBadRequest),
    )
    spec, _ := g.JSON()
    
  • github.com/libtnb/validator/contrib/gormrules adds database-backed exists and not_exists rules over a *gorm.DB: gormrules.New(db) returns a Validator that understands not_exists:users,email.

API details and runnable examples are on pkg.go.dev; a demo program lives in _examples (go run ./_examples).

🤝 Contributing

Please read the contributing guide before submitting a PR.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package validator validates structs, maps, JSON documents, form values and single values against boolean rule expressions, and binds validated input into structs.

Inputs

A Validator turns an input into a Validation. Struct reads rules from struct tags (validate:"..." by default); Map, JSON and Values take a field -> expression map; Value checks one value under the name "value". The package-level functions of the same names use a shared Validator with the built-in rules; construct your own with New when rules, messages or options differ. Constructors return input and rule errors immediately. Validate evaluates every field and collects failures; Valid reports only the verdict and stops at the first failure; ValidateAs validates and then binds the filtered input into a struct atomically.

Expressions

An expression combines rules with !, && and ||, in that precedence order, and parentheses:

required && (email || regex:"^[a-z]+@example\\.com$")

Arguments follow a colon and are separated by commas (between:3,20); double quotes and backslashes protect commas, operators and parentheses. "dive" splits a collection field's expression into rules for the collection and rules for each element: "required && min:1 && dive && alpha". "sometimes" on the top-level && chain skips the field when it is absent from the input, which suits PATCH requests.

Rules and filters

Most built-in rules pass empty values so that presence is the job of required, filled and notblank; WithStrictRequired makes required reject zero values as well. Size rules compare numbers by value and strings by rune count, unless a numeric or number assertion in the same expression switches them to numeric comparison. Rules returns the built-in catalog; custom rules implement Rule, or FallibleRule when the check itself can fail, and are registered with options. Filters (trim, lower, int, ...) transform a value before its rules run and before ValidateAs binds it.

Errors

Validate returns *Errors for field failures, with messages resolved through WithMessages, WithTranslation, WithAttributes and AddMessages; AsErrors recovers the collection from a wrapped error. A rule that cannot complete yields a *RuleError wrapping ErrRuleEvaluation and is never reported as a field failure. Invalid expressions yield *RulesError wrapping ErrInvalidRules from the constructors and from Check. Bind and ValidateAs are atomic and report *BindError wrapping ErrBindConversion.

Concurrency

A Validator is immutable and safe for concurrent use, and its expression, plan and struct caches are bounded. A Validation is not safe for concurrent use. Rules and filters must be safe for concurrent use, because one Validator serves many goroutines, and Passes must be deterministic, because the engine may evaluate a rule more than once for one value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	type User struct {
		Email string `validate:"required && email"`
		Age   int    `validate:"required && gte:18"`
	}
	vd := validator.MustStruct(User{Email: "a@b.com", Age: 20})
	_ = vd.Validate(context.Background())
	fmt.Println("valid:", !vd.Fails())
}
Output:
valid: true

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidOption reports a nil Option or an invalid option argument: an
	// empty tag name, a nil function or map, a nil rule or filter, or a
	// non-positive WithParallel threshold.
	ErrInvalidOption = errors.New("validator: invalid option")
	// ErrInvalidSignature reports a rule or filter signature that is empty,
	// reserved ("dive") or not made of letters, digits and underscores
	// starting with a non-digit.
	ErrInvalidSignature = errors.New("validator: invalid signature")
	// ErrDuplicateRule reports a Rule or FallibleRule signature registered
	// twice, including a custom rule that shadows a built-in one.
	ErrDuplicateRule = errors.New("validator: duplicate rule")
	// ErrDuplicateFilter reports a Filter signature registered twice.
	ErrDuplicateFilter = errors.New("validator: duplicate filter")
)

Configuration errors. New wraps them with the offending option, signature or rule named, so match them with errors.Is.

View Source
var (
	// ErrNilContext reports a nil context passed to Validate, ValidateAs or
	// Valid. The call does not count as a validation run.
	ErrNilContext = errors.New("validator: nil context")
	// ErrInvalidInput reports input a constructor cannot use: a non-struct
	// value for Struct or Valid, or JSON that is malformed, has trailing data
	// or is not an object.
	ErrInvalidInput = errors.New("validator: invalid input")
	// ErrInvalidRules reports a rule expression that does not parse, names an
	// unknown rule, or has bad static arguments. It is wrapped by RulesError,
	// which names the field.
	ErrInvalidRules = errors.New("validator: invalid rules")
	// ErrRuleEvaluation reports a rule that could not complete: a FallibleRule
	// returned an error or a rule panicked. It is wrapped by RuleError and is
	// never recorded as a field failure.
	ErrRuleEvaluation = errors.New("validator: rule evaluation failed")
	// ErrRulePanic reports a panic recovered from a rule, a filter's caller or
	// reflection over the input; RuleError wraps it together with
	// ErrRuleEvaluation.
	ErrRulePanic = errors.New("validator: rule panicked")
	// ErrValidated reports a configuration change (AddRules, AddFilters,
	// AddMessages and their Remove/Clear forms) attempted after Validate ran.
	ErrValidated = errors.New("validator: validation already ran")
)

Input, rule and runtime errors returned by the constructors, Validate, ValidateAs and Valid. They are wrapped, so match them with errors.Is.

View Source
var (
	// ErrBindTarget reports a Bind or ValidateAs destination that is nil or
	// whose type parameter is not a non-pointer struct.
	ErrBindTarget = errors.New("validator: bind requires a non-nil pointer to a non-pointer struct")
	// ErrBindConversion reports a value that cannot be converted to its
	// destination field: integer overflow, an unparsable string, nil into a
	// non-nilable kind, or an incompatible shape. BindError wraps it and
	// names the field.
	ErrBindConversion = errors.New("validator: bind conversion failed")
	// ErrValidationFailed marks a ValidateAs error caused by field failures,
	// as opposed to a rule execution or binding error; the joined error also
	// carries the *Errors collection for AsErrors.
	ErrValidationFailed = errors.New("validator: validation failed")
)

Binding errors returned by Bind and ValidateAs.

Functions

func Check added in v0.4.2

func Check[T any]() error

Check compiles every rule tag reachable from struct type T with the Default Validator and reports the invalid ones; see Validator.Check.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	type Signup struct {
		Email string `validate:"required && emial"` // typo
	}
	err := validator.Check[Signup]()
	fmt.Println(errors.Is(err, validator.ErrInvalidRules))
	var rulesErr *validator.RulesError
	if errors.As(err, &rulesErr) {
		fmt.Println(rulesErr.Field)
	}
}
Output:
true
Email

func IsEmpty

func IsEmpty(v any) bool

IsEmpty reports whether v is absent or the empty value of its kind, the omitempty semantics the built-in rules share: nil, "", 0, false, an empty string, collection or channel, a nil pointer or func, and a zero time.Time. A non-time struct is never empty, so its rules still run. Custom rules use it to let empty values through and leave presence to required.

func IsEmptyValue

func IsEmptyValue(rv reflect.Value) bool

IsEmptyValue is IsEmpty for a reflect.Value such as the result of Field.Reflect or Field.SiblingValue; an invalid Value is empty.

func Valid

func Valid(ctx context.Context, data any) (bool, error)

Valid reports whether data passes its struct-tag rules using the Default Validator; see Validator.Valid for the verdict and error semantics.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	type User struct {
		Name string `validate:"required && alpha"`
	}
	ok, err := validator.Valid(context.Background(), User{Name: "alice"})
	fmt.Println(ok, err)
}
Output:
true <nil>

Types

type BindError added in v0.4.2

type BindError struct {
	// Field is the validation name of the destination field.
	Field string
	// Target is the Go type the value should have been converted to.
	Target reflect.Type
	// Value is the input value that failed to convert.
	Value any
	// Err is the cause, ErrBindConversion.
	Err error
}

BindError identifies the first field that Bind or ValidateAs could not convert into the destination struct. Binding is atomic, so the destination is unchanged when this error is returned. Unwrap yields Err.

func (*BindError) Error added in v0.4.2

func (e *BindError) Error() string

Error renders the field, target type and cause.

func (*BindError) Unwrap added in v0.4.2

func (e *BindError) Unwrap() error

Unwrap returns the cause so errors.Is(err, ErrBindConversion) holds.

type Errors

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

Errors is the read-only collection of field failures produced by a Validation. Messages are resolved on every read from the Validator's messages, translations and attributes plus the Validation's AddMessages overrides, so the raw templates are kept and the same collection can be rendered under another configuration. It implements error: Validate and Err return it when fields failed, possibly joined with a *RuleError, and AsErrors recovers it from a wrapped error.

func AsErrors added in v0.2.0

func AsErrors(err error) (*Errors, bool)

AsErrors extracts the *Errors collection from an error returned by Validate, Err or ValidateAs, looking through errors.Join and fmt.Errorf wrappers. ok is false for errors that carry no field failures: rule execution errors, binding errors, a nil context or cancellation.

func (*Errors) All

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

All returns every failure as field -> rule -> message, keeping the first message when a rule failed more than once on a field. The map is freshly built and safe to modify.

func (*Errors) Error added in v0.4.2

func (e *Errors) Error() string

Error joins the first message of each failed field as "field: message", sorted by field name and separated by "; ". It is "" when nothing failed, which only happens on an Errors obtained before Validate.

func (*Errors) Has

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

Has reports whether any rule or filter failed on field.

func (*Errors) Items added in v0.2.0

func (e *Errors) Items() []FieldError

Items returns copies of the recorded failures in the order they were recorded (filter failures first, then fields by name, each container before its dive elements), with Message resolved and Params cloned. The slice is never nil.

func (*Errors) Messages

func (e *Errors) Messages(field string) map[string]string

Messages returns the message of each rule that failed on field, keyed by rule signature ("!" for a negation, "||" for an alternation with no passing branch, "" for a failed filter). When a rule failed more than once the first message is kept. The map is freshly built and safe to modify.

func (*Errors) One

func (e *Errors) One() string

One returns the first recorded message, or "" when nothing failed. Use it when a single line, such as an HTTP 400 body, is enough.

func (*Errors) OneFor

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

OneFor returns the first message recorded for field, or "" when the field passed. Dive elements are separate fields named "field[i]" or "field[key]".

func (*Errors) String

func (e *Errors) String() string

String returns the same summary as Error, for %s and %v formatting.

type FallibleRule added in v0.4.2

type FallibleRule interface {
	// Signature returns the name used in expressions, under the same rules as
	// Rule.Signature; one name cannot be registered as both kinds.
	Signature() string
	// Validate reports whether the value is acceptable, or an error when the
	// check itself failed. It must not retain f after returning.
	Validate(f *Field) (bool, error)
	// Message returns the failure template used when Validate returns false
	// with a nil error.
	Message() string
}

FallibleRule is a rule backed by an operation that can fail, such as a database lookup. The Rule contract applies (concurrency-safe, deterministic for one input, pooled Field not retained), and false with a nil error is an ordinary field failure. A non-nil error means the check could not be completed: it is returned as a *RuleError wrapping ErrRuleEvaluation, is never recorded as a field failure, and skips the rest of that field's expression while other fields still run. Use f.Context to bound the operation.

type Field

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

Field is the value and request context handed to a Rule, FallibleRule or rule function. Fields are pooled: one is valid only during the call that receives it, so never retain a *Field or the slice returned by Attrs. A Field is not safe for concurrent use.

func (*Field) Attrs

func (f *Field) Attrs() []string

Attrs returns the rule's arguments as written in the expression, unquoted and split on commas; raw-argument rules such as regex receive exactly one. The slice is shared and read-only and is valid only during the call.

func (*Field) Context

func (f *Field) Context() context.Context

Context returns the context passed to Validate, ValidateAs or Valid, so a FallibleRule can bound its I/O. Outside a validation run it is context.Background.

func (*Field) Name

func (f *Field) Name() string

Name returns the validation name a failure is reported under: the Go or tag-derived name, dotted for nested struct fields, "field[i]" or "field[key]" for a dive element, and "value" for Value inputs.

func (*Field) Reflect added in v0.4.2

func (f *Field) Reflect() reflect.Value

Reflect returns the current value with pointers and interfaces unwrapped and filters applied. An absent, nil or explicitly null value is an invalid reflect.Value, so check IsValid before calling methods on it. Under WithPrivateFieldValidation a value read from an unexported field may not be interfaceable.

func (*Field) Root added in v0.4.2

func (f *Field) Root[T any]() (T, bool)

Root returns the whole input as T: the struct value (never its pointer) for Struct inputs, map[string]any for Map, JSON and Values inputs, and the value as given for Value inputs. ok is false when T does not match.

func (*Field) Sibling

func (f *Field) Sibling[T any](name string) (T, bool)

Sibling resolves another field like SiblingValue and returns it as T; ok is false when the name is unknown or the value has another dynamic type.

func (*Field) SiblingValue added in v0.4.2

func (f *Field) SiblingValue(name string) (reflect.Value, bool)

SiblingValue resolves another field's current value for cross-field rules. A bare name is looked up among the current field's siblings first (the same nested struct or map object; for a dive element, the container's siblings), then from the root; a dotted name is absolute. The relative step never resolves to the field itself. ok is false when the name is unknown; Value inputs have no siblings.

func (*Field) Value added in v0.4.2

func (f *Field) Value[T any]() (T, bool)

Value returns the current value as T. ok is false when the value is absent or has another dynamic type; no conversion is attempted, so an int field does not satisfy Value[int64].

type FieldError

type FieldError struct {
	// Field is the validation name; dive elements are reported as "field[i]"
	// for slices and arrays and "field[key]" for maps.
	Field string
	// Rule is the failing rule's signature, "!" for a negation, "||" for an
	// alternation with no passing branch, or "" for a failed filter.
	Rule string
	// Message is the raw template with {field}, {0}, {1}, ... and {N+}
	// placeholders; Errors resolves it, honoring overrides and translations.
	Message string
	// Params are the rule's arguments, substituted for {0}, {1}, ...; {N+}
	// joins the arguments from N on with ", ".
	Params []string
}

FieldError is one recorded failure: a rule or filter that rejected a field. Errors resolves Message through the Validator's templates on every read; Errors.Items returns copies with the final text filled in.

type FieldRules added in v0.4.0

type FieldRules struct {
	// Name is the field's validation name, dotted for nested fields.
	Name string
	// Index locates the field for reflect.Type.FieldByIndex.
	Index []int
	// Rules apply to the field value itself.
	Rules []RuleInfo
	// Element rules apply to slice/array/map elements (dive).
	Element []RuleInfo
	// Exact is false when || or ! branches were omitted.
	Exact bool
}

FieldRules lists the rules declared on one struct field, flattened from the top-level AND chain of its expression. Element holds the rules that apply to container elements (after dive). Rules under || or ! cannot be flattened losslessly: those subtrees are omitted and Exact reports false, so a consumer knows the list is a lower bound.

func Describe added in v0.4.2

func Describe[T any]() ([]FieldRules, error)

Describe reports the rules declared on struct type T using the Default Validator; see Validator.Describe.

Example
package main

import (
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	type User struct {
		Name string   `validate:"required && min:3"`
		Tags []string `validate:"dive && alpha"`
	}
	fields, _ := validator.Describe[User]()
	for _, f := range fields {
		fmt.Println(f.Name, f.Rules, f.Element)
	}
}
Output:
Name [{required []} {min [3]}] []
Tags [] [{alpha []}]

type Filter

type Filter interface {
	// Signature returns the name used in filter chains, under the same rules
	// as Rule.Signature.
	Signature() string
	// Handle returns the transformed value; args are the chain arguments
	// ("trim:xy" passes "xy"). A value that cannot be transformed should be
	// an error rather than a silent zero.
	Handle(val any, args ...string) (any, error)
}

Filter transforms an input value before its rules see it and before ValidateAs binds it: trimming, case folding, type coercion. A field's chain ("trim|lower") runs once at Validate, in order, only on a present non-nil value, and never on a field with dive rules. A returned error, or a panic (recovered), is recorded as a field failure whose message is the error text; the raw value is never used silently. Implementations must be safe for concurrent use.

func Filters

func Filters() []Filter

Filters returns a copy of the built-in filters (trim, ltrim, rtrim, lower, upper, title, int, float, bool, string). The slice is never nil and modifying it does not affect the package.

type Option

type Option func(*config) error

Option configures a Validator during New. Options run in the order given and the first error aborts construction; the resulting Validator is immutable.

func WithAttributes

func WithAttributes(attributes map[string]string) Option

WithAttributes maps validation names to the display names substituted for {field} in messages. Later options merge over earlier ones. A nil map is ErrInvalidOption.

func WithFallibleRules added in v0.4.2

func WithFallibleRules(rules ...FallibleRule) Option

WithFallibleRules registers FallibleRule implementations, whose errors surface as *RuleError instead of field failures. The same nil, signature and duplicate checks as WithRules apply.

func WithFilters added in v0.4.2

func WithFilters(filters ...Filter) Option

WithFilters registers custom Filter implementations. A nil filter is ErrInvalidOption, a malformed signature is ErrInvalidSignature, and a signature already registered is ErrDuplicateFilter at New.

func WithMessages

func WithMessages(messages map[string]string) Option

WithMessages overrides failure templates, keyed by "field.rule" or "rule"; the more specific key wins. Templates use {field}, {0}, {1}, ... and {N+} (the arguments from N on, comma-joined). Later options merge over earlier ones. A nil map is ErrInvalidOption.

func WithParallel

func WithParallel(minFields int) Option

WithParallel evaluates fields concurrently, on up to GOMAXPROCS goroutines, once a validation has at least minFields rule-bearing fields. Rules and filters must then be safe for concurrent use. Evaluation is sequential when the option is omitted; minFields <= 0 is ErrInvalidOption.

func WithPrivateFieldValidation

func WithPrivateFieldValidation() Option

WithPrivateFieldValidation includes unexported struct fields in plans, so their tags are validated and they resolve as siblings and bind targets. Their values are read through unsafe access to an addressable copy of the input.

func WithRuleFunc added in v0.4.2

func WithRuleFunc(signature string, fn func(*Field) bool, message string) Option

WithRuleFunc registers a Rule built from a function, for checks that need no state. fn is bound by the Rule contract: deterministic, safe for concurrent use, and not retaining the Field. message is the failure template. A nil fn is ErrInvalidOption; signature is checked like WithRules.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	v := validator.MustNew(validator.WithRuleFunc(
		"even",
		func(f *validator.Field) bool {
			n, ok := f.Value[int]()
			return ok && n%2 == 0
		},
		"The {field} must be even.",
	))
	vd := v.MustValue(3, "required && even")
	_ = vd.Validate(context.Background())
	fmt.Println(vd.Errors().One())
}
Output:
The value must be even.

func WithRules added in v0.4.2

func WithRules(rules ...Rule) Option

WithRules registers custom Rule implementations. A nil rule is ErrInvalidOption, a malformed signature is ErrInvalidSignature, and a signature already registered (built-in or custom) is ErrDuplicateRule at New.

func WithStrictRequired

func WithStrictRequired() Option

WithStrictRequired makes required and the required_* family reject zero values (0, "", false, empty collections, nil, zero time.Time) as well as absent ones. Without it required only checks presence, so a submitted empty string passes; use filled or notblank when that must fail.

func WithStringRule added in v0.4.2

func WithStringRule(signature string, fn func(string, ...string) bool, message string) Option

WithStringRule registers a Rule over the string form of the value: empty values pass (IsEmpty semantics) and other values are rendered with conv.ToString before fn sees them together with the rule's arguments. A nil fn is ErrInvalidOption; signature is checked like WithRules.

func WithTagName

func WithTagName(name string) Option

WithTagName selects the struct tag that holds rule expressions; the default is "validate". An empty name is ErrInvalidOption.

func WithTagNameFunc

func WithTagNameFunc(fn TagNameFunc) Option

WithTagNameFunc installs a TagNameFunc, typically to name fields after their json tag so error keys match the request body. A nil fn is ErrInvalidOption.

func WithTransformFunc

func WithTransformFunc(fn TransformFunc) Option

WithTransformFunc installs a TransformFunc applied to every resolved message. A nil fn is ErrInvalidOption.

func WithTranslation

func WithTranslation(messages map[string]string) Option

WithTranslation sets localized templates with the same keys and placeholders as WithMessages but lower precedence, so a WithMessages entry still wins. The translations package provides ready-made maps. A nil map is ErrInvalidOption.

func WithTranslator

func WithTranslator(fn TranslatorFunc) Option

WithTranslator installs a TranslatorFunc consulted after the message and translation maps, for catalogs that cannot be materialized as a map. A nil fn is ErrInvalidOption.

func WithoutBuiltinRules

func WithoutBuiltinRules() Option

WithoutBuiltinRules starts from an empty registry, so only the rules and filters added by other options exist and an expression naming a built-in fails to compile. Rules and Filters expose the catalog for re-registering a subset.

type Rule

type Rule interface {
	// Signature returns the name used in expressions: letters, digits and
	// underscores, not starting with a digit, and not the reserved "dive".
	Signature() string
	// Passes reports whether the field's current value is acceptable. It
	// must not retain f or the slice returned by f.Attrs after returning.
	Passes(f *Field) bool
	// Message returns the failure template; {field} is the display name and
	// {0}, {1}, ... the arguments. An empty template renders as
	// "The {field} is invalid.".
	Message() string
}

Rule is a leaf boolean check; composition with &&, || and ! is the expression's job. A Validator is shared between goroutines, so implementations must be safe for concurrent use. Passes must be deterministic and side-effect free: the engine evaluates a rule more than once for one value (a fast probe before diagnostics on dive elements, Valid versus Validate), so a rule that answers differently on the second call produces inconsistent reports. The *Field is pooled and only valid during the call. A check that performs I/O or can fail implements FallibleRule instead.

By convention empty values pass (IsEmpty), leaving presence to required, filled and notblank, so rules compose without repeating presence checks.

func Rules

func Rules() []Rule

Rules returns a copy of the built-in rules in registration order, for documentation, introspection, or re-registering a subset on a Validator built with WithoutBuiltinRules. The slice is never nil and modifying it does not affect the package.

type RuleError added in v0.4.2

type RuleError struct {
	// Field is the validation name of the field being evaluated, or "" when
	// the failure cannot be attributed to one field.
	Field string
	// Rule is the signature of the FallibleRule that returned the error; ""
	// for a recovered panic.
	Rule string
	// Err is the cause: the FallibleRule's error, or ErrRulePanic wrapped with
	// the panic value. It is nil only in a zero RuleError.
	Err error
}

RuleError identifies a rule whose evaluation could not be completed: a FallibleRule returned a non-nil error, or a rule or the reflection reading the input panicked. It is an operational failure, not a verdict on the data, so it is never added to Errors: Validate and Valid return it (joined with any field failures), and ValidateAs returns it without ErrValidationFailed. The failing field's remaining rules are skipped; other fields are still evaluated.

Unwrap yields ErrRuleEvaluation joined with Err, so errors.Is matches the sentinel as well as the cause (ErrRulePanic for a recovered panic, or the FallibleRule's own error).

func (*RuleError) Error added in v0.4.2

func (e *RuleError) Error() string

Error renders the field, rule and cause, omitting the parts that are unset; a nil receiver or a nil Err reports rule evaluation failed.

func (*RuleError) Unwrap added in v0.4.2

func (e *RuleError) Unwrap() error

Unwrap returns ErrRuleEvaluation joined with Err, or ErrRuleEvaluation alone when Err is nil, so errors.Is sees both.

type RuleInfo added in v0.4.0

type RuleInfo struct {
	// Name is the rule signature as written in the expression.
	Name string
	// Args are the unquoted arguments, nil when none were given.
	Args []string
}

RuleInfo is one rule invocation from an expression: min:3 becomes Name "min" with Args ["3"]. Args are unquoted; a rule written without arguments has nil Args.

type RulesError added in v0.4.2

type RulesError struct {
	// Field is the validation name whose expression failed to compile.
	Field string
	// Err is the cause, typically a parse error carrying the position.
	Err error
}

RulesError identifies a field whose rule expression is invalid: a parse error, an unknown rule, bad static arguments (a regex that does not compile, a missing sibling name, an unparsable file size), or "sometimes" placed under ||, ! or dive. The constructors, Check and CheckType return one RulesError per bad field, joined with errors.Join; nothing is evaluated.

Unwrap yields ErrInvalidRules joined with Err, so errors.Is matches both.

func (*RulesError) Error added in v0.4.2

func (e *RulesError) Error() string

Error renders the field and cause; a nil receiver or a nil Err reports invalid rules.

func (*RulesError) Unwrap added in v0.4.2

func (e *RulesError) Unwrap() error

Unwrap returns ErrInvalidRules joined with Err, or ErrInvalidRules alone when Err is nil, so errors.Is sees both.

type TagNameFunc

type TagNameFunc func(field reflect.StructField) string

TagNameFunc derives a field's validation name from its struct field, for example from a json tag so error keys match the wire format; returning "" keeps the Go field name. The name is used in error keys, messages, attributes and cross-field references.

type TransformFunc

type TransformFunc func(message string) string

TransformFunc post-processes every resolved message, for example to capitalize or wrap it. A panic inside it is recovered and the untransformed message is used.

type TranslatorFunc

type TranslatorFunc func(rule string) (string, bool)

TranslatorFunc looks up a template by rule signature at message resolution time, after the WithMessages and WithTranslation maps miss; returning false falls through to the rule's own Message. It must be safe for concurrent use.

type Validation

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

Validation is one input together with its rule configuration and, after Validate, its result. Constructors return it with the input's rules attached; AddRules, AddFilters and AddMessages may adjust it until Validate runs, after which they return ErrValidated. Validate runs once and the result is read through Errors, Err and Fails.

A Validation is not safe for concurrent use and is meant to serve one request; the Validator behind it is the shareable object.

func JSON

func JSON[Bytes ~[]byte | ~string](data Bytes, rules map[string]string) (*Validation, error)

JSON prepares a Validation from one JSON object and explicit field expressions with the Default Validator; see Validator.JSON.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	vd := validator.MustJSON(`{"name": "alice", "age": 17}`, map[string]string{
		"name": "required && alpha",
		"age":  "required && gte:18",
	})
	err := vd.Validate(context.Background())
	fmt.Println(err)
}
Output:
age: The age field must be greater than or equal to 18.

func Map

func Map[M ~map[K]V, K comparable, V any](data M, rules map[string]string) (*Validation, error)

Map prepares a Validation for a map and explicit field expressions with the Default Validator; see Validator.Map.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	vd := validator.MustMap(
		map[string]any{"name": "alice", "role": "root"},
		map[string]string{
			"name": "required && alpha",
			"role": "required && in:admin,user",
		},
	)
	_ = vd.Validate(context.Background())
	fmt.Println(vd.Errors().OneFor("role"))
}
Output:
The selected role is invalid.

func MustJSON added in v0.4.2

func MustJSON[Bytes ~[]byte | ~string](data Bytes, rules map[string]string) *Validation

MustJSON is JSON with panic-on-error semantics.

func MustMap added in v0.4.2

func MustMap[M ~map[K]V, K comparable, V any](data M, rules map[string]string) *Validation

MustMap is Map with panic-on-error semantics.

func MustStruct added in v0.4.2

func MustStruct(data any) *Validation

MustStruct is Struct with panic-on-error semantics.

func MustValue added in v0.4.2

func MustValue(value any, rule string) *Validation

MustValue is Value with panic-on-error semantics.

func MustValues added in v0.4.2

func MustValues(data url.Values, rules map[string]string) *Validation

MustValues is Values with panic-on-error semantics.

func Struct

func Struct(data any) (*Validation, error)

Struct prepares a struct-tag validation with the Default Validator; see Validator.Struct for the input rules and errors.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	type User struct {
		Email string `validate:"required && email"`
		Age   int    `validate:"required && gte:18"`
	}
	vd, err := validator.Struct(User{Email: "not-an-email", Age: 17})
	if err != nil {
		panic(err) // not a struct, or a tag that does not compile
	}
	err = vd.Validate(context.Background())
	errs, _ := validator.AsErrors(err)
	fmt.Println(errs.OneFor("Email"))
	fmt.Println(errs.OneFor("Age"))
}
Output:
The Email must be a valid email address.
The Age field must be greater than or equal to 18.

func Value added in v0.4.2

func Value(value any, rule string) (*Validation, error)

Value prepares a Validation for one value under the field name "value" with the Default Validator; see Validator.Value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	vd := validator.MustValue("nope", "required && email")
	_ = vd.Validate(context.Background())
	fmt.Println(vd.Fails())
}
Output:
true

func Values added in v0.4.2

func Values(data url.Values, rules map[string]string) (*Validation, error)

Values prepares a Validation from form or query data with the Default Validator; a repeated key becomes a []string. See Validator.Values.

Example
package main

import (
	"context"
	"fmt"
	"net/url"
	"reflect"
	"strings"

	"github.com/libtnb/validator"
)

func main() {
	type Form struct {
		Name string   `json:"name"`
		Tags []string `json:"tags"`
	}
	// Name fields after their json tag so form keys and struct fields match.
	v := validator.MustNew(validator.WithTagNameFunc(func(f reflect.StructField) string {
		name, _, _ := strings.Cut(f.Tag.Get("json"), ",")
		return name
	}))
	// tags=go&tags=web: a repeated key becomes a []string, so dive checks each value.
	form := url.Values{"name": {"alice"}, "tags": {"go", "web"}}
	vd := v.MustValues(form, map[string]string{
		"name": "required && alpha",
		"tags": "required && dive && alpha",
	})
	var f Form
	if err := vd.ValidateAs(context.Background(), &f); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(f.Name, f.Tags)
}
Output:
alice [go web]

func (*Validation) AddFilters

func (vd *Validation) AddFilters(field string, filters ...string) error

AddFilters appends filters to field's chain. Each string is a "|"-separated chain of names with optional ":"-arguments ("trim", "trim:xy", "int"); "\" escapes a separator. The whole chain is checked and an unknown filter returns an error leaving the chain unchanged. Filters run at Validate, before rules, and their output is what ValidateAs binds; a field with dive rules is never filtered, because a scalar filter would stringify the collection. Empty strings are skipped.

Returns ErrValidated after Validate has run.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	type Form struct {
		Email string
	}
	vd := validator.MustMap(
		map[string]any{"Email": "  Alice@Example.com "},
		map[string]string{"Email": "required && email"},
	)
	// Filters run before the rules; ValidateAs binds the filtered value.
	_ = vd.AddFilters("Email", "trim|lower")
	var f Form
	if err := vd.ValidateAs(context.Background(), &f); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(f.Email)
}
Output:
alice@example.com

func (*Validation) AddMessages added in v0.1.1

func (vd *Validation) AddMessages(messages map[string]string) error

AddMessages overrides failure templates for this Validation only, keyed by "field.rule" or "rule" as in WithMessages, and takes precedence over the Validator's messages and translations. Overrides also apply to failures already recorded, since messages are resolved on read. An empty map is a no-op.

Returns ErrValidated after Validate has run.

func (*Validation) AddRules

func (vd *Validation) AddRules(field string, rules ...string) error

AddRules appends expressions to field's rules, joined with &&. A top-level || on either side is parenthesized so precedence is preserved, and when the existing expression dives, the new rules apply to the container, before dive. Empty strings are skipped. The candidate is compiled first: an invalid expression returns its parse error and leaves the field's rules unchanged. A field unknown to the input is simply validated as absent.

Returns ErrValidated after Validate has run.

func (*Validation) Bind

func (vd *Validation) Bind[T any](dst *T) error

Bind writes the raw input into dst without validating. Every field of T that the input names is converted (strings to numbers, booleans and times, []any to typed slices, nested objects to structs), untagged fields included; fields the input does not name keep their value. Binding is atomic: on any failure dst is left exactly as it was. T must be a non-pointer struct type.

Returns ErrBindTarget for a nil dst or a non-struct T, and *BindError (wrapping ErrBindConversion) naming the first field that could not be converted.

func (*Validation) ClearFilters

func (vd *Validation) ClearFilters(field string) error

ClearFilters drops field's entire filter chain; an unknown field is a no-op.

Returns ErrValidated after Validate has run.

func (*Validation) ClearRules

func (vd *Validation) ClearRules(field string) error

ClearRules drops field's entire expression, dive rules included; an unknown field is a no-op.

Returns ErrValidated after Validate has run.

func (*Validation) Err

func (vd *Validation) Err() error

Err returns the result of Validate as an error: nil when the input passed (a literal nil, never a typed nil), *Errors when fields failed, *RuleError or the cancellation cause when the run could not complete, and the two joined when both apply. Before Validate it is nil.

func (*Validation) Errors

func (vd *Validation) Errors() *Errors

Errors returns the collection of field failures. It is empty before Validate and after a passing run. The pointer stays valid for the life of the Validation and its messages reflect AddMessages overrides.

func (*Validation) Fails

func (vd *Validation) Fails() bool

Fails reports whether Validate recorded field failures or could not complete. It is false before Validate.

func (*Validation) Filters

func (vd *Validation) Filters() map[string]string

Filters returns a copy of the current field -> chain map. The map is never nil and modifying it has no effect on the Validation.

func (*Validation) RemoveFilters

func (vd *Validation) RemoveFilters(field string, filters ...string) error

RemoveFilters drops the named filters from field's chain, matching either the name ("trim") or the full segment ("trim:xy"). An unknown field or no names is a no-op; a chain that becomes empty loses its entry.

Returns ErrValidated after Validate has run.

func (*Validation) RemoveRules

func (vd *Validation) RemoveRules(field string, rules ...string) error

RemoveRules deletes the named rules from the top-level && chain of field's expression; a name may also be a full segment such as "min:3". Rules nested under ||, ! or parentheses, and "dive" itself, are never removed. An unknown field or no names is a no-op; a field whose expression becomes empty loses its entry.

Returns ErrValidated after Validate has run.

func (*Validation) Rules

func (vd *Validation) Rules() map[string]string

Rules returns a copy of the current field -> expression map, reflecting AddRules, RemoveRules and ClearRules. The map is never nil and modifying it has no effect on the Validation.

func (*Validation) Validate

func (vd *Validation) Validate(ctx context.Context) error

Validate runs filters and rules and records the result. The first call with a non-nil context freezes the Validation: later calls, and Err, return the same result, including one caused by a context that was already done. Fields are evaluated in name order (concurrently above the WithParallel threshold), a "sometimes" field is skipped when absent, and every field and every && operand is evaluated so Errors is complete.

Returns ErrNilContext for a nil ctx, the context's cancellation cause when ctx was done before or during evaluation, *Errors when fields failed, and *RuleError when a rule could not complete; the last two may be joined. A nil error means the input passed.

func (*Validation) ValidateAs added in v0.4.2

func (vd *Validation) ValidateAs[T any](ctx context.Context, dst *T) error

ValidateAs runs Validate and, on success, binds the filtered input into dst like Bind, so trimmed or coerced values are what the caller receives. Nothing is written unless validation and every conversion succeed.

Returns ErrBindTarget for a nil dst or a non-struct T (checked before validating); ErrNilContext for a nil ctx; *RuleError or the context's cancellation cause when validation could not complete; *Errors joined with ErrValidationFailed when fields failed, so AsErrors recovers the details; and *BindError when a conversion fails. Binding errors are not field failures and carry no ErrValidationFailed.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	type User struct {
		Email string `validate:"required && email"`
		Age   int    `validate:"required && numeric"`
	}
	vd := validator.MustMap(
		map[string]any{"Email": "a@b.com", "Age": "42"},
		map[string]string{"Email": "required && email", "Age": "required && numeric"},
	)
	_ = vd.Validate(context.Background())

	var u User
	if err := vd.ValidateAs(context.Background(), &u); err == nil {
		fmt.Printf("%s / %d\n", u.Email, u.Age)
	}
}
Output:
a@b.com / 42

type Validator

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

Validator compiles rule expressions and produces Validations. It is immutable after New and safe for concurrent use, so share one per configuration (rules, filters, messages, options) rather than constructing one per request. Compiled expressions, struct plans and rules-map plans are cached inside it: the first validation of a type or rule set pays the compilation cost, later ones do not.

func Default

func Default() *Validator

Default returns the shared Validator behind the package-level functions: the built-in rules and filters with default options, created on first use and safe for concurrent use. Construct your own Validator with New when rules, messages, translations or other options differ.

func MustNew added in v0.4.2

func MustNew(options ...Option) *Validator

MustNew is New for static configuration: it panics with the error New would return.

func New added in v0.4.2

func New(options ...Option) (*Validator, error)

New constructs a Validator from options, applied in order. The built-in rules and filters are registered first unless WithoutBuiltinRules is given, so a custom rule named like a built-in one fails with ErrDuplicateRule.

Returns ErrInvalidOption for a nil option or an invalid option argument, ErrInvalidSignature for a malformed rule or filter name, and ErrDuplicateRule or ErrDuplicateFilter for a signature registered twice.

func (*Validator) Check added in v0.4.2

func (v *Validator) Check[T any]() error

Check compiles every rule expression reachable from struct type T, nested and embedded fields included, and reports each invalid tag as a *RulesError joined into one error. Call it at start-up so a typo in a tag fails there rather than on the first request. T must be a non-pointer struct type; any other type is an error.

func (*Validator) CheckType added in v0.4.2

func (v *Validator) CheckType(t reflect.Type) error

CheckType is Check for a reflect.Type, for schema generators and other callers that discover types at runtime. Pointer types are dereferenced; a nil or non-struct type is an error.

func (*Validator) Describe added in v0.4.2

func (v *Validator) Describe[T any]() ([]FieldRules, error)

Describe reports the rules declared on struct type T for consumers that translate them into another representation, such as an OpenAPI generator mapping min:3 to minLength. Every field with a non-empty tag yields one FieldRules, in plan order; struct-valued and embedded fields with tags are included because their rules are enforced too. Unknown rule names are reported as written rather than rejected, so pair it with Check to catch typos. T must be a non-pointer struct type.

Returns an error when T is not a struct type or an expression does not parse; the message carries the position.

func (*Validator) DescribeType added in v0.4.2

func (v *Validator) DescribeType(t reflect.Type) ([]FieldRules, error)

DescribeType is Describe for a reflect.Type, for schema generators that discover nested types dynamically. Pointer types are dereferenced; a nil or non-struct type is an error.

func (*Validator) JSON

func (v *Validator) JSON[Bytes ~[]byte | ~string](
	data Bytes,
	rules map[string]string,
) (*Validation, error)

JSON decodes one JSON object and prepares a Validation with explicit field expressions, as Map does. Numbers are decoded as int64, uint64 or float64 rather than float64 alone, so large integers keep their precision. The document must be a single object: duplicate names, invalid UTF-8, trailing data and non-object roots are rejected.

Returns ErrInvalidInput for malformed or non-object input and *RulesError values (wrapping ErrInvalidRules) for expressions that do not compile.

func (*Validator) Map

func (v *Validator) Map[M ~map[K]V, K comparable, V any](
	data M,
	rules map[string]string,
) (*Validation, error)

Map prepares a Validation over any map type with explicit field expressions (field name -> expression). Non-string keys are rendered with conv.ToString, and when two keys render the same the survivor is chosen deterministically. Dotted expression names reach into nested maps. The rules map is compiled once and cached by content; it is neither retained nor modified, so it may be a package-level literal.

Returns *RulesError values (wrapping ErrInvalidRules) for expressions that do not compile.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	v := validator.MustNew()
	vd := v.MustMap(
		map[string]any{"name": "alice"},
		// boolean DSL: AND-grouped OR
		map[string]string{"name": "required && (alpha || in:bob,carol)"},
	)
	_ = vd.Validate(context.Background())
	fmt.Println("valid:", !vd.Fails())
}
Output:
valid: true

func (*Validator) MustJSON added in v0.4.2

func (v *Validator) MustJSON[Bytes ~[]byte | ~string](data Bytes, rules map[string]string) *Validation

MustJSON is JSON with panic-on-error semantics.

func (*Validator) MustMap added in v0.4.2

func (v *Validator) MustMap[M ~map[K]V, K comparable, V any](data M, rules map[string]string) *Validation

MustMap is Map with panic-on-error semantics.

func (*Validator) MustStruct added in v0.4.2

func (v *Validator) MustStruct(data any) *Validation

MustStruct is Struct with panic-on-error semantics, for inputs whose type and tags are known to be valid.

func (*Validator) MustValue added in v0.4.2

func (v *Validator) MustValue(value any, rule string) *Validation

MustValue is Value with panic-on-error semantics.

func (*Validator) MustValues added in v0.4.2

func (v *Validator) MustValues(data url.Values, rules map[string]string) *Validation

MustValues is Values with panic-on-error semantics.

func (*Validator) Struct

func (v *Validator) Struct(data any) (*Validation, error)

Struct prepares a Validation whose rules come from data's struct tags. data is a struct or a pointer to one; a nil pointer validates as the zero value, so required fields still fail. Fields are named by their Go name (or by WithTagNameFunc), nested structs use dotted names, embedded structs promote their fields, and a "-" tag excludes a field and its nested fields from validation while keeping them bindable. The type's plan is built once and cached. Nothing is evaluated until Validate.

Returns ErrInvalidInput when data is not a struct, and *RulesError values (wrapping ErrInvalidRules) for tags that do not compile.

func (*Validator) Valid

func (v *Validator) Valid(ctx context.Context, data any) (bool, error)

Valid reports whether data passes its struct-tag rules without building an error collection: it stops at the first failing field and, for built-in rules, allocates nothing, so prefer it when only the verdict matters. It never applies filters. The error is non-nil only when validation could not complete (ErrNilContext, ErrInvalidInput, *RulesError, *RuleError or the context's cancellation cause); false with a nil error is a plain failure.

func (*Validator) Value added in v0.4.2

func (v *Validator) Value(value any, rule string) (*Validation, error)

Value prepares a Validation for one value under the field name "value", for ad-hoc checks such as a path parameter or a configuration setting. The expression is compiled once and cached. Cross-field rules find no siblings here.

Returns *RulesError (wrapping ErrInvalidRules) when rule does not compile.

func (*Validator) Values added in v0.4.2

func (v *Validator) Values(data url.Values, rules map[string]string) (*Validation, error)

Values prepares a Validation from form or query data with explicit field expressions. A key with one value maps to that string and a key with several values (tags=a&tags=b) maps to a []string, so repeated parameters validate with dive and bind into slice fields. Every value is a string: use numeric, number or boolean rules, or filters such as int, to interpret them.

Returns *RulesError values (wrapping ErrInvalidRules) for expressions that do not compile.

Directories

Path Synopsis
Command examples is a runnable validator demo; the leading-underscore dir is skipped by the go tool, so run it directly: go run ./_examples
Command examples is a runnable validator demo; the leading-underscore dir is skipped by the go tool, so run it directly: go run ./_examples
contrib
gormrules module
openapi module
Package conv converts loosely typed input (form strings, decoded JSON, named scalar types) into Go values without silent data loss: overflow, non-finite floats and unparsable strings return errors wrapping ErrConvert, and float-to-integer conversion truncates toward zero.
Package conv converts loosely typed input (form strings, decoded JSON, named scalar types) into Go values without silent data loss: overflow, non-finite floats and unparsable strings return errors wrapping ErrConvert, and float-to-integer conversion truncates toward zero.
internal
dsl
Package dsl is the rule-expression engine behind the validator: a lexer, a parser with precedence ! > && > ||, the AST the compiler consumes, and the token-level helpers (dive splitting, top-level leaf removal, top-level || detection) that let the validator edit expressions without re-parsing them as text.
Package dsl is the rule-expression engine behind the validator: a lexer, a parser with precedence ! > && > ||, the AST the compiler consumes, and the token-level helpers (dive splitting, top-level leaf removal, top-level || detection) that let the validator edit expressions without re-parsing them as text.
Package is provides pure syntax predicates for common string formats: network addresses, identifiers, encodings and coordinates.
Package is provides pure syntax predicates for common string formats: network addresses, identifiers, encodings and coordinates.
Package translations provides localized message templates for the validator's built-in rules, keyed by rule signature, for use with validator.WithTranslation.
Package translations provides localized message templates for the validator's built-in rules, keyed by rule signature, for use with validator.WithTranslation.

Jump to

Keyboard shortcuts

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