Documentation
¶
Overview ¶
Package valex is a small value-validation engine built on the tagex struct-tag processor.
Requires Go 1.22 or later.
It supports two primary workflows:
- Programmatic validation using Validator or ValidatorFunc, with ValidatedValue for guarded assignment and MustValidate for fail-fast use.
- Struct-tag validation using the "val" tag and ValidateStruct. Register directives with MustRegisterDirective (or RegisterDirective, which returns an error instead of panicking); pass additional tagex.Tag values to ValidateStruct to process multiple tags in a single pass.
The engine ships no directives of its own. Ready-made validators live in the github.com/tedla-brandsema/valex/validators subpackage; register the ones you need with MustRegisterDirective. HTTP request binding and validation live in the github.com/tedla-brandsema/valex/forms subpackage, which keeps net/http out of the core engine.
Concurrency ¶
Registering a directive and ValidateStruct are safe for concurrent use: the "val" tag's directive registry is guarded by a mutex, and ValidateStruct only reads it. The intended pattern is to register directives once at startup (typically in an init function) and validate from many goroutines thereafter. Registering while other goroutines validate is safe but unusual.
Registries ¶
The package-level RegisterDirective, MustRegisterDirective, and ValidateStruct share one process-global default registry — like flag.CommandLine or http.DefaultServeMux, it belongs to the application, which registers once at startup and validates anywhere.
Libraries, and tests that need isolation, should create their own with NewRegistry instead of touching the global. Each Registry has an independent directive set, so two can hold the same directive name without colliding. Register on one with the free functions RegisterDirectiveTo / MustRegisterDirectiveTo (free functions because Go methods can't be generic), and validate with its ValidateStruct method.
Index ¶
- Constants
- Variables
- func FieldErrors(err error) map[string]error
- func MustRegisterDirective[T any](d tagex.Directive[T])
- func MustRegisterDirectiveTo[T any](r *Registry, d tagex.Directive[T])
- func MustValidate[T any](val T, v Validator[T]) T
- func RegisterDirective[T any](d tagex.Directive[T]) error
- func RegisterDirectiveTo[T any](r *Registry, d tagex.Directive[T]) error
- func ValidateStruct(data any, tags ...*tagex.Tag) error
- func ValidateStructAll(data any, tags ...*tagex.Tag) error
- type ConversionError
- type DirectiveParseError
- type DuplicateDirectiveError
- type EmptyDirectiveNameError
- type FieldAccessError
- type FieldSetError
- type HandleError
- type HookError
- type InvalidTargetError
- type MaxDepthError
- type MissingParamError
- type NilTagError
- type ParamConflictError
- type ParamParseError
- type ProcessError
- type Registry
- type Stage
- type TagError
- type TypeMismatchError
- type UnknownDirectiveError
- type UnsupportedParamTypeError
- type ValidatedValue
- type Validator
- type ValidatorFunc
Examples ¶
Constants ¶
const ( StageInput = tagex.StageInput StagePre = tagex.StagePre StageDirective = tagex.StageDirective StageParam = tagex.StageParam StagePost = tagex.StagePost StageStruct = tagex.StageStruct )
Processing stages, re-exported from tagex for use with Stage and ProcessError.
Variables ¶
var ErrNoValidator = errors.New("valex: no validator set")
ErrNoValidator is returned by ValidatedValue.Set when no Validator is configured.
Functions ¶
func FieldErrors ¶ added in v0.2.0
FieldErrors flattens an error returned by ValidateStructAll into a map from field path to the error for that field, keeping the first error seen per field.
The keys are struct field paths — the same values ProcessError.FieldPath carries, e.g. "Email" or "Items[2].SKU" — not request keys or display names; translate them yourself when rendering. It walks errors.Join trees, so it works on the accumulated result of ValidateStructAll. A nil error yields a nil map. Field-less errors (such as *InvalidTargetError) are omitted, so the original error stays authoritative — check err != nil first, then render the map on top.
func MustRegisterDirective ¶
MustRegisterDirective is like RegisterDirective but panics if registration fails. It is the convenient choice for registering directives once at startup (typically in an init function), where a duplicate or empty directive name is a programming error that should fail fast.
func MustRegisterDirectiveTo ¶ added in v0.2.0
MustRegisterDirectiveTo is like RegisterDirectiveTo but panics if registration fails — the convenient choice for registering directives once at startup.
func MustValidate ¶
MustValidate validates a value or panics if validation fails.
func RegisterDirective ¶
RegisterDirective registers a directive on the default registry for use with the "val" struct tag. It returns *EmptyDirectiveNameError if the directive's Name is blank, or *DuplicateDirectiveError if that name is already registered (it does not overwrite). Use MustRegisterDirective to panic on these instead, which is usually what you want when registering at startup.
Example ¶
RegisterDirective extends the "val" tag with a custom directive.
package main
import (
"fmt"
"github.com/tedla-brandsema/tagex"
"github.com/tedla-brandsema/valex"
)
// evenDirective is a custom "val" directive that accepts only even ints.
// A directive is registered as a pointer so tagex can populate its parameters.
type evenDirective struct{}
func (*evenDirective) Name() string { return "even" }
func (*evenDirective) Mode() tagex.DirectiveMode { return tagex.EvalMode }
func (*evenDirective) Handle(n int) (int, error) {
if n%2 != 0 {
return n, fmt.Errorf("value %d is not even", n)
}
return n, nil
}
func main() {
valex.RegisterDirective(&evenDirective{})
type Ticket struct {
Seats int `val:"even"`
}
fmt.Println(valex.ValidateStruct(&Ticket{Seats: 4}))
fmt.Println(valex.ValidateStruct(&Ticket{Seats: 3}))
}
Output: <nil> tag "val" error: directive processing field "Seats" directive "even": value 3 is not even
func RegisterDirectiveTo ¶ added in v0.2.0
RegisterDirectiveTo registers a directive on r. It is a free function rather than a method because Go methods cannot have type parameters. It returns *EmptyDirectiveNameError if the directive's Name is blank, or *DuplicateDirectiveError if that name is already registered on r; use MustRegisterDirectiveTo to panic on these instead.
func ValidateStruct ¶
ValidateStruct validates struct fields using the default registry's "val" directives. It returns nil when the struct is valid. Additional tagex.Tag values can be provided to process more tags in the same pass.
Example ¶
ValidateStruct validates fields via the "val" tag. Directives are opt-in: register the ones you use (here from the valex/validators catalog) first.
package main
import (
"fmt"
"github.com/tedla-brandsema/valex"
"github.com/tedla-brandsema/valex/validators"
)
func main() {
valex.RegisterDirective(&validators.EmailValidator{})
valex.RegisterDirective(&validators.IntRangeValidator{})
type User struct {
Email string `val:"email"`
Age int `val:"rangeint,min=0,max=120"`
}
fmt.Println(valex.ValidateStruct(&User{Email: "gopher@example.com", Age: 30}))
fmt.Println(valex.ValidateStruct(&User{Email: "gopher@example.com", Age: 200}))
}
Output: <nil> tag "val" error: directive processing field "Age" directive "rangeint": value 200 is out of range [0, 120]
func ValidateStructAll ¶ added in v0.2.0
ValidateStructAll is like ValidateStruct but does not stop at the first failure: it validates every field against the default registry and returns errors.Join of the per-field errors (nil when all pass). Use FieldErrors to turn the result into a map keyed by field path.
Types ¶
type ConversionError ¶
type ConversionError = tagex.ConversionError
ConversionError is returned when a raw parameter value cannot be converted.
type DirectiveParseError ¶
type DirectiveParseError = tagex.DirectiveParseError
DirectiveParseError is returned when a tag value omits the directive name.
type DuplicateDirectiveError ¶
type DuplicateDirectiveError = tagex.DuplicateDirectiveError
DuplicateDirectiveError is returned by RegisterDirective when the directive name is already registered.
type EmptyDirectiveNameError ¶
type EmptyDirectiveNameError = tagex.EmptyDirectiveNameError
EmptyDirectiveNameError is returned by RegisterDirective for a directive with a blank Name.
type FieldAccessError ¶
type FieldAccessError = tagex.FieldAccessError
FieldAccessError is returned when a struct field cannot be read via reflection.
type FieldSetError ¶
type FieldSetError = tagex.FieldSetError
FieldSetError is returned when a struct field cannot be set via reflection.
type HandleError ¶
type HandleError = tagex.HandleError
HandleError wraps an error returned by a directive's Handle method.
type InvalidTargetError ¶
type InvalidTargetError = tagex.InvalidTargetError
InvalidTargetError is returned when ValidateStruct gets a value that is not a pointer to a struct.
type MaxDepthError ¶
type MaxDepthError = tagex.MaxDepthError
MaxDepthError is returned when processing recurses past the nesting limit (usually cyclic data).
type MissingParamError ¶
type MissingParamError = tagex.MissingParamError
MissingParamError is returned when a required parameter is absent.
type NilTagError ¶
type NilTagError = tagex.NilTagError
NilTagError is returned when a nil tag is processed.
type ParamConflictError ¶
type ParamConflictError = tagex.ParamConflictError
ParamConflictError is returned when a parameter sets both required and default.
type ParamParseError ¶
type ParamParseError = tagex.ParamParseError
ParamParseError is returned for a malformed "key=value" parameter pair.
type ProcessError ¶
type ProcessError = tagex.ProcessError
ProcessError describes a failure while processing a single struct field.
type Registry ¶ added in v0.2.0
type Registry struct {
// contains filtered or unexported fields
}
Registry is an isolated set of "val" directives. Most programs use the package-level RegisterDirective / MustRegisterDirective / ValidateStruct, which share one process-global default registry. Create your own with NewRegistry when you need an independent one — for test isolation, or to run two differently-configured validators in the same process.
func NewRegistry ¶ added in v0.2.0
func NewRegistry() *Registry
NewRegistry returns a new, empty Registry with its own directive set.
func (*Registry) ValidateStruct ¶ added in v0.2.0
ValidateStruct validates struct fields against the registry's "val" directives. It returns nil when the struct is valid. Additional tagex.Tag values can be provided to process more tags in the same pass.
func (*Registry) ValidateStructAll ¶ added in v0.2.0
ValidateStructAll is like ValidateStruct but does not stop at the first failure: it validates every field and returns errors.Join of the per-field errors (nil when all pass). Use FieldErrors to turn the result into a map keyed by field path.
type TypeMismatchError ¶
type TypeMismatchError = tagex.TypeMismatchError
TypeMismatchError is returned when a field's type does not match the directive.
type UnknownDirectiveError ¶
type UnknownDirectiveError = tagex.UnknownDirectiveError
UnknownDirectiveError is returned when a tag names an unregistered directive.
type UnsupportedParamTypeError ¶
type UnsupportedParamTypeError = tagex.UnsupportedParamTypeError
UnsupportedParamTypeError is returned for a parameter of an unsupported type.
type ValidatedValue ¶
type ValidatedValue[T any] struct { Validator Validator[T] // contains filtered or unexported fields }
ValidatedValue stores a value and validates updates with the provided Validator. It is an in-memory guard, not a serialization type: the stored value is unexported and a decoder has no way to supply the Validator, so it does not round-trip through encoding/json. For serialized or request input, validate with ValidateStruct (the "val" tag) instead.
Example ¶
ValidatedValue stores a value only when it passes the configured Validator, leaving the previous value in place on failure.
package main
import (
"errors"
"fmt"
"github.com/tedla-brandsema/valex"
)
func main() {
positive := valex.ValidatorFunc[int](func(n int) error {
if n <= 0 {
return errors.New("must be positive")
}
return nil
})
v := valex.ValidatedValue[int]{Validator: positive}
if err := v.Set(42); err == nil {
fmt.Println("stored:", v.Get())
}
if err := v.Set(-1); err != nil {
fmt.Println("rejected -1:", err)
}
fmt.Println("still:", v.Get())
}
Output: stored: 42 rejected -1: must be positive still: 42
func (*ValidatedValue[T]) Set ¶
func (v *ValidatedValue[T]) Set(val T) error
Set validates and stores the value.
func (*ValidatedValue[T]) String ¶
func (v *ValidatedValue[T]) String() string
String returns the string representation of the stored value.
type Validator ¶
Validator defines the behavior for validating a value of type T. A nil error means the value is valid; a non-nil error reports why it is invalid.
type ValidatorFunc ¶
ValidatorFunc adapts a function to the Validator interface.
Example ¶
ValidatorFunc adapts a plain function into a Validator. A nil return means the value is valid.
package main
import (
"errors"
"fmt"
"github.com/tedla-brandsema/valex"
)
func main() {
nonEmpty := valex.ValidatorFunc[string](func(s string) error {
if s == "" {
return errors.New("must not be empty")
}
return nil
})
fmt.Println(nonEmpty.Validate("hello"))
fmt.Println(nonEmpty.Validate(""))
}
Output: <nil> must not be empty
func (ValidatorFunc[T]) Validate ¶
func (p ValidatorFunc[T]) Validate(val T) error
Validate calls the underlying function.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
chained
command
Chained applies several directives to one field by separating them with ';'.
|
Chained applies several directives to one field by separating them with ';'. |
|
custom-directive
command
Custom-directive extends the "val" tag with a directive of your own.
|
Custom-directive extends the "val" tag with a directive of your own. |
|
forms
command
Forms binds an net/http request into a struct and validates its "val" tags.
|
Forms binds an net/http request into a struct and validates its "val" tags. |
|
programmatic
command
Programmatic validates values in code with ValidatorFunc and ValidatedValue, without any struct tags.
|
Programmatic validates values in code with ValidatorFunc and ValidatedValue, without any struct tags. |
|
validate-struct
command
Validate-struct registers catalog directives and validates a struct with the "val" tag.
|
Validate-struct registers catalog directives and validates a struct with the "val" tag. |
|
Package forms binds HTTP request values into structs and validates them with the valex engine's "val" tag.
|
Package forms binds HTTP request values into structs and validates them with the valex engine's "val" tag. |
|
internal
|
|
|
stub
Package stub provides shared test fixtures for the valex module.
|
Package stub provides shared test fixtures for the valex module. |
|
Package validators provides a catalog of ready-made validation directives for the valex engine.
|
Package validators provides a catalog of ready-made validation directives for the valex engine. |