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.
go get github.com/donatorsky/go-validator
The library is still a work in progress. More details soon.
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()
.
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.
Sets a DataCollector
instance to be used while validating data which will collect all successfully validated data.
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(),
},
// ...
},
)
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.
Sets a DataCollector
instance to be used while validating data which will collect all successfully validated data.
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(),
},
// ...
},
)
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.
Sets a DataCollector
instance to be used while validating data which will collect all successfully validated data.
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
.
Sets a pointer to a variable to which data will be exported after successful validation.
validator.ForValue(
123,
validator.RulesMap{
rule.Required(),
rule.Integer[int](),
rule.Min(100),
// ...
},
)
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 value2
. - 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 value2
. - 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 value2
.
You can also validate every single value of slice and array by using *
wildcard symbol.
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.
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.
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),
// ...
},
)
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.
This pseudo-rule allows for adding rules based on simple boolean value.
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(),
// ...
// ...
},
)
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.
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(),
// ...
// ...
},
)
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
Checks whether a value is after after
date.
Applies to:
nil
: passes.afterComparable
: passes only when a value is afterafter
date.any
: fails.
Modifies output:
No.
Bails:
No.
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 toafterOrEqual
date.any
: fails.
Modifies output:
No.
Bails:
No.
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.
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.
Checks whether a value is before before
date.
Applies to:
nil
: passes.any
: passes only when a value implementsbeforeComparable
interface and is beforebefore
date.
Modifies output:
No.
Bails:
No.
Checks whether a value is before or equal to beforeOrEqual
date.
Applies to:
nil
: passes.any
: passes only when a value implementsbeforeOrEqualComparable
interface and is before or equal tobeforeOrEqual
date.
Modifies output:
No.
Bails:
No.
Checks whether a value is between min
and max
, inclusive.
Applies to:
nil
: passes.numberType
: checks if a value is betweenmin
andmax
.string
: checks if string's length is betweenmin
andmax
.slice
,array
: checks if slice/array has betweenmin
andmax
elements.map
: checks if map has at leastmin
andmax
keys.
Modifies output:
No.
Bails:
No.
Checks whether a value is between min
and max
, exclusive.
Applies to:
nil
: passes.numberType
: checks if a value is betweenmin
andmax
.string
: checks if string's length is betweenmin
andmax
.slice
,array
: checks if slice/array has betweenmin
andmax
elements.map
: checks if map has at leastmin
andmax
keys.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of bool
type.
Applies to:
nil
: any value.bool
: any value.integerType
: when a value equals to0
or1
.floatType
: when a value equals to0.0
or1.0
.string
: when string is convertible tobool
according to thestrconv.ParseBool
function.any
: fails.
Modifies output:
nil
: returns*bool
nil pinter.bool
: input value.integerType
:false
when0
,true
when1
.floatType
:false
when0.0
,true
when1.0
.string
: according to thestrconv.ParseBool
function.
Bails:
Yes.
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 totime.Duration
according to thetime.Parse
function andtime.RFC3339Nano
format.any
: fails.
Modifies output:
nil
: returns*time.Time
nil pointer.time.Time
: input value.string
: according to thetime.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 totime.Duration
according to thetime.Parse
function andformat
format.any
: fails.
Modifies output:
nil
: returns*time.Time
nil pinter.time.Time
: input value.string
: according to thetime.Parse
function.
Bails:
No.
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.
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.
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 totime.Duration
according to thetime.ParseDuration
function.any
: fails.
Modifies output:
nil
: returns*time.Duration
nil pinter.time.Duration
: returns unchanged value.string
: according to thetime.ParseDuration
function.
Bails:
No.
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 bynet/mail.ParseAddress
function.any
: fails.
Modifies output:
No.
Bails:
No.
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 bynet/mail.ParseAddress
function.any
: fails.
Modifies output:
nil
: input value.string
: unlike theEmail()
rule, it returns the email address of the string. E.g. given a valueFoo Bar <foo@bar.baz> (some comment)
, the output will befoo@bar.baz
.
Bails:
No.
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.
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.
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.
Checks whether a value exists in values
.
Options:
InRuleWithComparator(comparator Comparator)
: sets custom elements comparator.comparator
receives an input value and each element ofvalues
, 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 invalues
.comparable
: passes only when a value exists invalues
, optionally using customcomparator
.any
: fails.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of Out
type or its pointer.
Applies to:
nil
: passes.integerType
: passes only when a value is ofOut
type or its pointer.any
: fails.
Modifies output:
No.
Bails:
Yes.
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 thenet.ParseIP
function.any
: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is exactly length
.
Applies to:
nil
: passes.string
: checks if string's length is exactlylength
characters.slice
,array
: checks if slice/array has exactlylength
elements.map
: checks if map has exactlylength
keys.any
: fails.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of map type.
Applies to:
nil
: passes.map
: passes.any
: fails.
Modifies output:
No.
Bails:
Yes.
Checks whether a value is at most max
.
Applies to:
nil
: passes.numberType
: checks if a value is at mostmax
.string
: checks if string's length is at mostmax
characters.slice
,array
: checks if slice/array has at mostmax
elements.map
: checks if map has at mostmax
keys.any
: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is less than max
.
Applies to:
nil
: passes.numberType
: checks if a value is less thanmax
.string
: checks if string's length is less thanmax
characters.slice
,array
: checks if slice/array has less thanmax
elements.map
: checks if map has less thanmax
keys.any
: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is at least min
.
Applies to:
nil
: passes.numberType
: checks if a value is at leastmin
.string
: checks if string's length is at leastmin
characters.slice
,array
: checks if slice/array has at leastmin
elements.map
: checks if map has at leastmin
keys.any
: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is greater than min
.
Applies to:
nil
: passes.numberType
: checks if a value is greater thanmin
.string
: checks if string's length is more thanmin
characters.slice
,array
: checks if slice/array has more thanmin
elements.map
: checks if map has more thanmin
keys.any
: fails.
Modifies output:
No.
Bails:
No.
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 ofvalues
, 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 invalues
.comparable
: passes only when a value does not exist invalues
, optionally using customcomparator
.any
: passes.
Modifies output:
No.
Bails:
No.
Checks whether a value does not match regex
expression.
Applies to:
nil
: passes.string
: checks if string does not matchregex
expression.any
: fails.
Modifies output:
No.
Bails:
No.
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 usingstrconv.ParseInt
,strconv.ParseUint
,strconv.ParseFloat
andstrconv.ParseComplex
, in that order.any
: fails.
Modifies output:
nil
: unchanged value.numberType
,complex64
,complex128
: unchanged value.string
: value converted to number.
Bails:
No.
Checks whether a value matches regex
expression.
Applies to:
nil
: passes.string
: checks if string matchesregex
expression.any
: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is not nil
.
Applies to:
any
: passes only when a value notnil
.
Modifies output:
No.
Bails:
Yes.
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.
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
: returnsnil
slice of[]Out
type.any
: input value.
Bails:
Yes.
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.
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.
Checks and ensures that a value is of struct type.
Applies to:
nil
: passes.struct
: passes.any
: fails.
Modifies output:
No.
Bails:
Yes.
Checks whether a value is a valid URL string.
Applies to:
nil
: passes.string
: checks if string is a valid URL according tonet/url.ParseRequestURI
function.any
: fails.
Modifies output:
No.
Bails:
No.
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.
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.
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"
]
}
],
}