Add Field Tag caching

now the benchmarks are getting back to awesome!
pull/114/head
joeybloggs 9 years ago
parent e2a5b9881e
commit e005b06f11
  1. 131
      validator.go

@ -40,6 +40,7 @@ var (
timeType = reflect.TypeOf(time.Time{})
timePtrType = reflect.TypeOf(&time.Time{})
errsPool = &sync.Pool{New: newValidationErrors}
tagsCache = &tagCacheMap{m: map[string][]*tagCache{}}
)
// returns new ValidationErrors to the pool
@ -205,6 +206,29 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
}
}
type tagCache struct {
tagVals [][]string
isOrVal bool
}
type tagCacheMap struct {
lock sync.RWMutex
m map[string][]*tagCache
}
func (s *tagCacheMap) Get(key string) ([]*tagCache, bool) {
s.lock.RLock()
defer s.lock.RUnlock()
value, ok := s.m[key]
return value, ok
}
func (s *tagCacheMap) Set(key string, value []*tagCache) {
s.lock.Lock()
defer s.lock.Unlock()
s.m[key] = value
}
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string) {
if tag == skipValidationTag {
@ -296,21 +320,57 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
}
}
tags, isCached := tagsCache.Get(tag)
if !isCached {
tags = []*tagCache{}
for _, t := range strings.Split(tag, tagSeparator) {
if t == diveTag {
tags = append(tags, &tagCache{tagVals: [][]string{[]string{t}}})
break
}
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
cTag := &tagCache{isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))}
tags = append(tags, cTag)
var key string
var param string
for i, val := range orVals {
vals := strings.SplitN(val, tagKeySeparator, 2)
key = vals[0]
if len(key) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, name)))
}
if len(vals) > 1 {
param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
cTag.tagVals[i] = []string{key, param}
}
}
tagsCache.Set(tag, tags)
}
var dive bool
var diveSubTag string
for _, t := range strings.Split(tag, tagSeparator) {
if t == diveTag {
for _, cTag := range tags {
if cTag.tagVals[0][0] == diveTag {
dive = true
diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",")
break
}
// no use in checking tags if it's empty and is ok to be
// omitempty needs to be the first tag if you wish to use it
if t == omitempty {
if cTag.tagVals[0][0] == omitempty {
if !hasValue(topStruct, currentStruct, current, typ, kind, "") {
return
@ -318,26 +378,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
continue
}
var key string
var param string
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
if strings.Index(t, orSeparator) == -1 {
vals := strings.SplitN(t, tagKeySeparator, 2)
key = vals[0]
if len(key) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, name)))
}
if len(vals) > 1 {
param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
} else {
key = t
}
if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, key, param, name) {
if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, cTag, name) {
return
}
}
@ -387,45 +428,31 @@ func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Va
}
// validateField validates a field based on the provided key tag and param and return true if there is an error false if all ok
func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, key string, param string, name string) bool {
// check if key is orVals, it could be!
orVals := strings.Split(key, orSeparator)
// func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, key string, param string, name string) bool {
func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, currentType reflect.Type, currentKind reflect.Kind, errPrefix string, errs ValidationErrors, cTag *tagCache, name string) bool {
if len(orVals) > 1 {
if cTag.isOrVal {
var errTag string
errTag := ""
for _, val := range orVals {
vals := strings.SplitN(val, tagKeySeparator, 2)
if len(vals[0]) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, name)))
}
param := ""
if len(vals) > 1 {
param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1)
}
for _, val := range cTag.tagVals {
// validate and keep track!
valFunc, ok := v.config.ValidationFuncs[vals[0]]
valFunc, ok := v.config.ValidationFuncs[val[0]]
if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
}
if valFunc(topStruct, currentStruct, current, currentType, currentKind, param) {
if valFunc(topStruct, currentStruct, current, currentType, currentKind, val[1]) {
return false
}
errTag += orSeparator + vals[0]
errTag += orSeparator + val[0]
}
errs[errPrefix+name] = &FieldError{
Field: name,
Tag: errTag[1:],
Value: current.Interface(),
Param: param,
Type: currentType,
Kind: currentKind,
}
@ -433,20 +460,20 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.
return true
}
valFunc, ok := v.config.ValidationFuncs[key]
valFunc, ok := v.config.ValidationFuncs[cTag.tagVals[0][0]]
if !ok {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name)))
}
if valFunc(topStruct, currentStruct, current, currentType, currentKind, param) {
if valFunc(topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) {
return false
}
errs[errPrefix+name] = &FieldError{
Field: name,
Tag: key,
Tag: cTag.tagVals[0][0],
Value: current.Interface(),
Param: param,
Param: cTag.tagVals[0][1],
Type: currentType,
Kind: currentKind,
}

Loading…
Cancel
Save