From 22d031deb0c479cecb84c020acf4bc48406376e0 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 21:39:21 -0400 Subject: [PATCH 01/10] add struct caching, significant speedup even in a simple test NOTE: validating a single fields speed however was reduced and more memory allocations, need to correct this before marking caching as complete. --- validator.go | 326 ++++++++++++++++++++++++++++++++++++++-------- validator_test.go | 56 ++++---- 2 files changed, 306 insertions(+), 76 deletions(-) diff --git a/validator.go b/validator.go index 0ae7faf..2bf65b5 100644 --- a/validator.go +++ b/validator.go @@ -162,6 +162,31 @@ func (v *Validate) Struct(s interface{}) *StructErrors { return v.structRecursive(s, s, s) } +type cacheTags struct { + keyVals [][]string + isOrVal bool +} + +type cachedField struct { + index int + name string + tags []*cacheTags + // tags [][]string + tag string + kind reflect.Kind + typ reflect.Type + isTime bool +} + +type cachedStruct struct { + children int + name string + kind reflect.Kind + fields []*cachedField +} + +var cache = map[reflect.Type]*cachedStruct{} + // structRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}) *StructErrors { @@ -176,7 +201,23 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter } structType := reflect.TypeOf(s) - structName := structType.Name() + + var structName string + var numFields int + + // fmt.Println(structType) + + cs, ok := cache[structType] + + if ok { + structName = cs.name + numFields = cs.children + } else { + structName = structType.Name() + numFields = structValue.NumField() + cs = &cachedStruct{name: structName, children: numFields} + cache[structType] = cs + } validationErrors := &StructErrors{ Struct: structName, @@ -184,39 +225,88 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter StructErrors: map[string]*StructErrors{}, } - var numFields = structValue.NumField() - for i := 0; i < numFields; i++ { - valueField := structValue.Field(i) - typeField := structType.Field(i) + var valueField reflect.Value + var cField *cachedField + // var fName string + // var tag string - if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - valueField = valueField.Elem() - } + var typeField reflect.StructField + + // if ok { + // cField = cs.fields[i] + // valueField = structValue.Field(cField.index) + // } else { + // valueField = structValue.Field(i) + // } + + // if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { + // valueField = valueField.Elem() + // } + + if ok { + cField = cs.fields[i] + // fName = cField.name + // tag = cField.tag + 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() { + valueField = valueField.Elem() + } + + typeField = structType.Field(i) + + cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName)} + + // tag = typeField.Tag.Get(v.tagName) + + if cField.tag == noValidationTag { + cs.children-- + continue + } - tag := typeField.Tag.Get(v.tagName) + // if no validation and not a struct (which may containt fields for validation) + if cField.tag == "" && ((valueField.Kind() != reflect.Struct && valueField.Kind() != reflect.Interface) || valueField.Type() == reflect.TypeOf(time.Time{})) { + cs.children-- + continue + } - if tag == noValidationTag { - continue + // fName = typeField.Name + cField.name = typeField.Name + cField.kind = valueField.Kind() + cField.typ = valueField.Type() + // cField = &cachedField{index: i, name: typeField.Name, tag: tag, kind: valueField.Kind()} + // cs.fields = append(cs.fields, cField) } - // 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{})) { - continue + // 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 valueField.Kind() { + switch cField.kind { case reflect.Struct, reflect.Interface: - if !unicode.IsUpper(rune(typeField.Name[0])) { + if !unicode.IsUpper(rune(cField.name[0])) { + cs.children-- + // cs.fields = cs.fields[:len(cs.fields)] 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, cField); fieldError != nil { validationErrors.Errors[fieldError.Field] = fieldError // free up memory reference fieldError = nil @@ -224,24 +314,34 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter } else { - if strings.Contains(tag, structOnlyTag) { + if strings.Contains(cField.tag, structOnlyTag) { + cs.children-- + // cs.fields = cs.fields[:len(cs.fields)] continue } 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 structErrors = nil } } + // cs.fields = append(cs.fields, cField) + 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, cField); fieldError != nil { validationErrors.Errors[fieldError.Field] = fieldError // free up memory reference fieldError = nil } + + // cs.fields = append(cs.fields, cField) + } + + if !ok { + cs.fields = append(cs.fields, cField) } } @@ -261,10 +361,14 @@ 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 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, "", 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, cacheField *cachedField) *FieldError { + + // var fieldType reflect.Type + // var fieldKind reflect.Kind + var cField *cachedField // This is a double check if coming from validate.Struct but need to be here in case function is called directly if tag == noValidationTag { @@ -275,39 +379,118 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f return nil } - valueField := reflect.ValueOf(f) - fieldKind := valueField.Kind() + if cacheField == nil { + valueField := reflect.ValueOf(f) - if fieldKind == reflect.Ptr && !valueField.IsNil() { - return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), name, tag) - } + cField = &cachedField{name: name, kind: valueField.Kind()} + // fieldKind = valueField.Kind() - fieldType := valueField.Type() + if cField.kind == reflect.Ptr && !valueField.IsNil() { + return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), tag, name, cacheField) + } - switch fieldKind { + cField.typ = valueField.Type() + // cField.tags = make([][]string, 0) + // fieldType = valueField.Type() + // for _, t := range strings.Split(tag, tagSeparator) { + + // vals := strings.Split(t, tagKeySeparator) + + // key := strings.TrimSpace(vals[0]) + + // if len(key) == 0 { + // panic(fmt.Sprintf("Invalid validation tag on field %s", name)) + // } + + // param := "" + // if len(vals) > 1 { + // param = strings.TrimSpace(vals[1]) + // } + + // // for vals := range strings.Split(t, tagKeySeparator) { + // cField.tags = append(cField.tags, []string{key, param}) + // // } + // // vals := strings.Split(valTag, tagKeySeparator) + // // key := strings.TrimSpace(vals[0]) + // } + } else { + cField = cacheField + // fieldType = cacheField.typ + // fieldKind = cacheField.kind + } + + switch cField.kind { 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") } } + if len(cField.tags) == 0 { + for _, t := range strings.Split(tag, tagSeparator) { + + orVals := strings.Split(t, orSeparator) + // fmt.Println(len(orVals) - 1) + cTag := &cacheTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} + cField.tags = append(cField.tags, cTag) + + for i, val := range orVals { + vals := strings.Split(val, tagKeySeparator) + + key := strings.TrimSpace(vals[0]) + + if len(key) == 0 { + panic(fmt.Sprintf("Invalid validation tag on field %s", name)) + } + + param := "" + if len(vals) > 1 { + param = strings.TrimSpace(vals[1]) + } + + // fmt.Println(cTag.keyVals) + cTag.keyVals[i] = []string{key, param} + // cTag.keyVals = append(cTag.keyVals, []string{key, param}) + + // for vals := range strings.Split(t, tagKeySeparator) { + // cField.tags = append(cField.tags, cacheTags{ isOrVal: len(orVals) > 1, []string{key, param}) + + } + + // } + // vals := strings.Split(valTag, tagKeySeparator) + // key := strings.TrimSpace(vals[0]) + } + } + + // fmt.Println(fieldKind, cacheField.kind) + + // switch cField.kind { + + // case reflect.Struct, reflect.Interface, reflect.Invalid: + + // if cField.typ != reflect.TypeOf(time.Time{}) { + // panic("Invalid field passed to ValidateFieldWithTag") + // } + // } + var valErr *FieldError var err error - valTags := strings.Split(tag, tagSeparator) + // valTags := strings.Split(tag, tagSeparator) - for _, valTag := range valTags { + for _, cTag := range cField.tags { - orVals := strings.Split(valTag, orSeparator) - - if len(orVals) > 1 { + if cTag.isOrVal { errTag := "" - for _, val := range orVals { + for _, val := range cTag.keyVals { + + // fmt.Println(cTag) - valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, val) + valErr, err = v.fieldWithNameAndSingleTag(val, current, f, val[0], val[1], name, cacheField) if err == nil { return nil @@ -320,31 +503,70 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f errTag = strings.TrimLeft(errTag, orSeparator) valErr.Tag = errTag - valErr.Kind = fieldKind + valErr.Kind = cField.kind return valErr + } + // else { + + // fmt.Println(cTag.keyVals[0]) - if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, name, valTag); err != nil { + if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, cTag.keyVals[0][0], cTag.keyVals[0][1], name, cacheField); err != nil { - valErr.Kind = valueField.Kind() - valErr.Type = fieldType + valErr.Kind = cField.kind + valErr.Type = cField.typ return valErr } + // } + + // orVals := strings.Split(valTag, orSeparator) + + // if len(orVals) > 1 { + + // errTag := "" + + // for _, val := range orVals { + + // valErr, err = v.fieldWithNameAndSingleTag(val, current, f, val, name, cacheField) + + // if err == nil { + // return nil + // } + + // errTag += orSeparator + valErr.Tag + + // } + + // errTag = strings.TrimLeft(errTag, orSeparator) + + // valErr.Tag = errTag + // valErr.Kind = cField.kind + + // return valErr + // } + + // if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, valTag, name, cacheField); err != nil { + + // valErr.Kind = cField.kind + // valErr.Type = cField.typ + + // return valErr + // } } return nil } -func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, name string, valTag string) (*FieldError, error) { +func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string, cacheField *cachedField) (*FieldError, error) { - vals := strings.Split(valTag, tagKeySeparator) - key := strings.TrimSpace(vals[0]) + // vals := strings.Split(valTag, tagKeySeparator) + // key := strings.TrimSpace(vals[0]) - if len(key) == 0 { - panic(fmt.Sprintf("Invalid validation tag on field %s", name)) - } + // if len(key) == 0 { + // panic(fmt.Sprintf("Invalid validation tag on field %s", name)) + // } valErr := &FieldError{ Field: name, @@ -363,10 +585,10 @@ func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{ panic(fmt.Sprintf("Undefined validation function on field %s", name)) } - param := "" - if len(vals) > 1 { - param = strings.TrimSpace(vals[1]) - } + // param := "" + // if len(vals) > 1 { + // param = strings.TrimSpace(vals[1]) + // } if err := valFunc(val, current, f, param); !err { valErr.Param = param diff --git a/validator_test.go b/validator_test.go index ce7c546..9a71573 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2336,36 +2336,44 @@ func BenchmarkValidateStruct(b *testing.B) { // Int64Val int64 `bson:"gt=0,lt=10"` // } - 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", - }, - } + // 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", + // }, + // } // t := &Test{ // StringVal: "test", // Int64Val: 5, // } + 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(tFail) + validate.Struct(validFoo) + validate.Struct(invalidFoo) } } From e4f2ff67bd457930b9d224e9e05f5d7194bae7d2 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 22:24:26 -0400 Subject: [PATCH 02/10] add caching of field only tags, now less time, memory and only 2 allocations vs 9 --- validator.go | 97 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/validator.go b/validator.go index 2bf65b5..bb27feb 100644 --- a/validator.go +++ b/validator.go @@ -306,7 +306,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter cField.isTime = true - if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, cField); fieldError != nil { + if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { validationErrors.Errors[fieldError.Field] = fieldError // free up memory reference fieldError = nil @@ -331,7 +331,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter default: - if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, cField); fieldError != nil { + if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { validationErrors.Errors[fieldError.Field] = fieldError // free up memory reference fieldError = nil @@ -361,14 +361,17 @@ 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 func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *FieldError { - return v.fieldWithNameAndValue(nil, val, f, tag, "", nil) + return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil) } -func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, tag string, name string, cacheField *cachedField) *FieldError { +var cacheFields = map[string][]*cacheTags{} + +func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, tag string, name string, isSingleField bool, cacheField *cachedField) *FieldError { // var fieldType reflect.Type // var fieldKind reflect.Kind var cField *cachedField + var ok bool // This is a double check if coming from validate.Struct but need to be here in case function is called directly if tag == noValidationTag { @@ -382,14 +385,30 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f if cacheField == nil { valueField := reflect.ValueOf(f) - cField = &cachedField{name: name, kind: valueField.Kind()} + if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { + // valueField = valueField.Elem() + // f = valueField.Interface() + return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), tag, name, isSingleField, cacheField) + } + + // if !ok { + // cacheFields[cField.tag] = cField + + // valueField = valueField.Elem() + + cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()} // fieldKind = valueField.Kind() - if cField.kind == reflect.Ptr && !valueField.IsNil() { - return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), tag, name, cacheField) - } + // if cField.kind == reflect.Ptr && !valueField.IsNil() { + // return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), tag, name, isSingleField, cacheField) + // } + + // cField.typ = valueField.Type() + // cField.tag = tag - cField.typ = valueField.Type() + // if isSingleField { + // cacheFields[cField.tag] = cField + // } // cField.tags = make([][]string, 0) // fieldType = valueField.Type() // for _, t := range strings.Split(tag, tagSeparator) { @@ -413,6 +432,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f // // vals := strings.Split(valTag, tagKeySeparator) // // key := strings.TrimSpace(vals[0]) // } + // } } else { cField = cacheField // fieldType = cacheField.typ @@ -429,39 +449,52 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f } if len(cField.tags) == 0 { - for _, t := range strings.Split(tag, tagSeparator) { - orVals := strings.Split(t, orSeparator) - // fmt.Println(len(orVals) - 1) - cTag := &cacheTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} - cField.tags = append(cField.tags, cTag) + if isSingleField { + cField.tags, ok = cacheFields[tag] + } - for i, val := range orVals { - vals := strings.Split(val, tagKeySeparator) + if !ok { - key := strings.TrimSpace(vals[0]) + for _, t := range strings.Split(tag, tagSeparator) { - if len(key) == 0 { - panic(fmt.Sprintf("Invalid validation tag on field %s", name)) - } + orVals := strings.Split(t, orSeparator) + // fmt.Println(len(orVals) - 1) + cTag := &cacheTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} + cField.tags = append(cField.tags, cTag) - param := "" - if len(vals) > 1 { - param = strings.TrimSpace(vals[1]) - } + for i, val := range orVals { + vals := strings.Split(val, tagKeySeparator) + + key := strings.TrimSpace(vals[0]) - // fmt.Println(cTag.keyVals) - cTag.keyVals[i] = []string{key, param} - // cTag.keyVals = append(cTag.keyVals, []string{key, param}) + if len(key) == 0 { + panic(fmt.Sprintf("Invalid validation tag on field %s", name)) + } - // for vals := range strings.Split(t, tagKeySeparator) { - // cField.tags = append(cField.tags, cacheTags{ isOrVal: len(orVals) > 1, []string{key, param}) + param := "" + if len(vals) > 1 { + param = strings.TrimSpace(vals[1]) + } + // fmt.Println(cTag.keyVals) + cTag.keyVals[i] = []string{key, param} + // cTag.keyVals = append(cTag.keyVals, []string{key, param}) + + // for vals := range strings.Split(t, tagKeySeparator) { + // cField.tags = append(cField.tags, cacheTags{ isOrVal: len(orVals) > 1, []string{key, param}) + + } + + // } + // vals := strings.Split(valTag, tagKeySeparator) + // key := strings.TrimSpace(vals[0]) } - // } - // vals := strings.Split(valTag, tagKeySeparator) - // key := strings.TrimSpace(vals[0]) + if isSingleField && !ok { + // fmt.Println(cField.tag) + cacheFields[cField.tag] = cField.tags + } } } From 0c5fbee316f4b8a20156fb1740726615e35dd8fb Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 22:59:36 -0400 Subject: [PATCH 03/10] code cleanup + variable renaming restructure fieldWithNameAndSingleTag for speed + less allocations --- validator.go | 277 +++++++++++---------------------------------------- 1 file changed, 60 insertions(+), 217 deletions(-) diff --git a/validator.go b/validator.go index bb27feb..2471e2a 100644 --- a/validator.go +++ b/validator.go @@ -29,6 +29,31 @@ const ( 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 +} + +var structCache = map[reflect.Type]*cachedStruct{} +var fieldsCache = map[string][]*cachedTags{} + // FieldError contains a single field's validation error along // with other properties that may be needed for error message creation type FieldError struct { @@ -162,31 +187,6 @@ func (v *Validate) Struct(s interface{}) *StructErrors { return v.structRecursive(s, s, s) } -type cacheTags struct { - keyVals [][]string - isOrVal bool -} - -type cachedField struct { - index int - name string - tags []*cacheTags - // tags [][]string - tag string - kind reflect.Kind - typ reflect.Type - isTime bool -} - -type cachedStruct struct { - children int - name string - kind reflect.Kind - fields []*cachedField -} - -var cache = map[reflect.Type]*cachedStruct{} - // structRecursive validates a struct recursivly and passes the top level and current struct around for use in validator functions and returns a struct containing the errors func (v *Validate) structRecursive(top interface{}, current interface{}, s interface{}) *StructErrors { @@ -205,18 +205,16 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter var structName string var numFields int - // fmt.Println(structType) + cs, isCached := structCache[structType] - cs, ok := cache[structType] - - if ok { + if isCached { structName = cs.name numFields = cs.children } else { structName = structType.Name() numFields = structValue.NumField() cs = &cachedStruct{name: structName, children: numFields} - cache[structType] = cs + structCache[structType] = cs } validationErrors := &StructErrors{ @@ -229,26 +227,10 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter var valueField reflect.Value var cField *cachedField - // var fName string - // var tag string - var typeField reflect.StructField - // if ok { - // cField = cs.fields[i] - // valueField = structValue.Field(cField.index) - // } else { - // valueField = structValue.Field(i) - // } - - // if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - // valueField = valueField.Elem() - // } - - if ok { + if isCached { cField = cs.fields[i] - // fName = cField.name - // tag = cField.tag valueField = structValue.Field(cField.index) if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { @@ -265,8 +247,6 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter cField = &cachedField{index: i, tag: typeField.Tag.Get(v.tagName)} - // tag = typeField.Tag.Get(v.tagName) - if cField.tag == noValidationTag { cs.children-- continue @@ -278,12 +258,9 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter continue } - // fName = typeField.Name cField.name = typeField.Name cField.kind = valueField.Kind() cField.typ = valueField.Type() - // cField = &cachedField{index: i, name: typeField.Name, tag: tag, kind: valueField.Kind()} - // cs.fields = append(cs.fields, cField) } // this can happen if the first cache value was nil @@ -298,7 +275,6 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter if !unicode.IsUpper(rune(cField.name[0])) { cs.children-- - // cs.fields = cs.fields[:len(cs.fields)] continue } @@ -316,7 +292,6 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter if strings.Contains(cField.tag, structOnlyTag) { cs.children-- - // cs.fields = cs.fields[:len(cs.fields)] continue } @@ -327,8 +302,6 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter } } - // cs.fields = append(cs.fields, cField) - default: if fieldError := v.fieldWithNameAndValue(top, current, valueField.Interface(), cField.tag, cField.name, false, cField); fieldError != nil { @@ -336,11 +309,9 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter // free up memory reference fieldError = nil } - - // cs.fields = append(cs.fields, cField) } - if !ok { + if !isCached { cs.fields = append(cs.fields, cField) } } @@ -364,14 +335,10 @@ func (v *Validate) FieldWithValue(val interface{}, f interface{}, tag string) *F return v.fieldWithNameAndValue(nil, val, f, tag, "", true, nil) } -var cacheFields = map[string][]*cacheTags{} - func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f interface{}, tag string, name string, isSingleField bool, cacheField *cachedField) *FieldError { - // var fieldType reflect.Type - // var fieldKind reflect.Kind var cField *cachedField - var ok bool + var isCached bool // This is a double check if coming from validate.Struct but need to be here in case function is called directly if tag == noValidationTag { @@ -386,57 +353,13 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f valueField := reflect.ValueOf(f) if valueField.Kind() == reflect.Ptr && !valueField.IsNil() { - // valueField = valueField.Elem() - // f = valueField.Interface() - return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), tag, name, isSingleField, cacheField) + valueField = valueField.Elem() + f = valueField.Interface() } - // if !ok { - // cacheFields[cField.tag] = cField - - // valueField = valueField.Elem() - cField = &cachedField{name: name, kind: valueField.Kind(), tag: tag, typ: valueField.Type()} - // fieldKind = valueField.Kind() - - // if cField.kind == reflect.Ptr && !valueField.IsNil() { - // return v.fieldWithNameAndValue(val, current, valueField.Elem().Interface(), tag, name, isSingleField, cacheField) - // } - - // cField.typ = valueField.Type() - // cField.tag = tag - - // if isSingleField { - // cacheFields[cField.tag] = cField - // } - // cField.tags = make([][]string, 0) - // fieldType = valueField.Type() - // for _, t := range strings.Split(tag, tagSeparator) { - - // vals := strings.Split(t, tagKeySeparator) - - // key := strings.TrimSpace(vals[0]) - - // if len(key) == 0 { - // panic(fmt.Sprintf("Invalid validation tag on field %s", name)) - // } - - // param := "" - // if len(vals) > 1 { - // param = strings.TrimSpace(vals[1]) - // } - - // // for vals := range strings.Split(t, tagKeySeparator) { - // cField.tags = append(cField.tags, []string{key, param}) - // // } - // // vals := strings.Split(valTag, tagKeySeparator) - // // key := strings.TrimSpace(vals[0]) - // } - // } } else { cField = cacheField - // fieldType = cacheField.typ - // fieldKind = cacheField.kind } switch cField.kind { @@ -451,16 +374,15 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f if len(cField.tags) == 0 { if isSingleField { - cField.tags, ok = cacheFields[tag] + cField.tags, isCached = fieldsCache[tag] } - if !ok { + if !isCached { for _, t := range strings.Split(tag, tagSeparator) { orVals := strings.Split(t, orSeparator) - // fmt.Println(len(orVals) - 1) - cTag := &cacheTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} + cTag := &cachedTags{isOrVal: len(orVals) > 1, keyVals: make([][]string, len(orVals))} cField.tags = append(cField.tags, cTag) for i, val := range orVals { @@ -477,41 +399,18 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f param = strings.TrimSpace(vals[1]) } - // fmt.Println(cTag.keyVals) cTag.keyVals[i] = []string{key, param} - // cTag.keyVals = append(cTag.keyVals, []string{key, param}) - - // for vals := range strings.Split(t, tagKeySeparator) { - // cField.tags = append(cField.tags, cacheTags{ isOrVal: len(orVals) > 1, []string{key, param}) - } - - // } - // vals := strings.Split(valTag, tagKeySeparator) - // key := strings.TrimSpace(vals[0]) } - if isSingleField && !ok { - // fmt.Println(cField.tag) - cacheFields[cField.tag] = cField.tags + if isSingleField { + fieldsCache[cField.tag] = cField.tags } } } - // fmt.Println(fieldKind, cacheField.kind) - - // switch cField.kind { - - // case reflect.Struct, reflect.Interface, reflect.Invalid: - - // if cField.typ != reflect.TypeOf(time.Time{}) { - // panic("Invalid field passed to ValidateFieldWithTag") - // } - // } - - var valErr *FieldError + var fieldErr *FieldError var err error - // valTags := strings.Split(tag, tagSeparator) for _, cTag := range cField.tags { @@ -521,96 +420,41 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f for _, val := range cTag.keyVals { - // fmt.Println(cTag) - - valErr, err = v.fieldWithNameAndSingleTag(val, current, f, val[0], val[1], name, cacheField) + fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, val[0], val[1], name) if err == nil { return nil } - errTag += orSeparator + valErr.Tag - + errTag += orSeparator + fieldErr.Tag } errTag = strings.TrimLeft(errTag, orSeparator) - valErr.Tag = errTag - valErr.Kind = cField.kind - - return valErr + fieldErr.Tag = errTag + fieldErr.Kind = cField.kind + fieldErr.Type = cField.typ + return fieldErr } - // else { - // fmt.Println(cTag.keyVals[0]) + if fieldErr, err = v.fieldWithNameAndSingleTag(val, current, f, cTag.keyVals[0][0], cTag.keyVals[0][1], name); err != nil { - if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, cTag.keyVals[0][0], cTag.keyVals[0][1], name, cacheField); err != nil { + fieldErr.Kind = cField.kind + fieldErr.Type = cField.typ - valErr.Kind = cField.kind - valErr.Type = cField.typ - - return valErr + return fieldErr } - // } - - // orVals := strings.Split(valTag, orSeparator) - - // if len(orVals) > 1 { - - // errTag := "" - - // for _, val := range orVals { - - // valErr, err = v.fieldWithNameAndSingleTag(val, current, f, val, name, cacheField) - - // if err == nil { - // return nil - // } - - // errTag += orSeparator + valErr.Tag - - // } - - // errTag = strings.TrimLeft(errTag, orSeparator) - - // valErr.Tag = errTag - // valErr.Kind = cField.kind - - // return valErr - // } - - // if valErr, err = v.fieldWithNameAndSingleTag(val, current, f, valTag, name, cacheField); err != nil { - - // valErr.Kind = cField.kind - // valErr.Type = cField.typ - - // return valErr - // } } return nil } -func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{}, f interface{}, key string, param string, name string, cacheField *cachedField) (*FieldError, error) { - - // vals := strings.Split(valTag, tagKeySeparator) - // key := strings.TrimSpace(vals[0]) - - // if len(key) == 0 { - // panic(fmt.Sprintf("Invalid validation tag on field %s", name)) - // } - - valErr := &FieldError{ - 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 if key == omitempty { - return valErr, nil + return nil, nil } valFunc, ok := v.validationFuncs[key] @@ -618,15 +462,14 @@ func (v *Validate) fieldWithNameAndSingleTag(val interface{}, current interface{ panic(fmt.Sprintf("Undefined validation function on field %s", name)) } - // param := "" - // if len(vals) > 1 { - // param = strings.TrimSpace(vals[1]) - // } - - if err := valFunc(val, current, f, param); !err { - valErr.Param = param - return valErr, errors.New(key) + if err := valFunc(val, current, f, param); err { + return nil, nil + } else { + return &FieldError{ + Field: name, + Tag: key, + Value: f, + Param: param, + }, errors.New(key) } - - return valErr, nil } From 8c2248a5cee46f54ec089f7890f6fe885b305225 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 23:22:13 -0400 Subject: [PATCH 04/10] add map thread safety --- validator.go | 50 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/validator.go b/validator.go index 2471e2a..8a85c69 100644 --- a/validator.go +++ b/validator.go @@ -14,6 +14,7 @@ import ( "fmt" "reflect" "strings" + "sync" "time" "unicode" ) @@ -51,8 +52,45 @@ type cachedStruct struct { fields []*cachedField } -var structCache = map[reflect.Type]*cachedStruct{} -var fieldsCache = map[string][]*cachedTags{} +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 // with other properties that may be needed for error message creation @@ -205,7 +243,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter var structName string var numFields int - cs, isCached := structCache[structType] + cs, isCached := structCache.Get(structType) if isCached { structName = cs.name @@ -214,7 +252,7 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter structName = structType.Name() numFields = structValue.NumField() cs = &cachedStruct{name: structName, children: numFields} - structCache[structType] = cs + structCache.Set(structType, cs) } validationErrors := &StructErrors{ @@ -374,7 +412,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f if len(cField.tags) == 0 { if isSingleField { - cField.tags, isCached = fieldsCache[tag] + cField.tags, isCached = fieldsCache.Get(tag) } if !isCached { @@ -404,7 +442,7 @@ func (v *Validate) fieldWithNameAndValue(val interface{}, current interface{}, f } if isSingleField { - fieldsCache[cField.tag] = cField.tags + fieldsCache.Set(cField.tag, cField.tags) } } } From 6ffa5d14556c84d53c5b7e3dfe630c3ee1a81ce6 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 23:25:14 -0400 Subject: [PATCH 05/10] add thread safety note to AddFunction and SetTag functions --- validator.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/validator.go b/validator.go index 8a85c69..020d3c8 100644 --- a/validator.go +++ b/validator.go @@ -195,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 // 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) { v.tagName = tagName } // 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: this method is not thread-safe func (v *Validate) AddFunction(key string, f Func) error { if len(key) == 0 { From ad39bb92ddf41a195605ba4710a41401be078bf4 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 23:40:23 -0400 Subject: [PATCH 06/10] code cleanup --- validator.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/validator.go b/validator.go index 020d3c8..186111e 100644 --- a/validator.go +++ b/validator.go @@ -244,8 +244,10 @@ func (v *Validate) structRecursive(top interface{}, current interface{}, s inter var structName string var numFields int + var cs *cachedStruct + var isCached bool - cs, isCached := structCache.Get(structType) + cs, isCached = structCache.Get(structType) if isCached { structName = cs.name From 81ce302975642be612f20717304827de38bebb55 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 23:54:06 -0400 Subject: [PATCH 07/10] add parallel benchmark --- validator_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/validator_test.go b/validator_test.go index 9a71573..aed06a9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2377,3 +2377,21 @@ func BenchmarkValidateStruct(b *testing.B) { validate.Struct(invalidFoo) } } + +func BenchmarkTemplateParallel(b *testing.B) { + + 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} + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + validate.Struct(validFoo) + validate.Struct(invalidFoo) + } + }) +} From 56b4ce18117f195c19bc6cf94fabb3552e7207c6 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 7 Jun 2015 23:57:32 -0400 Subject: [PATCH 08/10] add larger tests for benchmarks --- validator_test.go | 163 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 123 insertions(+), 40 deletions(-) diff --git a/validator_test.go b/validator_test.go index aed06a9..268ddd6 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2325,45 +2325,8 @@ func BenchmarkValidateField(b *testing.B) { } } -func BenchmarkValidateStruct(b *testing.B) { - - // type Inner struct { - - // } - - // type Test struct { - // StringVal string `bson:"required,lt=10"` - // Int64Val int64 `bson:"gt=0,lt=10"` - // } - - // 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", - // }, - // } - - // t := &Test{ - // StringVal: "test", - // Int64Val: 5, - // } +func BenchmarkValidateStructSimple(b *testing.B) { + type Foo struct { StringValue string `validate:"min=5,max=10"` IntValue int `validate:"min=5,max=10"` @@ -2378,7 +2341,7 @@ func BenchmarkValidateStruct(b *testing.B) { } } -func BenchmarkTemplateParallel(b *testing.B) { +func BenchmarkTemplateParallelSimple(b *testing.B) { type Foo struct { StringValue string `validate:"min=5,max=10"` @@ -2395,3 +2358,123 @@ func BenchmarkTemplateParallel(b *testing.B) { } }) } + +func BenchmarkValidateStructLarge(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", + }, + } + + for n := 0; n < b.N; n++ { + validate.Struct(tSuccess) + 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) + } + }) +} From aa275c658d5ca985ec1a2019342815f935dce02d Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 8 Jun 2015 08:27:07 -0400 Subject: [PATCH 09/10] merge pul request changes from @xboston --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 1b02c0f..a4cf673 100644 --- a/doc.go +++ b/doc.go @@ -10,7 +10,7 @@ Validate A simple example usage: - type UserDetail { + type UserDetail struct { Details string `validate:"-"` } From 1fda4930f6077581e10362d69c34a7f0bf85dbe9 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 8 Jun 2015 08:37:53 -0400 Subject: [PATCH 10/10] comment out parallel benchmarks to maintain go 1.2 compatibility --- validator_test.go | 148 +++++++++++++++++++++++----------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/validator_test.go b/validator_test.go index 268ddd6..67a34b0 100644 --- a/validator_test.go +++ b/validator_test.go @@ -2341,23 +2341,23 @@ func BenchmarkValidateStructSimple(b *testing.B) { } } -func BenchmarkTemplateParallelSimple(b *testing.B) { +// func BenchmarkTemplateParallelSimple(b *testing.B) { - type Foo struct { - StringValue string `validate:"min=5,max=10"` - IntValue int `validate:"min=5,max=10"` - } +// 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} +// validFoo := &Foo{StringValue: "Foobar", IntValue: 7} +// invalidFoo := &Foo{StringValue: "Fo", IntValue: 3} - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - validate.Struct(validFoo) - validate.Struct(invalidFoo) - } - }) -} +// b.RunParallel(func(pb *testing.PB) { +// for pb.Next() { +// validate.Struct(validFoo) +// validate.Struct(invalidFoo) +// } +// }) +// } func BenchmarkValidateStructLarge(b *testing.B) { @@ -2418,63 +2418,63 @@ func BenchmarkValidateStructLarge(b *testing.B) { } } -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) - } - }) -} +// 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) +// } +// }) +// }