Merge pull request #62 from bluesuncorp/v5

Merge Latest v5 changes into v6-development
pull/115/head
Dean Karn 9 years ago
commit 76bccce51f
  1. 272
      validator.go
  2. 127
      validator_test.go

@ -14,6 +14,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"sync"
"time" "time"
"unicode" "unicode"
) )
@ -29,6 +30,68 @@ const (
structErrMsg = "Struct:%s\n" structErrMsg = "Struct:%s\n"
) )
type cachedTags struct {
keyVals [][]string
isOrVal bool
}
type cachedField struct {
index int
name string
tags []*cachedTags
tag string
kind reflect.Kind
typ reflect.Type
isTime bool
}
type cachedStruct struct {
children int
name string
kind reflect.Kind
fields []*cachedField
}
type structsCacheMap struct {
lock sync.RWMutex
m map[reflect.Type]*cachedStruct
}
func (s *structsCacheMap) Get(key reflect.Type) (*cachedStruct, bool) {
s.lock.RLock()
defer s.lock.RUnlock()
value, ok := s.m[key]
return value, ok
}
func (s *structsCacheMap) Set(key reflect.Type, value *cachedStruct) {
s.lock.Lock()
defer s.lock.Unlock()
s.m[key] = value
}
var structCache = &structsCacheMap{m: map[reflect.Type]*cachedStruct{}}
type fieldsCacheMap struct {
lock sync.RWMutex
m map[string][]*cachedTags
}
func (s *fieldsCacheMap) Get(key string) ([]*cachedTags, bool) {
s.lock.RLock()
defer s.lock.RUnlock()
value, ok := s.m[key]
return value, ok
}
func (s *fieldsCacheMap) Set(key string, value []*cachedTags) {
s.lock.Lock()
defer s.lock.Unlock()
s.m[key] = value
}
var fieldsCache = &fieldsCacheMap{m: map[string][]*cachedTags{}}
// FieldError contains a single field's validation error along // FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation // with other properties that may be needed for error message creation
type FieldError struct { type FieldError struct {
@ -132,12 +195,14 @@ func New(tagName string, funcs map[string]Func) *Validate {
// SetTag sets tagName of the Validator to one of your choosing after creation // SetTag sets tagName of the Validator to one of your choosing after creation
// perhaps to dodge a tag name conflict in a specific section of code // perhaps to dodge a tag name conflict in a specific section of code
// NOTE: this method is not thread-safe
func (v *Validate) SetTag(tagName string) { func (v *Validate) SetTag(tagName string) {
v.tagName = tagName v.tagName = tagName
} }
// AddFunction adds a validation Func to a Validate's map of validators denoted by the key // AddFunction adds a validation Func to a Validate's map of validators denoted by the key
// NOTE: if the key already exists, it will get replaced. // NOTE: if the key already exists, it will get replaced.
// NOTE: this method is not thread-safe
func (v *Validate) AddFunction(key string, f Func) error { func (v *Validate) AddFunction(key string, f Func) error {
if len(key) == 0 { if len(key) == 0 {
@ -176,7 +241,23 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
} }
structType := reflect.TypeOf(s) structType := reflect.TypeOf(s)
structName := structType.Name()
var structName string
var numFields int
var cs *cachedStruct
var isCached bool
cs, isCached = structCache.Get(structType)
if isCached {
structName = cs.name
numFields = cs.children
} else {
structName = structType.Name()
numFields = structValue.NumField()
cs = &cachedStruct{name: structName, children: numFields}
structCache.Set(structType, cs)
}
validationErrors := &StructErrors{ validationErrors := &StructErrors{
Struct: structName, Struct: structName,
@ -184,39 +265,66 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
StructErrors: map[string]*StructErrors{}, StructErrors: map[string]*StructErrors{},
} }
var numFields = structValue.NumField()
for i := 0; i < numFields; i++ { for i := 0; i < numFields; i++ {
valueField := structValue.Field(i) var valueField reflect.Value
typeField := structType.Field(i) var cField *cachedField
var typeField reflect.StructField
if isCached {
cField = cs.fields[i]
valueField = structValue.Field(cField.index)
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
valueField = valueField.Elem()
}
} else {
valueField = structValue.Field(i)
if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
valueField = valueField.Elem() valueField = valueField.Elem()
} }
tag := typeField.Tag.Get(v.tagName) typeField = structType.Field(i)
if tag == noValidationTag { cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName)}
if cField.tag == noValidationTag {
cs.children--
continue continue
} }
// if no validation and not a struct (which may containt fields for validation) // if no validation and not a struct (which may containt fields for validation)
if tag == "" && ((valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface) || valueField.Type() == reflect.TypeOf(time.Time{})) { if cField.tag == "" && ((valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface) || valueField.Type() == reflect.TypeOf(time.Time{})) {
cs.children--
continue continue
} }
switch valueField.Kind() { cField.name = typeField.Name
cField.kind = valueField.Kind()
cField.typ = valueField.Type()
}
// this can happen if the first cache value was nil
// but the second actually has a value
if cField.kind == reflect.Ptr {
cField.kind = valueField.Kind()
}
switch cField.kind {
case reflect.Struct, reflect.Interface: case reflect.Struct, reflect.Interface:
if !unicode.IsUpper(rune(typeField.Name[0])) { if !unicode.IsUpper(rune(cField.name[0])) {
cs.children--
continue continue
} }
if valueField.Type() == reflect.TypeOf(time.Time{}) { if cField.isTime || valueField.Type() == reflect.TypeOf(time.Time{}) {
cField.isTime = true
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
fieldError = nil fieldError = nil
@ -224,12 +332,13 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
} else { } else {
if strings.Contains(tag, structOnlyTag) { if strings.Contains(cField.tag, structOnlyTag) {
cs.children--
continue continue
} }
if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil { if structErrors := v.structRecursive(top, valueField.Interface(), valueField.Interface()); structErrors != nil {
validationErrors.StructErrors[typeField.Name] = structErrors validationErrors.StructErrors[cField.name] = structErrors
// free up memory map no longer needed // free up memory map no longer needed
structErrors = nil structErrors = nil
} }
@ -237,12 +346,16 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter
default: default:
if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), typeField.Name, tag); fieldError != nil { if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil {
validationErrors.Errors[fieldError.Field] = fieldError validationErrors.Errors[fieldError.Field] = fieldError
// free up memory reference // free up memory reference
fieldError = nil fieldError = nil
} }
} }
if !isCached {
cs.fields = append(cs.fields, cField)
}
} }
if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 { if len(validationErrors.Errors) == 0 && len(validationErrors.StructErrors) == 0 {
@ -261,10 +374,13 @@ func (v *Validate) Field(f interface{}, tag string) *FieldError {
// FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors // FieldWithValue allows validation of a single field, possibly even against another fields value, still using tag style validation to check multiple errors
func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError { func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError {
return v.fieldWithNameAndValue(nil, val, f, "", tag) return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil)
} }
func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, name string, tag string) *FieldError { func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, tag string, name string, isSingleField bool, cacheField *cachedField) *FieldError {
var cField *cachedField
var isCached bool
// This is a double check if coming from validate.Struct but need to be here in case function is called directly // This is a double check if coming from validate.Struct but need to be here in case function is called directly
if tag == noValidationTag { if tag == noValidationTag {
@ -275,87 +391,112 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f
return nil return nil
} }
if cacheField == nil {
valueField := reflect.ValueOf(f) valueField := reflect.ValueOf(f)
fieldKind := valueField.Kind()
if fieldKind == reflect.Ptr && !valueField.IsNil() { if valueField.Kind() == reflect.Ptr && !valueField.IsNil() {
return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), name, tag) valueField = valueField.Elem()
f = valueField.Interface()
} }
fieldType := valueField.Type() cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()}
} else {
cField = cacheField
}
switch fieldKind { switch cField.kind {
case reflect.Struct, reflect.Interface, reflect.Invalid: case reflect.Struct, reflect.Interface, reflect.Invalid:
if fieldType != reflect.TypeOf(time.Time{}) { if cField.typ != reflect.TypeOf(time.Time{}) {
panic("Invalid field passed to ValidateFieldWithTag") panic("Invalid field passed to ValidateFieldWithTag")
} }
} }
var valErr *FieldError if len(cField.tags) == 0 {
var err error
valTags := strings.Split(tag, tagSeparator)
for _, valTag := range valTags { if isSingleField {
cField.tags, isCached = fieldsCache.Get(tag)
}
orVals := strings.Split(valTag, orSeparator) if !isCached {
if len(orVals) > 1 { for _, t := range strings.Split(tag, tagSeparator) {
errTag := "" orVals := strings.Split(t, orSeparator)
cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))}
cField.tags = append(cField.tags, cTag)
for _, val := range orVals { for i, val := range orVals {
vals := strings.Split(val, tagKeySeparator)
valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, val) key := strings.TrimSpace(vals[0])
if err == nil { if len(key) == 0 {
return nil panic(fmt.Sprintf("Invalid validation tag on field %s", name))
}
param := ""
if len(vals) > 1 {
param = strings.TrimSpace(vals[1])
} }
errTag += orSeparator + valErr.Tag cTag.keyVals[i] = []string{key, param}
}
}
if isSingleField {
fieldsCache.Set(cField.tag, cField.tags)
}
}
} }
errTag = strings.TrimLeft(errTag, orSeparator) var fieldErr *FieldError
var err error
valErr.Tag = errTag for _, cTag := range cField.tags {
valErr.Kind = fieldKind
return valErr if cTag.isOrVal {
}
errTag := ""
if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, valTag); err != nil { for _, val := range cTag.keyVals {
valErr.Kind = valueField.Kind() fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, val[0], val[1], name)
valErr.Type = fieldType
return valErr if err == nil {
return nil
} }
errTag += orSeparator + fieldErr.Tag
} }
return nil errTag = strings.TrimLeft(errTag, orSeparator)
fieldErr.Tag = errTag
fieldErr.Kind = cField.kind
fieldErr.Type = cField.typ
return fieldErr
} }
func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, name string, valTag string) (*FieldError, error) { if fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, cTag.keyVals[0][0], cTag.keyVals[0][1], name); err != nil {
vals := strings.Split(valTag, tagKeySeparator) fieldErr.Kind = cField.kind
key := strings.TrimSpace(vals[0]) fieldErr.Type = cField.typ
if len(key) == 0 { return fieldErr
panic(fmt.Sprintf("Invalid validation tag on field %s", name)) }
} }
valErr := &FieldError{ return nil
Field: name,
Tag: key,
Value: f,
Param: "",
} }
func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string) (*FieldError, error) {
// OK to continue because we checked it's existance before getting into this loop // OK to continue because we checked it's existance before getting into this loop
if key == omitempty { if key == omitempty {
return valErr, nil return nil, nil
} }
valFunc, ok := v.validationFuncs[key] valFunc, ok := v.validationFuncs[key]
@ -363,15 +504,14 @@ func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{
panic(fmt.Sprintf("Undefined validation function on field %s", name)) panic(fmt.Sprintf("Undefined validation function on field %s", name))
} }
param := "" if err := valFunc(val, current, f, param); err {
if len(vals) > 1 { return nil, nil
param = strings.TrimSpace(vals[1]) } else {
} return &FieldError{
Field: name,
if err := valFunc(val, current, f, param); !err { Tag: key,
valErr.Param = param Value: f,
return valErr, errors.New(key) Param: param,
}, errors.New(key)
} }
return valErr, nil
} }

