Go Validator
A Laravel-like data validator for Go.
Allows you to define a list of validation rules (constraints) for a specific field and produces a summary with failures.
A rule can change the value it validates (only during validation, the original value remains unchanged), which can be beneficial in later validation.
Some rules adapt to the currently validated value and act differently. More details can be found in descriptions of rules.
Installation
go get github.com/donatorsky/go-validator
The library is still a work in progress. More details soon.
Available validators
Each validator has a context counterpart which lets you set the context used during validation. It will be passed to rules. The default context is context.Background()
.
ForMap(data map[string]any, rules RulesMap, options ...forMapValidatorOption)
, ForMapWithContext
Validates a map[string]any
. You can specify a map of rules for each key and internal values (either slices, arrays, maps or structs).
Returns ErrorsBag
with keys being the map keys of input map.
Options
ForMapWithDataCollector(collector DataCollector)
Sets a DataCollector
instance to be used while validating data which will collect all successfully validated data.
Example
validator.ForMap(
map[string]any{
"foo": 123,
"bar": "bar",
"baz": map[string]any{
"inner_foo": 123,
"inner_bar": "bar",
// ...
},
},
validator.RulesMap{
"foo": {
rule.Required(),
rule.Integer[int](),
},
"bar": {
rule.Required(),
rule.String(),
},
"baz.inner_foo": {
rule.Required(),
rule.Integer[int](),
},
"baz.inner_bar": {
rule.Required(),
rule.Slice(),
},
// ...
},
)
ForStruct(data any, rules RulesMap, options ...forStructValidatorOption)
, ForStructWithContext
Validates a struct. You can specify a map of rules for each field name and internal values (either slices, arrays, maps or structs).
Note that you can also use custom name for a field using validation
tag.
You can also pass pointer which will be automatically dereferenced.
Returns ErrorsBag
with keys being the field names (or values from validation
if provided) of input struct.
Options
ForStructWithDataCollector(collector DataCollector)
Sets a DataCollector
instance to be used while validating data which will collect all successfully validated data.
Example
type SomeRequest struct {
Foo int
Bar string `validation:"bar"`
Baz SomeRequestBaz
}
type SomeRequestBaz struct {
InnerFoo int `validation:"foo"`
InnerBar string
// ...
}
validator.ForStruct(
SomeRequest{
Foo: 123,
Bar: "bar",
Baz: SomeRequestBaz{
InnerFoo: 123,
InnerBar: "bar",
// ...
},
},
validator.RulesMap{
"Foo": {
rule.Required(),
rule.Integer[int](),
},
"bar": {
rule.Required(),
rule.String(),
},
"Baz.foo": {
rule.Required(),
rule.Integer[int](),
},
"Baz.InnerBar": {
rule.Required(),
rule.Slice(),
},
// ...
},
)
ForSlice(data any, rules []vr.Rule, options ...forSliceValidatorOption)
, ForSliceWithContext
Validates both a slice []any
or an array [size]any
. You can specify a list of rules for each element in given slice/array.
You can also pass pointer which will be automatically dereferenced.
Returns ErrorsBag
with keys being the indices of input slice/array.
Options
ForSliceWithDataCollector(collector DataCollector)
Sets a DataCollector
instance to be used while validating data which will collect all successfully validated data.
Example
validator.ForSlice(
[]any{
"foo",
"bar",
},
validator.RulesMap{
rule.Required(),
rule.String(),
// ...
},
)
ForValue[In any](value In, rules []vr.Rule, options ...forValueValidatorOption)
, ForValueWithContext
Validates any value. You can specify a list of rules for given value.
If you pass a pointer, it will not be dereferenced.
Returns a slice of ValidationError
.
Options
ForValueWithValueExporter[Out any](value *Out)
Sets a pointer to a variable to which data will be exported after successful validation.
Example
validator.ForValue(
123,
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.Min(100),
// ...
},
)
Validation of nested objects
It is possible to validate nested objects (i.e.: slice, array, map or struct) using the dot notation:
- For slices and arrays: it refers to the index of element, e.g.: given
"slice": []int{1, 2, 3},
, slice.1
refers to the value 2
.
- For maps: it refers to the element by given key, e.g.: given
"map": map[string]int{"foo": 1, "bar": 2, "baz": 3},
, map.bar
refers to the value 2
.
- For structs: it refers to the field with same
validation
tag or field name if tag is not present, e.g.: given "struct": {Foo: 1, Bar: 2, Baz: 3},
, struct.Bar
refers to the value 2
.
You can also validate every single value of slice and array by using *
wildcard symbol.
Example
type SomeRequest struct {
SingleValue int
Array [3]string
Slice []string
Map map[string]string
Struct SomeRequestStruct
SliceOfStructs []SomeRequestStruct
MapOfStructs map[string]SomeRequestStruct
SliceOfSlices [][]string
}
type SomeRequestStruct struct {
InnerSingleValue int
InnerArray [3]string
InnerSlice []string
InnerMap map[string]string
InnerStruct SomeRequestInnerStruct
}
type SomeRequestInnerStruct struct {
InnerInnerSingleValue int
InnerInnerArray [3]string
InnerInnerSlice []string
InnerInnerMap map[string]string
// ...
}
validator.ForStruct(
SomeRequest{
SingleValue: 123,
Array: [3]string{"foo", "bar", "baz"},
Slice: []string{"foo", "bar", "baz"},
Map: map[string]string{"foo": 1, "bar": 2},
Struct: SomeRequestStruct{
InnerSingleValue: 123,
InnerArray: [3]string{"foo", "bar", "baz"},
InnerSlice: []string{"foo", "bar", "baz"},
InnerMap: map[string]string{"foo": 1, "bar": 2},
InnerStruct: SomeRequestInnerStruct{
// ...
},
},
SliceOfStructs: []SomeRequestStruct{
{
InnerSingleValue: 123,
InnerSlice: []string{"foo", "bar", "baz"},
// ...
},
// ...
},
MapOfStructs: map[string]SomeRequestStruct{
"foo": {
InnerSingleValue: 123,
InnerSlice: []string{"foo", "bar", "baz"},
// ...
},
"bar": {
InnerSingleValue: 123,
InnerSlice: []string{"foo", "bar", "baz"},
// ...
},
// ...
},
SliceOfSlices: [][]string{
{"foo", "bar", "baz"},
{"foo", "bar"},
},
},
validator.RulesMap{
// Rules for SomeRequest
"SingleValue": {
rule.Required(),
rule.Integer[int](),
},
"Array": {
rule.Required(),
rule.SliceOf[string](),
},
"Array.*": {
rule.Required(),
rule.String(),
},
"Slice": {
rule.Required(),
rule.SliceOf[string](),
rule.Length(3),
},
"Slice.*": {
rule.Required(),
rule.String(),
},
"Map.foo": {
rule.Required(),
rule.Integer[int](),
},
"Map.bar": {
rule.Required(),
rule.Integer[int](),
},
// Rules for SomeRequestStruct
"Struct.InnerSingleValue": {
rule.Required(),
rule.Integer[int](),
},
"Struct.InnerArray": {
rule.Required(),
rule.SliceOf[string](),
},
"Struct.InnerArray.*": {
rule.Required(),
rule.String(),
},
"Struct.InnerSlice": {
rule.Required(),
rule.SliceOf[string](),
rule.Length(3),
},
"Struct.InnerSlice.*": {
rule.Required(),
rule.String(),
},
"Struct.InnerMap.foo": {
rule.Required(),
rule.Integer[int](),
},
"Struct.InnerMap.bar": {
rule.Required(),
rule.Integer[int](),
},
// Rules for SomeRequest (complex examples)
"SliceOfStructs": {
rule.Required(),
rule.Length(1),
},
"SliceOfStructs.*.InnerSingleValue": {
rule.Required(),
rule.Integer[int](),
},
"SliceOfStructs.*.InnerSlice.*": {
rule.Required(),
rule.String(),
},
"MapOfStructs": {
rule.Required(),
},
"MapOfStructs.foo.InnerSingleValue": {
rule.Required(),
rule.Integer[int](),
},
"MapOfStructs.foo.InnerSlice.*": {
rule.Required(),
rule.String(),
},
"SliceOfSlices": {
rule.Required(),
rule.Slice(),
rule.Length(2),
},
"SliceOfSlices.*": {
rule.Required(),
rule.Slice(),
rule.Min(2),
},
"SliceOfSlices.*.*": {
rule.Required(),
rule.String(),
},
// Non-existing keys
"IDoNotExist": {
rule.Required(),
},
"IDoNotExist.*": {
rule.Required(),
},
"IDoNotExist.foo": {
rule.Required(),
},
"SliceOfSlices.*.*.*": {
rule.Required(),
},
"SliceOfSlices.*.*.foo": {
rule.Required(),
},
// ...
},
)
As a result you will get errors for each element separately, e.g.: SliceOfSlices.0.1
, SliceOfSlices.3.0
etc.
Note that some wildcards will not be matched. In that case you will get *
for every unmatched nested element, e.g.: IDoNotExist.*
, "IDoNotExist.foo"
, "SliceOfSlices.0.0.*"
, "SliceOfSlices.0.1.*"
, "SliceOfSlices.0.0.foo"
, "SliceOfSlices.0.1.foo"
etc.
Stopping validation on first error
Some rules stop validation of given element once they fail (e.g.: Required
since further validation makes no sense when value is not present).
You can also manually stop validation by using Bail
pseudo-rule.
Example
validator.ForValue(
"123",
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.Bail(), // Next rules will not be checked if value is not an integer
rule.Min(100),
// ...
},
)
Conditional validation
You can add validation rules based on custom conditions. It can be either simple boolean value using When
or complex condition using WhenFunc
.
Conditional rules can be nested to cover more complex requirements.
Once conditional rule is valid, its rules will be merged to the main list of rules. It also includes the Bail
pseudo-rule which will stop any further validation of given rules list, no matter how deep it was defined.
When
This pseudo-rule allows for adding rules based on simple boolean value.
Example
validator.ForValue(
123,
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.When(true,
rule.Min(100),
rule.When(false,
rule.Min(200),
// ...
),
rule.Bail(),
// ...
),
// ...
},
)
The example above becomes:
validator.ForValue(
123,
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.Min(100),
rule.Bail(),
// ...
// ...
},
)
WhenFunc
This pseudo-rule allows for adding rules based on a custom logic.
It receives the context
passed to the validator, the currently validated value
and the original data
passed to the validator.
Example
validator.ForValue(
123,
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.WhenFunc(
func(ctx context.Context, value any, data any) bool {
value, isNil := rule.Dereference(value)
if isNil {
return false
}
return value.(int)%2 == 1
},
rule.Min(100),
rule.WhenFunc(
func(_ context.Context, value any, _ any) bool {
value, isNil := rule.Dereference(value)
if isNil {
return false
}
return value.(int)%2 == 0
},
rule.Min(200),
// ...
),
rule.Bail(),
// ...
),
// ...
},
)
The example above becomes:
validator.ForValue(
123,
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.Min(100),
rule.Bail(),
// ...
// ...
},
)
Available rules
Common types:
integerType
- Any integer type, i.e.: ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
.
floatType
- Any integer type, i.e.: ~float32 | ~float64
.
numberType
- Any number, i.e.: integerType | floatType
.
afterComparable
- Any object that implements the following interface:
type afterComparable interface {
After(time.Time) bool
}
afterOrEqualComparable
- Any object that implements the following interface:
type afterOrEqualComparable interface {
Equal(time.Time) bool
After(time.Time) bool
}
beforeComparable
- Any object that implements the following interface:
type beforeComparable interface {
Before(time.Time) bool
}
beforeOrEqualComparable
- Any object that implements the following interface:
type beforeOrEqualComparable interface {
Equal(time.Time) bool
Before(time.Time) bool
}
Comparator
- Any object of the following type:
type Comparator func(x, y any) bool
After(after time.Time)
Checks whether a value is after after
date.
Applies to:
nil
: passes.
afterComparable
: passes only when a value is after after
date.
any
: fails.
Modifies output:
No.
Bails:
No.
AfterOrEqual(afterOrEqual time.Time)
Checks whether a value is after or equal to afterOrEqual
date.
Applies to:
nil
: passes.
afterOrEqualComparable
: passes only when a value is after or equal to afterOrEqual
date.
any
: fails.
Modifies output:
No.
Bails:
No.
Array()
Checks and ensures that a value is of array type.
Applies to:
nil
: passes.
any
: passes only when a value is of array type or its pointer, any length and element type.
Modifies output:
No.
Bails:
Yes.
ArrayOf[Out any]()
Checks and ensures that a value is of [n]Out
type.
Applies to:
nil
: passes.
any
: passes only when a value is of [n]Out
type or its pointer, any length.
Modifies output:
nil
: nil array of [0]Out
.
any
: input value.
Bails:
Yes.
Before(before time.Time)
Checks whether a value is before before
date.
Applies to:
nil
: passes.
any
: passes only when a value implements beforeComparable
interface and is before before
date.
Modifies output:
No.
Bails:
No.
BeforeOrEqual(beforeOrEqual time.Time)
Checks whether a value is before or equal to beforeOrEqual
date.
Applies to:
nil
: passes.
any
: passes only when a value implements beforeOrEqualComparable
interface and is before or equal to beforeOrEqual
date.
Modifies output:
No.
Bails:
No.
Between[T numberType](min, max T)
Checks whether a value is between min
and max
, inclusive.
Applies to:
nil
: passes.
numberType
: checks if a value is between min
and max
.
string
: checks if string's length is between min
and max
.
slice
, array
: checks if slice/array has between min
and max
elements.
map
: checks if map has at least min
and max
keys.
Modifies output:
No.
Bails:
No.
BetweenExclusive[T numberType](min, max T)
Checks whether a value is between min
and max
, exclusive.
Applies to:
nil
: passes.
numberType
: checks if a value is between min
and max
.
string
: checks if string's length is between min
and max
.
slice
, array
: checks if slice/array has between min
and max
elements.
map
: checks if map has at least min
and max
keys.
Modifies output:
No.
Bails:
No.
Boolean()
Checks and ensures that a value is of bool
type.
Applies to:
nil
: any value.
bool
: any value.
integerType
: when a value equals to 0
or 1
.
floatType
: when a value equals to 0.0
or 1.0
.
string
: when string is convertible to bool
according to the strconv.ParseBool
function.
any
: fails.
Modifies output:
nil
: returns *bool
nil pinter.
bool
: input value.
integerType
: false
when 0
, true
when 1
.
floatType
: false
when 0.0
, true
when 1.0
.
string
: according to the strconv.ParseBool
function.
Bails:
Yes.
Date()
Checks whether a value is of time.Time
type, its pointer or valid date string in time.RFC3339Nano
format.
Applies to:
nil
: passes.
time.Duration
: any value.
string
: when string is convertible to time.Duration
according to the time.Parse
function and time.RFC3339Nano
format.
any
: fails.
Modifies output:
nil
: returns *time.Time
nil pointer.
time.Time
: input value.
string
: according to the time.Parse
function.
Bails:
No.
Checks whether a value is of time.Time
type, its pointer or valid date string in format
format.
Applies to:
nil
: passes.
time.Duration
: any value.
string
: when string is convertible to time.Duration
according to the time.Parse
function and format
format.
any
: fails.
Modifies output:
nil
: returns *time.Time
nil pinter.
time.Time
: input value.
string
: according to the time.Parse
function.
Bails:
No.
DoesntEndWith(suffix string, suffixes ...string)
Checks whether a value is a string not ending with any of provided suffixes.
Applies to:
nil
: passes.
string
: checks if string does not end with any of provided suffixes (case-sensitive).
any
: fails.
Modifies output:
No.
Bails:
No.
DoesntStartWith(prefix string, prefixes ...string)
Checks whether a value is a string not starting with any of provided prefixes.
Applies to:
nil
: passes.
string
: checks if string does not start with any of provided prefixes (case-sensitive).
any
: fails.
Modifies output:
No.
Bails:
No.
Duration()
Checks whether a value is of time.Duration
type, its pointer or valid duration string.
Applies to:
nil
: passes.
time.Duration
: any value.
string
: when string is convertible to time.Duration
according to the time.ParseDuration
function.
any
: fails.
Modifies output:
nil
: returns *time.Duration
nil pinter.
time.Duration
: returns unchanged value.
string
: according to the time.ParseDuration
function.
Bails:
No.
Email()
Checks whether a value is valid email address, according to the net/mail.ParseAddress
function.
Applies to:
nil
: passes.
string
: passes only when a value is successfully parsed by net/mail.ParseAddress
function.
any
: fails.
Modifies output:
No.
Bails:
No.
EmailAddress()
Checks whether a value is valid email address, according to the net/mail.ParseAddress
function.
Applies to:
nil
: passes.
string
: passes only when a value is successfully parsed by net/mail.ParseAddress
function.
any
: fails.
Modifies output:
nil
: input value.
string
: unlike the Email()
rule, it returns the email address of the string. E.g. given a value Foo Bar <foo@bar.baz> (some comment)
, the output will be foo@bar.baz
.
Bails:
No.
EndsWith(suffix string)
Checks whether a value is a string ending with one of provided suffixes.
Applies to:
nil
: passes.
string
: checks if string ends with one of provided suffixes (case-sensitive).
any
: fails.
Modifies output:
No.
Bails:
No.
Filled()
Checks whether a value is not empty when it is present.
Applies to:
nil
: passes.
*any
: passes.
!nil
: checks if value is not a zero value.
Modifies output:
No.
Bails:
No.
Float[Out floatType]()
Checks and ensures that a value is of Out
type or its pointer.
Applies to:
nil
: passes.
floatType
: passes.
any
: fails.
Modifies output:
No.
Bails:
Yes.
In[T comparable](values []T, options ...inRuleOption)
Checks whether a value exists in values
.
Options:
InRuleWithComparator(comparator Comparator)
: sets custom elements comparator. comparator
receives an input value and each element of values
, one at a time.
InRuleWithoutAutoDereference()
: disables automatic dereference of a value, i.e. values
will be compared against the exact input value which may be a pointer.
Applies to:
nil
: passes with auto-dereference enabled. Otherwise, fails when not present in values
.
comparable
: passes only when a value exists in values
, optionally using custom comparator
.
any
: fails.
Modifies output:
No.
Bails:
No.
Integer[Out integerType]()
Checks and ensures that a value is of Out
type or its pointer.
Applies to:
nil
: passes.
integerType
: passes only when a value is of Out
type or its pointer.
any
: fails.
Modifies output:
No.
Bails:
Yes.
IP()
Checks whether a value is a string in IP v4 or v6 format.
Applies to:
nil
: passes.
string
: checks if a value is in IP v4 or v6 format according to the net.ParseIP
function.
any
: fails.
Modifies output:
No.
Bails:
No.
Length[T integerType](length T)
Checks whether a value is exactly length
.
Applies to:
nil
: passes.
string
: checks if string's length is exactly length
characters.
slice
, array
: checks if slice/array has exactly length
elements.
map
: checks if map has exactly length
keys.
any
: fails.
Modifies output:
No.
Bails:
No.
Map()
Checks and ensures that a value is of map type.
Applies to:
nil
: passes.
map
: passes.
any
: fails.
Modifies output:
No.
Bails:
Yes.
Max[T numberType](max T)
Checks whether a value is at most max
.
Applies to:
nil
: passes.
numberType
: checks if a value is at most max
.
string
: checks if string's length is at most max
characters.
slice
, array
: checks if slice/array has at most max
elements.
map
: checks if map has at most max
keys.
any
: fails.
Modifies output:
No.
Bails:
No.
MaxExclusive[T numberType](max T)
Checks whether a value is less than max
.
Applies to:
nil
: passes.
numberType
: checks if a value is less than max
.
string
: checks if string's length is less than max
characters.
slice
, array
: checks if slice/array has less than max
elements.
map
: checks if map has less than max
keys.
any
: fails.
Modifies output:
No.
Bails:
No.
Min[T numberType](min T)
Checks whether a value is at least min
.
Applies to:
nil
: passes.
numberType
: checks if a value is at least min
.
string
: checks if string's length is at least min
characters.
slice
, array
: checks if slice/array has at least min
elements.
map
: checks if map has at least min
keys.
any
: fails.
Modifies output:
No.
Bails:
No.
MinExclusive[T numberType](min T)
Checks whether a value is greater than min
.
Applies to:
nil
: passes.
numberType
: checks if a value is greater than min
.
string
: checks if string's length is more than min
characters.
slice
, array
: checks if slice/array has more than min
elements.
map
: checks if map has more than min
keys.
any
: fails.
Modifies output:
No.
Bails:
No.
NotIn[T comparable](values []T, options ...notInRuleOption)
Checks whether a value does not exist in values
.
Options:
NotInRuleWithComparator(comparator Comparator)
: sets custom elements comparator. comparator
receives an input value and each element of values
, one at a time.
NotInRuleWithoutAutoDereference()
: disables automatic dereference of a value, i.e. values
will be compared against the exact input value which may be a pointer.
Applies to:
nil
: passes with auto-dereference enabled. Otherwise, fails when present in values
.
comparable
: passes only when a value does not exist in values
, optionally using custom comparator
.
any
: passes.
Modifies output:
No.
Bails:
No.
NotRegex(regex *regexp.Regexp)
Checks whether a value does not match regex
expression.
Applies to:
nil
: passes.
string
: checks if string does not match regex
expression.
any
: fails.
Modifies output:
No.
Bails:
No.
Numeric()
Checks whether a value is a numeric value or a string that can be converted to one.
Applies to:
nil
: passes.
numberType
, complex64
, complex128
: passes.
string
: passes only when a value can be converted to number using strconv.ParseInt
, strconv.ParseUint
, strconv.ParseFloat
and strconv.ParseComplex
, in that order.
any
: fails.
Modifies output:
nil
: unchanged value.
numberType
, complex64
, complex128
: unchanged value.
string
: value converted to number.
Bails:
No.
Regex(regex *regexp.Regexp)
Checks whether a value matches regex
expression.
Applies to:
nil
: passes.
string
: checks if string matches regex
expression.
any
: fails.
Modifies output:
No.
Bails:
No.
Required()
Checks whether a value is not nil
.
Applies to:
any
: passes only when a value not nil
.
Modifies output:
No.
Bails:
Yes.
Slice()
Checks and ensures that a value is of slice type or its pointer.
Applies to:
nil
: passes.
slice
: passes.
any
: fails.
Modifies output:
No.
Bails:
Yes.
SliceOf[Out any]()
Checks and ensures that a value is of []Out
type.
Applies to:
nil
: passes.
slice
: passes only when a value is of []Out
type or its pointer, any length.
any
: fails.
Modifies output:
nil
: returns nil
slice of []Out
type.
any
: input value.
Bails:
Yes.
StartsWith(prefix string, prefixes ...string)
Checks whether a value is a string starting with one of provided prefixes.
Applies to:
nil
: passes.
string
: checks if string starts with one of provided prefixes (case-sensitive).
any
: fails.
Modifies output:
No.
Bails:
No.
String()
Checks and ensures that a value is of string
type or its pointer.
Applies to:
nil
: passes.
string
: passes.
any
: fails.
Modifies output:
nil
: returns string nil pointer.
string
: input value.
Bails:
Yes.
Struct()
Checks and ensures that a value is of struct type.
Applies to:
nil
: passes.
struct
: passes.
any
: fails.
Modifies output:
No.
Bails:
Yes.
URL()
Checks whether a value is a valid URL string.
Applies to:
nil
: passes.
string
: checks if string is a valid URL according to net/url.ParseRequestURI
function.
any
: fails.
Modifies output:
No.
Bails:
No.
UUID(options ...uuidRuleOption)
Checks whether a value is a valid RFC 4122 (version 1, 3, 4 or 5) universally unique identifier (UUID).
Options:
UUIDRuleVersion1()
: allows for UUIDv1.
UUIDRuleVersion3()
: allows for UUIDv3.
UUIDRuleVersion4()
: allows for UUIDv4.
UUIDRuleVersion5()
: allows for UUIDv5.
UUIDRuleDisallowNilUUID()
: disallows for nil UUID, i.e. 00000000-0000-0000-0000-000000000000
.
Applies to:
nil
: passes.
string
: checks if string is a valid UUID.
any
: fails.
Modifies output:
No.
Bails:
No.
Custom validation
You can write a custom validator to cover custom needs. There are to ways of doing it: by implementing rule.Rule
interface or by using rule.Custom
rule.
A custom rule struct can also implement BailingRule
interface so that it may stop further validation. There is also Bailer
helper struct for that.
The rule.Custom
rule can return any error
. In that case, the error is added to the response. However, you can return a custom message by returning an error of error.ValidationError
type.
Since the value can be anything, including pointer, there is a helper function rule.Dereference
that returns the underlying value.
Example
import ve "github.com/donatorsky/go-validator/error"
type DividesByNValidationError struct {
ve.BasicValidationError
Divider int `json:"divider"`
}
func (e DividesByNValidationError) Error() string {
return fmt.Sprintf("Cannot be divided by %d", e.Divider)
}
type DividesByN struct {
divider int
}
func (r DividesByN) Apply(_ context.Context, value any, _ any) (any, ve.ValidationError) {
v, isNil := Dereference(value)
if isNil {
return value, nil
}
if v.(int) % r.divider != 0 {
return value, &DividesByNValidationError{
BasicValidationError: ve.BasicValidationError{
Rule: ve.TypeCustom,
},
Divider: r.divider,
}
}
return value, nil
}
validator.ForValue(
123,
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.Custom(func(_ context.Context, value int, _ any) (newValue int, err error) {
switch value % 2 {
case 0:
return value, nil
case 1:
return value + 1, nil
}
}),
rule.Min(124), // passes because custom rule modified the value by adding 1
&DividesByN{divider: 3}, // fails
// ...
},
)
Both error.ValidationError
and error.ErrorsBag
support JSON marshalling giving your application a handy way of reporting errors that occured.
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/donatorsky/go-validator"
"github.com/donatorsky/go-validator/rule"
)
func main() {
errorsBag := validator.ForMap(map[string]any{...}, validator.RulesMap{...})
fmt.Println(errorsBag)
fmt.Println()
printJSON(errorsBag)
}
func printJSON(data any) {
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
_ = encoder.Encode(data)
}
Produces something similar to:
4 field(s) failed:
int: [1][must be at least 150]
child.id: [1][must be an int but is float64]
child.roles.*: [1][is required]
array.4: [2][must end with "oo"; does not exist in [Foo foo]}]
{
"int": [
{
"rule": "MIN.NUMBER"
"threshold": 150
}
],
"child.id": [
{
"rule": "INT"
"expected_type": "int"
"actual_type": "float64"
}
],
"child.roles.*": [
{
"rule": "REQUIRED"
}
],
"array.4": [
{
"rule": "ENDS_WITH"
"end_part": "oo"
},
{
"rule": "IN",
"values": [
"Foo",
"foo"
]
}
],
}