From 75162c2da6a438f8fa962eb3b7d5ed48e4564d17 Mon Sep 17 00:00:00 2001 From: Thao Nguyen Date: Sun, 23 Jul 2017 23:29:27 +0700 Subject: [PATCH 1/2] Go 1.8 context supports for struct validation --- baked_in.go | 9 +++++++++ cache.go | 2 +- validator.go | 17 ++++++++-------- validator_instance.go | 47 +++++++++++++++++++++++++++++-------------- validator_test.go | 8 ++++++++ 5 files changed, 59 insertions(+), 24 deletions(-) diff --git a/baked_in.go b/baked_in.go index 9e0b173c8..d8c13c900 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1,6 +1,7 @@ package validator import ( + "context" "fmt" "net" "net/url" @@ -16,6 +17,14 @@ import ( // fieldType = fields // param = parameter used in validation i.e. gt=0 param would be 0 type Func func(fl FieldLevel) bool +type FuncCtx func(ctx context.Context, fl FieldLevel) bool + +// wrapFunc make Func compatible with FuncCtx +func wrapFunc(fn Func) FuncCtx { + return func(ctx context.Context, fl FieldLevel) bool { + return fn(fl) + } +} var ( restrictedTags = map[string]struct{}{ diff --git a/cache.go b/cache.go index d596bd5b5..a45120d90 100644 --- a/cache.go +++ b/cache.go @@ -90,7 +90,7 @@ type cTag struct { hasAlias bool typeof tagType hasTag bool - fn Func + fn FuncCtx next *cTag } diff --git a/validator.go b/validator.go index 98bf26523..da8a80e17 100644 --- a/validator.go +++ b/validator.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "strconv" + "context" ) // per validate contruct @@ -34,7 +35,7 @@ type validate struct { } // parent and current will be the same the first run of validateStruct -func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { +func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { cs, ok := v.v.structCache.Get(typ) if !ok { @@ -78,7 +79,7 @@ func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, t } } - v.traverseField(parent, current.Field(f.idx), ns, structNs, f, f.cTags) + v.traverseField(ctx, parent, current.Field(f.idx), ns, structNs, f, f.cTags) } } @@ -97,7 +98,7 @@ func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, t } // 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(parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { +func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) { var typ reflect.Type var kind reflect.Kind @@ -192,7 +193,7 @@ func (v *validate) traverseField(parent reflect.Value, current reflect.Value, ns structNs = append(append(structNs, cf.name...), '.') } - v.validateStruct(current, current, typ, ns, structNs, ct) + v.validateStruct(ctx, current, current, typ, ns, structNs, ct) return } } @@ -261,7 +262,7 @@ OUTER: reusableCF.altName = string(v.misc) } - v.traverseField(parent, current.Index(i), ns, structNs, reusableCF, ct) + v.traverseField(ctx, parent, current.Index(i), ns, structNs, reusableCF, ct) } case reflect.Map: @@ -291,7 +292,7 @@ OUTER: reusableCF.altName = string(v.misc) } - v.traverseField(parent, current.MapIndex(key), ns, structNs, reusableCF, ct) + v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct) } default: @@ -314,7 +315,7 @@ OUTER: v.cf = cf v.ct = ct - if ct.fn(v) { + if ct.fn(ctx, v) { // drain rest of the 'or' values, then continue or leave for { @@ -407,7 +408,7 @@ OUTER: // v.ns = ns // v.actualNs = structNs - if !ct.fn(v) { + if !ct.fn(ctx, v) { v.str1 = string(append(ns, cf.altName...)) diff --git a/validator_instance.go b/validator_instance.go index 5f2cd7401..213c8cc7b 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -1,6 +1,7 @@ package validator import ( + "context" "errors" "fmt" "reflect" @@ -61,7 +62,7 @@ type Validate struct { structLevelFuncs map[reflect.Type]StructLevelFunc customFuncs map[reflect.Type]CustomTypeFunc aliases map[string]string - validations map[string]Func + validations map[string]FuncCtx transTagFunc map[ut.Translator]map[string]TranslationFunc // map[]map[]TranslationFunc tagCache *tagCache structCache *structCache @@ -79,7 +80,7 @@ func New() *Validate { v := &Validate{ tagName: defaultTagName, aliases: make(map[string]string, len(bakedInAliases)), - validations: make(map[string]Func, len(bakedInValidators)), + validations: make(map[string]FuncCtx, len(bakedInValidators)), tagCache: tc, structCache: sc, } @@ -93,7 +94,7 @@ func New() *Validate { for k, val := range bakedInValidators { // no need to error check here, baked in will alwaays be valid - v.registerValidation(k, val, true) + v.registerValidation(k, wrapFunc(val), true) } v.pool = &sync.Pool{ @@ -128,10 +129,15 @@ func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) { // - if the key already exists, the previous validation function will be replaced. // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterValidation(tag string, fn Func) error { + return v.registerValidation(tag, wrapFunc(fn), false) +} + +// RegisterValidationCtx adds a validation which supports context.Context +func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx) error { return v.registerValidation(tag, fn, false) } -func (v *Validate) registerValidation(tag string, fn Func, bakedIn bool) error { +func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool) error { if len(tag) == 0 { return errors.New("Function Key cannot be empty") @@ -225,11 +231,8 @@ func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, register return } -// Struct validates a structs exposed fields, 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) Struct(s interface{}) (err error) { +// StructCtx go1.8 context supports Struct validation +func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { val := reflect.ValueOf(s) top := val @@ -248,7 +251,7 @@ func (v *Validate) Struct(s interface{}) (err error) { vd.isPartial = false // 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) + vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs @@ -260,6 +263,15 @@ func (v *Validate) Struct(s interface{}) (err error) { return } +// Struct validates a structs exposed fields, 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) Struct(s interface{}) (err error) { + ctx := context.Background() + return v.StructCtx(ctx, s) +} + // StructFiltered validates a structs exposed fields, that pass the FilterFunc check and automatically validates // nested structs, unless otherwise specified. // @@ -267,6 +279,7 @@ func (v *Validate) Struct(s interface{}) (err error) { // 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) { + ctx := context.Background() val := reflect.ValueOf(s) top := val @@ -285,7 +298,7 @@ func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) (err error) { 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) + vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs @@ -305,6 +318,7 @@ func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) (err error) { // 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) StructPartial(s interface{}, fields ...string) (err error) { + ctx := context.Background() val := reflect.ValueOf(s) top := val @@ -364,7 +378,7 @@ func (v *Validate) StructPartial(s interface{}, fields ...string) (err error) { } } - vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) + vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs @@ -384,6 +398,7 @@ func (v *Validate) StructPartial(s interface{}, fields ...string) (err error) { // 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) StructExcept(s interface{}, fields ...string) (err error) { + ctx := context.Background() val := reflect.ValueOf(s) top := val @@ -419,7 +434,7 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) { vd.includeExclude[string(vd.misc)] = struct{}{} } - vd.validateStruct(top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) + vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs @@ -445,6 +460,7 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) { // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) Var(field interface{}, tag string) (err error) { + ctx := context.Background() if len(tag) == 0 || tag == skipValidationTag { return nil } @@ -470,7 +486,7 @@ func (v *Validate) Var(field interface{}, tag string) (err error) { vd.top = val vd.isPartial = false - vd.traverseField(val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) + vd.traverseField(ctx, val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) if len(vd.errs) > 0 { err = vd.errs @@ -497,6 +513,7 @@ func (v *Validate) Var(field interface{}, tag string) (err error) { // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) (err error) { + ctx := context.Background() if len(tag) == 0 || tag == skipValidationTag { return nil } @@ -522,7 +539,7 @@ func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string vd.top = otherVal vd.isPartial = false - vd.traverseField(otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) + vd.traverseField(ctx, otherVal, reflect.ValueOf(field), vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag) if len(vd.errs) > 0 { err = vd.errs diff --git a/validator_test.go b/validator_test.go index c1dcb637c..e7936ce92 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2,6 +2,7 @@ package validator import ( "bytes" + "context" "database/sql" "database/sql/driver" "encoding/json" @@ -5127,6 +5128,10 @@ func TestAddFunctions(t *testing.T) { return true } + fnCtx := func(ctx context.Context, fl FieldLevel) bool { + return true + } + validate := New() errs := validate.RegisterValidation("new", fn) @@ -5141,6 +5146,9 @@ func TestAddFunctions(t *testing.T) { errs = validate.RegisterValidation("new", fn) Equal(t, errs, nil) + errs = validate.RegisterValidationCtx("new", fnCtx) + Equal(t, errs, nil) + PanicMatches(t, func() { validate.RegisterValidation("dive", fn) }, "Tag 'dive' either contains restricted characters or is the same as a restricted tag needed for normal operation") } From 0ed92874c607e22977987446c879c2fb3b6a5ab3 Mon Sep 17 00:00:00 2001 From: Dean Karn Date: Sat, 29 Jul 2017 20:44:24 -0700 Subject: [PATCH 2/2] Add contextual validation support via context.Context Added: - RegisterValidationCtx - RegisterStructValidationCtx - StructCtx - StructFilteredCtx - StructPartialCtx - StructExceptCtx - VarCtx - VarWithValueCtx --- README.md | 107 ++++++++++++++++++------------------ baked_in.go | 13 ++--- cache.go | 2 +- struct_level.go | 18 +++++-- validator.go | 4 +- validator_instance.go | 122 +++++++++++++++++++++++++++++++++--------- validator_test.go | 50 +++++++++++++---- 7 files changed, 218 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index f5869664b..51c6cb4f8 100644 --- a/README.md +++ b/README.md @@ -65,60 +65,61 @@ Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usa Benchmarks ------ -###### Run on i5-7600 16 GB DDR4-2400 using Go version go1.8 linux/amd64 +###### Run on Dell XPS 15 i7-7700HQ 32GB Go version go1.8.3 linux/amd64 ```go -BenchmarkFieldSuccess-4 20000000 74.3 ns/op 0 B/op 0 allocs/op -BenchmarkFieldSuccessParallel-4 50000000 31.5 ns/op 0 B/op 0 allocs/op -BenchmarkFieldFailure-4 3000000 556 ns/op 208 B/op 4 allocs/op -BenchmarkFieldFailureParallel-4 20000000 88.7 ns/op 208 B/op 4 allocs/op -BenchmarkFieldDiveSuccess-4 2000000 630 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveSuccessParallel-4 10000000 173 ns/op 201 B/op 11 allocs/op -BenchmarkFieldDiveFailure-4 1000000 1350 ns/op 412 B/op 16 allocs/op -BenchmarkFieldDiveFailureParallel-4 5000000 250 ns/op 412 B/op 16 allocs/op -BenchmarkFieldCustomTypeSuccess-4 10000000 202 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeSuccessParallel-4 20000000 63.5 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 5000000 568 ns/op 208 B/op 4 allocs/op -BenchmarkFieldCustomTypeFailureParallel-4 20000000 87.5 ns/op 208 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-4 2000000 703 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagSuccessParallel-4 3000000 447 ns/op 16 B/op 1 allocs/op -BenchmarkFieldOrTagFailure-4 3000000 604 ns/op 224 B/op 5 allocs/op -BenchmarkFieldOrTagFailureParallel-4 5000000 353 ns/op 224 B/op 5 allocs/op -BenchmarkStructLevelValidationSuccess-4 10000000 190 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationSuccessParallel-4 30000000 59.9 ns/op 32 B/op 2 allocs/op -BenchmarkStructLevelValidationFailure-4 2000000 705 ns/op 304 B/op 8 allocs/op -BenchmarkStructLevelValidationFailureParallel-4 10000000 146 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 5000000 361 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeSuccessParallel-4 20000000 101 ns/op 32 B/op 2 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1210 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleCustomTypeFailureParallel-4 10000000 196 ns/op 440 B/op 10 allocs/op -BenchmarkStructFilteredSuccess-4 2000000 757 ns/op 288 B/op 9 allocs/op -BenchmarkStructFilteredSuccessParallel-4 10000000 167 ns/op 288 B/op 9 allocs/op -BenchmarkStructFilteredFailure-4 3000000 619 ns/op 256 B/op 7 allocs/op -BenchmarkStructFilteredFailureParallel-4 10000000 134 ns/op 256 B/op 7 allocs/op -BenchmarkStructPartialSuccess-4 2000000 687 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialSuccessParallel-4 10000000 159 ns/op 256 B/op 6 allocs/op -BenchmarkStructPartialFailure-4 1000000 1281 ns/op 480 B/op 11 allocs/op -BenchmarkStructPartialFailureParallel-4 10000000 218 ns/op 480 B/op 11 allocs/op -BenchmarkStructExceptSuccess-4 1000000 1041 ns/op 496 B/op 12 allocs/op -BenchmarkStructExceptSuccessParallel-4 10000000 140 ns/op 240 B/op 5 allocs/op -BenchmarkStructExceptFailure-4 1000000 1014 ns/op 464 B/op 10 allocs/op -BenchmarkStructExceptFailureParallel-4 10000000 201 ns/op 464 B/op 10 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-4 5000000 364 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldSuccessParallel-4 20000000 103 ns/op 72 B/op 3 allocs/op -BenchmarkStructSimpleCrossFieldFailure-4 2000000 789 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossFieldFailureParallel-4 10000000 174 ns/op 304 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 3000000 522 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-4 10000000 146 ns/op 80 B/op 4 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-4 2000000 879 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-4 10000000 225 ns/op 320 B/op 9 allocs/op -BenchmarkStructSimpleSuccess-4 10000000 223 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleSuccessParallel-4 20000000 63.3 ns/op 0 B/op 0 allocs/op -BenchmarkStructSimpleFailure-4 2000000 1097 ns/op 424 B/op 9 allocs/op -BenchmarkStructSimpleFailureParallel-4 10000000 182 ns/op 424 B/op 9 allocs/op -BenchmarkStructComplexSuccess-4 1000000 1362 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexSuccessParallel-4 5000000 359 ns/op 128 B/op 8 allocs/op -BenchmarkStructComplexFailure-4 300000 6446 ns/op 3040 B/op 53 allocs/op -BenchmarkStructComplexFailureParallel-4 1000000 1203 ns/op 3040 B/op 53 allocs/op +go test -run=XXX -bench=. -benchmem=true +BenchmarkFieldSuccess-8 20000000 88.3 ns/op 0 B/op 0 allocs/op +BenchmarkFieldSuccessParallel-8 50000000 30.4 ns/op 0 B/op 0 allocs/op +BenchmarkFieldFailure-8 3000000 428 ns/op 208 B/op 4 allocs/op +BenchmarkFieldFailureParallel-8 20000000 96.0 ns/op 208 B/op 4 allocs/op +BenchmarkFieldDiveSuccess-8 2000000 695 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveSuccessParallel-8 10000000 205 ns/op 201 B/op 11 allocs/op +BenchmarkFieldDiveFailure-8 1000000 1083 ns/op 412 B/op 16 allocs/op +BenchmarkFieldDiveFailureParallel-8 5000000 278 ns/op 413 B/op 16 allocs/op +BenchmarkFieldCustomTypeSuccess-8 10000000 229 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeSuccessParallel-8 20000000 72.4 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-8 5000000 377 ns/op 208 B/op 4 allocs/op +BenchmarkFieldCustomTypeFailureParallel-8 20000000 93.0 ns/op 208 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-8 2000000 767 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagSuccessParallel-8 3000000 425 ns/op 16 B/op 1 allocs/op +BenchmarkFieldOrTagFailure-8 2000000 548 ns/op 224 B/op 5 allocs/op +BenchmarkFieldOrTagFailureParallel-8 3000000 411 ns/op 224 B/op 5 allocs/op +BenchmarkStructLevelValidationSuccess-8 10000000 219 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationSuccessParallel-8 20000000 69.2 ns/op 32 B/op 2 allocs/op +BenchmarkStructLevelValidationFailure-8 2000000 628 ns/op 304 B/op 8 allocs/op +BenchmarkStructLevelValidationFailureParallel-8 10000000 165 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-8 3000000 411 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 122 ns/op 32 B/op 2 allocs/op +BenchmarkStructSimpleCustomTypeFailure-8 1000000 1022 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleCustomTypeFailureParallel-8 10000000 228 ns/op 440 B/op 10 allocs/op +BenchmarkStructFilteredSuccess-8 2000000 737 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredSuccessParallel-8 10000000 192 ns/op 288 B/op 9 allocs/op +BenchmarkStructFilteredFailure-8 3000000 583 ns/op 256 B/op 7 allocs/op +BenchmarkStructFilteredFailureParallel-8 10000000 152 ns/op 256 B/op 7 allocs/op +BenchmarkStructPartialSuccess-8 2000000 731 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialSuccessParallel-8 10000000 173 ns/op 256 B/op 6 allocs/op +BenchmarkStructPartialFailure-8 1000000 1164 ns/op 480 B/op 11 allocs/op +BenchmarkStructPartialFailureParallel-8 5000000 253 ns/op 480 B/op 11 allocs/op +BenchmarkStructExceptSuccess-8 1000000 1337 ns/op 496 B/op 12 allocs/op +BenchmarkStructExceptSuccessParallel-8 10000000 153 ns/op 240 B/op 5 allocs/op +BenchmarkStructExceptFailure-8 2000000 954 ns/op 464 B/op 10 allocs/op +BenchmarkStructExceptFailureParallel-8 5000000 234 ns/op 464 B/op 10 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-8 3000000 420 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 125 ns/op 72 B/op 3 allocs/op +BenchmarkStructSimpleCrossFieldFailure-8 2000000 790 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 205 ns/op 304 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 611 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 172 ns/op 80 B/op 4 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1112 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 258 ns/op 320 B/op 9 allocs/op +BenchmarkStructSimpleSuccess-8 5000000 263 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleSuccessParallel-8 20000000 83.1 ns/op 0 B/op 0 allocs/op +BenchmarkStructSimpleFailure-8 2000000 964 ns/op 424 B/op 9 allocs/op +BenchmarkStructSimpleFailureParallel-8 10000000 212 ns/op 424 B/op 9 allocs/op +BenchmarkStructComplexSuccess-8 1000000 1504 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexSuccessParallel-8 3000000 427 ns/op 128 B/op 8 allocs/op +BenchmarkStructComplexFailure-8 300000 7585 ns/op 3041 B/op 53 allocs/op +BenchmarkStructComplexFailureParallel-8 1000000 1387 ns/op 3041 B/op 53 allocs/op ``` Complementary Software diff --git a/baked_in.go b/baked_in.go index d8c13c900..1241f35f5 100644 --- a/baked_in.go +++ b/baked_in.go @@ -11,16 +11,17 @@ import ( "unicode/utf8" ) -// Func accepts all values needed for file and cross field validation -// fl = FieldLevel validation helper -// field = field value for validation -// fieldType = fields -// param = parameter used in validation i.e. gt=0 param would be 0 +// Func accepts a FieldLevel interface for all validation needs type Func func(fl FieldLevel) bool + +// FuncCtx accepts a context.Context and FieldLevel interface for all validation needs type FuncCtx func(ctx context.Context, fl FieldLevel) bool -// wrapFunc make Func compatible with FuncCtx +// wrapFunc wraps noramal Func makes it compatible with FuncCtx func wrapFunc(fn Func) FuncCtx { + if fn == nil { + return nil // be sure not to wrap a bad function. + } return func(ctx context.Context, fl FieldLevel) bool { return fn(fl) } diff --git a/cache.go b/cache.go index a45120d90..f128f596b 100644 --- a/cache.go +++ b/cache.go @@ -71,7 +71,7 @@ func (tc *tagCache) Set(key string, value *cTag) { type cStruct struct { name string fields []*cField - fn StructLevelFunc + fn StructLevelFuncCtx } type cField struct { diff --git a/struct_level.go b/struct_level.go index aefad2f98..e17bc5c1b 100644 --- a/struct_level.go +++ b/struct_level.go @@ -1,10 +1,24 @@ package validator -import "reflect" +import ( + "context" + "reflect" +) // StructLevelFunc accepts all values needed for struct level validation type StructLevelFunc func(sl StructLevel) +// StructLevelFuncCtx accepts all values needed for struct level validation +// but also allows passing of contextual validation information vi context.Context. +type StructLevelFuncCtx func(ctx context.Context, sl StructLevel) + +// wrapStructLevelFunc wraps noramal StructLevelFunc makes it compatible with StructLevelFuncCtx +func wrapStructLevelFunc(fn StructLevelFunc) StructLevelFuncCtx { + return func(ctx context.Context, sl StructLevel) { + fn(sl) + } +} + // StructLevel contains all the information and helper functions // to validate a struct type StructLevel interface { @@ -21,8 +35,6 @@ type StructLevel interface { Parent() reflect.Value // returns the current struct. - // this is not needed when implementing 'Validatable' interface, - // only when a StructLevel is registered Current() reflect.Value // ExtractType gets the actual underlying type of field value. diff --git a/validator.go b/validator.go index da8a80e17..115dab44f 100644 --- a/validator.go +++ b/validator.go @@ -1,10 +1,10 @@ package validator import ( + "context" "fmt" "reflect" "strconv" - "context" ) // per validate contruct @@ -93,7 +93,7 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur v.ns = ns v.actualNs = structNs - cs.fn(v) + cs.fn(ctx, v) } } diff --git a/validator_instance.go b/validator_instance.go index 213c8cc7b..da5248103 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -59,7 +59,7 @@ type Validate struct { hasCustomFuncs bool hasTagNameFunc bool tagNameFunc TagNameFunc - structLevelFuncs map[reflect.Type]StructLevelFunc + structLevelFuncs map[reflect.Type]StructLevelFuncCtx customFuncs map[reflect.Type]CustomTypeFunc aliases map[string]string validations map[string]FuncCtx @@ -93,7 +93,7 @@ func New() *Validate { // must copy validators for separate validations to be used in each instance for k, val := range bakedInValidators { - // no need to error check here, baked in will alwaays be valid + // no need to error check here, baked in will always be valid v.registerValidation(k, wrapFunc(val), true) } @@ -129,10 +129,11 @@ func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) { // - if the key already exists, the previous validation function will be replaced. // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterValidation(tag string, fn Func) error { - return v.registerValidation(tag, wrapFunc(fn), false) + return v.RegisterValidationCtx(tag, wrapFunc(fn)) } -// RegisterValidationCtx adds a validation which supports context.Context +// RegisterValidationCtx does the same as RegisterValidation on accepts a FuncCtx validation +// allowing context.Context validation support. func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx) error { return v.registerValidation(tag, fn, false) } @@ -183,9 +184,22 @@ func (v *Validate) RegisterAlias(alias, tags string) { // a struct out of your control's validation to be overridden // - this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interface{}) { + v.RegisterStructValidationCtx(wrapStructLevelFunc(fn), types...) +} + +// RegisterStructValidationCtx registers a StructLevelFuncCtx against a number of types and allows passing +// of contextual validation information via context.Context. +// This is akin to implementing a 'Validatable' interface, but for structs for which +// you may not have access or rights to change. +// +// NOTES: +// - if this and the 'Validatable' interface are implemented the Struct Level takes precedence as to enable +// a struct out of your control's validation to be overridden +// - this method is not thread-safe it is intended that these all be registered prior to any validation +func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...interface{}) { if v.structLevelFuncs == nil { - v.structLevelFuncs = make(map[reflect.Type]StructLevelFunc) + v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx) } for _, t := range types { @@ -231,7 +245,19 @@ func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, register return } -// StructCtx go1.8 context supports Struct validation +// Struct validates a structs exposed fields, 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) Struct(s interface{}) error { + return v.StructCtx(context.Background(), s) +} + +// StructCtx validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified +// and also allows passing of context.Context for contextual validation information. +// +// 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) StructCtx(ctx context.Context, s interface{}) (err error) { val := reflect.ValueOf(s) @@ -263,23 +289,22 @@ func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { return } -// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. +// 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) Struct(s interface{}) (err error) { - ctx := context.Background() - return v.StructCtx(ctx, s) +func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) error { + return v.StructFilteredCtx(context.Background(), s, fn) } -// StructFiltered validates a structs exposed fields, that pass the FilterFunc check and automatically validates -// nested structs, unless otherwise specified. +// StructFilteredCtx validates a structs exposed fields, that pass the FilterFunc check and automatically validates +// nested structs, unless otherwise specified and also allows passing of contextual validation information via +// context.Context // // 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) { - - ctx := context.Background() +func (v *Validate) StructFilteredCtx(ctx context.Context, s interface{}, fn FilterFunc) (err error) { val := reflect.ValueOf(s) top := val @@ -298,7 +323,7 @@ func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) (err error) { vd.ffn = fn // vd.hasExcludes = false // only need to reset in StructPartial and StructExcept - vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) + vd.validateStruct(context.Background(), top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil) if len(vd.errs) > 0 { err = vd.errs @@ -316,9 +341,18 @@ func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) (err error) { // // 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) StructPartial(s interface{}, fields ...string) (err error) { +func (v *Validate) StructPartial(s interface{}, fields ...string) error { + return v.StructPartialCtx(context.Background(), s, fields...) +} - ctx := context.Background() +// StructPartialCtx validates the fields passed in only, ignoring all others and allows passing of contextual +// validation validation information via context.Context +// Fields may be provided in a namespaced fashion relative to the struct provided +// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name +// +// 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) StructPartialCtx(ctx context.Context, s interface{}, fields ...string) (err error) { val := reflect.ValueOf(s) top := val @@ -396,9 +430,18 @@ func (v *Validate) StructPartial(s interface{}, fields ...string) (err error) { // // 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) StructExcept(s interface{}, fields ...string) (err error) { +func (v *Validate) StructExcept(s interface{}, fields ...string) error { + return v.StructExceptCtx(context.Background(), s, fields...) +} - ctx := context.Background() +// StructExceptCtx validates all fields except the ones passed in and allows passing of contextual +// validation validation information via context.Context +// Fields may be provided in a namespaced fashion relative to the struct provided +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name +// +// 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) StructExceptCtx(ctx context.Context, s interface{}, fields ...string) (err error) { val := reflect.ValueOf(s) top := val @@ -458,9 +501,24 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) { // 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. // validate Array, Slice and maps fields which may contain more than one error -func (v *Validate) Var(field interface{}, tag string) (err error) { +func (v *Validate) Var(field interface{}, tag string) error { + return v.VarCtx(context.Background(), field, tag) +} - ctx := context.Background() +// VarCtx validates a single variable using tag style validation and allows passing of contextual +// validation validation information via context.Context. +// eg. +// var i int +// validate.Var(i, "gt=1,lt=10") +// +// WARNING: a struct can be passed for validation eg. time.Time is a struct or if you have a custom type and have registered +// a custom type handler, so must allow it; however unforseen validations will occur if trying to validate a struct +// that is meant to be passed to 'validate.Struct' +// +// 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. +// validate Array, Slice and maps fields which may contain more than one error +func (v *Validate) VarCtx(ctx context.Context, field interface{}, tag string) (err error) { if len(tag) == 0 || tag == skipValidationTag { return nil } @@ -511,9 +569,25 @@ func (v *Validate) Var(field interface{}, tag string) (err error) { // 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. // validate Array, Slice and maps fields which may contain more than one error -func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) (err error) { +func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string) error { + return v.VarWithValueCtx(context.Background(), field, other, tag) +} - ctx := context.Background() +// VarWithValueCtx validates a single variable, against another variable/field's value using tag style validation and +// allows passing of contextual validation validation information via context.Context. +// eg. +// s1 := "abcd" +// s2 := "abcd" +// validate.VarWithValue(s1, s2, "eqcsfield") // returns true +// +// WARNING: a struct can be passed for validation eg. time.Time is a struct or if you have a custom type and have registered +// a custom type handler, so must allow it; however unforseen validations will occur if trying to validate a struct +// that is meant to be passed to 'validate.Struct' +// +// 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. +// validate Array, Slice and maps fields which may contain more than one error +func (v *Validate) VarWithValueCtx(ctx context.Context, field interface{}, other interface{}, tag string) (err error) { if len(tag) == 0 || tag == skipValidationTag { return nil } diff --git a/validator_test.go b/validator_test.go index e7936ce92..565e23d99 100644 --- a/validator_test.go +++ b/validator_test.go @@ -777,7 +777,7 @@ func TestStructPartial(t *testing.T) { // the following should all return no errors as everything is valid in // the default state - errs := validate.StructPartial(tPartial, p1...) + errs := validate.StructPartialCtx(context.Background(), tPartial, p1...) Equal(t, errs, nil) errs = validate.StructPartial(tPartial, p2...) @@ -787,7 +787,7 @@ func TestStructPartial(t *testing.T) { errs = validate.StructPartial(tPartial.SubSlice[0], p3...) Equal(t, errs, nil) - errs = validate.StructExcept(tPartial, p1...) + errs = validate.StructExceptCtx(context.Background(), tPartial, p1...) Equal(t, errs, nil) errs = validate.StructExcept(tPartial, p2...) @@ -992,7 +992,7 @@ func TestCrossStructLteFieldValidation(t *testing.T) { AssertError(t, errs, "Test.Float", "Test.Float", "Float", "Float", "ltecsfield") AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "ltecsfield") - errs = validate.VarWithValue(1, "", "ltecsfield") + errs = validate.VarWithValueCtx(context.Background(), 1, "", "ltecsfield") NotEqual(t, errs, nil) AssertError(t, errs, "", "", "", "", "ltecsfield") @@ -1828,7 +1828,7 @@ func TestSQLValue2Validation(t *testing.T) { AssertError(t, errs, "", "", "", "", "required") val.Name = "Valid Name" - errs = validate.Var(val, "required") + errs = validate.VarCtx(context.Background(), val, "required") Equal(t, errs, nil) val.Name = "errorme" @@ -5246,7 +5246,7 @@ func TestIsGt(t *testing.T) { errs = validate.Var(tm, "gt") Equal(t, errs, nil) - t2 := time.Now().UTC() + t2 := time.Now().UTC().Add(-time.Hour) errs = validate.Var(t2, "gt") NotEqual(t, errs, nil) @@ -5284,7 +5284,7 @@ func TestIsGte(t *testing.T) { errs := validate.Var(t1, "gte") Equal(t, errs, nil) - t2 := time.Now().UTC() + t2 := time.Now().UTC().Add(-time.Hour) errs = validate.Var(t2, "gte") NotEqual(t, errs, nil) @@ -5331,7 +5331,7 @@ func TestIsLt(t *testing.T) { i := true PanicMatches(t, func() { validate.Var(i, "lt") }, "Bad field type bool") - t1 := time.Now().UTC() + t1 := time.Now().UTC().Add(-time.Hour) errs = validate.Var(t1, "lt") Equal(t, errs, nil) @@ -5370,7 +5370,7 @@ func TestIsLte(t *testing.T) { i := true PanicMatches(t, func() { validate.Var(i, "lte") }, "Bad field type bool") - t1 := time.Now().UTC() + t1 := time.Now().UTC().Add(-time.Hour) errs := validate.Var(t1, "lte") Equal(t, errs, nil) @@ -6703,7 +6703,7 @@ func TestStructFiltered(t *testing.T) { // the following should all return no errors as everything is valid in // the default state - errs := validate.StructFiltered(tPartial, p1) + errs := validate.StructFilteredCtx(context.Background(), tPartial, p1) Equal(t, errs, nil) errs = validate.StructFiltered(tPartial, p2) @@ -7087,3 +7087,35 @@ func TestFieldLevelName(t *testing.T) { Equal(t, res5, "json5") Equal(t, alt5, "Map2") } + +func TestValidateStructRegisterCtx(t *testing.T) { + + var ctxVal string + + fnCtx := func(ctx context.Context, fl FieldLevel) bool { + ctxVal = ctx.Value(&ctxVal).(string) + return true + } + + var ctxSlVal string + slFn := func(ctx context.Context, sl StructLevel) { + ctxSlVal = ctx.Value(&ctxSlVal).(string) + } + + type Test struct { + Field string `validate:"val"` + } + + var tst Test + + validate := New() + validate.RegisterValidationCtx("val", fnCtx) + validate.RegisterStructValidationCtx(slFn, Test{}) + + ctx := context.WithValue(context.Background(), &ctxVal, "testval") + ctx = context.WithValue(ctx, &ctxSlVal, "slVal") + errs := validate.StructCtx(ctx, tst) + Equal(t, errs, nil) + Equal(t, ctxVal, "testval") + Equal(t, ctxSlVal, "slVal") +}