@ -2325,17 +2325,42 @@ func BenchmarkValidateField(b *testing.B) {
} }
} }
func BenchmarkValidateStruct(b *testing.B) { func BenchmarkValidateStructSimple(b *testing.B) {
// type Inner struct { type Foo struct {
StringValue string `validate:"min=5,max=10"`
IntValue int `validate:"min=5,max=10"`
}
validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
for n := 0; n < b.N; n++ {
validate.Struct(validFoo)
validate.Struct(invalidFoo)
}
}
// func BenchmarkTemplateParallelSimple(b *testing.B) {
// type Foo struct {
// StringValue string `validate:"min=5,max=10"`
// IntValue int `validate:"min=5,max=10"`
// } // }
// type Test struct { // validFoo := &Foo{StringValue: "Foobar", IntValue: 7}
// StringVal string `bson:"required,lt=10"` // invalidFoo := &Foo{StringValue: "Fo", IntValue: 3}
// Int64Val int64 `bson:"gt=0,lt=10"`
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// validate.Struct(validFoo)
// validate.Struct(invalidFoo)
// }
// })
// } // }
func BenchmarkValidateStructLarge(b *testing.B) {
tFail := &TestString{ tFail := &TestString{
Required: "", Required: "",
Len: "", Len: "",
@ -2360,12 +2385,96 @@ func BenchmarkValidateStruct(b *testing.B) {
}, },
} }
// t := &Test{ tSuccess := &TestString{
// StringVal: "test", Required: "Required",
// Int64Val: 5, Len: "length==10",
// } Min: "min=1",
Max: "1234567890",
MinMax: "12345",
Lt: "012345678",
Lte: "0123456789",
Gt: "01234567890",
Gte: "0123456789",
OmitEmpty: "",
Sub: &SubTest{
Test: "1",
},
SubIgnore: &SubTest{
Test: "",
},
Anonymous: struct {
A string `validate:"required"`
}{
A: "1",
},
Iface: &Impl{
F: "123",
},
}
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
validate.Struct(tSuccess)
validate.Struct(tFail) validate.Struct(tFail)
} }
} }
// func BenchmarkTemplateParallelLarge(b *testing.B) {
// tFail := &TestString{
// Required: "",
// Len: "",
// Min: "",
// Max: "12345678901",
// MinMax: "",
// Lt: "0123456789",
// Lte: "01234567890",
// Gt: "1",
// Gte: "1",
// OmitEmpty: "12345678901",
// Sub: &SubTest{
// Test: "",
// },
// Anonymous: struct {
// A string `validate:"required"`
// }{
// A: "",
// },
// Iface: &Impl{
// F: "12",
// },
// }
// tSuccess := &TestString{
// Required: "Required",
// Len: "length==10",
// Min: "min=1",
// Max: "1234567890",
// MinMax: "12345",
// Lt: "012345678",
// Lte: "0123456789",
// Gt: "01234567890",
// Gte: "0123456789",
// OmitEmpty: "",
// Sub: &SubTest{
// Test: "1",
// },
// SubIgnore: &SubTest{
// Test: "",
// },
// Anonymous: struct {
// A string `validate:"required"`
// }{
// A: "1",
// },
// Iface: &Impl{
// F: "123",
// },
// }
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// validate.Struct(tSuccess)
// validate.Struct(tFail)
// }
// })
// }

Loading…
Cancel
Save