Documentation
¶
Overview ¶
Package vx validates Go values against schemas built from small, composable checks (Gt, Len, Item, Structure, ...).
A Schema is a function that binds to a reflect.Type and returns a BoundSchema for that type:
schema := Gt(0) validate := schema.BindTypeOf(0) err := validate.Check(-1) // error: value should be greater than 0
Binding happens either early, against a concrete type known when the schema is built (BindTypeOf, or a struct field's own type inside Structure), or late, against any value's type at Check time (BindAny). A schema bound early fails fast: a mismatched type panics when the schema is built, not when data is checked. A schema bound late can only report a type mismatch as a regular error, since any value may arrive at Check time.
Checks compose: Structure and Field describe a struct's fields, Item and Len reach into a slice or its length, AllOf/AnyOf/OneOf combine several schemas. A failure is a *CompoundCheckError (or, for a check with no nested schema, a *CheckError); SchemaPath and DataPath reconstruct exactly which rule fired and where in the data, e.g. "Field(Name).Item. Len.Gt(3)" and "Name[2]".
Example ¶
Example validates a struct: every field needs a Field option, checks run in the order they are declared, and the first failure wins.
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
type Person struct {
Name string
Age int
}
var p Person
schema := vx.Structure(&p,
vx.Field(&p.Name, vx.Len(vx.Gt(0))),
vx.Field(&p.Age, vx.Ge(0)),
)
err := schema.BindAny().Check(Person{Name: "", Age: -1})
fmt.Println(err)
}
Output: Field(Name).Len.Gt(0): invalid Name: value should be greater than 0
Example (ErrorPaths) ¶
Example_errorPaths shows SchemaPath and DataPath on a failure nested through a struct field, a slice item, and a second struct field.
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
type Item struct{ Name string }
type Container struct{ Items []Item }
var it Item
itemSchema := vx.Structure(&it, vx.Field(&it.Name, vx.Len(vx.Gt(3))))
var c Container
schema := vx.Structure(&c, vx.Field(&c.Items, vx.Item(itemSchema)))
err := schema.BindAny().Check(Container{Items: []Item{{Name: "abcd"}, {Name: "ab"}}})
ce := err.(*vx.CompoundCheckError)
fmt.Println(ce.SchemaPath())
fmt.Println(vx.RenderPath(ce.DataPath()))
}
Output: [Field(Items) Item Field(Name) Len Gt(3)] Items[1].Name
Index ¶
- Variables
- func CanCheckEarly(t reflect.Type) bool
- func RenderPath(path []PathElement) string
- func Validate(value any, validate BoundSchema) error
- type BoundSchema
- type CheckError
- type CompoundCheckError
- type PathElement
- type PathElementKind
- type Schema
- func AllOf(schema ...Schema) Schema
- func AnyOf(schema ...Schema) Schema
- func BindCompound(check func(t reflect.Type) error, early func(t reflect.Type) BoundSchema, ...) Schema
- func BindTypeCheck(check func(t reflect.Type) error, validate BoundSchema) Schema
- func Format(name string, isValid func(str string) bool) Schema
- func Ge(x any) Schema
- func Gt(x any) Schema
- func In(allowed ...any) Schema
- func Item(schema Schema) Schema
- func Le(x any) Schema
- func Len(schema Schema) Schema
- func Lt(x any) Schema
- func OneOf(schema ...Schema) Schema
- func RegexpFormat(pattern string) Schema
- func Structure(ptr any, opts ...StructOption) Schema
- type StructOption
- type UnknownValueError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNonEmptyValue is returned by Empty when the value is not empty. ErrNonEmptyValue = newCheckError("Empty", "", "value is not empty") // ErrEmptyValue is returned by NonEmpty when the value is empty. ErrEmptyValue = newCheckError("NonEmpty", "", "value is empty") )
var ErrMultipleSchemasMatch = errors.New("value matches multiple schemas")
ErrMultipleSchemasMatch is the cause of a OneOf error when more than one schema matches.
var ErrNilValue = newCheckError("NotNil", "", "value is nil")
ErrNilValue is returned by a late-bound check when the value to check, reached through an any-typed slot (a []any element, a map value, a struct field typed any, ...), turns out to be a nil interface. It is a *CheckError (CheckName "NotNil"), not a plain error, so it still contributes its own segment to SchemaPath like any other leaf failure — "NotNil", not the name of whatever check never got to run on a nil value.
var PrintableLine = Format("PrintableLine", isPrintableLine)
PrintableLine checks a string has no non-printable runes, including line breaks.
var PrintableText = Format("PrintableText", isPrintableText)
PrintableText is like PrintableLine but allows line breaks and other whitespace control runes.
Functions ¶
func CanCheckEarly ¶
CanCheckEarly reports whether t is a concrete type a Schema can check immediately when it is built, rather than having to wait for a value at Check time. It is false only for the interface type BindAny binds to.
func RenderPath ¶
func RenderPath(path []PathElement) string
RenderPath renders a DataPath as a single string, e.g. "Field1.NestedArray[2].Field2".
func Validate ¶
func Validate(value any, validate BoundSchema) error
Validate runs validate against value, indirecting through a pointer if value is one.
Types ¶
type BoundSchema ¶
BoundSchema validates a single reflect.Value, already bound to a known type.
func Empty ¶
func Empty(t reflect.Type) BoundSchema
Empty checks the value is the zero value for its type.
func NonEmpty ¶
func NonEmpty(t reflect.Type) BoundSchema
NonEmpty checks the value is not the zero value for its type.
func (BoundSchema) Check ¶
func (v BoundSchema) Check(val any) error
Check adapts BoundSchema to validate a Go value directly.
type CheckError ¶
type CheckError struct {
CheckName string // the check that failed, e.g. "Gt"
Param string // optional schema-time argument, e.g. "5"
Msg string
}
CheckError is a leaf validation error: it is returned directly by a single, non-compound check (Gt, In, Format, Empty, ...) and terminates both the schema path and the data path at the point it occurs.
Param, when set, is the schema-time argument of the check (e.g. "5" for Gt(5)) and is rendered as part of the schema path so that two uses of the same check can be told apart there, e.g. "Gt(5)" vs "Gt(10)".
Checks that need to carry extra, programmatically accessible details (e.g. the threshold of a Gt check) embed CheckError in a more specific type; embedding promotes Error and leafSegment, so the specific type satisfies leafError too.
func (*CheckError) Error ¶
func (err *CheckError) Error() string
type CompoundCheckError ¶
type CompoundCheckError struct {
CheckName string // e.g. "Field", "Item", "Len", "AllOf"
Param string // optional schema-time argument, e.g. the field name for Field
DataItem PathElement // zero value (NoElement) if this check validates the value itself
Cause error
}
CompoundCheckError is returned by checks that delegate to a nested schema (Structure, Item, Len, AllOf, AnyOf, OneOf, ...). It attributes the failure of Cause to a place in the schema (CheckName, qualified by Param when the check takes a schema-time argument that cannot otherwise be told apart in the schema path, e.g. the field name of Field) and, optionally, a place in the validated data (DataItem).
func (*CompoundCheckError) DataPath ¶
func (err *CompoundCheckError) DataPath() []PathElement
DataPath returns the chain of field names and indices that led to the invalid value, e.g. []PathElement{Field("NestedArray"), Index(2), Field("Field2")}.
func (*CompoundCheckError) Error ¶
func (err *CompoundCheckError) Error() string
func (*CompoundCheckError) SchemaPath ¶
func (err *CompoundCheckError) SchemaPath() []string
SchemaPath returns the chain of check names that led to the failure, qualified by their schema-time argument where one exists, e.g. []string{"Item", "Field(Name1)", "Len", "Gt(5)"}.
func (*CompoundCheckError) Unwrap ¶
func (err *CompoundCheckError) Unwrap() error
type PathElement ¶
type PathElement struct {
Kind PathElementKind
Name string // set when Kind == FieldElement or KeyElement
Index int // set when Kind == IndexElement
}
PathElement is a single step of a DataPath: a struct field, an array/slice index, or some other keyed element a custom check describes with KeyElement.
func (PathElement) String ¶
func (p PathElement) String() string
String renders the element the way it is appended to a path, e.g. ".Name" for a field, "[2]" for an index, and "[key]" for a KeyElement.
type PathElementKind ¶
type PathElementKind int
PathElementKind identifies what a PathElement refers to in the validated data.
const ( // NoElement marks a check that validates the value itself rather than // navigating into one of its sub-elements (e.g. Len, AllOf, AnyOf, OneOf). NoElement PathElementKind = iota // FieldElement marks a struct field, identified by Name. FieldElement // IndexElement marks an array/slice element, identified by Index. IndexElement // KeyElement marks any other keyed element (e.g. a map value), // identified by Name. Name is the key's already-rendered text, e.g. // fmt.Sprintf("%q", k) for a string key; a custom check picks the // rendering appropriate to its own key type. KeyElement )
type Schema ¶
type Schema func(t reflect.Type) BoundSchema
Schema builds a BoundSchema for t. It must check t is a type the schema supports and panic if it is not, unless it defers that check until validation time (see BindAny).
func AllOf ¶
AllOf checks the value against every schema. The first failure is returned.
Example ¶
ExampleAllOf shows AllOf failing on the first schema that does not match.
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
schema := vx.AllOf(vx.Gt(0), vx.Lt(10)).BindAny()
fmt.Println(schema.Check(5))
fmt.Println(schema.Check(15))
}
Output: <nil> AllOf.Lt(10): value should be less than 10
func BindCompound ¶
func BindCompound( check func(t reflect.Type) error, early func(t reflect.Type) BoundSchema, late func() BoundSchema, ) Schema
BindCompound builds a Schema for a check that delegates to a nested schema reached through some transformation of the bound type (Item's element type, Len's length, ...). It is exported for writing new checks of this shape: early builds the BoundSchema when the type is known at bind time (so the nested schema's own checks run, and panic, then too); late builds it once, against "any", when it is not (so the nested schema's checks defer to validation time and return errors instead of panicking). See BindTypeCheck for checks with no such transformation.
func BindTypeCheck ¶
func BindTypeCheck(check func(t reflect.Type) error, validate BoundSchema) Schema
BindTypeCheck builds a Schema for a check with a fixed validate func that requires a specific kind of type (check reports a non-nil error if t is unsupported). It is the building block behind most of this package's checks (Gt, Format, Structure, ...) and is exported for writing new ones: check runs, and panics on failure, at bind time when the type is known early; otherwise it runs on every Check call and returns its error instead of panicking. See CanCheckEarly.
func Gt ¶
Gt checks the value is greater than x.
Example ¶
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
schema := vx.Gt(0).BindAny()
fmt.Println(schema.Check(5))
fmt.Println(schema.Check(-1))
}
Output: <nil> value should be greater than 0
func In ¶
In checks the value is one of allowed. A slice argument is unwound into its elements.
allowed is captured once, into a map, when In is called, not re-read afterward: pass a small, fixed set of immutable values (e.g. enum constants), not a large or later-mutated collection.
Example ¶
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
schema := vx.In("red", "green", "blue").BindAny()
fmt.Println(schema.Check("green"))
fmt.Println(schema.Check("purple"))
}
Output: <nil> value is not one of [red green blue]
func Item ¶
Item checks every element of an array or slice against schema.
Example ¶
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
schema := vx.Item(vx.Gt(0)).BindAny()
fmt.Println(schema.Check([]int{1, 2, 3}))
fmt.Println(schema.Check([]int{1, -2, 3}))
}
Output: <nil> Item.Gt(0): invalid [1]: value should be greater than 0
func Len ¶
Len checks the length of a string, array, slice or map against schema. A string's length is its rune count, not its byte count.
Example ¶
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
schema := vx.Len(vx.Gt(3)).BindAny()
fmt.Println(schema.Check("hello"))
fmt.Println(schema.Check("hi"))
}
Output: <nil> Len.Gt(3): value should be greater than 3
func OneOf ¶
OneOf checks the value matches exactly one schema.
Example ¶
ExampleOneOf shows OneOf requiring exactly one schema to match; here neither Lt(0) nor Gt(10) matches 5.
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
schema := vx.OneOf(vx.Lt(0), vx.Gt(10)).BindAny()
fmt.Println(schema.Check(-1))
fmt.Println(schema.Check(5))
}
Output: <nil> OneOf.Gt(10): value should be greater than 10
func RegexpFormat ¶
RegexpFormat checks a string value matches pattern, requiring the bound type to be string.
Example ¶
package main
import (
"fmt"
"github.com/sevlyar/vx"
)
func main() {
schema := vx.RegexpFormat(`^\d+$`).BindAny()
fmt.Println(schema.Check("12345"))
fmt.Println(schema.Check("12a45"))
}
Output: <nil> value does not match ^\d+$
func Structure ¶
func Structure(ptr any, opts ...StructOption) Schema
Structure checks a struct's fields according to the given options. ptr must point to the struct being described; it is used only to resolve field types and addresses, not validated itself.
Structure panics if any field has no Field option attached to it; use AllowUncheckedFields to opt specific fields out of this requirement.
func (Schema) BindAny ¶
func (schema Schema) BindAny() BoundSchema
BindAny binds schema late, against values of any type. A type mismatch the schema would otherwise panic on at bind time is instead returned as an error from the resulting BoundSchema.
func (Schema) BindTypeOf ¶
func (schema Schema) BindTypeOf(v any) BoundSchema
BindTypeOf binds schema early, against the type of v (or, if v is a pointer, the type it points to). A value of a different type passed to the resulting BoundSchema panics.
type StructOption ¶
type StructOption func(*structureValidator)
StructOption configures a Structure schema; see Field and AllowUncheckedFields.
func AllowUncheckedFields ¶
func AllowUncheckedFields(fieldPtrs ...any) StructOption
AllowUncheckedFields opts the addressed fields out of Structure's requirement that every field have a Field option.
func Field ¶
func Field(fieldPtr any, schema Schema) StructOption
Field declares how to validate the struct field addressed by fieldPtr. fieldPtr must point to a field of the struct passed to Structure.
type UnknownValueError ¶
type UnknownValueError struct {
CheckError
KnownValues []any
}
UnknownValueError is returned by In when the value is not one of KnownValues.