validate is a lightweight, extensible validation library for Go with zero reflection overhead for callbacks, struct tagging support, and optional code generation for type-safe adapters.
- Rule‑based validation – register named rules with custom handlers.
- Struct validation – use
validatestruct tags with support forrequiredand multiple rules. - Callback‑based validation – validate multiple values in a single pass with
Optional/Require. - Type‑safe adapters – generate boilerplate‑free adapters from your own functions using
govld. - String decoding – automatically convert string inputs to most built‑in types and common interfaces.
- Zero‑allocation pools – internal pooling for callback validators to reduce GC pressure.
- Generics – used internally for caches and pools (Go 1.18+).
go get go.osspkg.com/validateTo use the code generation tool:
go install go.osspkg.com/validate/cmd/govld@latestpackage main
import (
"context""fmt""go.osspkg.com/validate"
)
funcmain() {
v:=validate.New()
// Register a rule that checks if an int64 is greater than a reference_=v.Register(validate.Rule{
Name: "gt",
Handle: validate.HandlerFunc(func(ctx context.Context, valueany, opts...any) error {
val, ok:=value.(int64)
if!ok {
returnfmt.Errorf("expected int64, got %T", value)
}
iflen(opts) !=1 {
returnfmt.Errorf("expected 1 option")
}
ref, ok:=opts[0].(int64)
if!ok {
returnfmt.Errorf("option must be int64")
}
ifval<=ref {
returnfmt.Errorf("value %d must be greater than %d", val, ref)
}
returnnil
}),
})
typeUserstruct {
Ageint64`validate:"required;gt=18"`
}
u:=&User{Age: 25}
iferr:=v.ValidateStruct(context.Background(), u); err!=nil {
fmt.Println("Validation failed:", err)
} else {
fmt.Println("User is valid")
}
}funcvalidateUser(ctx context.Context, v*validate.Validator, userIDint64, namestring) error {
returnv.Validate(ctx, func(c validate.Callback) {
c.Require("gt", userID, int64(0)) // userID > 0c.Optional("nonempty", name) // only validated if name != ""
})
}A rule consists of a name (unique identifier) and a handler that implements validate.Handle:
typeHandleinterface {
ValidateHandle(ctx context.Context, valueany, opts...any) error
}The validate.HandlerFunc type allows you to turn any function with the matching signature into a handler.
Use the tag key validate. Multiple rules are separated by ;. The special required tag makes the field mandatory (zero values are not skipped).
Examples:
typeExamplestruct {
IDint`validate:"required;gt=0"`Namestring`validate:"nonempty;max=64"`Scorefloat64`validate:"min=-10;max=100"`
}Rules can accept comma‑separated options:
`validate:"in=admin,moderator,user"`Require(name, value, opts...)– always runs the validation. Fails if the rule returns an error.Optional(name, value, opts...)– only runs the validation when the value is not its zero value (seeutil.IsDefaultValue). Useful for partial updates.
Writing handlers manually with any type assertions is verbose. The govld tool generates type‑safe adapters from your own functions.
//go:generate govld -pkg .//govld:genfuncValidateUID(ctx context.Context, valueint64, minint64) error {
ifvalue<min {
returnfmt.Errorf("uid %d is less than minimum %d", value, min)
}
returnnil
}The function must:
- Have at least two parameters:
context.Contextand the value to validate. - Return only an
error. - Be marked with the comment
//govld:gen(exactly, no spaces).
go generate ./...This creates adapt_handlers_gen.go containing ValidateUIDAdaptHandler – a function that matches validate.HandlerFunc.
v.Register(validate.Rule{
Name: "uid",
Handle: validate.HandlerFunc(ValidateUIDAdaptHandler),
})Now you can call the rule with proper types:
v.Validate(ctx, func(c validate.Callback) {
c.Require("uid", int64(123), int64(100))
})The generated adapter automatically converts any values and string‑encoded options using validate.StringDecode.
The StringDecode function (used internally by adapters) converts a string into many Go types:
- Basic types:
string,[]byte,int,uint,float,complex,bool time.Duration,time.Time(RFC3339)- Interfaces:
io.Writer,encoding.TextUnmarshaler,json.Unmarshaler,xml.Unmarshaler - Structs, maps, slices, arrays – via
json.Unmarshal
You can use it directly:
varportintiferr:=validate.StringDecode(&port, "8080"); err!=nil {
// handle error
}Typical performance on a modern machine (Intel i9-12900KF):
| Operation | ns/op | allocs/op | B/op |
|---|---|---|---|
Validate (callback) | ~166 | 3 | 48 |
ValidateStruct (simple tags) | ~118 | 2 | 16 |
ValidateStruct with adapters | ~188 | 17 | 376 |
package main
import (
"context""fmt""go.osspkg.com/validate"
)
//go:generate govld -pkg .//govld:genfuncpositiveInt(ctx context.Context, valueint, _any) error {
ifvalue<=0 {
returnfmt.Errorf("value must be positive")
}
returnnil
}
//govld:genfuncrangeCheck(ctx context.Context, valueint, min, maxint) error {
ifvalue<min||value>max {
returnfmt.Errorf("value %d out of range [%d,%d]", value, min, max)
}
returnnil
}
typeConfigstruct {
Portint`validate:"required;positiveInt"`Timeoutint`validate:"rangeCheck=100,5000"`
}
funcmain() {
v:=validate.New()
_=v.Register(
validate.Rule{Name: "positiveInt", Handle: validate.HandlerFunc(positiveIntAdaptHandler)},
validate.Rule{Name: "rangeCheck", Handle: validate.HandlerFunc(rangeCheckAdaptHandler)},
)
cfg:=&Config{Port: 8080, Timeout: 2000}
iferr:=v.ValidateStruct(context.Background(), cfg); err!=nil {
fmt.Println("Invalid config:", err)
} else {
fmt.Println("Config OK")
}
}Run with:
go generate
go run .BSD 3-Clause – see LICENSE file.