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
------
###### 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

@ -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: {},

@ -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
}

@ -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.

@ -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...))

@ -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[<locale>]map[<tag>]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

@ -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")
}

Loading…
Cancel
Save