diff --git a/README.md b/README.md index b2af420..3841f1b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Package validator ================ [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-9.1.3-green.svg) +![Project status](https://img.shields.io/badge/version-9.2.0-green.svg) [![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) @@ -68,54 +68,58 @@ Benchmarks ------ ###### Run on MacBook Pro (Retina, 15-inch, Late 2013) 2.6 GHz Intel Core i7 16 GB 1600 MHz DDR3 using Go version go1.7 darwin/amd64 ```go -BenchmarkFieldSuccess-8 20000000 105 ns/op 0 B/op 0 allocs/op -BenchmarkFieldSuccessParallel-8 50000000 35.1 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-8 5000000 337 ns/op 208 B/op 4 allocs/op -BenchmarkFieldFailureParallel-8 20000000 120 ns/op 208 B/op 4 allocs/op -BenchmarkFieldDiveSuccess-8 2000000 716 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveSuccessParallel-8 10000000 253 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveFailure-8 1000000 1060 ns/op 412 B/op 16 allocs/op -BenchmarkFieldDiveFailureParallel-8 5000000 360 ns/op 413 B/op 16 allocs/op -BenchmarkFieldCustomTypeSuccess-8 5000000 299 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeSuccessParallel-8 20000000 86.0 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-8 5000000 341 ns/op 208 B/op 4 allocs/op -BenchmarkFieldCustomTypeFailureParallel-8 20000000 140 ns/op 208 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-8 2000000 893 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagSuccessParallel-8 5000000 431 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-8 2000000 563 ns/op 224 B/op 5 allocs/op -BenchmarkFieldOrTagFailureParallel-8 5000000 417 ns/op 224 B/op 5 allocs/op -BenchmarkStructLevelValidationSuccess-8 5000000 339 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationSuccessParallel-8 20000000 114 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationFailure-8 2000000 630 ns/op 304 B/op 8 allocs/op -BenchmarkStructLevelValidationFailureParallel-8 5000000 291 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-8 3000000 540 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 176 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeFailure-8 2000000 821 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 336 ns/op 440 B/op 10 allocs/op -BenchmarkStructPartialSuccess-8 2000000 686 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialSuccessParallel-8 5000000 282 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialFailure-8 2000000 931 ns/op 480 B/op 11 allocs/op -BenchmarkStructPartialFailureParallel-8 5000000 394 ns/op 480 B/op 11 allocs/op -BenchmarkStructExceptSuccess-8 1000000 1017 ns/op 496 B/op 12 allocs/op -BenchmarkStructExceptSuccessParallel-8 10000000 233 ns/op 240 B/op 5 allocs/op -BenchmarkStructExceptFailure-8 2000000 864 ns/op 464 B/op 10 allocs/op -BenchmarkStructExceptFailureParallel-8 5000000 393 ns/op 464 B/op 10 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-8 3000000 552 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 202 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldFailure-8 2000000 798 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 356 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 825 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 300 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-8 2000000 1103 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 433 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleSuccess-8 5000000 360 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleSuccessParallel-8 20000000 110 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleFailure-8 2000000 783 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleFailureParallel-8 5000000 358 ns/op 424 B/op 9 allocs/op -BenchmarkStructComplexSuccess-8 1000000 2120 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexSuccessParallel-8 2000000 659 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexFailure-8 300000 5126 ns/op 3041 B/op 53 allocs/op -BenchmarkStructComplexFailureParallel-8 1000000 2261 ns/op 3041 B/op 53 allocs/op +BenchmarkFieldSuccess-8 20000000 104 ns/op 0 B/op 0 allocs/op +BenchmarkFieldSuccessParallel-8 50000000 34.5 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-8 5000000 335 ns/op 208 B/op 4 allocs/op +BenchmarkFieldFailureParallel-8 20000000 118 ns/op 208 B/op 4 allocs/op +BenchmarkFieldDiveSuccess-8 2000000 718 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveSuccessParallel-8 10000000 234 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveFailure-8 2000000 971 ns/op 412 B/op 16 allocs/op +BenchmarkFieldDiveFailureParallel-8 5000000 341 ns/op 413 B/op 16 allocs/op +BenchmarkFieldCustomTypeSuccess-8 5000000 268 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeSuccessParallel-8 20000000 82.3 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-8 5000000 331 ns/op 208 B/op 4 allocs/op +BenchmarkFieldCustomTypeFailureParallel-8 20000000 116 ns/op 208 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-8 2000000 872 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagSuccessParallel-8 5000000 389 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-8 3000000 569 ns/op 224 B/op 5 allocs/op +BenchmarkFieldOrTagFailureParallel-8 5000000 397 ns/op 224 B/op 5 allocs/op +BenchmarkStructLevelValidationSuccess-8 5000000 334 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationSuccessParallel-8 20000000 111 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationFailure-8 2000000 622 ns/op 304 B/op 8 allocs/op +BenchmarkStructLevelValidationFailureParallel-8 10000000 274 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-8 3000000 525 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 165 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeFailure-8 2000000 826 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleCustomTypeFailureParallel-8 5000000 378 ns/op 440 B/op 10 allocs/op +BenchmarkStructFilteredSuccess-8 2000000 734 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredSuccessParallel-8 5000000 313 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredFailure-8 2000000 592 ns/op 256 B/op 7 allocs/op +BenchmarkStructFilteredFailureParallel-8 10000000 272 ns/op 256 B/op 7 allocs/op +BenchmarkStructPartialSuccess-8 2000000 682 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialSuccessParallel-8 10000000 279 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialFailure-8 2000000 938 ns/op 480 B/op 11 allocs/op +BenchmarkStructPartialFailureParallel-8 5000000 398 ns/op 480 B/op 11 allocs/op +BenchmarkStructExceptSuccess-8 1000000 1088 ns/op 496 B/op 12 allocs/op +BenchmarkStructExceptSuccessParallel-8 10000000 257 ns/op 240 B/op 5 allocs/op +BenchmarkStructExceptFailure-8 2000000 897 ns/op 464 B/op 10 allocs/op +BenchmarkStructExceptFailureParallel-8 5000000 394 ns/op 464 B/op 10 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-8 3000000 535 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 184 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldFailure-8 2000000 789 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossFieldFailureParallel-8 5000000 386 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 793 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 5000000 287 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1065 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 3000000 417 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleSuccess-8 5000000 364 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleSuccessParallel-8 20000000 112 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleFailure-8 2000000 785 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleFailureParallel-8 5000000 339 ns/op 424 B/op 9 allocs/op +BenchmarkStructComplexSuccess-8 1000000 2136 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexSuccessParallel-8 2000000 755 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexFailure-8 300000 5248 ns/op 3041 B/op 53 allocs/op +BenchmarkStructComplexFailureParallel-8 1000000 2363 ns/op 3041 B/op 53 allocs/op ``` Complimentary Software diff --git a/benchmarks_test.go b/benchmarks_test.go index 6852d95..b2a1c42 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -1,6 +1,7 @@ package validator import ( + "bytes" sql "database/sql/driver" "testing" "time" @@ -375,6 +376,110 @@ func BenchmarkStructSimpleCustomTypeFailureParallel(b *testing.B) { }) } +func BenchmarkStructFilteredSuccess(b *testing.B) { + + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + byts := []byte("Name") + + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + validate.StructFiltered(test, fn) + } +} + +func BenchmarkStructFilteredSuccessParallel(b *testing.B) { + + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + byts := []byte("Name") + + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + validate.StructFiltered(test, fn) + } + }) +} + +func BenchmarkStructFilteredFailure(b *testing.B) { + + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + byts := []byte("NickName") + + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + validate.StructFiltered(test, fn) + } +} + +func BenchmarkStructFilteredFailureParallel(b *testing.B) { + + validate := New() + + type Test struct { + Name string `validate:"required"` + NickName string `validate:"required"` + } + + test := &Test{ + Name: "Joey Bloggs", + } + + byts := []byte("NickName") + + fn := func(ns []byte) bool { + return !bytes.HasSuffix(ns, byts) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + validate.StructFiltered(test, fn) + } + }) +} + func BenchmarkStructPartialSuccess(b *testing.B) { validate := New() diff --git a/errors.go b/errors.go index 84344ac..820b4bd 100644 --- a/errors.go +++ b/errors.go @@ -13,6 +13,7 @@ const ( fieldErrMsg = "Key: '%s' Error:Field validation for '%s' failed on the '%s' tag" ) +// ValidationErrorsTranslations is the translation return type type ValidationErrorsTranslations map[string]string // InvalidValidationError describes an invalid argument passed to @@ -55,6 +56,7 @@ func (ve ValidationErrors) Error() string { return strings.TrimSpace(buff.String()) } +// Translate translates all of the ValidationErrors func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations { trans := make(ValidationErrorsTranslations) diff --git a/validator.go b/validator.go index d9f2f17..7ae76be 100644 --- a/validator.go +++ b/validator.go @@ -17,6 +17,8 @@ type validate struct { hasExcludes bool includeExclude map[string]struct{} // reset only if StructPartial or StructExcept are called, no need otherwise + ffn FilterFunc + // StructLevel & FieldLevel fields slflParent reflect.Value slCurrent reflect.Value @@ -54,10 +56,19 @@ func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, t if v.isPartial { - _, ok = v.includeExclude[string(append(structNs, f.name...))] + if v.ffn != nil { + // used with StructFiltered + if v.ffn(append(structNs, f.name...)) { + continue + } - if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) { - continue + } else { + // used with StructPartial & StructExcept + _, ok = v.includeExclude[string(append(structNs, f.name...))] + + if (ok && v.hasExcludes) || (!ok && !v.hasExcludes) { + continue + } } } diff --git a/validator_instance.go b/validator_instance.go index f956490..c50aa5f 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -36,6 +36,12 @@ var ( defaultCField = &cField{namesEqual: true} ) +// FilterFunc is the type used to filter fields using +// StructFiltered(...) function. +// returning true results in the field being filtered/skiped from +// validation +type FilterFunc func(ns []byte) bool + // CustomTypeFunc allows for overriding or adding custom field type handler functions // field = field value of the type to return a value to be validated // example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29 @@ -192,6 +198,7 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ v.hasCustomFuncs = true } +// RegisterTranslation registers translations against the provided tag. func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) { if v.transTagFunc == nil { @@ -248,6 +255,43 @@ func (v *Validate) Struct(s interface{}) (err error) { return } +// StructFiltered validates a structs exposed fields, that pass the FilterFunc check and automatically validates +// nested structs, unless otherwise specified. +// +// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. +// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. +func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) (err error) { + + val := reflect.ValueOf(s) + top := val + + if val.Kind() == reflect.Ptr && !val.IsNil() { + val = val.Elem() + } + + if val.Kind() != reflect.Struct || val.Type() == timeType { + return &InvalidValidationError{Type: reflect.TypeOf(s)} + } + + // good to validate + vd := v.pool.Get().(*validate) + vd.top = top + vd.isPartial = true + vd.ffn = fn + // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept + + vd.validateStruct(top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) + + if len(vd.errs) > 0 { + err = vd.errs + vd.errs = nil + } + + v.pool.Put(vd) + + return +} + // StructPartial validates the fields passed in only, ignoring all others. // Fields may be provided in a namespaced fashion relative to the struct provided // eg. NestedStruct.Field or NestedArrayField[0].Struct.Name @@ -271,6 +315,7 @@ func (v *Validate) StructPartial(s interface{}, fields ...string) (err error) { vd := v.pool.Get().(*validate) vd.top = top vd.isPartial = true + vd.ffn = nil vd.hasExcludes = false vd.includeExclude = make(map[string]struct{}) @@ -349,6 +394,7 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) { vd := v.pool.Get().(*validate) vd.top = top vd.isPartial = true + vd.ffn = nil vd.hasExcludes = true vd.includeExclude = make(map[string]struct{}) diff --git a/validator_test.go b/validator_test.go index ac922b7..1b93214 100644 --- a/validator_test.go +++ b/validator_test.go @@ -1,6 +1,7 @@ package validator import ( + "bytes" "database/sql" "database/sql/driver" "encoding/json" @@ -6560,3 +6561,173 @@ func TestTranslationErrors(t *testing.T) { NotEqual(t, err, nil) Equal(t, err.Error(), "error: conflicting key 'required' rule 'Unknown' with text '{0} is a required field', value being ignored") } + +func TestStructFiltered(t *testing.T) { + + p1 := func(ns []byte) bool { + if bytes.HasSuffix(ns, []byte("NoTag")) || bytes.HasSuffix(ns, []byte("Required")) { + return false + } + + return true + } + + p2 := func(ns []byte) bool { + if bytes.HasSuffix(ns, []byte("SubSlice[0].Test")) || + bytes.HasSuffix(ns, []byte("SubSlice[0]")) || + bytes.HasSuffix(ns, []byte("SubSlice")) || + bytes.HasSuffix(ns, []byte("Sub")) || + bytes.HasSuffix(ns, []byte("SubIgnore")) || + bytes.HasSuffix(ns, []byte("Anonymous")) || + bytes.HasSuffix(ns, []byte("Anonymous.A")) { + return false + } + + return true + } + + p3 := func(ns []byte) bool { + if bytes.HasSuffix(ns, []byte("SubTest.Test")) { + return false + } + + return true + } + + // 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"}, + }, + }, + } + + validate := New() + + // the following should all return no errors as everything is valid in + // the default state + errs := validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + Equal(t, errs, nil) + + // this isn't really a robust test, but is ment to illustrate the ANON CASE below + errs = validate.StructFiltered(tPartial.SubSlice[0], p3) + Equal(t, errs, nil) + + // mod tParial for required feild and re-test making sure invalid fields are NOT required: + tPartial.Required = "" + + // inversion and retesting Partial to generate failures: + errs = validate.StructFiltered(tPartial, p1) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Required", "TestPartial.Required", "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.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + // will fail as unset feild is tested + errs = validate.StructFiltered(tPartial, p2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.Anonymous.A", "TestPartial.Anonymous.A", "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.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + NotEqual(t, errs, nil) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "Test", "Test", "required") + Equal(t, len(errs.(ValidationErrors)), 1) + + // Unset second slice member concurrently to test dive behavior: + tPartial.SubSlice[1].Test = "" + + errs = validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + NotEqual(t, errs, nil) + Equal(t, len(errs.(ValidationErrors)), 1) + AssertError(t, errs, "TestPartial.SubSlice[0].Test", "TestPartial.SubSlice[0].Test", "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.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + Equal(t, errs, nil) + + tPartial.SubSlice[1].Test = "Required" + tPartial.Anonymous.SubAnonStruct[0].Test = "" + + // these will pass as the unset item is NOT tested + errs = validate.StructFiltered(tPartial, p1) + Equal(t, errs, nil) + + errs = validate.StructFiltered(tPartial, p2) + Equal(t, errs, nil) + + dt := time.Now() + err := validate.StructFiltered(&dt, func(ns []byte) bool { return true }) + NotEqual(t, err, nil) + Equal(t, err.Error(), "validator: (nil *time.Time)") +}