Add contextual validation support via context.Context (#296)

* Add contextual validation support via context.Context

Added:
- RegisterValidationCtx
- RegisterStructValidationCtx
- StructCtx
- StructFilteredCtx
- StructPartialCtx
- StructExceptCtx
- VarCtx
- VarWithValueCtx
pull/298/head v9.5.0
Dean Karn 7 years ago committed by GitHub
parent fb68f39656
commit 0f6f568263
  1. 107
      README.md
  2. 20
      baked_in.go
  3. 4
      cache.go
  4. 18
      struct_level.go
  5. 19
      validator.go
  6. 129
      validator_instance.go
  7. 58
      validator_test.go

@ -65,60 +65,61 @@ Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usa
Benchmarks 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 ```go
BenchmarkFieldSuccess-4 20000000 74.3 ns/op 0 B/op 0 allocs/op go test -run=XXX -bench=. -benchmem=true
BenchmarkFieldSuccessParallel-4 50000000 31.5 ns/op 0 B/op 0 allocs/op BenchmarkFieldSuccess-8 20000000 88.3 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailure-4 3000000 556 ns/op 208 B/op 4 allocs/op BenchmarkFieldSuccessParallel-8 50000000 30.4 ns/op 0 B/op 0 allocs/op
BenchmarkFieldFailureParallel-4 20000000 88.7 ns/op 208 B/op 4 allocs/op BenchmarkFieldFailure-8 3000000 428 ns/op 208 B/op 4 allocs/op
BenchmarkFieldDiveSuccess-4 2000000 630 ns/op 201 B/op 11 allocs/op BenchmarkFieldFailureParallel-8 20000000 96.0 ns/op 208 B/op 4 allocs/op
BenchmarkFieldDiveSuccessParallel-4 10000000 173 ns/op 201 B/op 11 allocs/op BenchmarkFieldDiveSuccess-8 2000000 695 ns/op 201 B/op 11 allocs/op
BenchmarkFieldDiveFailure-4 1000000 1350 ns/op 412 B/op 16 allocs/op BenchmarkFieldDiveSuccessParallel-8 10000000 205 ns/op 201 B/op 11 allocs/op
BenchmarkFieldDiveFailureParallel-4 5000000 250 ns/op 412 B/op 16 allocs/op BenchmarkFieldDiveFailure-8 1000000 1083 ns/op 412 B/op 16 allocs/op
BenchmarkFieldCustomTypeSuccess-4 10000000 202 ns/op 32 B/op 2 allocs/op BenchmarkFieldDiveFailureParallel-8 5000000 278 ns/op 413 B/op 16 allocs/op
BenchmarkFieldCustomTypeSuccessParallel-4 20000000 63.5 ns/op 32 B/op 2 allocs/op BenchmarkFieldCustomTypeSuccess-8 10000000 229 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailure-4 5000000 568 ns/op 208 B/op 4 allocs/op BenchmarkFieldCustomTypeSuccessParallel-8 20000000 72.4 ns/op 32 B/op 2 allocs/op
BenchmarkFieldCustomTypeFailureParallel-4 20000000 87.5 ns/op 208 B/op 4 allocs/op BenchmarkFieldCustomTypeFailure-8 5000000 377 ns/op 208 B/op 4 allocs/op
BenchmarkFieldOrTagSuccess-4 2000000 703 ns/op 16 B/op 1 allocs/op BenchmarkFieldCustomTypeFailureParallel-8 20000000 93.0 ns/op 208 B/op 4 allocs/op
BenchmarkFieldOrTagSuccessParallel-4 3000000 447 ns/op 16 B/op 1 allocs/op BenchmarkFieldOrTagSuccess-8 2000000 767 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagFailure-4 3000000 604 ns/op 224 B/op 5 allocs/op BenchmarkFieldOrTagSuccessParallel-8 3000000 425 ns/op 16 B/op 1 allocs/op
BenchmarkFieldOrTagFailureParallel-4 5000000 353 ns/op 224 B/op 5 allocs/op BenchmarkFieldOrTagFailure-8 2000000 548 ns/op 224 B/op 5 allocs/op
BenchmarkStructLevelValidationSuccess-4 10000000 190 ns/op 32 B/op 2 allocs/op BenchmarkFieldOrTagFailureParallel-8 3000000 411 ns/op 224 B/op 5 allocs/op
BenchmarkStructLevelValidationSuccessParallel-4 30000000 59.9 ns/op 32 B/op 2 allocs/op BenchmarkStructLevelValidationSuccess-8 10000000 219 ns/op 32 B/op 2 allocs/op
BenchmarkStructLevelValidationFailure-4 2000000 705 ns/op 304 B/op 8 allocs/op BenchmarkStructLevelValidationSuccessParallel-8 20000000 69.2 ns/op 32 B/op 2 allocs/op
BenchmarkStructLevelValidationFailureParallel-4 10000000 146 ns/op 304 B/op 8 allocs/op BenchmarkStructLevelValidationFailure-8 2000000 628 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCustomTypeSuccess-4 5000000 361 ns/op 32 B/op 2 allocs/op BenchmarkStructLevelValidationFailureParallel-8 10000000 165 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCustomTypeSuccessParallel-4 20000000 101 ns/op 32 B/op 2 allocs/op BenchmarkStructSimpleCustomTypeSuccess-8 3000000 411 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeFailure-4 1000000 1210 ns/op 424 B/op 9 allocs/op BenchmarkStructSimpleCustomTypeSuccessParallel-8 10000000 122 ns/op 32 B/op 2 allocs/op
BenchmarkStructSimpleCustomTypeFailureParallel-4 10000000 196 ns/op 440 B/op 10 allocs/op BenchmarkStructSimpleCustomTypeFailure-8 1000000 1022 ns/op 424 B/op 9 allocs/op
BenchmarkStructFilteredSuccess-4 2000000 757 ns/op 288 B/op 9 allocs/op BenchmarkStructSimpleCustomTypeFailureParallel-8 10000000 228 ns/op 440 B/op 10 allocs/op
BenchmarkStructFilteredSuccessParallel-4 10000000 167 ns/op 288 B/op 9 allocs/op BenchmarkStructFilteredSuccess-8 2000000 737 ns/op 288 B/op 9 allocs/op
BenchmarkStructFilteredFailure-4 3000000 619 ns/op 256 B/op 7 allocs/op BenchmarkStructFilteredSuccessParallel-8 10000000 192 ns/op 288 B/op 9 allocs/op
BenchmarkStructFilteredFailureParallel-4 10000000 134 ns/op 256 B/op 7 allocs/op BenchmarkStructFilteredFailure-8 3000000 583 ns/op 256 B/op 7 allocs/op
BenchmarkStructPartialSuccess-4 2000000 687 ns/op 256 B/op 6 allocs/op BenchmarkStructFilteredFailureParallel-8 10000000 152 ns/op 256 B/op 7 allocs/op
BenchmarkStructPartialSuccessParallel-4 10000000 159 ns/op 256 B/op 6 allocs/op BenchmarkStructPartialSuccess-8 2000000 731 ns/op 256 B/op 6 allocs/op
BenchmarkStructPartialFailure-4 1000000 1281 ns/op 480 B/op 11 allocs/op BenchmarkStructPartialSuccessParallel-8 10000000 173 ns/op 256 B/op 6 allocs/op
BenchmarkStructPartialFailureParallel-4 10000000 218 ns/op 480 B/op 11 allocs/op BenchmarkStructPartialFailure-8 1000000 1164 ns/op 480 B/op 11 allocs/op
BenchmarkStructExceptSuccess-4 1000000 1041 ns/op 496 B/op 12 allocs/op BenchmarkStructPartialFailureParallel-8 5000000 253 ns/op 480 B/op 11 allocs/op
BenchmarkStructExceptSuccessParallel-4 10000000 140 ns/op 240 B/op 5 allocs/op BenchmarkStructExceptSuccess-8 1000000 1337 ns/op 496 B/op 12 allocs/op
BenchmarkStructExceptFailure-4 1000000 1014 ns/op 464 B/op 10 allocs/op BenchmarkStructExceptSuccessParallel-8 10000000 153 ns/op 240 B/op 5 allocs/op
BenchmarkStructExceptFailureParallel-4 10000000 201 ns/op 464 B/op 10 allocs/op BenchmarkStructExceptFailure-8 2000000 954 ns/op 464 B/op 10 allocs/op
BenchmarkStructSimpleCrossFieldSuccess-4 5000000 364 ns/op 72 B/op 3 allocs/op BenchmarkStructExceptFailureParallel-8 5000000 234 ns/op 464 B/op 10 allocs/op
BenchmarkStructSimpleCrossFieldSuccessParallel-4 20000000 103 ns/op 72 B/op 3 allocs/op BenchmarkStructSimpleCrossFieldSuccess-8 3000000 420 ns/op 72 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldFailure-4 2000000 789 ns/op 304 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldSuccessParallel-8 10000000 125 ns/op 72 B/op 3 allocs/op
BenchmarkStructSimpleCrossFieldFailureParallel-4 10000000 174 ns/op 304 B/op 8 allocs/op BenchmarkStructSimpleCrossFieldFailure-8 2000000 790 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 3000000 522 ns/op 80 B/op 4 allocs/op BenchmarkStructSimpleCrossFieldFailureParallel-8 10000000 205 ns/op 304 B/op 8 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-4 10000000 146 ns/op 80 B/op 4 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccess-8 2000000 611 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailure-4 2000000 879 ns/op 320 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldSuccessParallel-8 10000000 172 ns/op 80 B/op 4 allocs/op
BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-4 10000000 225 ns/op 320 B/op 9 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailure-8 1000000 1112 ns/op 320 B/op 9 allocs/op
BenchmarkStructSimpleSuccess-4 10000000 223 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleCrossStructCrossFieldFailureParallel-8 5000000 258 ns/op 320 B/op 9 allocs/op
BenchmarkStructSimpleSuccessParallel-4 20000000 63.3 ns/op 0 B/op 0 allocs/op BenchmarkStructSimpleSuccess-8 5000000 263 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleFailure-4 2000000 1097 ns/op 424 B/op 9 allocs/op BenchmarkStructSimpleSuccessParallel-8 20000000 83.1 ns/op 0 B/op 0 allocs/op
BenchmarkStructSimpleFailureParallel-4 10000000 182 ns/op 424 B/op 9 allocs/op BenchmarkStructSimpleFailure-8 2000000 964 ns/op 424 B/op 9 allocs/op
BenchmarkStructComplexSuccess-4 1000000 1362 ns/op 128 B/op 8 allocs/op BenchmarkStructSimpleFailureParallel-8 10000000 212 ns/op 424 B/op 9 allocs/op
BenchmarkStructComplexSuccessParallel-4 5000000 359 ns/op 128 B/op 8 allocs/op BenchmarkStructComplexSuccess-8 1000000 1504 ns/op 128 B/op 8 allocs/op
BenchmarkStructComplexFailure-4 300000 6446 ns/op 3040 B/op 53 allocs/op BenchmarkStructComplexSuccessParallel-8 3000000 427 ns/op 128 B/op 8 allocs/op
BenchmarkStructComplexFailureParallel-4 1000000 1203 ns/op 3040 B/op 53 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 Complementary Software

@ -1,6 +1,7 @@
package validator package validator
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@ -10,13 +11,22 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// Func accepts all values needed for file and cross field validation // Func accepts a FieldLevel interface for all validation needs
// fl = FieldLevel validation helper
// field = field value for validation
// fieldType = fields
// param = parameter used in validation i.e. gt=0 param would be 0
type Func func(fl FieldLevel) bool 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 ( var (
restrictedTags = map[string]struct{}{ restrictedTags = map[string]struct{}{
diveTag: {}, diveTag: {},

@ -71,7 +71,7 @@ func (tc *tagCache) Set(key string, value *cTag) {
type cStruct struct { type cStruct struct {
name string name string
fields []*cField fields []*cField
fn StructLevelFunc fn StructLevelFuncCtx
} }
type cField struct { type cField struct {
@ -90,7 +90,7 @@ type cTag struct {
hasAlias bool hasAlias bool
typeof tagType typeof tagType
hasTag bool hasTag bool
fn Func fn FuncCtx
next *cTag next *cTag
} }

@ -1,10 +1,24 @@
package validator package validator
import "reflect" import (
"context"
"reflect"
)
// StructLevelFunc accepts all values needed for struct level validation // StructLevelFunc accepts all values needed for struct level validation
type StructLevelFunc func(sl StructLevel) 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 // StructLevel contains all the information and helper functions
// to validate a struct // to validate a struct
type StructLevel interface { type StructLevel interface {
@ -21,8 +35,6 @@ type StructLevel interface {
Parent() reflect.Value Parent() reflect.Value
// returns the current struct. // returns the current struct.
// this is not needed when implementing 'Validatable' interface,
// only when a StructLevel is registered
Current() reflect.Value Current() reflect.Value
// ExtractType gets the actual underlying type of field value. // ExtractType gets the actual underlying type of field value.

@ -1,6 +1,7 @@
package validator package validator
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
@ -34,7 +35,7 @@ type validate struct {
} }
// parent and current will be the same the first run of validateStruct // 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) cs, ok := v.v.structCache.Get(typ)
if !ok { 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.ns = ns
v.actualNs = structNs 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 // 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 typ reflect.Type
var kind reflect.Kind 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...), '.') structNs = append(append(structNs, cf.name...), '.')
} }
v.validateStruct(current, current, typ, ns, structNs, ct) v.validateStruct(ctx, current, current, typ, ns, structNs, ct)
return return
} }
} }
@ -261,7 +262,7 @@ OUTER:
reusableCF.altName = string(v.misc) 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: case reflect.Map:
@ -291,7 +292,7 @@ OUTER:
reusableCF.altName = string(v.misc) 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: default:
@ -314,7 +315,7 @@ OUTER:
v.cf = cf v.cf = cf
v.ct = ct v.ct = ct
if ct.fn(v) { if ct.fn(ctx, v) {
// drain rest of the 'or' values, then continue or leave // drain rest of the 'or' values, then continue or leave
for { for {
@ -407,7 +408,7 @@ OUTER:
// v.ns = ns // v.ns = ns
// v.actualNs = structNs // v.actualNs = structNs
if !ct.fn(v) { if !ct.fn(ctx, v) {
v.str1 = string(append(ns, cf.altName...)) v.str1 = string(append(ns, cf.altName...))

@ -1,6 +1,7 @@
package validator package validator
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
@ -58,10 +59,10 @@ type Validate struct {
hasCustomFuncs bool hasCustomFuncs bool
hasTagNameFunc bool hasTagNameFunc bool
tagNameFunc TagNameFunc tagNameFunc TagNameFunc
structLevelFuncs map[reflect.Type]StructLevelFunc structLevelFuncs map[reflect.Type]StructLevelFuncCtx
customFuncs map[reflect.Type]CustomTypeFunc customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string aliases map[string]string
validations map[string]Func validations map[string]FuncCtx
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
tagCache *tagCache tagCache *tagCache
structCache *structCache structCache *structCache
@ -79,7 +80,7 @@ func New() *Validate {
v := &Validate{ v := &Validate{
tagName: defaultTagName, tagName: defaultTagName,
aliases: make(map[string]string, len(bakedInAliases)), aliases: make(map[string]string, len(bakedInAliases)),
validations: make(map[string]Func, len(bakedInValidators)), validations: make(map[string]FuncCtx, len(bakedInValidators)),
tagCache: tc, tagCache: tc,
structCache: sc, structCache: sc,
} }
@ -92,8 +93,8 @@ func New() *Validate {
// must copy validators for separate validations to be used in each instance // must copy validators for separate validations to be used in each instance
for k, val := range bakedInValidators { 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, val, true) v.registerValidation(k, wrapFunc(val), true)
} }
v.pool = &sync.Pool{ 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. // - 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 // - 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 { 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) 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 { if len(tag) == 0 {
return errors.New("Function Key cannot be empty") 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 // 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 // - 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{}) { 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 { if v.structLevelFuncs == nil {
v.structLevelFuncs = make(map[reflect.Type]StructLevelFunc) v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx)
} }
for _, t := range types { 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. // 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. // 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) val := reflect.ValueOf(s)
top := val top := val
@ -248,7 +277,7 @@ func (v *Validate) Struct(s interface{}) (err error) {
vd.isPartial = false vd.isPartial = false
// vd.hasExcludes = false // only need to reset in StructPartial and StructExcept // 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 { if len(vd.errs) > 0 {
err = vd.errs 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. // 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. // 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) val := reflect.ValueOf(s)
top := val top := val
@ -285,7 +323,7 @@ func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) (err error) {
vd.ffn = fn vd.ffn = fn
// vd.hasExcludes = false // only need to reset in StructPartial and StructExcept // 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 { if len(vd.errs) > 0 {
err = vd.errs 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. // 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. // 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) val := reflect.ValueOf(s)
top := val 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 { if len(vd.errs) > 0 {
err = vd.errs 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. // 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. // 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) val := reflect.ValueOf(s)
top := val top := val
@ -419,7 +477,7 @@ func (v *Validate) StructExcept(s interface{}, fields ...string) (err error) {
vd.includeExclude[string(vd.misc)] = struct{}{} 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 { if len(vd.errs) > 0 {
err = vd.errs 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. // 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. // 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 // 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 { if len(tag) == 0 || tag == skipValidationTag {
return nil return nil
} }
@ -470,7 +544,7 @@ func (v *Validate) Var(field interface{}, tag string) (err error) {
vd.top = val vd.top = val
vd.isPartial = false 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 { if len(vd.errs) > 0 {
err = vd.errs 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. // 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. // 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 // 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 { if len(tag) == 0 || tag == skipValidationTag {
return nil return nil
} }
@ -522,7 +613,7 @@ func (v *Validate) VarWithValue(field interface{}, other interface{}, tag string
vd.top = otherVal vd.top = otherVal
vd.isPartial = false 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 { if len(vd.errs) > 0 {
err = vd.errs err = vd.errs

@ -2,6 +2,7 @@ package validator
import ( import (
"bytes" "bytes"
"context"
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/json" "encoding/json"
@ -776,7 +777,7 @@ func TestStructPartial(t *testing.T) {
// the following should all return no errors as everything is valid in // the following should all return no errors as everything is valid in
// the default state // the default state
errs := validate.StructPartial(tPartial, p1...) errs := validate.StructPartialCtx(context.Background(), tPartial, p1...)
Equal(t, errs, nil) Equal(t, errs, nil)
errs = validate.StructPartial(tPartial, p2...) errs = validate.StructPartial(tPartial, p2...)
@ -786,7 +787,7 @@ func TestStructPartial(t *testing.T) {
errs = validate.StructPartial(tPartial.SubSlice[0], p3...) errs = validate.StructPartial(tPartial.SubSlice[0], p3...)
Equal(t, errs, nil) Equal(t, errs, nil)
errs = validate.StructExcept(tPartial, p1...) errs = validate.StructExceptCtx(context.Background(), tPartial, p1...)
Equal(t, errs, nil) Equal(t, errs, nil)
errs = validate.StructExcept(tPartial, p2...) 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.Float", "Test.Float", "Float", "Float", "ltecsfield")
AssertError(t, errs, "Test.Array", "Test.Array", "Array", "Array", "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) NotEqual(t, errs, nil)
AssertError(t, errs, "", "", "", "", "ltecsfield") AssertError(t, errs, "", "", "", "", "ltecsfield")
@ -1827,7 +1828,7 @@ func TestSQLValue2Validation(t *testing.T) {
AssertError(t, errs, "", "", "", "", "required") AssertError(t, errs, "", "", "", "", "required")
val.Name = "Valid Name" val.Name = "Valid Name"
errs = validate.Var(val, "required") errs = validate.VarCtx(context.Background(), val, "required")
Equal(t, errs, nil) Equal(t, errs, nil)
val.Name = "errorme" val.Name = "errorme"
@ -5127,6 +5128,10 @@ func TestAddFunctions(t *testing.T) {
return true return true
} }
fnCtx := func(ctx context.Context, fl FieldLevel) bool {
return true
}
validate := New() validate := New()
errs := validate.RegisterValidation("new", fn) errs := validate.RegisterValidation("new", fn)
@ -5141,6 +5146,9 @@ func TestAddFunctions(t *testing.T) {
errs = validate.RegisterValidation("new", fn) errs = validate.RegisterValidation("new", fn)
Equal(t, errs, nil) 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") 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") errs = validate.Var(tm, "gt")
Equal(t, errs, nil) Equal(t, errs, nil)
t2 := time.Now().UTC() t2 := time.Now().UTC().Add(-time.Hour)
errs = validate.Var(t2, "gt") errs = validate.Var(t2, "gt")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
@ -5276,7 +5284,7 @@ func TestIsGte(t *testing.T) {
errs := validate.Var(t1, "gte") errs := validate.Var(t1, "gte")
Equal(t, errs, nil) Equal(t, errs, nil)
t2 := time.Now().UTC() t2 := time.Now().UTC().Add(-time.Hour)
errs = validate.Var(t2, "gte") errs = validate.Var(t2, "gte")
NotEqual(t, errs, nil) NotEqual(t, errs, nil)
@ -5323,7 +5331,7 @@ func TestIsLt(t *testing.T) {
i := true i := true
PanicMatches(t, func() { validate.Var(i, "lt") }, "Bad field type bool") 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") errs = validate.Var(t1, "lt")
Equal(t, errs, nil) Equal(t, errs, nil)
@ -5362,7 +5370,7 @@ func TestIsLte(t *testing.T) {
i := true i := true
PanicMatches(t, func() { validate.Var(i, "lte") }, "Bad field type bool") 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") errs := validate.Var(t1, "lte")
Equal(t, errs, nil) 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 following should all return no errors as everything is valid in
// the default state // the default state
errs := validate.StructFiltered(tPartial, p1) errs := validate.StructFilteredCtx(context.Background(), tPartial, p1)
Equal(t, errs, nil) Equal(t, errs, nil)
errs = validate.StructFiltered(tPartial, p2) errs = validate.StructFiltered(tPartial, p2)
@ -7079,3 +7087,35 @@ func TestFieldLevelName(t *testing.T) {
Equal(t, res5, "json5") Equal(t, res5, "json5")
Equal(t, alt5, "Map2") 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")
}

Loading…
Cancel
Save