validator

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 25 Imported by: 0

README

validator

Doc Go Release Test Report Card Stars License

A modern, zero-dependency, high-performance Go Struct and Field validator with a boolean rule DSL.

Features

  • Zero third-party dependencies — only the Go standard library.
  • Boolean DSL&&, ||, !, (), precedence ! > && > ||.
  • Compile-once, run-many — expressions compile to cached closures.
  • 6 entry pointsStruct, Map, JSON, URLValues, Any, Var.
  • And more — cross-field, diving, bind/SafeBind, context, optional concurrency, i18n.

Requires Go 1.25+.

Install

go get github.com/libtnb/validator

Quick start

import (
    "context"
    "fmt"

    "github.com/libtnb/validator"
)

type User struct {
    Email string `validate:"required && email"`
    Pwd   string `validate:"required && min:8"`
    Pwd2  string `validate:"required && same:Pwd"`   // cross-field
    Tags  []string `validate:"dive && alpha"`        // each element
    Role  string `validate:"required && in:admin,user,guest"`
}

user := User{Email: "a@b.com", Pwd: "secret12", Pwd2: "secret12", Role: "user"}

vd := validator.Struct(user)
vd.Validate(context.Background())
if vd.Fails() {
    fmt.Println(vd.Errors().All()) // map[field]map[rule]message
}

var out User
vd.SafeBind(&out) // bind the validated, filtered values into out

// just pass/fail? Valid takes the allocation-free fast path (0 B/op)
if validator.Valid(user) { /* ... */ }

Maps / JSON / form values, with explicit rules:

vd := validator.Map(
    map[string]any{"email": "a@b.com", "age": 30},
    map[string]string{
        "email": "required && email",
        "age":   "required && (gte:18 && lte:120)",
    },
)
vd.Validate(context.Background())

validator.JSON(`{"email":"a@b.com"}`, rules)           // decodes then validates
validator.URLValues(r.Form, rules)                     // form data
validator.Var("a@b.com", "required && email")          // a single value

The DSL

Construct Meaning
a && b both must pass
a || b at least one must pass
!a must NOT pass
(a || b) && c grouping; precedence is ! > && > ||
rule:arg1,arg2 rule with arguments (in:a,b,c, min:3)
regex:"^(a|b)+$" quoted argument: | & ( ) are literal inside quotes
dive apply the following rules to each slice/array/map element

