verax

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 17 Imported by: 1

Documentation

Overview

Package verax provides configurable and extensible rules for validating data of various types.

Index

Examples

Constants

View Source
const (
	// ECReqNil represents error code when a value must be nil but is not.
	ECReqNil = "ECReqNil"

	// ECReqEmpty represents error code when a value must be empty but is not.
	ECReqEmpty = "ECReqEmpty"
)

AbsentRule error codes.

View Source
const (
	// ECNotEqual indicates that a value does not match the expected one.
	ECNotEqual = "ECNotEqual"

	// ECEqual indicates that a value unexpectedly matches the expected one.
	ECEqual = "ECEqual"
)

EqualRule error codes.

View Source
const (
	// MapRuleName represents [MapRule] name.
	MapRuleName = "map-rule"

	// MapKeyName represents [MapKey] name.
	MapKeyName = "map-key"
)

Rule names.

View Source
const (
	// ECMapKeyMissing represents error code for the missing required key.
	ECMapKeyMissing = "ECMapKeyMissing"

	// ECMapKeyUnexpected represents error code for the not expected key.
	ECMapKeyUnexpected = "ECMapKeyUnexpected"
)

MapRule and MapKey error codes.

View Source
const (
	// ECReq represents error code for missing required value.
	ECReq = "ECRequired"

	// ECReqNotEmpty represents error code for not nil but empty value.
	ECReqNotEmpty = "ECReqNotEmpty"

	// ECReqNotNil represents error code for nil value.
	ECReqNotNil = "ECReqNotNil"
)

RequiredRule rule error codes.

View Source
const (
	// ECInternal is the error code for internal error (library misuse).
	ECInternal = "ECInternal"

	// ECInvType is the error code for an unexpected type.
	ECInvType = "ECInvType"

	// ECInvFormat is the error code for an invalid value format.
	ECInvFormat = "ECInvFormat"

	// ECInvValue is the error code for invalid value.
	ECInvValue = "ECInvValue"

	// ECUnkRule represents an unknown rule error code.
	ECUnkRule = "ECUnkRule"

	// ECInvRuleMode is error code used when a [spec.Spec] defines unsupported
	// rule mode.
	ECInvRuleMode = "ECInvRuleMode"
)

Package level error codes.

View Source
const (
	ArgErrMsg   = "err_msg"       // Custom error message.
	ArgErrCode  = "err_code"      // Custom error code.
	ArgMode     = "mode"          // Rule mode (e.g., exclusive, inclusive).
	ArgMin      = "min"           // Minimum value.
	ArgMax      = "max"           // Maximum value.
	ArgOptional = "optional"      // Optional value flag.
	ArgAllowUnk = "allow_unknown" // Allow unknown map keys.
)

Spec argument names used in rule serialization.

View Source
const AbsentRuleName = "absent-rule"

AbsentRuleName represents AbsentRule name.

View Source
const ByRuleName = "by-rule"

ByRuleName represents ByRule name.

View Source
const ContainRuleName = "contain-rule"

ContainRuleName represents ContainRule name.

View Source
const ECInvIn = "ECInvIn"

ECInvIn represents error code for invalid in rule.

View Source
const ECInvLength = "ECInvLength"

ECInvLength represents error code for invalid length.

View Source
const ECInvMatch = "ECInvMatch"

ECInvMatch represents error code for an invalid regexp match.

View Source
const (
	// ECInvRange is the error code for values failing range conditions.
	ECInvRange = "ECInvRange"
)

RangeRule rule error codes.

View Source
const EachRuleName = "each-rule"

EachRuleName represents EachRule name.

View Source
const EqualRuleName = "equal-rule"

EqualRuleName represents EqualRule name.

View Source
const ErrorTag = "json"

ErrorTag is the default struct tag name used to customize the error field name for a struct field.

View Source
const FailRuleName = "fail-rule"

FailRuleName represents FailRule name.

View Source
const InRuleName = "in-rule"

InRuleName represents InRule name.

View Source
const LengthRuleName = "length-rule"

LengthRuleName represents LengthRule name.

View Source
const MatchRuleName = "match-rule"

MatchRuleName represents MatchRule name.

View Source
const NoopRuleName = "noop-rule"

NoopRuleName represents NoopRule name.

View Source
const RangeRuleName = "range-rule"

RangeRuleName represents RangeRule name.

View Source
const RequiredRuleName = "required-rule"

RequiredRuleName represents RequiredRule name.

View Source
const SetRuleName = "set-rule"

SetRuleName represents Set name.

View Source
const SkipRuleName = "skip-rule"

SkipRuleName represents SkipRule name.

Variables

View Source
var (
	// Nil conditions if a value is nil.
	Nil = AbsentRule{
		// contains filtered or unexported fields
	}

	// Empty conditions if a not nil value is empty.
	Empty = AbsentRule{
			// contains filtered or unexported fields
	}
)

Rules based on AbsentRule.

View Source
var (
	// ErrNotMapPtr is the error returned when the validated value is not a map.
	ErrNotMapPtr = NewInternalError("only a map can be validated", ECInternal)

	// ErrInvKeyType is the error returned when a map key type is incorrect.
	ErrInvKeyType = NewInternalError("the key type does not match the map", ECInternal)

	// ErrKeyMissing is the error returned when a required map key is missing.
	ErrKeyMissing = NewError("missing key", ECMapKeyMissing)

	// ErrKeyUnexpected is the error returned when a map key is unexpected.
	ErrKeyUnexpected = NewError("unexpected key", ECMapKeyUnexpected)
)

MapRule sentinel errors.

View Source
var (
	// Required is a validation rule that conditions if a value is not empty.
	//
	// A value is considered not empty if:
	//  - integer, float: not zero
	//  - bool: always not empty
	//  - string, array, slice, map: len() > 0
	//  - interface, pointer: not nil, and the referenced value is not empty
	//  - other: IsZero() returns false
	Required = RequiredRule{
				// contains filtered or unexported fields
	}

	// NotEmpty conditions if a value is either nil or not empty. It differs
	// from [Required] in that it treats a nil pointer or nil interface as valid.
	NotEmpty = RequiredRule{
				// contains filtered or unexported fields
	}

	// NotNil is a validation rule that conditions if a value is not nil. It
	// applies to interface, pointer, slice, and map types. All other types are
	// considered valid.
	NotNil = RequiredRule{
			// contains filtered or unexported fields
	}
)

Rules based on RequiredRule.

View Source
var (
	// ErrInvType is returned in case of an unexpected value type.
	ErrInvType = NewInternalError("unexpected value type", ECInvType)
)

