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 9e0b173c8..1241f35f5 100644 --- a/baked_in.go +++ b/baked_in.go @@ -1,6 +1,7 @@ package validator import ( + "context" "fmt" "net" "net/url" @@ -10,13 +11,22 @@ 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 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) + } +} + var ( restrictedTags = map[string]struct{}{ diveTag: {}, diff --git a/cache.go b/cache.go index d596bd5b5..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 { @@ -90,7 +90,7 @@ type cTag struct { hasAlias bool typeof tagType hasTag bool - fn Func + fn FuncCtx next *cTag } 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 98bf26523..115dab44f 100644 --- a/validator.go +++ b/validator.go @@ -1,6 +1,7 @@ package validator import ( + "context" "fmt" "reflect" "strconv" @@ -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) } } @@ -92,12 +93,12 @@ func (v *validate) validateStruct(parent reflect.Value, current reflect.Value, t v.ns = ns v.actualNs = structNs - cs.fn(v) + cs.fn(ctx, v) } } // 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..da5248103 100644 --- a/validator_instance.go +++ b/validator_instance.go @@ -1,6 +1,7 @@ package validator import ( + "context" "errors" "fmt" "reflect" @@ -58,10 +59,10 @@ 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]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, } @@ -92,8 +93,8 @@ 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 - v.registerValidation(k, val, true) + // no need to error check here, baked in will always be valid + v.registerValidation(k, wrapFunc(val), true) } v.pool = &sync.Pool{ @@ -128,10 +129,16 @@ 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.RegisterValidationCtx(tag, wrapFunc(fn)) +} + +// 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) } -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") @@ -177,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 { @@ -229,7 +249,16 @@ func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, register // // 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) { +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) top := val @@ -248,7 +277,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 @@ -265,8 +294,17 @@ func (v *Validate) Struct(s interface{}) (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) StructFiltered(s interface{}, fn FilterFunc) (err error) { +func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) error { + return v.StructFilteredCtx(context.Background(), s, fn) +} +// 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) StructFilteredCtx(ctx context.Context, s interface{}, fn FilterFunc) (err error) { val := reflect.ValueOf(s) top := val @@ -285,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(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 @@ -303,8 +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...) +} +// 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 @@ -364,7 +412,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 @@ -382,8 +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...) +} +// 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 @@ -419,7 +477,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 @@ -443,8 +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) +} +// 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 } @@ -470,7 +544,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 @@ -495,8 +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) +} +// 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 } @@ -522,7 +613,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..565e23d99 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" @@ -776,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...) @@ -786,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...) @@ -991,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") @@ -1827,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" @@ -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") } @@ -5238,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) @@ -5276,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) @@ -5323,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) @@ -5362,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) @@ -6695,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) @@ -7079,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") +}