Documentation ¶
Overview ¶
Package validation implements a functional API for consistent struct level validation.
Index ¶
- Constants
- func HasErrorCode(err error, code ErrorCode) bool
- func JoinErrors[T error](b *strings.Builder, errs []T, indent string)
- func SliceElementName(sliceName string, index int) string
- type ErrorCode
- type HashFunction
- type Predicate
- type PropertyError
- type PropertyErrors
- type PropertyGetter
- type PropertyRules
- func (r PropertyRules[T, S]) HideValue() PropertyRules[T, S]
- func (r PropertyRules[T, S]) Include(rules ...Validator[T]) PropertyRules[T, S]
- func (r PropertyRules[T, S]) OmitEmpty() PropertyRules[T, S]
- func (r PropertyRules[T, S]) Required() PropertyRules[T, S]
- func (r PropertyRules[T, S]) Rules(rules ...Rule[T]) PropertyRules[T, S]
- func (r PropertyRules[T, S]) StopOnError() PropertyRules[T, S]
- func (r PropertyRules[T, S]) Validate(st S) PropertyErrors
- func (r PropertyRules[T, S]) When(predicates ...Predicate[S]) PropertyRules[T, S]
- func (r PropertyRules[T, S]) WithName(name string) PropertyRules[T, S]
- type PropertyRulesForEach
- func (r PropertyRulesForEach[T, S]) IncludeForEach(rules ...Validator[T]) PropertyRulesForEach[T, S]
- func (r PropertyRulesForEach[T, S]) Rules(rules ...Rule[[]T]) PropertyRulesForEach[T, S]
- func (r PropertyRulesForEach[T, S]) RulesForEach(rules ...Rule[T]) PropertyRulesForEach[T, S]
- func (r PropertyRulesForEach[T, S]) StopOnError() PropertyRulesForEach[T, S]
- func (r PropertyRulesForEach[T, S]) Validate(st S) PropertyErrors
- func (r PropertyRulesForEach[T, S]) When(predicate Predicate[S]) PropertyRulesForEach[T, S]
- func (r PropertyRulesForEach[T, S]) WithName(name string) PropertyRulesForEach[T, S]
- type Rule
- type RuleError
- type RuleSet
- type SingleRule
- func EqualTo[T comparable](compared T) SingleRule[T]
- func Forbidden[T any]() SingleRule[T]
- func GreaterThan[T constraints.Ordered](n T) SingleRule[T]
- func GreaterThanOrEqualTo[T constraints.Ordered](n T) SingleRule[T]
- func LessThan[T constraints.Ordered](n T) SingleRule[T]
- func LessThanOrEqualTo[T constraints.Ordered](n T) SingleRule[T]
- func MapLength[M ~map[K]V, K comparable, V any](min, max int) SingleRule[M]
- func MapMaxLength[M ~map[K]V, K comparable, V any](max int) SingleRule[M]
- func MapMinLength[M ~map[K]V, K comparable, V any](min int) SingleRule[M]
- func MutuallyExclusive[S any](required bool, getters map[string]func(s S) any) SingleRule[S]
- func NewSingleRule[T any](validate func(v T) error) SingleRule[T]
- func NotEqualTo[T comparable](compared T) SingleRule[T]
- func OneOf[T comparable](values ...T) SingleRule[T]
- func Required[T any]() SingleRule[T]
- func SliceLength[S ~[]E, E any](min, max int) SingleRule[S]
- func SliceMaxLength[S ~[]E, E any](max int) SingleRule[S]
- func SliceMinLength[S ~[]E, E any](min int) SingleRule[S]
- func SliceUnique[S []V, V any, H comparable](hashFunc HashFunction[V, H], constraints ...string) SingleRule[S]
- func StringASCII() SingleRule[string]
- func StringContains(substrings ...string) SingleRule[string]
- func StringDenyRegexp(re *regexp.Regexp, examples ...string) SingleRule[string]
- func StringDescription() SingleRule[string]
- func StringJSON() SingleRule[string]
- func StringLength(min, max int) SingleRule[string]
- func StringMatchRegexp(re *regexp.Regexp, examples ...string) SingleRule[string]
- func StringMaxLength(max int) SingleRule[string]
- func StringMinLength(min int) SingleRule[string]
- func StringNotEmpty() SingleRule[string]
- func StringURL() SingleRule[string]
- func StringUUID() SingleRule[string]
- func URL() SingleRule[*url.URL]
- type Transformer
- type Validator
- type ValidatorError
Examples ¶
- ForEach
- ForPointer
- GetSelf
- HasErrorCode
- New
- NewPropertyError
- PropertyRules.Include
- PropertyRules.OmitEmpty
- PropertyRules.Required
- PropertyRules.StopOnError
- PropertyRules.When
- PropertyRules.WithName
- RuleSet
- SingleRule.WithDetails
- SingleRule.WithErrorCode
- Validator
- Validator (BranchingPattern)
- Validator.When
- Validator.WithName
- ValidatorError.WithName
Constants ¶
const ErrorCodeSeparator = ":"
Variables ¶
This section is empty.
Functions ¶
func HasErrorCode ¶ added in v0.50.0
HasErrorCode checks if an error contains given ErrorCode. It supports all validation errors.
Example ¶
To inspect if an error contains a given validation.ErrorCode, use HasErrorCode function. This function will also return true if the expected ErrorCode is part of a chain of wrapped error codes. In this example we're dealing with two error code chains: - 'teacher_name:string_length' - 'teacher_name:string_match_regexp'
package main import ( "fmt" "regexp" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { teacherNameRule := validation.NewRuleSet[string]( validation.StringLength(1, 5), validation.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")), ). WithErrorCode("teacher_name") v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(teacherNameRule), ).WithName("Teacher") teacher := Teacher{ Name: "Jonathan", Age: 51 * year, } err := v.Validate(teacher) if err != nil { for _, code := range []validation.ErrorCode{ "teacher_name", "string_length", "string_match_regexp", } { if validation.HasErrorCode(err, code) { fmt.Println("Has error code:", code) } } } }
Output: Has error code: teacher_name Has error code: string_length Has error code: string_match_regexp
func SliceElementName ¶ added in v0.59.0
Types ¶
type ErrorCode ¶ added in v0.50.0
type ErrorCode = string
const ( ErrorCodeRequired ErrorCode = "required" ErrorCodeTransform ErrorCode = "transform" ErrorCodeForbidden ErrorCode = "forbidden" ErrorCodeEqualTo ErrorCode = "equal_to" ErrorCodeNotEqualTo ErrorCode = "not_equal_to" ErrorCodeGreaterThan ErrorCode = "greater_than" ErrorCodeGreaterThanOrEqualTo ErrorCode = "greater_than_or_equal_to" ErrorCodeLessThan ErrorCode = "less_than" ErrorCodeLessThanOrEqualTo ErrorCode = "less_than_or_equal_to" ErrorCodeStringNotEmpty ErrorCode = "string_not_empty" ErrorCodeStringMatchRegexp ErrorCode = "string_match_regexp" ErrorCodeStringDenyRegexp ErrorCode = "string_deny_regexp" ErrorCodeStringDescription ErrorCode = "string_description" ErrorCodeStringIsDNSSubdomain ErrorCode = "string_is_dns_subdomain" ErrorCodeStringASCII ErrorCode = "string_ascii" ErrorCodeStringURL ErrorCode = "string_url" ErrorCodeStringUUID ErrorCode = "string_uuid" ErrorCodeStringJSON ErrorCode = "string_json" ErrorCodeStringContains ErrorCode = "string_contains" ErrorCodeStringLength ErrorCode = "string_length" ErrorCodeStringMinLength ErrorCode = "string_min_length" ErrorCodeStringMaxLength ErrorCode = "string_max_length" ErrorCodeSliceLength ErrorCode = "slice_length" ErrorCodeSliceMinLength ErrorCode = "slice_min_length" ErrorCodeSliceMaxLength ErrorCode = "slice_max_length" ErrorCodeMapLength ErrorCode = "map_length" ErrorCodeMapMinLength ErrorCode = "map_min_length" ErrorCodeMapMaxLength ErrorCode = "map_max_length" ErrorCodeOneOf ErrorCode = "one_of" ErrorCodeMutuallyExclusive ErrorCode = "mutually_exclusive" ErrorCodeSliceUnique ErrorCode = "slice_unique" ErrorCodeURL ErrorCode = "url" )
type HashFunction ¶ added in v0.59.0
type HashFunction[V any, H comparable] func(v V) H
HashFunction accepts a value and returns a comparable hash.
func SelfHashFunc ¶ added in v0.59.0
func SelfHashFunc[H comparable]() HashFunction[H, H]
SelfHashFunc returns a HashFunction which returns it's input value as a hash itself. The value must be comparable.
type PropertyError ¶ added in v0.50.0
type PropertyError struct { PropertyName string `json:"propertyName"` PropertyValue string `json:"propertyValue"` Errors []*RuleError `json:"errors"` }
func NewPropertyError ¶ added in v0.50.0
func NewPropertyError(propertyName string, propertyValue interface{}, errs ...error) *PropertyError
Example ¶
Sometimes you need top level context, but you want to scope the error to a specific, nested property. One of the ways to do that is to use NewPropertyError and return PropertyError from your validation rule. Note that you can still use ErrorCode and pass RuleError to the constructor. You can pass any number of RuleError.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { v := validation.New[Teacher]( validation.For(validation.GetSelf[Teacher]()). Rules(validation.NewSingleRule(func(t Teacher) error { if t.Name == "Jake" { return validation.NewPropertyError( "name", t.Name, validation.NewRuleError("name cannot be Jake", "error_code_jake"), validation.NewRuleError("you can pass me too!")) } return nil })), ).WithName("Teacher") teacher := Teacher{ Name: "Jake", Age: 51 * year, } err := v.Validate(teacher) if err != nil { propertyErrors := err.Errors ruleErrors := propertyErrors[0].Errors fmt.Printf("Error code: %s\n\n", ruleErrors[0].Code) fmt.Println(err) } }
Output: Error code: error_code_jake Validation for Teacher has failed for the following properties: - 'name' with value 'Jake': - name cannot be Jake - you can pass me too!
func (*PropertyError) Error ¶ added in v0.50.0
func (e *PropertyError) Error() string
func (*PropertyError) HideValue ¶ added in v0.66.0
func (e *PropertyError) HideValue() *PropertyError
HideValue hides the property value from PropertyError.Error and also hides it from.
func (*PropertyError) PrependPropertyName ¶ added in v0.50.0
func (e *PropertyError) PrependPropertyName(name string) *PropertyError
type PropertyErrors ¶ added in v0.59.0
type PropertyErrors []*PropertyError
func (PropertyErrors) Error ¶ added in v0.59.0
func (e PropertyErrors) Error() string
func (PropertyErrors) HideValue ¶ added in v0.66.0
func (e PropertyErrors) HideValue() PropertyErrors
type PropertyGetter ¶ added in v0.50.0
type PropertyGetter[T, S any] func(S) T
func GetSelf ¶ added in v0.50.0
func GetSelf[S any]() PropertyGetter[S, S]
GetSelf is a convenience method for extracting 'self' property of a validated value.
Example ¶
If you want to access the value of the entity you're writing the Validator for, you can use GetSelf function which is a convenience PropertyGetter that returns self. Note that we don't call PropertyRules.WithName here, as we're comparing two properties in our top level, [Teacher] scope.
You can provide your own rules using NewSingleRule constructor. It returns new SingleRule instance which wraps your validation function.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { customRule := validation.NewSingleRule(func(v Teacher) error { return fmt.Errorf("now I have access to the whole teacher") }) v := validation.New[Teacher]( validation.For(validation.GetSelf[Teacher]()). Rules(customRule), ).WithName("Teacher") teacher := Teacher{ Name: "Jake", Age: 51 * year, } err := v.Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation for Teacher has failed for the following properties: - now I have access to the whole teacher
type PropertyRules ¶ added in v0.50.0
type PropertyRules[T, S any] struct { // contains filtered or unexported fields }
PropertyRules is responsible for validating a single property.
func For ¶ added in v0.59.0
func For[T, S any](getter PropertyGetter[T, S]) PropertyRules[T, S]
For creates a new PropertyRules instance for the property which value is extracted through PropertyGetter function.
func ForPointer ¶ added in v0.59.0
func ForPointer[T, S any](getter PropertyGetter[*T, S]) PropertyRules[T, S]
ForPointer accepts a getter function returning a pointer and wraps its call in order to safely extract the value under the pointer or return a zero value for a give type T. If required is set to true, the nil pointer value will result in an error and the validation will not proceed.
Example ¶
For constructor creates new PropertyRules instance. It's only argument, PropertyGetter is used to extract the property value. It works fine for direct values, but falls short when working with pointers. Often times we use pointers to indicate that a property is optional, or we want to discern between nil and zero values. In either case we want our validation rules to work on direct values, not the pointer, otherwise we'd have to always check if pointer != nil.
ForPointer constructor can be used to solve this problem and allow us to work with the underlying value in our rules. Under the hood it wraps PropertyGetter and safely extracts the underlying value. If the value was nil, it will not attempt to evaluate any rules for this property. The rationale for that is it doesn't make sense to evaluate any rules for properties which are essentially empty. The only rule that makes sense in this context is to ensure the property is required. We'll learn about a way to achieve that in the next example: [ExamplePropertyRules_Required].
Let's define a rule for [Teacher.MiddleName] property. Not everyone has to have a middle name, that's why we've defined this field as a pointer to string, rather than a string itself.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { v := validation.New[Teacher]( validation.ForPointer(func(t Teacher) *string { return t.MiddleName }). WithName("middleName"). Rules(validation.StringMaxLength(5)), ).WithName("Teacher") middleName := "Thaddeus" teacher := Teacher{ Name: "Jake", Age: 51 * year, MiddleName: &middleName, } err := v.Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation for Teacher has failed for the following properties: - 'middleName' with value 'Thaddeus': - length must be less than or equal to 5
func Transform ¶ added in v0.65.0
func Transform[T, N, S any](getter PropertyGetter[T, S], transform Transformer[T, N]) PropertyRules[N, S]
Transform transforms value from one type to another. Value returned by PropertyGetter is transformed through Transformer function. If Transformer returns an error, the validation will not proceed and transformation error will be reported. Transformer is only called if PropertyGetter returns a non-zero value.
func (PropertyRules[T, S]) HideValue ¶ added in v0.66.0
func (r PropertyRules[T, S]) HideValue() PropertyRules[T, S]
func (PropertyRules[T, S]) Include ¶ added in v0.50.0
func (r PropertyRules[T, S]) Include(rules ...Validator[T]) PropertyRules[T, S]
Example ¶
So far we've defined validation rules for simple, top-level properties. What If we want to define validation rules for nested properties? We can use PropertyRules.Include to include another Validator in our PropertyRules.
Let's extend our [Teacher] struct to include a nested [University] property. [University] in of itself is another struct with its own validation rules.
Notice how the nested property path is automatically built for you, each segment separated by a dot.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { universityValidation := validation.New[University]( validation.For(func(u University) string { return u.Address }). WithName("address"). Required(), ) teacherValidation := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(validation.EqualTo("Tom")), validation.For(func(t Teacher) University { return t.University }). WithName("university"). Include(universityValidation), ).WithName("Teacher") teacher := Teacher{ Name: "Jerry", Age: 51 * year, University: University{ Name: "Poznan University of Technology", Address: "", }, } err := teacherValidation.Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation for Teacher has failed for the following properties: - 'name' with value 'Jerry': - should be equal to 'Tom' - 'university.address': - property is required but was empty
func (PropertyRules[T, S]) OmitEmpty ¶ added in v0.65.0
func (r PropertyRules[T, S]) OmitEmpty() PropertyRules[T, S]
Example ¶
While ForPointer will by default omit validation for nil pointers, it might be useful to have a similar behavior for optional properties which are direct values. PropertyRules.OmitEmpty will do the trick.
NOTE: PropertyRules.OmitEmpty will have no effect on pointers handled by ForPointer, as they already behave in the same way.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { alwaysFailingRule := validation.NewSingleRule(func(string) error { return fmt.Errorf("always fails") }) v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). OmitEmpty(). Rules(alwaysFailingRule), validation.ForPointer(func(t Teacher) *string { return t.MiddleName }). WithName("middleName"). Rules(alwaysFailingRule), ).WithName("Teacher") teacher := Teacher{ Name: "", Age: 51 * year, MiddleName: nil, } err := v.Validate(teacher) if err == nil { fmt.Println("no error! we skipped 'name' validation and 'middleName' is implicitly skipped") } }
Output: no error! we skipped 'name' validation and 'middleName' is implicitly skipped
func (PropertyRules[T, S]) Required ¶ added in v0.59.0
func (r PropertyRules[T, S]) Required() PropertyRules[T, S]
Example ¶
By default, when PropertyRules is constructed using ForPointer it will skip validation of the property if the pointer is nil. To enforce a value is set for pointer use PropertyRules.Required.
You may ask yourself why not just use validation.Required rule instead? If we were to do that, we'd be forced to operate on pointer in all of our rules. Other than checking if the pointer is nil, there aren't any rules which would benefit from working on the pointer instead of the underlying value.
If you want to also make sure the underlying value is filled, i.e. it's not a zero value, you can also use validation.Required rule on top of PropertyRules.Required.
PropertyRules.Required when used with For constructor, will ensure the property does not contain a zero value.
NOTE: PropertyRules.Required is introducing a short circuit. If the assertion fails, validation will stop and return validation.ErrorCodeRequired. None of the rules you've defined would be evaluated.
NOTE: Placement of PropertyRules.Required does not matter, it's not evaluated in a sequential loop, unlike standard Rule. However, we recommend you always place it below PropertyRules.WithName to make your rules more readable.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { alwaysFailingRule := validation.NewSingleRule(func(string) error { return fmt.Errorf("always fails") }) v := validation.New[Teacher]( validation.ForPointer(func(t Teacher) *string { return t.MiddleName }). WithName("middleName"). Required(). Rules(alwaysFailingRule), validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Required(). Rules(alwaysFailingRule), ).WithName("Teacher") teacher := Teacher{ Name: "", Age: 51 * year, MiddleName: nil, } err := v.Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation for Teacher has failed for the following properties: - 'middleName': - property is required but was empty - 'name': - property is required but was empty
func (PropertyRules[T, S]) Rules ¶ added in v0.50.0
func (r PropertyRules[T, S]) Rules(rules ...Rule[T]) PropertyRules[T, S]
func (PropertyRules[T, S]) StopOnError ¶ added in v0.50.0
func (r PropertyRules[T, S]) StopOnError() PropertyRules[T, S]
Example ¶
To fail validation immediately after certain Rule fails use PropertyRules.StopOnError. You need to call it directly AFTER you've called PropertyRules.Rules.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } func main() { alwaysFailingRule := validation.NewSingleRule(func(string) error { return fmt.Errorf("always fails") }) v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(validation.NotEqualTo("Jerry")). StopOnError(). Rules(alwaysFailingRule), ).WithName("Teacher") for _, name := range []string{"Tom", "Jerry"} { teacher := Teacher{Name: name} err := v.Validate(teacher) if err != nil { fmt.Println(err) } } }
Output: Validation for Teacher has failed for the following properties: - 'name' with value 'Tom': - always fails Validation for Teacher has failed for the following properties: - 'name' with value 'Jerry': - should be not equal to 'Jerry'
func (PropertyRules[T, S]) Validate ¶ added in v0.50.0
func (r PropertyRules[T, S]) Validate(st S) PropertyErrors
Validate validates the property value using provided rules. nolint: gocognit
func (PropertyRules[T, S]) When ¶ added in v0.50.0
func (r PropertyRules[T, S]) When(predicates ...Predicate[S]) PropertyRules[T, S]
Example ¶
To only proceed with further validation on condition, use PropertyRules.When. Similar to PropertyRules.Rules predicates provided through PropertyRules.When are evaluated in the order they are provided. If a predicate is not met, proceeding (as defined in code) validation rules are not evaluated.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } func main() { alwaysFailingRule := validation.NewSingleRule(func(string) error { return fmt.Errorf("always fails") }) v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(validation.NotEqualTo("Jerry")). When(func(t Teacher) bool { return t.Name == "Tom" }). Rules(alwaysFailingRule), ).WithName("Teacher") for _, name := range []string{"Tom", "Jerry", "Mickey"} { teacher := Teacher{Name: name} err := v.Validate(teacher) if err != nil { fmt.Println(err) } } }
Output: Validation for Teacher has failed for the following properties: - 'name' with value 'Tom': - always fails Validation for Teacher has failed for the following properties: - 'name' with value 'Jerry': - should be not equal to 'Jerry'
func (PropertyRules[T, S]) WithName ¶ added in v0.50.0
func (r PropertyRules[T, S]) WithName(name string) PropertyRules[T, S]
Example ¶
So far we've been using a very simple PropertyRules instance:
validation.For(func(t Teacher) string { return t.Name }). Rules(validation.NewSingleRule(func(name string) error { return fmt.Errorf("always fails") }))
The error message returned by this property rule does not tell us which property is failing. Let's change that by adding property name using PropertyRules.WithName.
We can also change the Rule to be something more real. Validation package comes with a number of predefined Rule, we'll use EqualTo which accepts a single argument, value to compare with.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(validation.EqualTo("Tom")), ).WithName("Teacher") teacher := Teacher{ Name: "Jake", Age: 51 * year, } err := v.Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation for Teacher has failed for the following properties: - 'name' with value 'Jake': - should be equal to 'Tom'
type PropertyRulesForEach ¶ added in v0.59.0
type PropertyRulesForEach[T, S any] struct { // contains filtered or unexported fields }
PropertyRulesForEach is responsible for validating a single property.
func ForEach ¶ added in v0.59.0
func ForEach[T, S any](getter PropertyGetter[[]T, S]) PropertyRulesForEach[T, S]
ForEach creates a new PropertyRulesForEach instance for a slice property which value is extracted through PropertyGetter function.
Example ¶
When dealing with slices we often want to both validate the whole slice and each of its elements. You can use ForEach function to do just that. It returns a new struct PropertyRulesForEach which behaves exactly the same as PropertyRules, but extends its API slightly.
To define rules for each element use: - PropertyRulesForEach.RulesForEach - PropertyRulesForEach.IncludeForEach These work exactly the same way as PropertyRules.Rules and PropertyRules.Include on each slice element.
PropertyRulesForEach.Rules is in turn used to define rules for the whole slice.
NOTE: PropertyRulesForEach does not implement Include function for the whole slice.
In the below example, we're defining that students slice must have at most 2 elements and that each element's index must be unique. For each element we're also including [Student] Validator. Notice that property path fro slices has the following format: <slice_name>[<index>].<slice_property_name>
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } func main() { studentValidator := validation.New[Student]( validation.For(func(s Student) string { return s.Index }). WithName("index"). Rules(validation.StringLength(9, 9)), ) teacherValidator := validation.New[Teacher]( validation.ForEach(func(t Teacher) []Student { return t.Students }). WithName("students"). Rules( validation.SliceMaxLength[[]Student](2), validation.SliceUnique(func(v Student) string { return v.Index })). IncludeForEach(studentValidator), ).When(func(t Teacher) bool { return t.Age < 50 }) teacher := Teacher{ Name: "John", Students: []Student{ {Index: "918230014"}, {Index: "9182300123"}, {Index: "918230014"}, }, } err := teacherValidator.Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation has failed for the following properties: - 'students[1].index' with value '9182300123': - length must be between 9 and 9 - 'students' with value '[{"index":"918230014"},{"index":"9182300123"},{"index":"918230014"}]': - length must be less than or equal to 2 - elements are not unique, index 0 collides with index 2
func (PropertyRulesForEach[T, S]) IncludeForEach ¶ added in v0.59.0
func (r PropertyRulesForEach[T, S]) IncludeForEach(rules ...Validator[T]) PropertyRulesForEach[T, S]
func (PropertyRulesForEach[T, S]) Rules ¶ added in v0.59.0
func (r PropertyRulesForEach[T, S]) Rules(rules ...Rule[[]T]) PropertyRulesForEach[T, S]
func (PropertyRulesForEach[T, S]) RulesForEach ¶ added in v0.59.0
func (r PropertyRulesForEach[T, S]) RulesForEach(rules ...Rule[T]) PropertyRulesForEach[T, S]
func (PropertyRulesForEach[T, S]) StopOnError ¶ added in v0.59.0
func (r PropertyRulesForEach[T, S]) StopOnError() PropertyRulesForEach[T, S]
func (PropertyRulesForEach[T, S]) Validate ¶ added in v0.59.0
func (r PropertyRulesForEach[T, S]) Validate(st S) PropertyErrors
Validate executes each of the rules sequentially and aggregates the encountered errors. nolint: prealloc, gocognit
func (PropertyRulesForEach[T, S]) When ¶ added in v0.59.0
func (r PropertyRulesForEach[T, S]) When(predicate Predicate[S]) PropertyRulesForEach[T, S]
func (PropertyRulesForEach[T, S]) WithName ¶ added in v0.59.0
func (r PropertyRulesForEach[T, S]) WithName(name string) PropertyRulesForEach[T, S]
type RuleError ¶ added in v0.50.0
func NewRequiredError ¶ added in v0.59.0
func NewRequiredError() *RuleError
func NewRuleError ¶ added in v0.63.1
NewRuleError creates a new RuleError with the given message and optional error codes. Error codes are added according to the rules defined by RuleError.AddCode.
type RuleSet ¶ added in v0.50.0
type RuleSet[T any] struct { // contains filtered or unexported fields }
RuleSet allows defining Rule which aggregates multiple sub-rules.
Example ¶
Sometimes it's useful to build a Rule using other rules. To do that we'll use RuleSet and NewRuleSet constructor. RuleSet is a simple container for multiple Rule. It is later on unpacked and each RuleError is reported separately. When RuleSet.WithErrorCode or RuleSet.WithDetails are used, error code and details are added to each RuleError. Note that validation package uses similar syntax to wrapped errors in Go; a ':' delimiter is used to chain error codes together.
package main import ( "fmt" "regexp" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { teacherNameRule := validation.NewRuleSet[string]( validation.StringLength(1, 5), validation.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")). WithDetails("Teacher can be either Tom or Jerry :)"), ). WithErrorCode("teacher_name"). WithDetails("I will add that to both rules!") v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(teacherNameRule), ).WithName("Teacher") teacher := Teacher{ Name: "Jonathan", Age: 51 * year, } err := v.Validate(teacher) if err != nil { propertyErrors := err.Errors ruleErrors := propertyErrors[0].Errors fmt.Printf("Error codes: %s, %s\n\n", ruleErrors[0].Code, ruleErrors[1].Code) fmt.Println(err) } // nolint: lll
Output:
func NewRuleSet ¶ added in v0.50.0
NewRuleSet creates a new RuleSet instance.
func StringIsDNSSubdomain ¶
func (RuleSet[T]) Validate ¶ added in v0.50.0
Validate works the same way as SingleRule.Validate, except each aggregated rule is validated individually. The errors are aggregated and returned as a single error which serves as a container for them.
func (RuleSet[T]) WithDetails ¶ added in v0.59.0
WithDetails adds details to each returned RuleError error message.
type SingleRule ¶
type SingleRule[T any] struct { // contains filtered or unexported fields }
SingleRule is the basic validation building block. It evaluates the provided validation function and enhances it with optional ErrorCode and arbitrary details.
func EqualTo ¶ added in v0.50.0
func EqualTo[T comparable](compared T) SingleRule[T]
func Forbidden ¶ added in v0.59.0
func Forbidden[T any]() SingleRule[T]
func GreaterThan ¶ added in v0.50.0
func GreaterThan[T constraints.Ordered](n T) SingleRule[T]
func GreaterThanOrEqualTo ¶ added in v0.50.0
func GreaterThanOrEqualTo[T constraints.Ordered](n T) SingleRule[T]
func LessThan ¶ added in v0.50.0
func LessThan[T constraints.Ordered](n T) SingleRule[T]
func LessThanOrEqualTo ¶ added in v0.50.0
func LessThanOrEqualTo[T constraints.Ordered](n T) SingleRule[T]
func MapLength ¶ added in v0.59.0
func MapLength[M ~map[K]V, K comparable, V any](min, max int) SingleRule[M]
func MapMaxLength ¶ added in v0.59.0
func MapMaxLength[M ~map[K]V, K comparable, V any](max int) SingleRule[M]
func MapMinLength ¶ added in v0.59.0
func MapMinLength[M ~map[K]V, K comparable, V any](min int) SingleRule[M]
func MutuallyExclusive ¶ added in v0.61.0
func MutuallyExclusive[S any](required bool, getters map[string]func(s S) any) SingleRule[S]
MutuallyExclusive checks if properties are mutually exclusive. This means, exactly one of the properties can be provided. If required is true, then a single non-empty property is required.
func NewSingleRule ¶ added in v0.50.0
func NewSingleRule[T any](validate func(v T) error) SingleRule[T]
NewSingleRule creates a new SingleRule instance.
func NotEqualTo ¶ added in v0.50.0
func NotEqualTo[T comparable](compared T) SingleRule[T]
func OneOf ¶ added in v0.59.0
func OneOf[T comparable](values ...T) SingleRule[T]
func Required ¶ added in v0.50.0
func Required[T any]() SingleRule[T]
func SliceLength ¶ added in v0.59.0
func SliceLength[S ~[]E, E any](min, max int) SingleRule[S]
func SliceMaxLength ¶ added in v0.59.0
func SliceMaxLength[S ~[]E, E any](max int) SingleRule[S]
func SliceMinLength ¶ added in v0.59.0
func SliceMinLength[S ~[]E, E any](min int) SingleRule[S]
func SliceUnique ¶ added in v0.59.0
func SliceUnique[S []V, V any, H comparable](hashFunc HashFunction[V, H], constraints ...string) SingleRule[S]
SliceUnique validates that a slice contains unique elements based on a provided HashFunction. You can optionally specify constraints which will be included in the error message to further clarify the reason for breaking uniqueness.
func StringASCII ¶ added in v0.59.0
func StringASCII() SingleRule[string]
func StringContains ¶ added in v0.59.0
func StringContains(substrings ...string) SingleRule[string]
func StringDenyRegexp ¶ added in v0.59.0
func StringDenyRegexp(re *regexp.Regexp, examples ...string) SingleRule[string]
func StringDescription ¶
func StringDescription() SingleRule[string]
func StringJSON ¶ added in v0.59.0
func StringJSON() SingleRule[string]
func StringLength ¶
func StringLength(min, max int) SingleRule[string]
func StringMatchRegexp ¶ added in v0.59.0
func StringMatchRegexp(re *regexp.Regexp, examples ...string) SingleRule[string]
func StringMaxLength ¶ added in v0.59.0
func StringMaxLength(max int) SingleRule[string]
func StringMinLength ¶ added in v0.59.0
func StringMinLength(min int) SingleRule[string]
func StringNotEmpty ¶ added in v0.59.0
func StringNotEmpty() SingleRule[string]
func StringURL ¶ added in v0.59.0
func StringURL() SingleRule[string]
func StringUUID ¶ added in v0.65.0
func StringUUID() SingleRule[string]
func URL ¶ added in v0.67.0
func URL() SingleRule[*url.URL]
func (SingleRule[T]) Validate ¶
func (r SingleRule[T]) Validate(v T) error
Validate runs validation function on the provided value. It can handle different types of errors returned by the function:
- *RuleError, which details and ErrorCode are optionally extended with the ones defined by SingleRule.
- *PropertyError, for each of its errors their ErrorCode is extended with the one defined by SingleRule.
By default, it will construct a new RuleError.
func (SingleRule[T]) WithDetails ¶ added in v0.59.0
func (r SingleRule[T]) WithDetails(format string, a ...any) SingleRule[T]
WithDetails adds details to the returned RuleError error message.
Example ¶
You can use SingleRule.WithDetails to add additional details to the error message. This allows you to extend existing rules by adding your use case context. Let's give a regex validation some more clarity.
package main import ( "fmt" "regexp" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(validation.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")). WithDetails("Teacher can be either Tom or Jerry :)")), ).WithName("Teacher") teacher := Teacher{ Name: "Jake", Age: 51 * year, } err := v.Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation for Teacher has failed for the following properties: - 'name' with value 'Jake': - string does not match regular expression: '^(Tom|Jerry)$'; Teacher can be either Tom or Jerry :)
func (SingleRule[T]) WithErrorCode ¶ added in v0.50.0
func (r SingleRule[T]) WithErrorCode(code ErrorCode) SingleRule[T]
WithErrorCode sets the error code for the returned RuleError.
Example ¶
When testing, it can be tedious to always rely on error messages as these can change over time. Enter ErrorCode, which is a simple string type alias used to ease testing, but also potentially allow third parties to integrate with your validation results. Use SingleRule.WithErrorCode to associate ErrorCode with a SingleRule. Notice that our modified version of StringMatchRegexp will now return a new ErrorCode. Predefined rules have ErrorCode already associated with them. To view the list of predefined ErrorCode checkout error_codes.go file.
package main import ( "fmt" "regexp" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Rules(validation.StringMatchRegexp(regexp.MustCompile("^(Tom|Jerry)$")). WithDetails("Teacher can be either Tom or Jerry :)"). WithErrorCode("custom_code")), ).WithName("Teacher") teacher := Teacher{ Name: "Jake", Age: 51 * year, } err := v.Validate(teacher) if err != nil { propertyErrors := err.Errors ruleErrors := propertyErrors[0].Errors fmt.Println(ruleErrors[0].Code) } }
Output: custom_code
type Transformer ¶ added in v0.65.0
type Validator ¶ added in v0.50.0
type Validator[S any] struct { // contains filtered or unexported fields }
Validator is the top level validation entity. It serves as an aggregator for PropertyRules.
Example ¶
Bringing it all (mostly) together, let's create a fully fledged Validator for [Teacher].
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } func main() { universityValidation := validation.New[University]( validation.For(func(u University) string { return u.Address }). WithName("address"). Required(), ) studentValidator := validation.New[Student]( validation.For(func(s Student) string { return s.Index }). WithName("index"). Rules(validation.StringLength(9, 9)), ) teacherValidator := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). WithName("name"). Required(). Rules( validation.StringNotEmpty(), validation.OneOf("Jake", "George")), validation.ForEach(func(t Teacher) []Student { return t.Students }). WithName("students"). Rules( validation.SliceMaxLength[[]Student](2), validation.SliceUnique(func(v Student) string { return v.Index })). IncludeForEach(studentValidator), validation.For(func(t Teacher) University { return t.University }). WithName("university"). Include(universityValidation), ).When(func(t Teacher) bool { return t.Age < 50 }) teacher := Teacher{ Name: "John", Students: []Student{ {Index: "918230014"}, {Index: "9182300123"}, {Index: "918230014"}, }, University: University{ Name: "Poznan University of Technology", Address: "", }, } err := teacherValidator.WithName("John").Validate(teacher) if err != nil { fmt.Println(err) } }
Output: Validation for John has failed for the following properties: - 'name' with value 'John': - must be one of [Jake, George] - 'students[1].index' with value '9182300123': - length must be between 9 and 9 - 'students' with value '[{"index":"918230014"},{"index":"9182300123"},{"index":"918230014"}]': - length must be less than or equal to 2 - elements are not unique, index 0 collides with index 2 - 'university.address': - property is required but was empty
Example (BranchingPattern) ¶
When dealing with properties that should only be validated if a certain other property has specific value, it's recommended to use PropertyRules.When and PropertyRules.Include to separate validation paths into non-overlapping branches.
Notice how in the below example [File.Format] is the common, shared property between [CSV] and [JSON] files. We define separate Validator for [CSV] and [JSON] and use PropertyRules.When to only validate their included Validator if the correct [File.Format] is provided.
package main import ( "fmt" "regexp" "github.com/nobl9/nobl9-go/validation" ) func main() { type ( CSV struct { Separator string `json:"separator"` } JSON struct { Indent string `json:"indent"` } File struct { Format string `json:"format"` CSV *CSV `json:"csv,omitempty"` JSON *JSON `json:"json,omitempty"` } ) csvValidation := validation.New[CSV]( validation.For(func(c CSV) string { return c.Separator }). WithName("separator"). Required(). Rules(validation.OneOf(",", ";")), ) jsonValidation := validation.New[JSON]( validation.For(func(j JSON) string { return j.Indent }). WithName("indent"). Required(). Rules(validation.StringMatchRegexp(regexp.MustCompile(`^\s*$`))), ) fileValidation := validation.New[File]( validation.ForPointer(func(f File) *CSV { return f.CSV }). When(func(f File) bool { return f.Format == "csv" }). Include(csvValidation), validation.ForPointer(func(f File) *JSON { return f.JSON }). When(func(f File) bool { return f.Format == "json" }). Include(jsonValidation), validation.For(func(f File) string { return f.Format }). WithName("format"). Required(). Rules(validation.OneOf("csv", "json")), ).WithName("File") file := File{ Format: "json", CSV: nil, JSON: &JSON{ Indent: "invalid", }, } err := fileValidation.Validate(file) if err != nil { fmt.Println(err) } }
Output: Validation for File has failed for the following properties: - 'indent' with value 'invalid': - string does not match regular expression: '^\s*$'
func New ¶ added in v0.50.0
New creates a new Validator aggregating the provided property rules.
Example ¶
In order to create a new Validator use New constructor. Let's define simple PropertyRules for [Teacher.Name]. For now, it will be always failing.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } func main() { v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). Rules(validation.NewSingleRule(func(name string) error { return fmt.Errorf("always fails") })), ) err := v.Validate(Teacher{}) if err != nil { fmt.Println(err) } }
Output: Validation has failed for the following properties: - always fails
func (Validator[S]) Validate ¶ added in v0.50.0
func (v Validator[S]) Validate(st S) *ValidatorError
Validate will first evaluate predicates before validating any rules. If any predicate does not pass the validation won't be executed (returns nil). All errors returned by property rules will be aggregated and wrapped in ValidatorError.
func (Validator[S]) When ¶ added in v0.59.0
When defines accepts predicates which will be evaluated BEFORE Validator validates ANY rules.
Example ¶
Validator rules can be evaluated on condition, to specify the predicate use Validator.When function.
In this example, validation for [Teacher] instance will only be evaluated if the [Age] property is less than 50 years.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } const year = 24 * 365 * time.Hour func main() { v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). Rules(validation.NewSingleRule(func(name string) error { return fmt.Errorf("always fails") })), ). When(func(t Teacher) bool { return t.Age < (50 * year) }) // Prepare teachers. teacherTom := Teacher{ Name: "Tom", Age: 51 * year, } teacherJerry := Teacher{ Name: "Jerry", Age: 30 * year, } // Run validation. err := v.Validate(teacherTom) if err != nil { fmt.Println(err.WithName("Tom")) } err = v.Validate(teacherJerry) if err != nil { fmt.Println(err.WithName("Jerry")) } }
Output: Validation for Jerry has failed for the following properties: - always fails
func (Validator[S]) WithName ¶ added in v0.59.0
WithName when a rule fails will pass the provided name to ValidatorError.WithName.
Example ¶
To associate Validator with an entity name use Validator.WithName function. When any of the rules fails, the error will contain the entity name you've provided.
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } func main() { v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). Rules(validation.NewSingleRule(func(name string) error { return fmt.Errorf("always fails") })), ).WithName("Teacher") err := v.Validate(Teacher{}) if err != nil { fmt.Println(err) } }
Output: Validation for Teacher has failed for the following properties: - always fails
type ValidatorError ¶ added in v0.59.0
type ValidatorError struct { Errors PropertyErrors `json:"errors"` Name string `json:"name"` }
func NewValidatorError ¶ added in v0.59.0
func NewValidatorError(errs PropertyErrors) *ValidatorError
func (*ValidatorError) Error ¶ added in v0.59.0
func (e *ValidatorError) Error() string
func (*ValidatorError) WithName ¶ added in v0.59.0
func (e *ValidatorError) WithName(name string) *ValidatorError
Example ¶
You can also add Validator name during runtime, by calling ValidatorError.WithName function on the returned error.
NOTE: We left the previous "Teacher" name assignment, to demonstrate that the ValidatorError.WithName function call will shadow it.
NOTE: This would also work:
err := v.WithName("Jake").Validate(Teacher{})
Validation package, aside from errors handling, tries to follow immutability principle. Calling any function on Validator will not change its previous declaration (unless you assign it back to 'v').
package main import ( "fmt" "time" "github.com/nobl9/nobl9-go/validation" ) type Teacher struct { Name string `json:"name"` Age time.Duration `json:"age"` Students []Student `json:"students"` MiddleName *string `json:"middleName,omitempty"` University University `json:"university"` } type University struct { Name string `json:"name"` Address string `json:"address"` } type Student struct { Index string `json:"index"` } func main() { v := validation.New[Teacher]( validation.For(func(t Teacher) string { return t.Name }). Rules(validation.NewSingleRule(func(name string) error { return fmt.Errorf("always fails") })), ).WithName("Teacher") err := v.Validate(Teacher{}) if err != nil { fmt.Println(err.WithName("Jake")) } }
Output: Validation for Jake has failed for the following properties: - always fails