You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
622 lines
20 KiB
622 lines
20 KiB
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"
|
|
skipValidationTag = "-"
|
|
diveTag = "dive"
|
|
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 instacne 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 another name from the
|
|
// StructField eg. the JSON 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 {
|
|
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(context.Background(), 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()
|
|
|
|
if fields != nil {
|
|
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 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) 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
|
|
}
|
|
|
|
// find cached tag
|
|
ctag, ok := v.tagCache.Get(tag)
|
|
if !ok {
|
|
v.tagCache.lock.Lock()
|
|
defer v.tagCache.lock.Unlock()
|
|
|
|
// could have been multiple trying to access, but once first is done this ensures tag
|
|
// isn't parsed again.
|
|
ctag, ok = v.tagCache.Get(tag)
|
|
if !ok {
|
|
ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false)
|
|
v.tagCache.Set(tag, ctag)
|
|
}
|
|
}
|
|
|
|
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 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) 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
|
|
}
|
|
|
|
// find cached tag
|
|
ctag, ok := v.tagCache.Get(tag)
|
|
if !ok {
|
|
v.tagCache.lock.Lock()
|
|
defer v.tagCache.lock.Unlock()
|
|
|
|
// could have been multiple trying to access, but once first is done this ensures tag
|
|
// isn't parsed again.
|
|
ctag, ok = v.tagCache.Get(tag)
|
|
if !ok {
|
|
ctag, _ = v.parseFieldTagsRecursive(tag, "", "", false)
|
|
v.tagCache.Set(tag, ctag)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|