From e005b06f11389f52def2c1ce03864b603ebebe44 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Thu, 16 Jul 2015 22:45:36 -0400 Subject: [PATCH] Add Field Tag caching now the benchmarks are getting back to awesome! --- validator.go | 131 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/validator.go b/validator.go index 4a71e03..db665d8 100644 --- a/validator.go +++ b/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, }