Package level sentinel errors.

View Source
var (
	// ErrNotStructPtr is an error returned from [ValidateStruct] when a
	// non-pointer struct is used.
	ErrNotStructPtr = NewInternalError("requires a struct pointer", ECInternal)
)

Struct validation errors.

View Source
var Noop = NoopRule{}

Noop is an always passing no-op rule.

View Source
var Skip = SkipRule(true)

Skip is a special validation rule that indicates all rules following it should be skipped.

Functions

func AsRuleBuilder added in v0.3.0

func AsRuleBuilder[T any](fn func(*spec.Spec) (T, error)) spec.Builder[Rule]

AsRuleBuilder wraps a typed spec constructor and returns a [Builder] for it.

func Builders added in v0.3.0

func Builders() map[string]spec.Builder[Rule]

Builders returns a map of builder definitions for all built-in rule specs. The returned map is a new instance and can be safely modified by callers.

Example (By)
package main

import (
	"fmt"

	"github.com/ctx42/verax/pkg/spec"
	"github.com/ctx42/verax/pkg/verax"
)

// nonEmptyWord is a custom RuleFunc used in ExampleBuilders_by.
// Declared as verax.RuleFunc so the type assertion in ByRuleFromSpec succeeds.
var nonEmptyWord verax.RuleFunc = func(v any) error {
	str, err := verax.EnsureString(v)
	if err != nil {
		return verax.ErrInvType
	}
	if len(str) < 3 {
		return verax.NewError("must be at least 3 characters long", "ECShort")
	}
	return nil
}

func main() {
	// Register the function as a named source so it survives serialization.
	src, _ := spec.NewSource("nonEmptyWord", nonEmptyWord)

	reg := spec.NewRegistry[verax.Rule]()
	reg.RegisterBuilders(verax.Builders())
	reg.RegisterSource(src)

	// Encode.
	rule := verax.By(nonEmptyWord)
	spc, _ := rule.Spec()
	data, _ := reg.EncodeSpec(spc)

	// Decode and rebuild — the function is resolved by name from the registry.
	restored, _ := reg.DecodeAndBuild(data)

	err := verax.Validate("hi", restored)
	fmt.Println(err)
}
Output:
must be at least 3 characters long
Example (Decode)
package main

import (
	"fmt"

	"github.com/ctx42/verax/pkg/spec"
	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	data := []byte(`{"name":"range-rule","args":{"mode":{"type":"string","value":"min"},"value":{"type":"int","value":18}}}`)

	reg := spec.NewRegistry[verax.Rule]()
	reg.RegisterBuilders(verax.Builders())

	restored, _ := reg.DecodeAndBuild(data)

	err := verax.Validate(15, restored)
	fmt.Println(err)
}
Output:
must be greater or equal to 18
Example (Encode)
package main

import (
	"fmt"

	"github.com/ctx42/verax/pkg/spec"
	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	rule := verax.Min(18)

	reg := spec.NewRegistry[verax.Rule]()
	reg.RegisterBuilders(verax.Builders())

	spc, _ := rule.Spec()
	data, _ := reg.EncodeSpec(spc)

	fmt.Println(string(data))
}
Output:
{"name":"range-rule","args":{"mode":"min","value":{"type":"int","value":18}}}

func EnsureString

func EnsureString(value any) (string, error)

EnsureString ensures the given value is a string. If the value is a byte slice, it will be typecast into a string. An error is returned otherwise.

func Indirect

func Indirect(v any) any

Indirect dereferences the given value if it is a pointer or interface, returning the underlying value. If the value implements driver.Valuer, it returns the result of calling its Value method. If the input is nil or not a pointer/interface, it is returned unchanged.

Examples:

  • For a *int pointing to 42, it returns 42.
  • For an interface{} containing a *string, it returns the string.
  • For a driver.Valuer, it returns the result of Value().

func IsEmpty

func IsEmpty(v any) bool

IsEmpty checks if a value is empty.

A value is considered empty if:

  • integer, float: zero
  • bool: never empty
  • string, array, slice, map, chan: nil or len() == 0
  • interface, pointer: nil or the referenced value is empty
  • other: IsZero() == true

If the input is nil, it returns true.

func IsInternalError added in v0.3.0

func IsInternalError(err error) bool

IsInternalError reports whether the error is non-nil InternalError.

func IsNil

func IsNil(v any) bool

IsNil checks whether the provided value is actual nil or wrapped nil. Actual nil means the interface itself has no type or value (have == nil).

func IsValidationError added in v0.3.0

func IsValidationError(err error) bool

IsValidationError reports whether the error is non-nil Error or FieldErrors.

func IsVeraxError added in v0.5.0

func IsVeraxError(err error) bool

IsVeraxError reports whether the error is non-nil Error, FieldErrors or InternalError.

func LengthOfValue

func LengthOfValue(value any) (int, error)

LengthOfValue returns the length of a value that is a string, slice, map, or array. An error is returned for all other types.

func NewError added in v0.3.0

func NewError(msg string, args ...any) error

NewError creates a new Error with the given message. The args may contain an optional string error code and any number of xrr.Option values in any order.

Examples:

NewError("message")
NewError("message", "ECode")
NewError("message", "ECode", xrr.Option())

When xrr.WithCause is provided:

  • If msg is empty, Error() returns the cause's message directly.
  • If msg is non-empty, Error() returns "msg: cause message".
  • If no code is given and xrr.WithCode is not provided, the cause's code is inherited via xrr.GetCode. Pass a code string or xrr.WithCode to override it.

For wrapping without a new message, prefer xrr.Wrap, which makes the intent clearer.

func NewErrorf added in v0.7.0

func NewErrorf(format string, args ...any) error

NewErrorf creates a new Error using a format string. It is the format-style counterpart of NewError: non-xrr.Option args are passed to the format string, while xrr.Option values are applied to the error. Unlike NewError, a bare string argument is treated as a format argument, not an error code — pass xrr.WithCode to set the code.

When the format string contains %w, the error is created via fmt.Errorf and stored as the cause; xrr.GenericError.Error delegates to it. Without %w, the message is set to fmt.Sprintf(format, args...).

Examples:

NewErrorf("user %d not found", userID)
NewErrorf("user %d not found", userID, xrr.WithCode("ECode"))
NewErrorf("connect failed: %w", err)
NewErrorf("connect failed: %w", err, xrr.WithCode("ECode"))

func NewInternalError added in v0.3.0

func NewInternalError(msg string, args ...any) error

NewInternalError creates a new InternalError with the given message. The args may contain an optional string error code and any number of xrr.Option values in any order.

