mirror of
https://github.com/romdo/go-validate.git
synced 2026-02-19 08:06:40 +00:00
When validating non-struct types, we should not try and convert field values in errors, because there is no struct field to lookup. Hence this fix stops a panic from happening. It is up to the Validate() method itself to provide the correct and final field value when validating non-struct types.
957 lines
23 KiB
Go
957 lines
23 KiB
Go
package validate_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/romdo/go-validate"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
//
|
|
// Test helper types
|
|
//
|
|
|
|
type validatableString string
|
|
|
|
func (s validatableString) Validate() error {
|
|
if strings.Contains(string(s), " ") {
|
|
return &validate.Error{Msg: "must not contain space"}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type validatableStruct struct {
|
|
Foo string
|
|
Bar string `json:"bar"`
|
|
Foz string `yaml:"foz"`
|
|
Baz string `form:"baz"`
|
|
|
|
f func() error
|
|
}
|
|
|
|
func (s *validatableStruct) Validate() error {
|
|
if s.f == nil {
|
|
return nil
|
|
}
|
|
|
|
return s.f()
|
|
}
|
|
|
|
type validatableSlice []validatableStruct
|
|
|
|
func (s validatableSlice) Validate() error {
|
|
var errs error
|
|
|
|
if len(s) < 1 {
|
|
errs = validate.AppendError(errs, "must contain at least 1 item")
|
|
}
|
|
|
|
fooVals := map[string]bool{}
|
|
for i, item := range s {
|
|
if fooVals[item.Foo] {
|
|
errs = validate.Append(errs, &validate.Error{
|
|
Field: fmt.Sprintf("%d.Foo", i),
|
|
Msg: "is not unique",
|
|
})
|
|
}
|
|
fooVals[item.Foo] = true
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
type nestedStruct struct {
|
|
OtherField *validatableStruct
|
|
OtherFieldJSON *validatableStruct `json:"other_field,omitempty"`
|
|
OtherFieldYAML *validatableStruct `yaml:"otherField,omitempty"`
|
|
OtherFieldFORM *validatableStruct `form:"other-field,omitempty"`
|
|
|
|
skippedField *validatableStruct
|
|
SkippedFieldJSON *validatableStruct `json:"-,omitempty"`
|
|
SkippedFieldYAML *validatableStruct `yaml:"-,omitempty"`
|
|
SkippedFieldFORM *validatableStruct `form:"-,omitempty"`
|
|
|
|
OtherArray [5]*validatableStruct
|
|
OtherArrayJSON [5]*validatableStruct `json:"other_array,omitempty"`
|
|
OtherArrayYAML [5]*validatableStruct `yaml:"otherArray,omitempty"`
|
|
OtherArrayFORM [5]*validatableStruct `form:"other-array,omitempty"`
|
|
|
|
OtherSlice []*validatableStruct
|
|
OtherSliceJSON []*validatableStruct `json:"other_slice,omitempty"`
|
|
OtherSliceYAML []*validatableStruct `yaml:"otherSlice,omitempty"`
|
|
OtherSliceFORM []*validatableStruct `form:"other-slice,omitempty"`
|
|
|
|
OtherStringMap map[string]*validatableStruct
|
|
OtherStringMapJSON map[string]*validatableStruct `json:"other_string_map,omitempty"`
|
|
OtherStringMapYAML map[string]*validatableStruct `yaml:"otherStringMap,omitempty"`
|
|
OtherStringMapFORM map[string]*validatableStruct `form:"other-string-map,omitempty"`
|
|
|
|
OtherIntMap map[int]*validatableStruct
|
|
OtherIntMapJSON map[int]*validatableStruct `json:"other_int_map,omitempty"`
|
|
OtherIntMapYAML map[int]*validatableStruct `yaml:"otherIntMap,omitempty"`
|
|
OtherIntMapFORM map[int]*validatableStruct `form:"other-int-map,omitempty"`
|
|
|
|
OtherStruct *nestedStruct
|
|
OtherStructJSON *nestedStruct `json:"other_struct,omitempty"`
|
|
OtherStructYAML *nestedStruct `yaml:"otherStruct,omitempty"`
|
|
OtherStructFORM *nestedStruct `form:"other-struct,omitempty"`
|
|
}
|
|
|
|
//
|
|
// Tests
|
|
//
|
|
|
|
func TestValidate(t *testing.T) {
|
|
mixedValidationErrors := &validatableStruct{
|
|
f: func() error {
|
|
var errs error
|
|
errs = validate.Append(errs, &validate.Error{
|
|
Field: "Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
})
|
|
errs = validate.Append(errs, errors.New("bar: is missing"))
|
|
|
|
return errs
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
obj interface{}
|
|
wantErrs []error
|
|
}{
|
|
{
|
|
name: "nil",
|
|
obj: nil,
|
|
wantErrs: []error{},
|
|
},
|
|
{
|
|
name: "no error",
|
|
obj: &validatableStruct{},
|
|
wantErrs: nil,
|
|
},
|
|
{
|
|
name: "valid validatable string type",
|
|
obj: validatableString("hello-world"),
|
|
wantErrs: []error{},
|
|
},
|
|
{
|
|
name: "invalid validatable string type",
|
|
obj: validatableString("hello world"),
|
|
wantErrs: []error{
|
|
&validate.Error{Msg: "must not contain space"},
|
|
},
|
|
},
|
|
{
|
|
name: "valid validatable slice type",
|
|
obj: validatableSlice{
|
|
{Foo: "Hello"},
|
|
{Foo: "World"},
|
|
},
|
|
wantErrs: []error{},
|
|
},
|
|
{
|
|
name: "invalid validatable slice type",
|
|
obj: validatableSlice{},
|
|
wantErrs: []error{
|
|
&validate.Error{Msg: "must contain at least 1 item"},
|
|
},
|
|
},
|
|
{
|
|
name: "invalid validatable slice type with custom field error",
|
|
obj: validatableSlice{
|
|
{Foo: "Hello"},
|
|
{Foo: "Hello"},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{Field: "1.Foo", Msg: "is not unique"},
|
|
},
|
|
},
|
|
{
|
|
name: "single Go error",
|
|
obj: &validatableStruct{f: func() error {
|
|
return errors.New("foo: is required")
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{Err: errors.New("foo: is required")},
|
|
},
|
|
},
|
|
{
|
|
name: "single *validate.Error",
|
|
obj: &validatableStruct{f: func() error {
|
|
return &validate.Error{
|
|
Field: "foo", Msg: "is required", Err: errors.New("oops"),
|
|
}
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "foo", Msg: "is required", Err: errors.New("oops"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple Go errors",
|
|
obj: &validatableStruct{f: func() error {
|
|
var errs error
|
|
errs = validate.Append(errs, errors.New("foo: is required"))
|
|
errs = validate.Append(errs, errors.New("bar: is missing"))
|
|
|
|
return errs
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{Err: errors.New("foo: is required")},
|
|
&validate.Error{Err: errors.New("bar: is missing")},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple *validate.Error",
|
|
obj: &validatableStruct{f: func() error {
|
|
var errs error
|
|
errs = validate.Append(errs, &validate.Error{
|
|
Field: "foo", Msg: "is required", Err: errors.New("oops"),
|
|
})
|
|
errs = validate.Append(errs, &validate.Error{
|
|
Field: "bar", Msg: "is required", Err: errors.New("whoops"),
|
|
})
|
|
|
|
return errs
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "foo", Msg: "is required", Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "bar", Msg: "is required", Err: errors.New("whoops"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "mix of Go error and *validate.Error",
|
|
obj: mixedValidationErrors,
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "Foo", Msg: "is required", Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{Err: errors.New("bar: is missing")},
|
|
},
|
|
},
|
|
//
|
|
// Field name conversion
|
|
//
|
|
{
|
|
name: "no json, yaml or form field tag",
|
|
obj: &validatableStruct{f: func() error {
|
|
return &validate.Error{Field: "Foo", Msg: "is required"}
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{Field: "Foo", Msg: "is required"},
|
|
},
|
|
},
|
|
{
|
|
name: "converts field name via json field tag",
|
|
obj: &validatableStruct{f: func() error {
|
|
return &validate.Error{Field: "Bar", Msg: "is required"}
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{Field: "bar", Msg: "is required"},
|
|
},
|
|
},
|
|
{
|
|
name: "converts field name via yaml field tag",
|
|
obj: &validatableStruct{f: func() error {
|
|
return &validate.Error{Field: "Foz", Msg: "is required"}
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{Field: "foz", Msg: "is required"},
|
|
},
|
|
},
|
|
{
|
|
name: "converts field name via form field tag",
|
|
obj: &validatableStruct{f: func() error {
|
|
return &validate.Error{Field: "Baz", Msg: "is required"}
|
|
}},
|
|
wantErrs: []error{
|
|
&validate.Error{Field: "baz", Msg: "is required"},
|
|
},
|
|
},
|
|
{
|
|
name: "nested with no validation errors",
|
|
obj: &nestedStruct{
|
|
OtherField: &validatableStruct{},
|
|
OtherArray: [5]*validatableStruct{{}, {}, {}, {}},
|
|
OtherSlice: []*validatableStruct{{}},
|
|
OtherStringMap: map[string]*validatableStruct{
|
|
"hi": {},
|
|
"bye": {},
|
|
},
|
|
OtherIntMap: map[int]*validatableStruct{42: {}, 64: {}},
|
|
OtherStruct: &nestedStruct{
|
|
OtherField: &validatableStruct{},
|
|
},
|
|
},
|
|
wantErrs: []error{},
|
|
},
|
|
//
|
|
// Nested in a struct field.
|
|
//
|
|
{
|
|
name: "nested in a struct field",
|
|
obj: &nestedStruct{
|
|
OtherField: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "OtherField.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherField", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a struct field with json tag",
|
|
obj: &nestedStruct{
|
|
OtherFieldJSON: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other_field.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_field", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a struct field with yaml tag",
|
|
obj: &nestedStruct{
|
|
OtherFieldYAML: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "otherField.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherField", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a struct field with form tag",
|
|
obj: &nestedStruct{
|
|
OtherFieldFORM: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other-field.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-field", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
//
|
|
// Nested in a unexposed/ignored fields.
|
|
//
|
|
{
|
|
name: "nested in a unexposed field",
|
|
obj: &nestedStruct{
|
|
skippedField: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{},
|
|
},
|
|
{
|
|
name: "nested in a struct field skipped by json tag",
|
|
obj: &nestedStruct{
|
|
SkippedFieldJSON: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{},
|
|
},
|
|
{
|
|
name: "nested in a struct field skipped by yaml tag",
|
|
obj: &nestedStruct{
|
|
SkippedFieldYAML: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{},
|
|
},
|
|
{
|
|
name: "nested in a struct field skipped by yaml tag",
|
|
obj: &nestedStruct{
|
|
SkippedFieldFORM: mixedValidationErrors,
|
|
},
|
|
wantErrs: []error{},
|
|
},
|
|
//
|
|
// Nested in an array.
|
|
//
|
|
{
|
|
name: "nested in an array",
|
|
obj: &nestedStruct{
|
|
OtherArray: [5]*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "OtherArray.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherArray.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherArray.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherArray.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in an array with json tag",
|
|
obj: &nestedStruct{
|
|
OtherArrayJSON: [5]*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other_array.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_array.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_array.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_array.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in an array with yaml tag",
|
|
obj: &nestedStruct{
|
|
OtherArrayYAML: [5]*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "otherArray.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherArray.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherArray.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherArray.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in an array with form tag",
|
|
obj: &nestedStruct{
|
|
OtherArrayFORM: [5]*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other-array.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-array.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-array.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-array.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
//
|
|
// Nested in a slice.
|
|
//
|
|
{
|
|
name: "nested in a slice",
|
|
obj: &nestedStruct{
|
|
OtherSlice: []*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "OtherSlice.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherSlice.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherSlice.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherSlice.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a slice with json tag",
|
|
obj: &nestedStruct{
|
|
OtherSliceJSON: []*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other_slice.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_slice.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_slice.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_slice.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a slice with yaml tag",
|
|
obj: &nestedStruct{
|
|
OtherSliceYAML: []*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "otherSlice.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherSlice.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherSlice.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherSlice.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a slice with form tag",
|
|
obj: &nestedStruct{
|
|
OtherSliceFORM: []*validatableStruct{
|
|
mixedValidationErrors,
|
|
mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other-slice.0.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-slice.0", Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-slice.1.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-slice.1", Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
//
|
|
// Nested in an string map.
|
|
//
|
|
{
|
|
name: "nested in a string map",
|
|
obj: &nestedStruct{
|
|
OtherStringMap: map[string]*validatableStruct{
|
|
"hello": mixedValidationErrors,
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "OtherStringMap.hello.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherStringMap.hello",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherStringMap.world.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherStringMap.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a string map with json tag",
|
|
obj: &nestedStruct{
|
|
OtherStringMapJSON: map[string]*validatableStruct{
|
|
"hello": mixedValidationErrors,
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other_string_map.hello.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_string_map.hello",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_string_map.world.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_string_map.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a string map with yaml tag",
|
|
obj: &nestedStruct{
|
|
OtherStringMapYAML: map[string]*validatableStruct{
|
|
"hello": mixedValidationErrors,
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "otherStringMap.hello.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherStringMap.hello",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherStringMap.world.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherStringMap.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a string map with form tag",
|
|
obj: &nestedStruct{
|
|
OtherStringMapFORM: map[string]*validatableStruct{
|
|
"hello": mixedValidationErrors,
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other-string-map.hello.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-string-map.hello",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-string-map.world.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-string-map.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
//
|
|
// Nested in an int map.
|
|
//
|
|
{
|
|
name: "nested in a int map",
|
|
obj: &nestedStruct{
|
|
OtherIntMap: map[int]*validatableStruct{
|
|
42: mixedValidationErrors,
|
|
64: mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "OtherIntMap.42.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherIntMap.42",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherIntMap.64.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherIntMap.64",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a int map with json tag",
|
|
obj: &nestedStruct{
|
|
OtherIntMapJSON: map[int]*validatableStruct{
|
|
42: mixedValidationErrors,
|
|
64: mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other_int_map.42.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_int_map.42",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_int_map.64.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_int_map.64",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a int map with yaml tag",
|
|
obj: &nestedStruct{
|
|
OtherIntMapYAML: map[int]*validatableStruct{
|
|
42: mixedValidationErrors,
|
|
64: mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "otherIntMap.42.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherIntMap.42",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherIntMap.64.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherIntMap.64",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a int map with form tag",
|
|
obj: &nestedStruct{
|
|
OtherIntMapFORM: map[int]*validatableStruct{
|
|
42: mixedValidationErrors,
|
|
64: mixedValidationErrors,
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other-int-map.42.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-int-map.42",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-int-map.64.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-int-map.64",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
//
|
|
// Nested in another struct.
|
|
//
|
|
{
|
|
name: "nested in another struct",
|
|
obj: &nestedStruct{
|
|
OtherStruct: &nestedStruct{
|
|
OtherField: mixedValidationErrors,
|
|
OtherStringMap: map[string]*validatableStruct{
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "OtherStruct.OtherField.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherStruct.OtherField",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherStruct.OtherStringMap.world.Foo",
|
|
Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "OtherStruct.OtherStringMap.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a int map with json tag",
|
|
obj: &nestedStruct{
|
|
OtherStructJSON: &nestedStruct{
|
|
OtherFieldJSON: mixedValidationErrors,
|
|
OtherStringMapJSON: map[string]*validatableStruct{
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other_struct.other_field.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_struct.other_field",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_struct.other_string_map.world.Foo",
|
|
Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other_struct.other_string_map.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a int map with yaml tag",
|
|
obj: &nestedStruct{
|
|
OtherStructYAML: &nestedStruct{
|
|
OtherFieldYAML: mixedValidationErrors,
|
|
OtherStringMapYAML: map[string]*validatableStruct{
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "otherStruct.otherField.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherStruct.otherField",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherStruct.otherStringMap.world.Foo",
|
|
Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "otherStruct.otherStringMap.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "nested in a int map with form tag",
|
|
obj: &nestedStruct{
|
|
OtherStructFORM: &nestedStruct{
|
|
OtherFieldFORM: mixedValidationErrors,
|
|
OtherStringMapFORM: map[string]*validatableStruct{
|
|
"world": mixedValidationErrors,
|
|
},
|
|
},
|
|
},
|
|
wantErrs: []error{
|
|
&validate.Error{
|
|
Field: "other-struct.other-field.Foo", Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-struct.other-field",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-struct.other-string-map.world.Foo",
|
|
Msg: "is required",
|
|
Err: errors.New("oops"),
|
|
},
|
|
&validate.Error{
|
|
Field: "other-struct.other-string-map.world",
|
|
Err: errors.New("bar: is missing"),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validate.Validate(tt.obj)
|
|
|
|
if len(tt.wantErrs) == 0 {
|
|
assert.Nil(t, err, "validation error should be nil")
|
|
}
|
|
|
|
got := validate.Errors(err)
|
|
assert.ElementsMatch(t, tt.wantErrs, got)
|
|
})
|
|
}
|
|
}
|