From b5317c5c5c7b6de9093e3b8d5879bce4592e4122 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Wed, 19 Aug 2015 08:47:31 -0400 Subject: [PATCH 1/2] Update examples_test.go Backport change to update import path from pull request #155 --- examples_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_test.go b/examples_test.go index 9d01450..f2dad92 100644 --- a/examples_test.go +++ b/examples_test.go @@ -3,7 +3,7 @@ package validator_test import ( "fmt" - "../validator" + "gopkg.in/bluesuncorp/validator.v6" ) func ExampleValidate_new() { From 14b90946b3a72c7933160bddc20f0c6a9f3b169f Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 19 Aug 2015 13:05:04 -0400 Subject: [PATCH 2/2] Backport v7 updates backporting v7 updates for the near released v7. --- README.md | 38 ++++---- baked_in.go | 37 -------- benchmarks_test.go | 120 +++++++++++++++++------ util.go | 82 ++++++++++++++++ validator.go | 205 +++++++++++++++++++++++++--------------- validator_test.go | 231 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 554 insertions(+), 159 deletions(-) create mode 100644 util.go diff --git a/README.md b/README.md index 8c40597..d977d20 100644 --- a/README.md +++ b/README.md @@ -192,24 +192,28 @@ NOTE: allocations for structs are up from v5, however ns/op for parallel operati It was a decicion not to cache struct info because although it reduced allocation to v5 levels, it hurt parallel performance too much. ```go -$ go test -cpu=4 -bench=. -benchmem=true + go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 5000000 318 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 316 ns/op 16 B/op 1 allocs/op -BenchmarkFieldCustomTypeSuccess-4 3000000 492 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 843 ns/op 416 B/op 6 allocs/op -BenchmarkFieldOrTagSuccess-4 500000 2384 ns/op 20 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1295 ns/op 384 B/op 6 allocs/op -BenchmarkStructSimpleSuccess-4 1000000 1175 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1822 ns/op 529 B/op 11 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1302 ns/op 56 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1847 ns/op 577 B/op 13 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 339 ns/op 24 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 733 ns/op 529 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 7104 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailure-4 100000 11996 ns/op 2861 B/op 72 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 2252 ns/op 368 B/op 30 allocs/op -BenchmarkStructComplexFailureParallel-4 300000 4691 ns/op 2862 B/op 72 allocs/op +BenchmarkFieldSuccess-4 5000000 337 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 331 ns/op 16 B/op 1 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 497 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 842 ns/op 416 B/op 6 allocs/op +BenchmarkFieldOrTagSuccess-4 500000 2432 ns/op 20 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1323 ns/op 384 B/op 6 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1409 ns/op 56 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1876 ns/op 577 B/op 13 allocs/op +BenchmarkStructPartialSuccess-4 1000000 1438 ns/op 384 B/op 13 allocs/op +BenchmarkStructPartialFailure-4 1000000 2040 ns/op 785 B/op 18 allocs/op +BenchmarkStructExceptSuccess-4 1000000 1000 ns/op 368 B/op 11 allocs/op +BenchmarkStructExceptFailure-4 1000000 1431 ns/op 384 B/op 13 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1375 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1893 ns/op 529 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 362 ns/op 24 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 883 ns/op 529 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 8237 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailure-4 100000 12617 ns/op 2861 B/op 72 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2398 ns/op 368 B/op 30 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 5733 ns/op 2862 B/op 72 allocs/op ``` How to Contribute diff --git a/baked_in.go b/baked_in.go index b323863..8bf4ff1 100644 --- a/baked_in.go +++ b/baked_in.go @@ -5,7 +5,6 @@ import ( "net" "net/url" "reflect" - "strconv" "strings" "time" "unicode/utf8" @@ -902,39 +901,3 @@ func hasMaxOf(topStruct reflect.Value, currentStruct reflect.Value, field reflec return isLte(topStruct, currentStruct, field, fieldType, fieldKind, param) } - -// asInt retuns the parameter as a int64 -// or panics if it can't convert -func asInt(param string) int64 { - - i, err := strconv.ParseInt(param, 0, 64) - panicIf(err) - - return i -} - -// asUint returns the parameter as a uint64 -// or panics if it can't convert -func asUint(param string) uint64 { - - i, err := strconv.ParseUint(param, 0, 64) - panicIf(err) - - return i -} - -// asFloat returns the parameter as a float64 -// or panics if it can't convert -func asFloat(param string) float64 { - - i, err := strconv.ParseFloat(param, 64) - panicIf(err) - - return i -} - -func panicIf(err error) { - if err != nil { - panic(err.Error()) - } -} diff --git a/benchmarks_test.go b/benchmarks_test.go index af201e6..35e702a 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -62,34 +62,6 @@ func BenchmarkFieldOrTagFailure(b *testing.B) { } } -func BenchmarkStructSimpleSuccess(b *testing.B) { - - type Foo struct { - StringValue string `validate:"min=5,max=10"` - IntValue int `validate:"min=5,max=10"` - } - - validFoo := &Foo{StringValue: "Foobar", IntValue: 7} - - for n := 0; n < b.N; n++ { - validate.Struct(validFoo) - } -} - -func BenchmarkStructSimpleFailure(b *testing.B) { - - type Foo struct { - StringValue string `validate:"min=5,max=10"` - IntValue int `validate:"min=5,max=10"` - } - - invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} - - for n := 0; n < b.N; n++ { - validate.Struct(invalidFoo) - } -} - func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { customTypes := map[reflect.Type]CustomTypeFunc{} @@ -136,6 +108,98 @@ func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { } } +func BenchmarkStructPartialSuccess(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "Name") + } +} + +func BenchmarkStructPartialFailure(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "NickName") + } +} + +func BenchmarkStructExceptSuccess(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "Nickname") + } +} + +func BenchmarkStructExceptFailure(b *testing.B) { + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + for n := 0; n < b.N; n++ { + validate.StructPartial(test, "Name") + } +} + +func BenchmarkStructSimpleSuccess(b *testing.B) { + + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + validFoo := &Foo{StringValue: "Foobar", IntValue: 7} + + for n := 0; n < b.N; n++ { + validate.Struct(validFoo) + } +} + +func BenchmarkStructSimpleFailure(b *testing.B) { + + type Foo struct { + StringValue string `validate:"min=5,max=10"` + IntValue int `validate:"min=5,max=10"` + } + + invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} + + for n := 0; n < b.N; n++ { + validate.Struct(invalidFoo) + } +} + func BenchmarkStructSimpleSuccessParallel(b *testing.B) { type Foo struct { diff --git a/util.go b/util.go new file mode 100644 index 0000000..1406fef --- /dev/null +++ b/util.go @@ -0,0 +1,82 @@ +package validator + +import ( + "reflect" + "strconv" +) + +const ( + namespaceSeparator = "." + leftBracket = "[" + rightBracket = "]" +) + +func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) { + + switch current.Kind() { + case reflect.Ptr: + + if current.IsNil() { + return current, reflect.Ptr + } + + return v.extractType(current.Elem()) + + case reflect.Interface: + + if current.IsNil() { + return current, reflect.Interface + } + + return v.extractType(current.Elem()) + + case reflect.Invalid: + return current, reflect.Invalid + + default: + + if v.config.hasCustomFuncs { + if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { + return v.extractType(reflect.ValueOf(fn(current))) + } + } + + return current, current.Kind() + } +} + +// asInt retuns the parameter as a int64 +// or panics if it can't convert +func asInt(param string) int64 { + + i, err := strconv.ParseInt(param, 0, 64) + panicIf(err) + + return i +} + +// asUint returns the parameter as a uint64 +// or panics if it can't convert +func asUint(param string) uint64 { + + i, err := strconv.ParseUint(param, 0, 64) + panicIf(err) + + return i +} + +// asFloat returns the parameter as a float64 +// or panics if it can't convert +func asFloat(param string) float64 { + + i, err := strconv.ParseFloat(param, 64) + panicIf(err) + + return i +} + +func panicIf(err error) { + if err != nil { + panic(err.Error()) + } +} diff --git a/validator.go b/validator.go index 709a1fc..b03b318 100644 --- a/validator.go +++ b/validator.go @@ -38,10 +38,11 @@ const ( ) var ( - timeType = reflect.TypeOf(time.Time{}) - timePtrType = reflect.TypeOf(&time.Time{}) - errsPool = &sync.Pool{New: newValidationErrors} - tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} + timeType = reflect.TypeOf(time.Time{}) + timePtrType = reflect.TypeOf(&time.Time{}) + errsPool = &sync.Pool{New: newValidationErrors} + tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} + emptyStructPtr = new(struct{}) ) // returns new ValidationErrors to the pool @@ -180,7 +181,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) - v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "") + v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "", false, false, nil) if len(errs) == 0 { errsPool.Put(errs) @@ -198,7 +199,92 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) - v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "") + v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "", false, false, nil) + + if len(errs) == 0 { + errsPool.Put(errs) + return nil + } + + return errs +} + +// StructPartial validates the fields passed in only, ignoring all others. +// Fields may be provided in a namespaced fashion relative to the struct provided +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// NOTE: This is normally not needed, however in some specific cases such as: tied to a +// legacy data structure, it will be useful +func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { + + sv, _ := v.extractType(reflect.ValueOf(current)) + name := sv.Type().Name() + m := map[string]*struct{}{} + + if fields != nil { + for _, k := range fields { + + flds := strings.Split(k, namespaceSeparator) + if len(flds) > 0 { + + key := name + namespaceSeparator + for _, s := range flds { + + idx := strings.Index(s, leftBracket) + + if idx != -1 { + for idx != -1 { + key += s[:idx] + m[key] = emptyStructPtr + + idx2 := strings.Index(s, rightBracket) + idx2++ + key += s[idx:idx2] + m[key] = emptyStructPtr + s = s[idx2:] + idx = strings.Index(s, leftBracket) + } + } else { + + key += s + m[key] = emptyStructPtr + } + + key += namespaceSeparator + } + } + } + } + + errs := errsPool.Get().(ValidationErrors) + + v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, false, m) + + if len(errs) == 0 { + errsPool.Put(errs) + return nil + } + + return errs +} + +// StructExcept validates all fields except the ones passed in. +// Fields may be provided in a namespaced fashion relative to the struct provided +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// NOTE: This is normally not needed, however in some specific cases such as: tied to a +// legacy data structure, it will be useful +func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { + + sv, _ := v.extractType(reflect.ValueOf(current)) + name := sv.Type().Name() + m := map[string]*struct{}{} + + for _, key := range fields { + m[name+"."+key] = emptyStructPtr + } + + errs := errsPool.Get().(ValidationErrors) + + v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, true, m) if len(errs) == 0 { errsPool.Put(errs) @@ -214,7 +300,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors { errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) - v.tranverseStruct(sv, sv, sv, "", errs, true) + v.tranverseStruct(sv, sv, sv, "", errs, true, false, false, nil) if len(errs) == 0 { errsPool.Put(errs) @@ -225,7 +311,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors { } // tranverseStruct traverses a structs fields and then passes them to be validated by traverseField -func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool) { +func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}) { if current.Kind() == reflect.Ptr && !current.IsNil() { current = current.Elem() @@ -235,6 +321,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec panic("value passed for validation is not a struct") } + var ok bool typ := current.Type() if useStructName { @@ -252,29 +339,31 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec continue } - v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name) + if partial { + + _, ok = includeExclude[errPrefix+fld.Name] + + if (ok && exclude) || (!ok && !exclude) { + continue + } + } + + v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name, partial, exclude, includeExclude) } } // traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options -func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string) { +func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) { if tag == skipValidationTag { return } - kind := current.Kind() - - if kind == reflect.Ptr && !current.IsNil() { - current = current.Elem() - kind = current.Kind() - } - - // this also allows for tags 'required' and 'omitempty' to be used on - // nested struct fields because when len(tags) > 0 below and the value is nil - // then required failes and we check for omitempty just before that - if ((kind == reflect.Ptr || kind == reflect.Interface) && current.IsNil()) || kind == reflect.Invalid { + current, kind := v.extractType(current) + var typ reflect.Type + switch kind { + case reflect.Ptr, reflect.Interface, reflect.Invalid: if strings.Contains(tag, omitempty) { return } @@ -314,67 +403,29 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if kind == reflect.Invalid { return } - } - - typ := current.Type() - - switch kind { - case reflect.Struct, reflect.Interface: - - if kind == reflect.Interface { - - current = current.Elem() - kind = current.Kind() - - if kind == reflect.Ptr && !current.IsNil() { - current = current.Elem() - kind = current.Kind() - } - - // changed current, so have to get inner type again - typ = current.Type() - - if kind != reflect.Struct { - goto FALLTHROUGH - } - } - if typ != timeType && typ != timePtrType { + case reflect.Struct: + typ = current.Type() - if kind == reflect.Struct { + if typ != timeType { - if v.config.hasCustomFuncs { - if fn, ok := v.config.CustomTypeFuncs[typ]; ok { - v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) - return - } - } - - // required passed validation above so stop here - // if only validating the structs existance. - if strings.Contains(tag, structOnlyTag) { - return - } - - v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false) + // required passed validation above so stop here + // if only validating the structs existance. + if strings.Contains(tag, structOnlyTag) { return } - } - FALLTHROUGH: - fallthrough - default: - if len(tag) == 0 { + + v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false, partial, exclude, includeExclude) return } } - if v.config.hasCustomFuncs { - if fn, ok := v.config.CustomTypeFuncs[typ]; ok { - v.traverseField(topStruct, currentStruct, reflect.ValueOf(fn(current)), errPrefix, errs, isStructField, tag, name) - return - } + if len(tag) == 0 { + return } + typ = current.Type() + tags, isCached := tagsCache.Get(tag) if !isCached { @@ -447,9 +498,9 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. // or panic ;) switch kind { case reflect.Slice, reflect.Array: - v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) + v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude) case reflect.Map: - v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name) + v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude) default: // throw error, if not a slice or map then should not have gotten here // bad dive tag @@ -459,18 +510,18 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. } // traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation -func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) { +func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) { for i := 0; i < current.Len(); i++ { - v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i)) + v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), partial, exclude, includeExclude) } } // traverseMap traverses a map's elements and passes them to traverseField for validation -func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) { +func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) { for _, key := range current.MapKeys() { - v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface())) + v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), partial, exclude, includeExclude) } } diff --git a/validator_test.go b/validator_test.go index c17167a..42085f0 100644 --- a/validator_test.go +++ b/validator_test.go @@ -192,6 +192,237 @@ func ValidateValuerType(field reflect.Value) interface{} { return nil } +type TestPartial struct { + NoTag string + BlankTag string `validate:""` + Required string `validate:"required"` + SubSlice []*SubTest `validate:"required,dive"` + Sub *SubTest + SubIgnore *SubTest `validate:"-"` + Anonymous struct { + A string `validate:"required"` + ASubSlice []*SubTest `validate:"required,dive"` + + SubAnonStruct []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + } `validate:"required,dive"` + } +} + +func TestStructPartial(t *testing.T) { + + p1 := []string{ + "NoTag", + "Required", + } + + p2 := []string{ + "SubSlice[0].Test", + "Sub", + "SubIgnore", + "Anonymous.A", + } + + p3 := []string{ + "SubTest.Test", + } + + p4 := []string{ + "A", + } + + tPartial := &TestPartial{ + NoTag: "NoTag", + Required: "Required", + + SubSlice: []*SubTest{ + { + + Test: "Required", + }, + { + + Test: "Required", + }, + }, + + Sub: &SubTest{ + Test: "1", + }, + SubIgnore: &SubTest{ + Test: "", + }, + Anonymous: struct { + A string `validate:"required"` + ASubSlice []*SubTest `validate:"required,dive"` + SubAnonStruct []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + } `validate:"required,dive"` + }{ + A: "1", + ASubSlice: []*SubTest{ + { + Test: "Required", + }, + { + Test: "Required", + }, + }, + + SubAnonStruct: []struct { + Test string `validate:"required"` + OtherTest string `validate:"required"` + }{ + {"Required", "RequiredOther"}, + {"Required", "RequiredOther"}, + }, + }, + } + + // the following should all return no errors as everything is valid in + // the default state + errs := validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // this isnt really a robust test, but is ment to illustrate the ANON CASE below + errs = validate.StructPartial(tPartial.SubSlice[0], p3...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // mod tParial for required feild and re-test making sure invalid fields are NOT required: + tPartial.Required = "" + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // inversion and retesting Partial to generate failures: + errs = validate.StructPartial(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Required", "Required", "required") + + errs = validate.StructExcept(tPartial, p2...) + AssertError(t, errs, "TestPartial.Required", "Required", "required") + + // reset Required field, and set nested struct + tPartial.Required = "Required" + tPartial.Anonymous.A = "" + + // will pass as unset feilds is not going to be tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // ANON CASE the response here is strange, it clearly does what it is being told to + errs = validate.StructExcept(tPartial.Anonymous, p4...) + Equal(t, errs, nil) + + // will fail as unset feild is tested + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required") + + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required") + + // reset nested struct and unset struct in slice + tPartial.Anonymous.A = "Required" + tPartial.SubSlice[0].Test = "" + + // these will pass as unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p2...) + Equal(t, errs, nil) + + // these will fail as unset item IS tested + errs = validate.StructExcept(tPartial, p1...) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + Equal(t, len(errs), 1) + + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + Equal(t, len(errs), 1) + + // Unset second slice member concurrently to test dive behavior: + tPartial.SubSlice[1].Test = "" + + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + // NOTE: When specifying nested items, it is still the users responsibility + // to specify the dive tag, the library does not override this. + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p1...) + Equal(t, len(errs), 2) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + errs = validate.StructPartial(tPartial, p2...) + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") + + // reset struct in slice, and unset struct in slice in unset posistion + tPartial.SubSlice[0].Test = "Required" + + // these will pass as the unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + // testing for missing item by exception, yes it dives and fails + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + Equal(t, len(errs), 1) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") + + tPartial.SubSlice[1].Test = "Required" + + tPartial.Anonymous.SubAnonStruct[0].Test = "" + // these will pass as the unset item is NOT tested + errs = validate.StructPartial(tPartial, p1...) + Equal(t, errs, nil) + + errs = validate.StructPartial(tPartial, p2...) + Equal(t, errs, nil) + + errs = validate.StructExcept(tPartial, p1...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") + + errs = validate.StructExcept(tPartial, p2...) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required") + +} + func TestExistsValidation(t *testing.T) { jsonText := "{ \"truthiness2\": true }"