Examples:

NewInternalError("message")
NewInternalError("message", "ECode")
NewInternalError("message", "ECode", xrr.Option())

When xrr.WithCause is provided:

  • If msg is empty, Error() returns the cause's message directly.
  • If msg is non-empty, Error() returns "msg: cause message".
  • If no code is given and xrr.WithCode is not provided, the cause's code is inherited via xrr.GetCode. Pass a code string or xrr.WithCode to override it.

For wrapping without a new message, prefer xrr.Wrap, which makes the intent clearer.

func NewInternalErrorf added in v0.7.0

func NewInternalErrorf(format string, args ...any) error

NewInternalErrorf creates a new InternalError using a format string. It is the format-style counterpart of NewInternalError: non-xrr.Option args are passed to the format string, while xrr.Option values are applied to the error. Unlike NewInternalError, a bare string argument is treated as a format argument, not an error code — pass xrr.WithCode to set the code.

When the format string contains %w, the error is created via fmt.Errorf and stored as the cause; xrr.GenericError.Error delegates to it. Without %w, the message is set to fmt.Sprintf(format, args...).

Examples:

NewInternalErrorf("user %d not found", userID)
NewInternalErrorf("user %d not found", userID, xrr.WithCode("ECode"))
NewInternalErrorf("connect failed: %w", err)
NewInternalErrorf("connect failed: %w", err, xrr.WithCode("ECode"))

func StringOrBytes

func StringOrBytes(value any) (
	isString bool,
	str string,
	isBytes bool,
	bs []byte,
)

StringOrBytes typecasts a value into a string or byte slice. Boolean flags are returned to indicate if the typecasting succeeds or not.

func ToAnySlice

func ToAnySlice[T any](vls ...T) []any

ToAnySlice returns a slice of T as a slice of any.

func Validate

func Validate(v any, rules ...Rule) error

Validate checks the given value against the provided validation rules. Returns nil if all rules pass, or the first validation error encountered. Skips validation if one of the rules is Skip. Supports types implementing Validator or WithValidator, and recursively validates maps, slices, arrays, pointers, or interfaces with validatable elements. Returns nil for nil pointers or interfaces.

nolint: cyclop

Example (Maps)
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

type Planet struct {
	Position int    `json:"position"`
	Name     string `json:"name" solar:"planet_name"`
	Life     float64
}

func (p *Planet) Validate() error {
	return verax.ValidateStruct(
		p,
		verax.Field(&p.Position, verax.Min(1), verax.Max(8)),
		verax.Field(&p.Name, verax.Length(4, 7)).Tag("solar"),
		verax.Field(&p.Life, verax.Min(0.0), verax.Max(1.0)),
	)
}