Argument quoting. Operators are the double-character forms && / ||, so a single | inside a regex is never mistaken for OR. For arguments containing | & ( ), wrap them in quotes (regex:"...", only \" and \\ are escapes inside quotes) or escape with a backslash in bare form (\| \& \( \)). Regex metacharacters like \d are preserved.

Semantics

Empty values (omitempty). Non-presence rules pass on empty/zero values, so an optional field is only checked when present. This is why ! differs from not_*: in:a,b passes on "" (omitempty), so !in:a,b rejects "" while not_in:a,b passes it. Negate a non-presence rule with its not_*/ne form, not ! (which is for presence rules like !required).

required. By default required asserts present and non-nil: a Go zero value ("", 0, false) counts as provided and passes, while a nil pointer or an absent map/JSON key fails. To reject empty values, use notblank/filled for strings, or WithStrictRequired() to make required reject zero values too.

Sizes: length vs value. min/max/between/gt/gte/lt/lte/len/size compare numbers by value and strings by rune count; add numeric to compare a string by value:

"age": "numeric && gte:18"   // "30" compares as the number 30
"pwd": "required && min:8"   // "999" is 3 characters, fails

Built-in rules

Args are the rule:arg values ( = none; = repeatable; ? = optional).

Presence

Rule Args Passes when
required present and non-nil (non-zero under WithStrictRequired)
filled not empty
notblank has a non-whitespace character
required_if field,val… required when field equals any listed value
required_unless field,val… required unless field equals any listed value
required_with field… required when any listed field is present
required_without field… required when any listed field is missing

String

Rule Args Passes when
alpha letters only
alphanum letters and digits only
ascii ASCII characters only
lowercase all lowercase
uppercase all uppercase
contains sub contains the substring
excludes sub does not contain the substring
startswith prefix has the prefix
endswith suffix has the suffix

Format

Rule Args Passes when
email a valid email address
url / uri a valid URL / URI
uuid a valid UUID
ip / ipv4 / ipv6 a valid IP / IPv4 / IPv6 address
json valid JSON
base64 a valid base64 string
mac a valid MAC address
hostname a valid RFC 1123 hostname
datetime layout? parses with the Go time layout (default layouts if omitted)
date a valid date
regex pattern matches the pattern
not_regex pattern does not match the pattern

Numeric / sizesize = value for numbers, rune length for strings (see above); n is a number.

Rule Args Passes when
min / max n size ≥ n / ≤ n
between min,max size within [min, max]
gt / gte n size > n / ≥ n
lt / lte n size < n / ≤ n
len / size n size == n
digits n exactly n digits
numeric a number or numeric string
number an integer
boolean convertible to a boolean

Comparison

Rule Args Passes when
in a,b,… equals one of the values
not_in a,b,… equals none of the values
eq / ne v equals / does not equal v

Cross-field

Rule Args Passes when
same / eqfield field equals another field
different / nefield field differs from another field
gtfield / gtefield field numerically > / ≥ another field
ltfield / ltefield field numerically < / ≤ another field
confirmed <field>_confirmation exists and matches

Filters (transform the value before validation / SafeBind)

Filter Args Effect
trim / ltrim / rtrim strip surrounding / leading / trailing whitespace
lower / upper / title lower-case / upper-case / title-case
int / float / bool / string convert to that type

The is subpackage exposes the format checks as plain functions (is.Email, is.URL, ...) for reuse outside the validator.

Custom rules

Field.Val() returns a reflect.Value (the value flows without boxing, so the validation hot path is allocation-free). Read it with the reflect.Value getters, or f.Val().Interface() to get the value as any for the conv helpers.

v := validator.NewValidator()

// function style — read the value via reflect.Value (0-alloc)
v.RegisterFunc("even", func(f validator.Field) bool {
    rv := f.Val()
    return rv.CanInt() && rv.Int()%2 == 0
}, "The {field} must be even.")

// interface style: implement Signature() / Passes(Field) bool / Message()
v.RegisterRule(&MyRule{})

Rules that need a context.Context or return an error (e.g. a DB uniqueness check) implement validator.ErrorRule instead and read f.Context().

Messages, attributes & i18n

v := validator.NewValidator(
    validator.WithAttributes(map[string]string{"email": "Email address"}),
    validator.WithMessages(map[string]string{
        "email.required": "Please provide your email.", // field-level
        "required":       "This field is required.",     // rule-level
    }),
    validator.WithTranslation(translations.ZhHans()),    // i18n fallback
)

Locale packs (ZhHans, ZhHant, Ja, Ko, Es) live in the github.com/libtnb/validator/translations subpackage.

Templates use {field} (replaced by the attribute alias or field name) and {0}, {1}, ... (the rule arguments). Validation.AddMessages overrides templates for a single run:

vd := v.Struct(req)
vd.AddMessages(map[string]string{"email.required": "We need your email."})

Priority: AddMessages > WithMessages > i18n > built-in English; within a map, field.rule beats rule.

License

See LICENSE.

Documentation

Overview

Package validator is a fast Go validator whose headline is a boolean rule DSL combining rules with &&, ||, !, () at precedence ! > && > || (e.g. `required && (in:admin,user || regex:"^g.+")`).

Data sources: struct tags, map, JSON, url.Values, Any, or a single Var.

required means present-and-non-nil: a zero value ("", 0, false) passes, a nil pointer or absent key fails. Use notblank/filled to reject empty strings, or WithStrictRequired to demand a non-zero value.

To negate a non-presence rule use its not_*/ne form, not !: omitempty makes in:a,b pass on "", so !in:a,b rejects "" while not_in:a,b passes it. ! is for presence rules like !required.

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.Struct(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

This section is empty.

Functions

func IsEmpty

func IsEmpty(v any) bool

IsEmpty reports the zero/empty value for omitempty semantics.

func IsEmptyValue

func IsEmptyValue(rv reflect.Value) bool

IsEmptyValue is the reflect.Value form of IsEmpty.

func Valid

func Valid(data any) bool

Valid reports whether data passes its struct-tag rules on the allocation-free fast path; data carrying no rules (a plain map or scalar) is vacuously valid. Use Struct when you need error details, Bind, or to add rules.

Types

type ErrorRule

type ErrorRule interface {
	Signature() string
	PassesE(f Field) error
	Message() string
}

ErrorRule is a Rule variant returning an error; the engine prefers PassesE over Passes.

func ErrorRules

func ErrorRules() []ErrorRule

type Errors

type Errors interface {
	// One returns the first message overall.
	One() string
	// OneFor returns the first message for field.
	OneFor(field string) string
	// Messages returns field's rule->message map.
	Messages(field string) map[string]string
	All() map[string]map[string]string
	Has(field string) bool
	// String aggregates all messages (fields sorted), or "" when none failed.
	String() string
}

Errors reads validation failures. Pure collection; deliberately not an error (use Validation.Err() for nil-on-success).

type Field

type Field interface {
	// Val is the current value; absent/nil is an invalid Value.
	Val() reflect.Value
	// Attrs are the parsed rule arguments (in:a,b,c -> [a b c]). The slice is
	// shared with the compiled expression cache: read-only, never modify it.
	Attrs() []string
	// Name is the error-reporting name; for a dive element it is the bracketed
	// key ("tags[0]") on the diagnostic path.
	Name() string
	// RootData is the whole data set, not this field's value.
	RootData() any
	Context() context.Context
	// Sibling resolves another field (dotted name) relative-first then root-anchored, never itself.
	Sibling(name string) (Field, bool)
}

Field is a value being validated, exposing sibling fields for cross-field rules. A Field is only valid for the duration of the rule call that receives it (the engine pools and reuses instances): never retain one.

type FieldError

type FieldError struct {
	Field string
	Rule  string
	// Message is the raw template ({field}/{0}/...); root resolves it.
	Message string
	// Params are the rule arguments, resolving {0},{1},... placeholders.
	Params []string
}

FieldError is the flat unit of a validation failure.

type Filter

type Filter interface {
	Signature() string
	Handle(val any, args ...string) (any, error)
}

Filter transforms or sanitizes an input value (trim, lower, int, ...).

func Filters

func Filters() []Filter

type Option

type Option func(*Validator)

Option configures a Validator during initialization.

func WithAttributes

func WithAttributes(attrs map[string]string) Option

WithAttributes maps field names to display names for {field}.

func WithMessages

func WithMessages(messages map[string]string) Option

WithMessages sets message templates keyed by "field.rule" (highest priority) or "rule".

func WithParallel

func WithParallel(minFields int) Option

WithParallel validates fields concurrently once a validation has at least minFields; values < 1 disable parallelism.

func WithPrivateFieldValidation

func WithPrivateFieldValidation() Option

WithPrivateFieldValidation enables validation of unexported fields (via unsafe).

func WithStrictRequired

func WithStrictRequired() Option

WithStrictRequired makes `required` reject zero values, not just absent/nil.

func WithTagName

func WithTagName(name string) Option

WithTagName sets the struct tag used for validation rules (default "validate").

func WithTagNameFunc

func WithTagNameFunc(fn TagNameFunc) Option

WithTagNameFunc derives the tag name per field.

func WithTransformFunc

func WithTransformFunc(fn TransformFunc) Option

WithTransformFunc sets a final transform over each resolved message.

func WithTranslation

func WithTranslation(messages map[string]string) Option

WithTranslation sets localized message templates keyed by "field.rule" or "rule".

func WithTranslator

func WithTranslator(fn TranslatorFunc) Option

WithTranslator sets a function consulted after WithTranslation's map.

func WithoutBuiltinRules

func WithoutBuiltinRules() Option

WithoutBuiltinRules disables registration of the built-in rules and filters.

type Rule

type Rule interface {
	Signature() string
	Passes(f Field) bool
	Message() string
}

Rule is a leaf boolean rule; composition (&&, ||, !) is handled by the DSL. Passes must be deterministic and side-effect free: the engine may evaluate a rule more than once for one value (fast probe + diagnostics on dive elements, exhaustive Errors() collection).

func Rules

func Rules() []Rule

Rules returns a copy of the catalog; the copy guards the shared backing slice.

type TagNameFunc

type TagNameFunc func(field reflect.StructField) string

TagNameFunc derives a field's validation name from its StructField; an empty return keeps the Go field name.

type TransformFunc

type TransformFunc func(message string) string

TransformFunc is a final transform applied to each resolved error message.

type TranslatorFunc

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

TranslatorFunc returns a rule's template, or false to fall through.

type Validation

type Validation interface {
	// AddRules ANDs rule expressions onto a field's existing expression.
	AddRules(field string, rules ...string) error
	AddFilters(field string, filters ...string) error
	RemoveRules(field string, rules ...string) error
	RemoveFilters(field string, filters ...string) error
	// ClearRules/ClearFilters drop a field's whole expression/chain.
	ClearRules(field string) error
	ClearFilters(field string) error
	// AddMessages overrides message templates for this run only, outranking WithMessages.
	AddMessages(messages map[string]string) error
	Rules() map[string]string
	Filters() map[string]string
	// Bind writes the ORIGINAL data to ptr without requiring Validate.
	Bind(ptr any) error
	// SafeBind writes the FILTERED data to ptr; errors unless Validate passed.
	SafeBind(ptr any) error
	// Validate must run before accessing errors.
	Validate(ctx context.Context)
	// Errors is ALWAYS non-nil and not an error; never compare it to nil.
	Errors() Errors
	// Err is nil on success, else the failures.
	Err() error
	Fails() bool
}

Validation represents a single validation run over one data set. It is not safe for concurrent use; configure it (AddRules/AddFilters) before Validate — mutators after Validate have no effect on the already-computed result.

func Any

func Any(data any) Validation

func JSON

func JSON(data string, rules map[string]string) Validation

func Map

func Map(data map[string]any, rules map[string]string) Validation

func Struct

func Struct(data any) Validation

func URLValues

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

func Var

func Var(value any, rule string) Validation
Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

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

type Validator

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

Validator is a reusable, concurrency-safe factory for validations.

func Default

func Default() *Validator

Default returns the shared Validator behind the package funcs; mutating it is process-global.

func NewValidator

func NewValidator(options ...Option) *Validator

NewValidator creates a Validator, registering built-in rules unless WithoutBuiltinRules is given.

func (*Validator) Any

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

Any validates a map, a struct (tags are read), or a scalar.

func (*Validator) JSON

func (v *Validator) JSON(data string, rules map[string]string) Validation

JSON decodes and validates a JSON object; a decode error or non-object top-level value is reported as a validation error.

func (*Validator) Map

func (v *Validator) Map(data map[string]any, rules map[string]string) Validation

Map validates a map against the given field->rule expressions.

Example
package main

import (
	"context"
	"fmt"

	"github.com/libtnb/validator"
)

func main() {
	v := validator.NewValidator()
	vd := v.Map(
		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) RegisterErrorRule

func (v *Validator) RegisterErrorRule(rule ErrorRule)

func (*Validator) RegisterFilter

func (v *Validator) RegisterFilter(f Filter)

func (*Validator) RegisterFunc

func (v *Validator) RegisterFunc(signature string, fn func(Field) bool, message string)

RegisterFunc registers a rule from a plain function.

func (*Validator) RegisterRule

func (v *Validator) RegisterRule(rule Rule)

RegisterRule registers a custom rule and invalidates caches so later validations pick it up; register before constructing the validations that use it. An unusable signature (blank, DSL syntax, reserved "dive") panics, like http.ServeMux.Handle.

func (*Validator) Struct

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

Struct validates a struct using its tags plus any added rules.

func (*Validator) URLValues

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

URLValues validates form data, using the first value of each key.

func (*Validator) Valid

func (v *Validator) Valid(data any) bool

Valid reports whether data passes its struct-tag rules on the allocation-free fast path; data carrying no rules is vacuously valid. Use Struct when you need error details, Bind, or to add rules.

func (*Validator) Var

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

Var validates a single value; the field is named "value" and cross-field rules are not meaningful.

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 provides fast type conversions.
Package conv provides fast type conversions.
internal
dsl
Package dsl is the boolean rule-expression engine: lexer, parser (! > && > ||), AST, dive splitting.
Package dsl is the boolean rule-expression engine: lexer, parser (! > && > ||), AST, dive splitting.
Package is provides pure predicates for common string formats.
Package is provides pure predicates for common string formats.

Jump to

Keyboard shortcuts

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