5849898283
Unit tests functional and coverage back to 100% Add more routes to dictionary, add more credentials, add default port 5554, rename cameradar logs ENV variable, improve unit test readability, remove tmp file
601 lines
20 KiB
Go
601 lines
20 KiB
Go
package validator
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
ut "github.com/go-playground/universal-translator"
|
|
)
|
|
|
|
const (
|
|
defaultTagName = "validate"
|
|
utf8HexComma = "0x2C"
|
|
utf8Pipe = "0x7C"
|
|
tagSeparator = ","
|
|
orSeparator = "|"
|
|
tagKeySeparator = "="
|
|
structOnlyTag = "structonly"
|
|
noStructLevelTag = "nostructlevel"
|
|
omitempty = "omitempty"
|
|
isdefault = "isdefault"
|
|
skipValidationTag = "-"
|
|
diveTag = "dive"
|
|
keysTag = "keys"
|
|
endKeysTag = "endkeys"
|
|
requiredTag = "required"
|
|
namespaceSeparator = "."
|
|
leftBracket = "["
|
|
rightBracket = "]"
|
|
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
|
|
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
|
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
|
|
)
|
|
|
|
var (
|
|
timeType = reflect.TypeOf(time.Time{})
|
|
defaultCField = &cField{namesEqual: true}
|
|
)
|
|
|
|
// FilterFunc is the type used to filter fields using
|
|
// StructFiltered(...) function.
|
|
// returning true results in the field being filtered/skiped from
|
|
// validation
|
|
type FilterFunc func(ns []byte) bool
|
|
|
|
// CustomTypeFunc allows for overriding or adding custom field type handler functions
|
|
// field = field value of the type to return a value to be validated
|
|
// example Valuer from sql drive see https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29
|
|
type CustomTypeFunc func(field reflect.Value) interface{}
|
|
|
|
// TagNameFunc allows for adding of a custom tag name parser
|
|
type TagNameFunc func(field reflect.StructField) string
|
|
|
|
// Validate contains the validator settings and cache
|
|
type Validate struct {
|
|
tagName string
|
|
pool *sync.Pool
|
|
hasCustomFuncs bool
|
|
hasTagNameFunc bool
|
|
tagNameFunc TagNameFunc
|
|
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
|
|
customFuncs map[reflect.Type]CustomTypeFunc
|
|
aliases map[string]string
|
|
validations map[string]FuncCtx
|
|
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
|
|
tagCache *tagCache
|
|
structCache *structCache
|
|
}
|
|
|
|
// New returns a new instance of 'validate' with sane defaults.
|
|
func New() *Validate {
|
|
|
|
tc := new(tagCache)
|
|
tc.m.Store(make(map[string]*cTag))
|
|
|
|
sc := new(structCache)
|
|
sc.m.Store(make(map[reflect.Type]*cStruct))
|
|
|
|
v := &Validate{
|
|
tagName: defaultTagName,
|
|
aliases: make(map[string]string, len(bakedInAliases)),
|
|
validations: make(map[string]FuncCtx, len(bakedInValidators)),
|
|
tagCache: tc,
|
|
structCache: sc,
|
|
}
|
|
|
|
// must copy alias validators for separate validations to be used in each validator instance
|
|
for k, val := range bakedInAliases {
|
|
v.RegisterAlias(k, val)
|
|
}
|
|
|
|
// 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 always be valid
|
|
_ = v.registerValidation(k, wrapFunc(val), true)
|
|
}
|
|
|
|
v.pool = &sync.Pool{
|
|
New: func() interface{} {
|
|
return &validate{
|
|
v: v,
|
|
ns: make([]byte, 0, 64),
|
|
actualNs: make([]byte, 0, 64),
|
|
misc: make([]byte, 32),
|
|
}
|
|
},
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
// SetTagName allows for changing of the default tag name of 'validate'
|
|
func (v *Validate) SetTagName(name string) {
|
|
v.tagName = name
|
|
}
|
|
|
|
// RegisterTagNameFunc registers a function to get alternate names for StructFields.
|
|
//
|
|
// eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names:
|
|
//
|
|
// validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
|
// name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
|
// if name == "-" {
|
|
// return ""
|
|
// }
|
|
// return name
|
|
// })
|
|
func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) {
|
|
v.tagNameFunc = fn
|
|
v.hasTagNameFunc = true
|
|
}
|
|
|
|
// RegisterValidation adds a validation with the given tag
|
|
//
|
|
// NOTES:
|
|
// - 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 FuncCtx, bakedIn bool) error {
|
|
|
|
if len(tag) == 0 {
|
|
return errors.New("Function Key cannot be empty")
|
|
}
|
|
|
|
if fn == nil {
|
|
return errors.New("Function cannot be empty")
|
|
}
|
|
|
|
_, ok := restrictedTags[tag]
|
|
|
|
if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) {
|
|
panic(fmt.Sprintf(restrictedTagErr, tag))
|
|
}
|
|
|
|
v.validations[tag] = fn
|
|
|
|
return nil
|
|
}
|
|
|
|
// RegisterAlias registers a mapping of a single validation tag that
|
|
// defines a common or complex set of validation(s) to simplify adding validation
|
|
// to structs.
|
|
//
|
|
// NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation
|
|
func (v *Validate) RegisterAlias(alias, tags string) {
|
|
|
|
_, ok := restrictedTags[alias]
|
|
|
|
if ok || strings.ContainsAny(alias, restrictedTagChars) {
|
|
panic(fmt.Sprintf(restrictedAliasErr, alias))
|
|
}
|
|
|
|
v.aliases[alias] = tags
|
|
}
|
|
|
|
// RegisterStructValidation registers a StructLevelFunc against a number of types.
|
|
//
|
|
// NOTE:
|
|
// - 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.
|
|
//
|
|
// NOTE:
|
|
// - 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]StructLevelFuncCtx)
|
|
}
|
|
|
|
for _, t := range types {
|
|
tv := reflect.ValueOf(t)
|
|
if tv.Kind() == reflect.Ptr {
|
|
t = reflect.Indirect(tv).Interface()
|
|
}
|
|
|
|
v.structLevelFuncs[reflect.TypeOf(t)] = fn
|
|
}
|
|
}
|
|
|
|
// RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types
|
|
//
|
|
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
|
|
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
|
|
|
|
if v.customFuncs == nil {
|
|
v.customFuncs = make(map[reflect.Type]CustomTypeFunc)
|
|
}
|
|
|
|
for _, t := range types {
|
|
v.customFuncs[reflect.TypeOf(t)] = fn
|
|
}
|
|
|
|
v.hasCustomFuncs = true
|
|
}
|
|
|
|
// RegisterTranslation registers translations against the provided tag.
|
|
func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) {
|
|
|
|
if v.transTagFunc == nil {
|
|
v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc)
|
|
}
|
|
|
|
if err = registerFn(trans); err != nil {
|
|
return
|
|
}
|
|
|
|
m, ok := v.transTagFunc[trans]
|
|
if !ok {
|
|
m = make(map[string]TranslationFunc)
|
|
v.transTagFunc[trans] = m
|
|
}
|
|
|
|
m[tag] = translationFn
|
|
|
|
return
|
|
}
|
|
|
|
// Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified.
|
|
//
|
|
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
|
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
|
|
func (v *Validate) Struct(s interface{}) 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
|
|
|
|
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
|
val = val.Elem()
|
|
}
|
|
|
|
if val.Kind() != reflect.Struct || val.Type() == timeType {
|
|
return &InvalidValidationError{Type: reflect.TypeOf(s)}
|
|
}
|
|
|
|
// good to validate
|
|
vd := v.pool.Get().(*validate)
|
|
vd.top = top
|
|
vd.isPartial = false
|
|
// vd.hasExcludes = false // only need to reset in StructPartial and StructExcept
|
|
|
|
vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil)
|
|
|
|
if len(vd.errs) > 0 {
|
|
err = vd.errs
|
|
vd.errs = nil
|
|
}
|
|
|
|
v.pool.Put(vd)
|
|
|
|
return
|
|
}
|
|
|
|
// StructFiltered validates a structs exposed fields, that pass the FilterFunc check and automatically validates
|
|
// nested structs, unless otherwise specified.
|
|
//
|
|
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
|
|
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
|
|
func (v *Validate) StructFiltered(s interface{}, fn FilterFunc) 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
|
|
|
|
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
|
val = val.Elem()
|
|
}
|
|
|
|
if val.Kind() != reflect.Struct || val.Type() == timeType {
|
|
return &InvalidValidationError{Type: reflect.TypeOf(s)}
|
|
}
|
|
|
|
// good to validate
|
|
vd := v.pool.Get().(*validate)
|
|
vd.top = top
|
|
vd.isPartial = true
|
|
vd.ffn = fn
|
|
// vd.hasExcludes = false // only need to reset in StructPartial and StructExcept
|
|
|
|
vd.validateStruct(ctx, top, val, val.Type(), vd.ns[0:0], vd.actualNs[0:0], nil)
|
|
|
|
if len(vd.errs) > 0 {
|
|
err = vd.errs
|
|
vd.errs = nil
|
|
}
|
|
|
|
v.pool.Put(vd)
|
|
|
|
return
|
|
}
|
|
|
|
// StructPartial validates the fields passed in only, ignoring all others.
|
|
// Fields may be provided in a namespaced fashion relative to the struct provided
|
|
// eg. NestedStruct.Field or NestedArrayField[0].Struct.Name
|
|
//
|
|
// 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) 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
|
|
|
|
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
|
val = val.Elem()
|
|
}
|
|
|
|
if val.Kind() != reflect.Struct || val.Type() == timeType {
|
|
return &InvalidValidationError{Type: reflect.TypeOf(s)}
|
|
}
|
|
|
|
// good to validate
|
|
vd := v.pool.Get().(*validate)
|
|
vd.top = top
|
|
vd.isPartial = true
|
|
vd.ffn = nil
|
|
vd.hasExcludes = false
|
|
vd.includeExclude = make(map[string]struct{})
|
|
|
|
typ := val.Type()
|
|
name := typ.Name()
|
|
|
|
for _, k := range fields {
|
|
|
|
flds := strings.Split(k, namespaceSeparator)
|
|
if len(flds) > 0 {
|
|
|
|
vd.misc = append(vd.misc[0:0], name...)
|
|
vd.misc = append(vd.misc, '.')
|
|
|
|
for _, s := range flds {
|
|
|
|
idx := strings.Index(s, leftBracket)
|
|
|
|
if idx != -1 {
|
|
for idx != -1 {
|
|
vd.misc = append(vd.misc, s[:idx]...)
|
|
vd.includeExclude[string(vd.misc)] = struct{}{}
|
|
|
|
idx2 := strings.Index(s, rightBracket)
|
|
idx2++
|
|
vd.misc = append(vd.misc, s[idx:idx2]...)
|
|
vd.includeExclude[string(vd.misc)] = struct{}{}
|
|
s = s[idx2:]
|
|
idx = strings.Index(s, leftBracket)
|
|
}
|
|
} else {
|
|
|
|
vd.misc = append(vd.misc, s...)
|
|
vd.includeExclude[string(vd.misc)] = struct{}{}
|
|
}
|
|
|
|
vd.misc = append(vd.misc, '.')
|
|
}
|
|
}
|
|
}
|
|
|
|
vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil)
|
|
|
|
if len(vd.errs) > 0 {
|
|
err = vd.errs
|
|
vd.errs = nil
|
|
}
|
|
|
|
v.pool.Put(vd)
|
|
|
|
return
|
|
}
|
|
|
|
// StructExcept validates all fields except the ones passed in.
|
|
// 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) 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
|
|
|
|
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
|
val = val.Elem()
|
|
}
|
|
|
|
if val.Kind() != reflect.Struct || val.Type() == timeType {
|
|
return &InvalidValidationError{Type: reflect.TypeOf(s)}
|
|
}
|
|
|
|
// good to validate
|
|
vd := v.pool.Get().(*validate)
|
|
vd.top = top
|
|
vd.isPartial = true
|
|
vd.ffn = nil
|
|
vd.hasExcludes = true
|
|
vd.includeExclude = make(map[string]struct{})
|
|
|
|
typ := val.Type()
|
|
name := typ.Name()
|
|
|
|
for _, key := range fields {
|
|
|
|
vd.misc = vd.misc[0:0]
|
|
|
|
if len(name) > 0 {
|
|
vd.misc = append(vd.misc, name...)
|
|
vd.misc = append(vd.misc, '.')
|
|
}
|
|
|
|
vd.misc = append(vd.misc, key...)
|
|
vd.includeExclude[string(vd.misc)] = struct{}{}
|
|
}
|
|
|
|
vd.validateStruct(ctx, top, val, typ, vd.ns[0:0], vd.actualNs[0:0], nil)
|
|
|
|
if len(vd.errs) > 0 {
|
|
err = vd.errs
|
|
vd.errs = nil
|
|
}
|
|
|
|
v.pool.Put(vd)
|
|
|
|
return
|
|
}
|
|
|
|
// Var validates a single variable using tag style validation.
|
|
// 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 unforeseen 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) 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 unforeseen 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
|
|
}
|
|
|
|
ctag := v.fetchCacheTag(tag)
|
|
val := reflect.ValueOf(field)
|
|
vd := v.pool.Get().(*validate)
|
|
vd.top = val
|
|
vd.isPartial = false
|
|
vd.traverseField(ctx, val, val, vd.ns[0:0], vd.actualNs[0:0], defaultCField, ctag)
|
|
|
|
if len(vd.errs) > 0 {
|
|
err = vd.errs
|
|
vd.errs = nil
|
|
}
|
|
v.pool.Put(vd)
|
|
return
|
|
}
|
|
|
|
// VarWithValue validates a single variable, against another variable/field's value using tag style validation
|
|
// 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 unforeseen 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) 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 unforeseen 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
|
|
}
|
|
ctag := v.fetchCacheTag(tag)
|
|
otherVal := reflect.ValueOf(other)
|
|
vd := v.pool.Get().(*validate)
|
|
vd.top = otherVal
|
|
vd.isPartial = false
|
|
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
|
|
vd.errs = nil
|
|
}
|
|
v.pool.Put(vd)
|
|
return
|
|
}
|