func main() {
	planets := map[string]*Planet{
		"mer": {1, "Mer", 0},
		"ear": {3, "Earth", 1.0},
		"x":   {9, "X", 0.1},
	}

	err := verax.Validate(planets)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- mer.planet_name: the length must be between 4 and 7
- x.planet_name: the length must be between 4 and 7
- x.position: must be less or equal to 8

JSON:
{
    "mer.planet_name": {
        "code": "ECInvLength",
        "error": "the length must be between 4 and 7"
    },
    "x.planet_name": {
        "code": "ECInvLength",
        "error": "the length must be between 4 and 7"
    },
    "x.position": {
        "code": "ECInvRange",
        "error": "must be less or equal to 8"
    }
}
Example (Primitive_int)
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	err := verax.Validate(
		45,
		verax.Required,
		verax.Min(42),
		verax.Max(44),
	)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- must be less or equal to 44

JSON:
{
    "code": "ECInvRange",
    "error": "must be less or equal to 44"
}
Example (Slices)
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

type Planet struct {
	Position int    `json:"position"`
	Name     string `json:"name" solar:"planet_name"`
	Life     float64
}

func (p *Planet) Validate() error {
	return verax.ValidateStruct(
		p,
		verax.Field(&p.Position, verax.Min(1), verax.Max(8)),
		verax.Field(&p.Name, verax.Length(4, 7)).Tag("solar"),
		verax.Field(&p.Life, verax.Min(0.0), verax.Max(1.0)),
	)
}

func main() {
	planets := []*Planet{
		{1, "Mer", 0},
		{3, "Earth", 1.0},
		{9, "X", 0.1},
	}

	err := verax.Validate(planets)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- 0.planet_name: the length must be between 4 and 7
- 2.planet_name: the length must be between 4 and 7
- 2.position: must be less or equal to 8

JSON:
{
    "0.planet_name": {
        "code": "ECInvLength",
        "error": "the length must be between 4 and 7"
    },
    "2.planet_name": {
        "code": "ECInvLength",
        "error": "the length must be between 4 and 7"
    },
    "2.position": {
        "code": "ECInvRange",
        "error": "must be less or equal to 8"
    }
}

func ValidateNamed

func ValidateNamed(name string, have any, rules ...Rule) error

ValidateNamed validates v using the provided rules, wrapping any error in FieldErrors with the specified field name.

func ValidateStruct

func ValidateStruct(v any, fields ...FieldRule) error

ValidateStruct validates a struct by checking the specified struct fields against the corresponding validation rules. Note that the struct being validated must be specified as a pointer to it. If the pointer is nil, it is considered valid. Use Field() to specify struct fields that need to be validated. Each Field() call specifies a single field which should be specified as a pointer to the field. A field can be associated with multiple rules.

For example,

value := struct {
    Name  string
    Value string
}{"name", "demo"}
err := validation.ValidateStruct(&value,
    verax.Field(&a.Name, verax.Required),
    verax.Field(&a.Value, verax.Required, verax.Length(5, 10)),
)
fmt.Println(err)
// Value: the length must be between 5 and 10.

Returns InternalError on unexpected errors, otherwise it returns FieldErrors error.

nolint: cyclop

Example
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

type Planet struct {
	Position int    `json:"position"`
	Name     string `json:"name" solar:"planet_name"`
	Life     float64
}

func (p *Planet) Validate() error {
	return verax.ValidateStruct(
		p,
		verax.Field(&p.Position, verax.Min(1), verax.Max(8)),
		verax.Field(&p.Name, verax.Length(4, 7)).Tag("solar"),
		verax.Field(&p.Life, verax.Min(0.0), verax.Max(1.0)),
	)
}

func main() {
	planet := Planet{9, "PlanetXYZ", -1}

	err := verax.ValidateStruct(
		&planet,
		verax.Field(&planet.Position, verax.Min(1), verax.Max(8)),
		verax.Field(&planet.Name, verax.Length(4, 7)),
		verax.Field(&planet.Life, verax.Min(0.0), verax.Max(1.0)),
	)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- Life: must be greater or equal to 0
- name: the length must be between 4 and 7
- position: must be less or equal to 8

JSON:
{
    "Life": {
        "code": "ECInvRange",
        "error": "must be greater or equal to 0"
    },
    "name": {
        "code": "ECInvLength",
        "error": "the length must be between 4 and 7"
    },
    "position": {
        "code": "ECInvRange",
        "error": "must be less or equal to 8"
    }
}
Example (Custom_tag)
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

type Planet struct {
	Position int    `json:"position"`
	Name     string `json:"name" solar:"planet_name"`
	Life     float64
}

func (p *Planet) Validate() error {
	return verax.ValidateStruct(
		p,
		verax.Field(&p.Position, verax.Min(1), verax.Max(8)),
		verax.Field(&p.Name, verax.Length(4, 7)).Tag("solar"),
		verax.Field(&p.Life, verax.Min(0.0), verax.Max(1.0)),
	)
}

func main() {
	planet := Planet{1, "Mer", 0.0}

	err := verax.ValidateStruct(
		&planet,
		verax.Field(&planet.Name, verax.Length(4, 7)).Tag("solar"),
	)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- planet_name: the length must be between 4 and 7

JSON:
{
    "planet_name": {
        "code": "ECInvLength",
        "error": "the length must be between 4 and 7"
    }
}

Types

type AbsentRule added in v0.3.0

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

AbsentRule conditions if a value is absent.

func AbsentRuleFromSpec added in v0.3.0

func AbsentRuleFromSpec(spc *spec.Spec) (AbsentRule, error)

AbsentRuleFromSpec creates an instance of AbsentRule from the spec.Spec.

func (AbsentRule) Code added in v0.3.0

func (r AbsentRule) Code(code string) AbsentRule

func (AbsentRule) Message added in v0.3.0

func (r AbsentRule) Message(msg string) AbsentRule

func (AbsentRule) Spec added in v0.3.0

func (r AbsentRule) Spec() (*spec.Spec, error)

func (AbsentRule) Validate added in v0.3.0

func (r AbsentRule) Validate(have any) error

func (AbsentRule) When added in v0.3.0

func (r AbsentRule) When(condition bool) AbsentRule

type ByRule

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

ByRule conditions if a value passed to a RuleFunc function is valid.

func By

func By(fn RuleFunc) ByRule

By validates a value using the provided RuleFunc. This is the primary escape hatch for custom validation logic. The function receives the value and should return nil on success or a verax domain error on failure (see Rule). Use Check for the simple func(bool) case.

Example
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	fn := func(v any) error {
		str, err := verax.EnsureString(v)
		if err != nil {
			return verax.ErrInvType
		}
		if str != "" && str != "abc" {
			return verax.NewError("i need abc", "ECMustABC")
		}
		return nil
	}

	AbcRule := verax.By(fn)

	err := AbcRule.Validate("xyz") // nolint: ineffassign
	// or
	err = verax.Validate("xyz", AbcRule)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- i need abc

JSON:
{
    "code": "ECMustABC",
    "error": "i need abc"
}

func ByRuleFromSpec added in v0.3.0

func ByRuleFromSpec(spc *spec.Spec) (ByRule, error)

ByRuleFromSpec creates a new instance of ByRule from the spec.Spec.

func (ByRule) Code

func (r ByRule) Code(code string) ByRule

func (ByRule) Message added in v0.3.0

func (r ByRule) Message(msg string) ByRule

func (ByRule) Spec added in v0.3.0

func (r ByRule) Spec() (*spec.Spec, error)

func (ByRule) Validate

func (r ByRule) Validate(have any) error

func (ByRule) When

func (r ByRule) When(condition bool) ByRule

type CompareFunc

type CompareFunc func(want, have any) (int, error)

CompareFunc is a custom validation function that compares two values and returns the result of the comparison. The integer result is the same as in the cmp.Compare function.

-1 if "want" is less than "have",
 0 if "want" equals "have",
+1 if "want" is greater than "have".

The error result must be an InternalError for misuse such as unsupported or mismatched types, and should be an Error for comparison failures.

type ContainRule

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

ContainRule is a validation rule that validates there is at least one element in a map/slice/array using the specified EqualRule.

func Contain

func Contain(rule EqualRule) ContainRule

Contain returns a validation rule that loops through iterable (map, slice, or array) and validates it contains at least one given value.

func ContainRuleFromSpec added in v0.3.0

func ContainRuleFromSpec(spc *spec.Spec) (ContainRule, error)

ContainRuleFromSpec creates a new instance of ContainRule from the spec.Spec.

func (ContainRule) Code added in v0.3.0

func (r ContainRule) Code(code string) ContainRule

func (ContainRule) Message added in v0.3.0

func (r ContainRule) Message(msg string) ContainRule

func (ContainRule) Spec added in v0.3.0

func (r ContainRule) Spec() (*spec.Spec, error)

func (ContainRule) Validate

func (r ContainRule) Validate(have any) error

func (ContainRule) When added in v0.3.0

func (r ContainRule) When(condition bool) ContainRule

type EachRule

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

EachRule is a validation rule that validates elements in a map/slice/array using the specified list of rules.

func Each

func Each(rules ...Rule) EachRule

Each returns a validation rule that loops through an iterable (map, slice, or array) and validates each value inside with the provided rules. Empty iterable is considered valid. Use the Required rule to make sure the iterable is not empty.

func EachRuleFromSpec added in v0.3.0

func EachRuleFromSpec(spc *spec.Spec) (EachRule, error)

EachRuleFromSpec creates a new instance of EachRule from the spec.Spec.

func (EachRule) Spec added in v0.3.0

func (r EachRule) Spec() (*spec.Spec, error)

func (EachRule) Validate

func (r EachRule) Validate(have any) error

func (EachRule) When added in v0.3.0

func (r EachRule) When(condition bool) EachRule

type EqualFunc added in v0.3.0

type EqualFunc func(want, have any) error

EqualFunc is the signature for a custom comparison function. It receives the expected value (want) and the value under validation (have) and returns nil when the comparison criterion is satisfied. When the criterion is violated, it should return an Error; which condition counts as a violation depends on the context the function is used in. For example, in an equality context the function returns an error when "want" and "have" differ, whereas in an inequality context it returns an error when they are equal. It must return an InternalError for misuse such as unsupported or mismatched types.

type EqualRule

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

EqualRule is a rule that conditions a value matches the expected value using the provided comparison function.

func Equal

func Equal(want any) EqualRule

Equal constructs rule conditioning a validated value is equal to "want".

func EqualField

func EqualField(want any, field string) EqualRule

EqualField constructs rule conditioning a validated value is equal to "want". When it isn't, the error message will say the value must be equal to "field".

func EqualRuleFromSpec added in v0.3.0

func EqualRuleFromSpec(spc *spec.Spec) (EqualRule, error)

EqualRuleFromSpec creates a new instance of EqualRule from the spec.Spec.

func NotEqual

func NotEqual(want any) EqualRule

NotEqual constructs rule conditioning a validated value is not equal to "want".

func NotEqualField

func NotEqualField(want any, field string) EqualRule

NotEqualField constructs rule conditioning a validated value is not equal to "want". When it is the error message will say the value must not be equal to "field".

func (EqualRule) Code

func (r EqualRule) Code(code string) EqualRule

func (EqualRule) Message added in v0.3.0

func (r EqualRule) Message(tpl string) EqualRule

func (EqualRule) Spec added in v0.3.0

func (r EqualRule) Spec() (*spec.Spec, error)

func (EqualRule) Validate

func (r EqualRule) Validate(have any) error

func (EqualRule) When

func (r EqualRule) When(condition bool) EqualRule

func (EqualRule) With added in v0.3.0

func (r EqualRule) With(fn EqualFunc) EqualRule

With sets a custom equality function for an EqualRule, overriding the default comparison behavior. The function, of type EqualFunc, defines how the value is compared. This is useful for types not supported natively.

type Error

type Error = xrr.GenericError[edError]

Error represents an error in the package's error domain.

type FailRule added in v0.3.0

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

FailRule returns an error when its condition evaluates to true.

By default, the condition is always true (fails unconditionally). Use FailRule.When to specify a custom condition.

func Fail added in v0.3.0

func Fail(msg, code string) FailRule

Fail returns the rule which fails with a given error when the condition is true. By default, the condition is always true.

func FailRuleFromSpec added in v0.3.0

func FailRuleFromSpec(spc *spec.Spec) (FailRule, error)

FailRuleFromSpec creates a new instance of FailRule from the spec.Spec.

func (FailRule) Code added in v0.3.0

func (r FailRule) Code(code string) FailRule

func (FailRule) Message added in v0.3.0

func (r FailRule) Message(msg string) FailRule

func (FailRule) Spec added in v0.3.0

func (r FailRule) Spec() (*spec.Spec, error)

func (FailRule) Validate added in v0.3.0

func (r FailRule) Validate(_ any) error

func (FailRule) When added in v0.3.0

func (r FailRule) When(condition bool) FailRule

type FieldErrors added in v0.5.0

type FieldErrors = xrr.GenericFields[edError]

FieldErrors represents a field error in the package's error domain.

func NewFieldError added in v0.5.0

func NewFieldError(field string, err error) *FieldErrors

NewFieldError returns a new field error in the package's error domain.

func NewFieldErrors added in v0.5.0

func NewFieldErrors(fields map[string]error) *FieldErrors

NewFieldErrors creates a new FieldErrors from the given map. The map is stored directly without copying.

type FieldRule added in v0.3.0

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

FieldRule represents a rule set associated with a struct field.

func Field

func Field(fieldPtr any, rules ...Rule) FieldRule

Field specifies a struct field and the corresponding validation rules. The struct field must be specified as a pointer to it.

func (FieldRule) Tag added in v0.3.0

func (fr FieldRule) Tag(tag string) FieldRule

Tag sets a tag to use for the error field name.

type InRule

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

InRule is a validation rule that validates if a value can be found in the given list of values.

func In

func In(values ...any) InRule

In returns a validation rule that conditions if a value can be found in the given list of values. Note that the value being conditioned and the possible range of values must be of the same type. Values are compared using reflect.DeepEqual. An empty value is considered valid. Use the Required rule to make sure a value is not empty.

func InRuleFromSpec added in v0.3.0

func InRuleFromSpec(spc *spec.Spec) (InRule, error)

InRuleFromSpec creates a new instance of InRule from the spec.Spec.

func NotIn

func NotIn(values ...any) InRule

NotIn returns a validation rule that conditions if a value cannot be found in the given list of values. Note that the value being conditioned and the possible range of values must be of the same type. Values are compared using reflect.DeepEqual. An empty value is considered valid. Use the Required rule to make sure a value is not empty.

func (InRule) Code

func (r InRule) Code(code string) InRule

func (InRule) Message added in v0.3.0

func (r InRule) Message(msg string) InRule

func (InRule) Spec added in v0.3.0

func (r InRule) Spec() (*spec.Spec, error)

func (InRule) Validate

func (r InRule) Validate(have any) error

func (InRule) When

func (r InRule) When(condition bool) InRule

type InternalError added in v0.3.0

type InternalError = xrr.GenericError[edInternal]

InternalError represents an internal error (library misuse) in the package's error domain.

type IsFunc added in v0.3.0

type IsFunc[T any] func(v T) bool

IsFunc represents a validator function returning true only for valid values. You may wrap it as a Rule by calling Check.

type LengthRule

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

LengthRule is a validation rule that conditions if a value's length is within the specified range.

func Length

func Length(minimum, maximum int) LengthRule

Length returns a validation rule that conditions if a value's length is within the specified range. If max is 0, it means there is no upper bound for the length. This rule should only be used for validating strings, slices, maps, and arrays. An empty value is considered valid. Use the Required rule to make sure a value is not empty.

func LengthRuleFromSpec added in v0.3.0

func LengthRuleFromSpec(spc *spec.Spec) (LengthRule, error)

LengthRuleFromSpec creates a new instance of LengthRule from the spec.Spec.

func RuneLength

func RuneLength(minimum, maximum int) LengthRule

RuneLength returns a validation rule that conditions if a string's rune length is within the specified range. If max is 0, it means there is no upper bound for the length. This rule should only be used for validating strings, slices, maps, and arrays. An empty value is considered valid. Use the Required rule to make sure a value is not empty. If the value being validated is not a string, the rule works the same as Length.

func (LengthRule) Code

func (r LengthRule) Code(code string) LengthRule

func (LengthRule) Message added in v0.3.0

func (r LengthRule) Message(tpl string) LengthRule

func (LengthRule) Spec added in v0.3.0

func (r LengthRule) Spec() (*spec.Spec, error)

func (LengthRule) Validate

func (r LengthRule) Validate(have any) error

nolint: cyclop

func (LengthRule) When

func (r LengthRule) When(condition bool) LengthRule

type MapKey added in v0.3.0

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

MapKey represents a rule set associated with a map key.

func Key

func Key(key any, rules ...Rule) MapKey

Key specifies a map key and the corresponding validation rules. Use with Map to validate specific entries (supports [Optional], When, chaining, and [Specable] roundtrips).

func MapKeyFromSpec added in v0.3.0

func MapKeyFromSpec(spc *spec.Spec) (MapKey, error)

MapKeyFromSpec creates a new instance of MapKey from the spec.Spec.

func (MapKey) KeyString added in v0.3.0

func (mk MapKey) KeyString() string

KeyString returns the string representation of the key.

func (MapKey) Optional added in v0.3.0

func (mk MapKey) Optional() MapKey

Optional configures the rule to ignore the key if missing.

func (MapKey) Spec added in v0.3.0

func (mk MapKey) Spec() (*spec.Spec, error)

func (MapKey) When added in v0.3.0

func (mk MapKey) When(condition bool) MapKey

When marks the key as required when the condition is true, optional otherwise.

type MapRule

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

MapRule represents a rule set associated with map types.

func Map

func Map(keys ...MapKey) MapRule

Map returns a validation rule that conditions the keys and values of a map. This rule should only be used for validating maps, or a validation error will be reported. Use Key() to specify map keys that need to be validated. Each Key() call specifies a single key which can be associated with multiple rules.

For example,

verax.Map(
    verax.Key("Name", verax.Required),
    verax.Key("Value", verax.Required, verax.Length(5, 10)),
)

A nil value is considered valid. Use the Required rule to make sure a map value is present.

Example
package main

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	data := map[string]any{
		"bool":  false,
		"int":   44,
		"float": 0.1,
		"time":  time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC),
	}

	MyRule := verax.Map(
		verax.Key("bool", verax.Equal(true)),
		verax.Key("int", verax.Max(42)),
		verax.Key("float", verax.Min(4.2)),
		verax.Key("time", verax.Min(
			time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
		)),
	)

	err := verax.Validate(data, MyRule) // nolint: ineffassign
	// or
	err = MyRule.Validate(data)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- bool: must be equal to 'true'
- float: must be greater or equal to 4.2
- int: must be less or equal to 42
- time: must be greater or equal to 2025-01-01T00:00:00Z

JSON:
{
    "bool": {
        "code": "ECNotEqual",
        "error": "must be equal to 'true'"
    },
    "float": {
        "code": "ECInvRange",
        "error": "must be greater or equal to 4.2"
    },
    "int": {
        "code": "ECInvRange",
        "error": "must be less or equal to 42"
    },
    "time": {
        "code": "ECInvRange",
        "error": "must be greater or equal to 2025-01-01T00:00:00Z"
    }
}

func MapRuleFromSpec added in v0.3.0

func MapRuleFromSpec(spc *spec.Spec) (MapRule, error)

MapRuleFromSpec creates a new instance of MapRule from the spec.Spec.

func (MapRule) AllowUnknown

func (r MapRule) AllowUnknown() MapRule

AllowUnknown configures the rule to ignore unknown keys.

func (MapRule) IsDefined

func (r MapRule) IsDefined(key any) bool

IsDefined returns true if the given map key is defined.

func (MapRule) IsOptional

func (r MapRule) IsOptional(key any) bool

IsOptional returns true if the given map key is optional. It will return true for keys that are not defined in the map.

func (MapRule) Spec added in v0.3.0

func (r MapRule) Spec() (*spec.Spec, error)

func (MapRule) Validate

func (r MapRule) Validate(have any) error

Validate checks the value against the rule's condition(s) and returns a validation error if it fails.

nolint: cyclop, gocognit

func (MapRule) When added in v0.3.0

func (r MapRule) When(condition bool) MapRule

type MatchRule

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

MatchRule is a validation rule that conditions if a value matches the specified regular expression.

func Match

func Match(want *regexp.Regexp) MatchRule

Match returns a validation rule that conditions if a value matches the specified regular expression. This rule should only be used for validating strings and byte slices, or a validation error will be reported. An empty value is considered valid. Use the Required rule to make sure a value is not empty.

func MatchRuleFromSpec added in v0.3.0

func MatchRuleFromSpec(spc *spec.Spec) (MatchRule, error)

MatchRuleFromSpec creates a new instance of MatchRule from the spec.Spec.

func (MatchRule) Code

func (r MatchRule) Code(code string) MatchRule

func (MatchRule) Message added in v0.3.0

func (r MatchRule) Message(msg string) MatchRule

func (MatchRule) Spec added in v0.3.0

func (r MatchRule) Spec() (*spec.Spec, error)

func (MatchRule) Validate

func (r MatchRule) Validate(have any) error

func (MatchRule) When

func (r MatchRule) When(condition bool) MatchRule

type Named

type Named map[string]Rule

Named represents a collection of named rules.

func NewNamed

func NewNamed() Named

NewNamed creates an empty Named.

func (Named) Get

func (n Named) Get(name string) Rule

Get returns the named rule or nil if it doesn't exist.

func (Named) GetOrError

func (n Named) GetOrError(name string) Rule

GetOrError returns the named rule; when it doesn't exist, it returns an instance of Fail with ECUnkRule error code.

func (Named) GetOrNoop

func (n Named) GetOrNoop(name string) Rule

GetOrNoop returns the named rule or Noop rule if it doesn't exist.

func (Named) Set

func (n Named) Set(name string, rule Rule) Named

Set associates the rule with the name. Chainable.

type NoopRule added in v0.3.0

type NoopRule struct{}

NoopRule is a special validation rule that always passes.

func NoopRuleFromSpec added in v0.3.0

func NoopRuleFromSpec(spc *spec.Spec) (NoopRule, error)

NoopRuleFromSpec creates a new instance of NoopRule from the spec.Spec.

func (NoopRule) Code added in v0.3.0

func (r NoopRule) Code(_ string) NoopRule

func (NoopRule) Message added in v0.3.0

func (r NoopRule) Message(_ string) NoopRule

func (NoopRule) Spec added in v0.3.0

func (r NoopRule) Spec() (*spec.Spec, error)

func (NoopRule) Validate added in v0.3.0

func (r NoopRule) Validate(_ any) error

func (NoopRule) When added in v0.3.0

func (r NoopRule) When(_ bool) NoopRule

type RangeRule added in v0.3.0

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

RangeRule is a rule validating a value satisfies a given range.

func Max

func Max(maximum any) RangeRule

Max creates a validation rule that conditions if a value is less than or equal to the specified range. Use RangeRule.Exclusive to enforce a strict less-than condition. The value being conditioned and the range must be of the same type, supporting only int, uint, float, and time.Time types. Empty values are considered valid; use the Required rule to ensure a value is not empty.

Example:

rule := Max(100)             // Value must be <= 100
rule := Max(100).Exclusive() // Value must be < 100

func Min

func Min(minimum any) RangeRule

Min creates a validation rule that conditions if a value is greater than or equal to the specified range. Use RangeRule.Exclusive to enforce a strict greater-than condition. The value being conditioned and the range must be of the same type, supporting only int, uint, float, and time.Time types. Empty values are considered valid; use the Required rule to ensure a value is not empty.

Example:

rule := Min(10)             // Value must be >= 10
rule := Min(10).Exclusive() // Value must be > 10

func RangeRuleFromSpec added in v0.3.0

func RangeRuleFromSpec(spc *spec.Spec) (RangeRule, error)

RangeRuleFromSpec creates a new instance of RangeRule from the spec.Spec.

func (RangeRule) Code added in v0.3.0

func (r RangeRule) Code(code string) RangeRule

func (RangeRule) Exclusive added in v0.3.0

func (r RangeRule) Exclusive() RangeRule

Exclusive modifies a RangeRule to exclude the boundary value, enforcing a strict comparison. For example, when used with Min, it conditions if a value is strictly greater than the range, and with Max, it conditions if a value is strictly less than the range. The value and range must be of the same type.

It must be called before RangeRule.Code or RangeRule.Message.

Example:

ruleMin := Min(10).Exclusive()  // Value must be > 10
ruleMax := Max(100).Exclusive() // Value must be < 100

func (RangeRule) Message added in v0.3.0

func (r RangeRule) Message(tpl string) RangeRule

func (RangeRule) Spec added in v0.3.0

func (r RangeRule) Spec() (*spec.Spec, error)

func (RangeRule) Validate added in v0.3.0

func (r RangeRule) Validate(have any) error

func (RangeRule) When added in v0.3.0

func (r RangeRule) When(condition bool) RangeRule

func (RangeRule) With added in v0.3.0

func (r RangeRule) With(fn CompareFunc) RangeRule

With sets a custom comparison function for a RangeRule, overriding the default comparison behavior. The function, of type CompareFunc, defines how the value is compared to the range. This is useful for custom validation logic.

The function must return an error if the type is not supported.

Example:

cmpMyType := func(a, b any) int { ... }
rule := Min(myTypeValue).With(cmpMyType)

type RequiredRule added in v0.3.0

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

RequiredRule is a rule that conditions if a value is not empty.

func RequiredRuleFromSpec added in v0.3.0

func RequiredRuleFromSpec(spc *spec.Spec) (RequiredRule, error)

RequiredRuleFromSpec creates a new instance of RequiredRule from the spec.Spec.

func (RequiredRule) Code added in v0.3.0

func (r RequiredRule) Code(code string) RequiredRule

func (RequiredRule) Message added in v0.3.0

func (r RequiredRule) Message(msg string) RequiredRule

func (RequiredRule) Spec added in v0.3.0

func (r RequiredRule) Spec() (*spec.Spec, error)

func (RequiredRule) Validate added in v0.3.0

func (r RequiredRule) Validate(have any) error

func (RequiredRule) When added in v0.3.0

func (r RequiredRule) When(condition bool) RequiredRule

type Rule

type Rule interface {
	// Validate checks the value against the rule's condition(s) and returns
	// a validation error if it fails.
	//
	// Implementations should return [Error] instance (or nil on success) to
	// support error targeting with [errors.As] and [errors.Is].
	//
	// For rules that delegate to custom validation functions ([RuleFunc],
	// [EqualFunc], [CompareFunc], etc.):
	//
	// - If both message and code are overridden: return a new [Error] instance.
	// - If only the message is overridden: return a new [Error] instance with
	//   the original error's code and the custom message.
	// - If only the code is overridden: return the original error after
	//   calling [SetCode] on it.
	// - If neither is overridden: return the error from the validation
	//   function unchanged.
	//
	// Callers implementing custom validation via [RuleFunc], [EqualFunc],
	// [CompareFunc], [Validator], etc. are responsible for returning a verax
	// domain error (or nil). The library does not wrap foreign errors for now.
	Validate(have any) error
}

Rule represents a validation rule.

Example
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/xrr/pkg/xrr"

	"github.com/ctx42/verax/pkg/verax"
)

type UserDoesNotExistRule struct{}

func (u UserDoesNotExistRule) Validate(v any) error {
	username, err := verax.EnsureString(v)
	if err != nil {
		return verax.ErrInvType
	}

	format := "user %s already exist"
	return verax.NewErrorf(format, username, xrr.WithCode("ECMustNotExist"))
}

func main() {
	err := verax.Validate("thor", verax.Required, UserDoesNotExistRule{})

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- user thor already exist

JSON:
{
    "code": "ECMustNotExist",
    "error": "user thor already exist"
}
Example (Conditioned)
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

type Range struct {
	Start int
	End   int
}

func main() {
	r := Range{Start: 51, End: 42}

	err := verax.ValidateStruct(
		&r,
		verax.Field(&r.End, verax.Min(100).When(r.Start > 50)),
	)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- End: must be greater or equal to 100

JSON:
{
    "End": {
        "code": "ECInvRange",
        "error": "must be greater or equal to 100"
    }
}
Example (CustomErrorCode)
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	rule := verax.Equal(42).Code("EC42")

	err := verax.Validate(44, rule)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- must be equal to '42'

JSON:
{
    "code": "EC42",
    "error": "must be equal to '42'"
}
Example (CustomMessage)
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	rule := verax.Equal(42).Message("must be my favorite number").Code("EC42")

	err := verax.Validate(44, rule)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- must be my favorite number

JSON:
{
    "code": "EC42",
    "error": "must be my favorite number"
}

type RuleFunc

type RuleFunc func(v any) error

RuleFunc represents a custom validation function.

It receives a value and must return nil on success, an Error for validation failures, or an InternalError for misuse such as unsupported or mismatched types.

Wrap it into a Rule using By.

func Check added in v0.3.0

func Check[T any](fn IsFunc[T], msg, code string) RuleFunc

Check creates a RuleFunc from IsFunc.

type Set

type Set []Rule

Set groups multiple validation rules and implements the Rule interface.

Example
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

func main() {
	NameRule := verax.Set{
		verax.Required,
		verax.Length(4, 5),
	}

	err := NameRule.Validate("abc") // nolint: ineffassign
	// or
	err = verax.Validate("abc", NameRule)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- the length must be between 4 and 5

JSON:
{
    "code": "ECInvLength",
    "error": "the length must be between 4 and 5"
}

func SetRuleFromSpec added in v0.6.0

func SetRuleFromSpec(spc *spec.Spec) (Set, error)

SetRuleFromSpec creates a new instance of Set from the spec.Spec.

func (Set) Spec added in v0.6.0

func (set Set) Spec() (*spec.Spec, error)

func (Set) Validate

func (set Set) Validate(have any) error

type SkipRule added in v0.3.0

type SkipRule bool

SkipRule represents a validation rule that skips later rules.

func SkipRuleFromSpec added in v0.3.0

func SkipRuleFromSpec(spc *spec.Spec) (SkipRule, error)

SkipRuleFromSpec creates a new instance of SkipRule from the spec.Spec.

func (SkipRule) Spec added in v0.3.0

func (_ SkipRule) Spec() (*spec.Spec, error)

func (SkipRule) Validate added in v0.3.0

func (_ SkipRule) Validate(_ any) error

func (SkipRule) When added in v0.3.0

func (_ SkipRule) When(condition bool) SkipRule

type SpecableRule added in v0.3.0

type SpecableRule interface {
	spec.Specable
	Rule
}

SpecableRule is implemented by rules that can both validate values and describe themselves as a spec.Spec. Implement this interface to make a rule serializable, for example, when storing validation configuration in a database or exchanging it over an API.

type TypeRule added in v0.2.0

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

TypeRule is a rule validating a value is of the expected type.

func Type added in v0.2.0

func Type(typ reflect.Type) TypeRule

Type is a rule validating a value is of the given reflect.Type.

func TypeOf added in v0.2.0

func TypeOf(val any) TypeRule

TypeOf is a rule validating a value is of the same type as the argument.

func (TypeRule) Code added in v0.2.0

func (r TypeRule) Code(code string) TypeRule

func (TypeRule) Message added in v0.3.0

func (r TypeRule) Message(msg string) TypeRule

func (TypeRule) Validate added in v0.2.0

func (r TypeRule) Validate(have any) error

func (TypeRule) When added in v0.2.0

func (r TypeRule) When(condition bool) TypeRule

type Validator

type Validator interface {
	// Validate validates the implementer and returns an error on failure.
	Validate() error
}

Validator wraps Validate method.

Example
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

type Planet struct {
	Position int    `json:"position"`
	Name     string `json:"name" solar:"planet_name"`
	Life     float64
}

func (p *Planet) Validate() error {
	return verax.ValidateStruct(
		p,
		verax.Field(&p.Position, verax.Min(1), verax.Max(8)),
		verax.Field(&p.Name, verax.Length(4, 7)).Tag("solar"),
		verax.Field(&p.Life, verax.Min(0.0), verax.Max(1.0)),
	)
}

func main() {
	planet := &Planet{9, "Mer", 0.0}

	err := planet.Validate()

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- planet_name: the length must be between 4 and 7
- position: must be less or equal to 8

JSON:
{
    "planet_name": {
        "code": "ECInvLength",
        "error": "the length must be between 4 and 7"
    },
    "position": {
        "code": "ECInvRange",
        "error": "must be less or equal to 8"
    }
}
Example (Quick_start)
package main

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

var emailRx = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

type CreateUserRequest struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	Age   int    `json:"age"`
}

func (r *CreateUserRequest) Validate() error {
	return verax.ValidateStruct(r,
		verax.Field(&r.Name, verax.Required, verax.Length(2, 50)),
		verax.Field(&r.Email, verax.Required, verax.Match(emailRx)),
		verax.Field(&r.Age, verax.Required, verax.Min(18), verax.Max(120)),
	)
}

func main() {
	req := &CreateUserRequest{
		Name:  "A",
		Email: "bad-email",
		Age:   15,
	}

	err := req.Validate()

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- age: must be greater or equal to 18
- email: must match a valid format
- name: the length must be between 2 and 50

JSON:
{
    "age": {
        "code": "ECInvRange",
        "error": "must be greater or equal to 18"
    },
    "email": {
        "code": "ECInvMatch",
        "error": "must match a valid format"
    },
    "name": {
        "code": "ECInvLength",
        "error": "the length must be between 2 and 50"
    }
}

type WhenRule

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

WhenRule is a validation rule that applies rules from When if the condition is met, or rules from WhenRule.Else otherwise.

func When

func When(condition bool, rules ...Rule) WhenRule

When returns a validation rule that executes the given list of rules when the condition is true. Use WhenRule.Else to specify rules for the false case. Empty values are not short-circuited (use Skip or When with IsEmpty for that).

Example
package main

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/ctx42/verax/pkg/verax"
)

type Range struct {
	Start int
	End   int
}

func main() {
	r := Range{Start: 44, End: 42}

	failRule := verax.Fail("the end must be greater than the start", "ECRange")

	err := verax.ValidateStruct(
		&r,
		verax.Field(&r.End, verax.When(r.End < r.Start, failRule)),
	)

	PrintError(err)
	PrintJSON(err)
}

// PrintJSON marshals value to JSON string.
func PrintJSON(v any) {
	data, err := json.MarshalIndent(v, "", "    ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON:\n%s\n", string(data))
}

// PrintError formats error message.
func PrintError(err error) {
	var msg strings.Builder
	for line := range strings.SplitSeq(err.Error(), "; ") {
		msg.WriteString("- " + line + "\n")
	}
	fmt.Printf("ERROR:\n\n%s\n", msg.String())
}
Output:
ERROR:

- End: the end must be greater than the start

JSON:
{
    "End": {
        "code": "ECRange",
        "error": "the end must be greater than the start"
    }
}

func (WhenRule) Code

func (r WhenRule) Code(code string) WhenRule

func (WhenRule) Else

func (r WhenRule) Else(rules ...Rule) WhenRule

Else returns a validation rule that executes the given list of rules when the condition passed to When constructor function is false.

func (WhenRule) Message added in v0.3.0

func (r WhenRule) Message(msg string) WhenRule

func (WhenRule) Validate

func (r WhenRule) Validate(have any) error

type WithValidator

type WithValidator interface {
	ValidateWith(rule Rule) error
}

WithValidator wraps ValidateWith method which validates implementor using the given Rule.

Directories

Path Synopsis
Package rule provides an assortment of validation rules.
Package rule provides an assortment of validation rules.

Jump to

Keyboard shortcuts

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