From 2cda50fb415c6ddc964cb4f278979a890c076542 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sat, 29 Aug 2015 23:07:43 -0400 Subject: [PATCH 01/22] Add Tag Alias + basic tests so far so good --- baked_in.go | 8 ++++++ util.go | 61 +++++++++++++++++++++++++++++++++++++++ validator.go | 73 +++++++++++++++++++++++------------------------ validator_test.go | 27 ++++++++++++++++-- 4 files changed, 129 insertions(+), 40 deletions(-) diff --git a/baked_in.go b/baked_in.go index f9a0245..c749266 100644 --- a/baked_in.go +++ b/baked_in.go @@ -10,6 +10,14 @@ import ( "unicode/utf8" ) +// BakedInAliasValidators is a default mapping of a single validationstag that +// defines a common or complex set of validation(s) to simplify +// adding validation to structs. i.e. set key "_ageok" and the tags +// are "gt=0,lte=130" or key "_preferredname" and tags "omitempty,gt=0,lte=60" +var BakedInAliasValidators = map[string]string{ + "iscolor": "hexcolor|rgb|rgba|hsl|hsla", +} + // BakedInValidators is the default map of ValidationFunc // you can add, remove or even replace items to suite your needs, // or even disregard and use your own map if so desired. diff --git a/util.go b/util.go index 641c2d4..dbe897d 100644 --- a/util.go +++ b/util.go @@ -1,6 +1,7 @@ package validator import ( + "fmt" "reflect" "strconv" "strings" @@ -10,6 +11,21 @@ const ( namespaceSeparator = "." leftBracket = "[" rightBracket = "]" + restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}" + restrictedAliasErr = "Alias \"%s\" either contains restricted characters or is the same as a restricted tag needed for normal operation\n" + restrictedTagErr = "Tag \"%s\" either contains restricted characters or is the same as a restricted tag needed for normal operation\n" +) + +var ( + restrictedTags = map[string]*struct{}{ + diveTag: emptyStructPtr, + existsTag: emptyStructPtr, + structOnlyTag: emptyStructPtr, + omitempty: emptyStructPtr, + skipValidationTag: emptyStructPtr, + utf8HexComma: emptyStructPtr, + utf8Pipe: emptyStructPtr, + } ) func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Kind) { @@ -214,3 +230,48 @@ func panicIf(err error) { panic(err.Error()) } } + +func (v *Validate) parseTags(tag, fieldName string) []*tagCache { + + tags := []*tagCache{} + + for _, t := range strings.Split(tag, tagSeparator) { + + if v.config.hasAliasValidators { + // check map for alias and process new tags, otherwise process as usual + if tagsVal, ok := v.config.AliasValidators[t]; ok { + return v.parseTags(tagsVal, fieldName) + } + } + + if t == diveTag { + tags = append(tags, &tagCache{tagVals: [][]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, fieldName))) + } + + if len(vals) > 1 { + param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1) + } + + cTag.tagVals[i] = []string{key, param} + } + } + + return tags +} diff --git a/validator.go b/validator.go index 3542569..40f7e32 100644 --- a/validator.go +++ b/validator.go @@ -81,10 +81,12 @@ type Validate struct { // Config contains the options that a Validator instance will use. // It is passed to the New() function type Config struct { - TagName string - ValidationFuncs map[string]Func - CustomTypeFuncs map[reflect.Type]CustomTypeFunc - hasCustomFuncs bool + TagName string + ValidationFuncs map[string]Func + CustomTypeFuncs map[reflect.Type]CustomTypeFunc + AliasValidators map[string]string + hasCustomFuncs bool + hasAliasValidators bool } // CustomTypeFunc allows for overriding or adding custom field type handler functions @@ -139,6 +141,10 @@ func New(config Config) *Validate { config.hasCustomFuncs = true } + if config.AliasValidators != nil && len(config.AliasValidators) > 0 { + config.hasAliasValidators = true + } + return &Validate{config: config} } @@ -155,6 +161,12 @@ func (v *Validate) RegisterValidation(key string, f Func) error { return errors.New("Function cannot be empty") } + _, ok := restrictedTags[key] + + if ok || strings.ContainsAny(key, restrictedTagChars) { + panic(fmt.Sprintf(restrictedTagErr, key)) + } + v.config.ValidationFuncs[key] = f return nil @@ -174,6 +186,25 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ v.config.hasCustomFuncs = true } +// RegisterAliasValidation registers a mapping of a single validationstag that +// defines a common or complex set of validation(s) to simplify adding validation +// to structs. +func (v *Validate) RegisterAliasValidation(alias, tags string) { + + if v.config.AliasValidators == nil { + v.config.AliasValidators = map[string]string{} + } + + _, ok := restrictedTags[alias] + + if ok || strings.ContainsAny(alias, restrictedTagChars) { + panic(fmt.Sprintf(restrictedAliasErr, alias)) + } + + v.config.AliasValidators[alias] = tags + v.config.hasAliasValidators = true +} + // Field validates a single field using tag style validation and returns ValidationErrors // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error @@ -430,39 +461,7 @@ 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{{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} - } - } + tags = v.parseTags(tag, name) tagsCache.Set(tag, tags) } diff --git a/validator_test.go b/validator_test.go index 22084b4..91d8324 100644 --- a/validator_test.go +++ b/validator_test.go @@ -111,7 +111,7 @@ type TestSlice struct { OmitEmpty []int `validate:"omitempty,min=1,max=10"` } -var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) +var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, AliasValidators: BakedInAliasValidators}) func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { @@ -210,6 +210,24 @@ type TestPartial struct { } } +func TestAliasTags(t *testing.T) { + + s := "rgb(255,255,255)" + errs := validate.Field(s, "iscolor") + Equal(t, errs, nil) + + type Test struct { + Color string `vlidate:"iscolor"` + } + + tst := &Test{ + Color: "#000", + } + + errs = validate.Struct(tst) + Equal(t, errs, nil) +} + func TestStructPartial(t *testing.T) { p1 := []string{ @@ -1805,7 +1823,8 @@ func TestMapDiveValidation(t *testing.T) { AssertError(t, errs, "TestMapStruct.Errs[3].Name", "Name", "required") // for full test coverage - fmt.Sprint(errs.Error()) + s := fmt.Sprint(errs.Error()) + NotEqual(t, s, "") type TestMapTimeStruct struct { Errs map[int]*time.Time `validate:"gt=0,dive,required"` @@ -1968,8 +1987,10 @@ func TestArrayDiveValidation(t *testing.T) { AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[2][1].Name", "Name", "required") + // for full test coverage - fmt.Sprint(errs.Error()) + s := fmt.Sprint(errs.Error()) + NotEqual(t, s, "") type TestMultiDimensionalStructsPtr2 struct { Errs [][]*Inner `validate:"gt=0,dive,dive,required"` From 407aac458f1e09cd14862bcdab926cf4bb84e60a Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 30 Aug 2015 14:14:51 -0400 Subject: [PATCH 02/22] Update Alias logic - Unexport Alias Map in order to preserve any custom user aliases that may conflict with new aliases added to the library itself....need to copy them for each new validator. - update error to report alias tag as the error tag and add actualTag to know what it would have actually been. --- baked_in.go | 4 +- util.go | 26 ++++++++-- validator.go | 126 ++++++++++++++++++++++++---------------------- validator_test.go | 21 +++++++- 4 files changed, 109 insertions(+), 68 deletions(-) diff --git a/baked_in.go b/baked_in.go index c749266..802804c 100644 --- a/baked_in.go +++ b/baked_in.go @@ -14,9 +14,7 @@ import ( // defines a common or complex set of validation(s) to simplify // adding validation to structs. i.e. set key "_ageok" and the tags // are "gt=0,lte=130" or key "_preferredname" and tags "omitempty,gt=0,lte=60" -var BakedInAliasValidators = map[string]string{ - "iscolor": "hexcolor|rgb|rgba|hsl|hsla", -} +var BakedInAliasValidators = map[string]string{} // BakedInValidators is the default map of ValidationFunc // you can add, remove or even replace items to suite your needs, diff --git a/util.go b/util.go index dbe897d..eb5d11f 100644 --- a/util.go +++ b/util.go @@ -8,6 +8,7 @@ import ( ) const ( + blank = "" namespaceSeparator = "." leftBracket = "[" rightBracket = "]" @@ -94,7 +95,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re fld = namespace[:idx] ns = namespace[idx+1:] } else { - ns = "" + ns = blank idx = len(namespace) } @@ -233,14 +234,24 @@ func panicIf(err error) { func (v *Validate) parseTags(tag, fieldName string) []*tagCache { + return v.parseTagsRecursive(tag, fieldName, blank, false) +} + +func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool) []*tagCache { + tags := []*tagCache{} + if len(tag) == 0 { + return tags + } + for _, t := range strings.Split(tag, tagSeparator) { if v.config.hasAliasValidators { // check map for alias and process new tags, otherwise process as usual - if tagsVal, ok := v.config.AliasValidators[t]; ok { - return v.parseTags(tagsVal, fieldName) + if tagsVal, ok := v.config.aliasValidators[t]; ok { + tags = append(tags, v.parseTagsRecursive(tagsVal, fieldName, t, true)...) + continue } } @@ -251,7 +262,8 @@ func (v *Validate) parseTags(tag, fieldName string) []*tagCache { // 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))} + cTag := &tagCache{isAlias: isAlias, isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))} + tags = append(tags, cTag) var key string @@ -261,6 +273,12 @@ func (v *Validate) parseTags(tag, fieldName string) []*tagCache { vals := strings.SplitN(val, tagKeySeparator, 2) key = vals[0] + cTag.tag = key + + if isAlias { + cTag.tag = alias + } + if len(key) == 0 { panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName))) } diff --git a/validator.go b/validator.go index 40f7e32..8dcfa60 100644 --- a/validator.go +++ b/validator.go @@ -53,6 +53,9 @@ func newValidationErrors() interface{} { type tagCache struct { tagVals [][]string isOrVal bool + isAlias bool + tag string + // actualTag string } type tagCacheMap struct { @@ -84,7 +87,7 @@ type Config struct { TagName string ValidationFuncs map[string]Func CustomTypeFuncs map[reflect.Type]CustomTypeFunc - AliasValidators map[string]string + aliasValidators map[string]string hasCustomFuncs bool hasAliasValidators bool } @@ -113,7 +116,7 @@ type ValidationErrors map[string]*FieldError // the FieldError found within the ValidationErrors map func (ve ValidationErrors) Error() string { - buff := bytes.NewBufferString("") + buff := bytes.NewBufferString(blank) for key, err := range ve { buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag)) @@ -126,12 +129,13 @@ func (ve ValidationErrors) Error() string { // FieldError contains a single field's validation error along // with other properties that may be needed for error message creation type FieldError struct { - Field string - Tag string - Kind reflect.Kind - Type reflect.Type - Param string - Value interface{} + Field string + Tag string + ActualTag string + Kind reflect.Kind + Type reflect.Type + Param string + Value interface{} } // New creates a new Validate instance for use. @@ -141,10 +145,6 @@ func New(config Config) *Validate { config.hasCustomFuncs = true } - if config.AliasValidators != nil && len(config.AliasValidators) > 0 { - config.hasAliasValidators = true - } - return &Validate{config: config} } @@ -191,8 +191,8 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // to structs. func (v *Validate) RegisterAliasValidation(alias, tags string) { - if v.config.AliasValidators == nil { - v.config.AliasValidators = map[string]string{} + if v.config.aliasValidators == nil { + v.config.aliasValidators = map[string]string{} } _, ok := restrictedTags[alias] @@ -201,7 +201,7 @@ func (v *Validate) RegisterAliasValidation(alias, tags string) { panic(fmt.Sprintf(restrictedAliasErr, alias)) } - v.config.AliasValidators[alias] = tags + v.config.aliasValidators[alias] = tags v.config.hasAliasValidators = true } @@ -213,7 +213,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) - v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "", false, false, nil) + v.traverseField(fieldVal, fieldVal, fieldVal, blank, errs, false, tag, blank, false, false, nil) if len(errs) == 0 { errsPool.Put(errs) @@ -231,7 +231,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) - v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "", false, false, nil) + v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, errs, false, tag, blank, false, false, nil) if len(errs) == 0 { errsPool.Put(errs) @@ -289,7 +289,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati errs := errsPool.Get().(ValidationErrors) - v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, false, m) + v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, false, m) if len(errs) == 0 { errsPool.Put(errs) @@ -316,7 +316,7 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio errs := errsPool.Get().(ValidationErrors) - v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, true, m) + v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, true, m) if len(errs) == 0 { errsPool.Put(errs) @@ -332,7 +332,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors { errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) - v.tranverseStruct(sv, sv, sv, "", errs, true, false, false, nil) + v.tranverseStruct(sv, sv, sv, blank, errs, true, false, false, nil) if len(errs) == 0 { errsPool.Put(errs) @@ -391,6 +391,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } + tags, isCached := tagsCache.Get(tag) + + if !isCached { + tags = v.parseTags(tag, name) + tagsCache.Set(tag, tags) + } + current, kind := v.extractType(current) var typ reflect.Type @@ -402,35 +409,30 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if len(tag) > 0 { - tags := strings.Split(tag, tagSeparator) - var param string - vals := strings.SplitN(tags[0], tagKeySeparator, 2) - - if len(vals) > 1 { - param = vals[1] - } - if kind == reflect.Invalid { errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: vals[0], - Param: param, - Kind: kind, + Field: name, + Tag: tags[0].tag, + ActualTag: tags[0].tagVals[0][0], + Param: tags[0].tagVals[0][1], + Kind: kind, } return } errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: vals[0], - Param: param, - Value: current.Interface(), - Kind: kind, - Type: current.Type(), + Field: name, + Tag: tags[0].tag, + ActualTag: tags[0].tagVals[0][0], + Param: tags[0].tagVals[0][1], + Value: current.Interface(), + Kind: kind, + Type: current.Type(), } return } + // if we get here tag length is zero and we can leave if kind == reflect.Invalid { return @@ -458,13 +460,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. typ = current.Type() - tags, isCached := tagsCache.Get(tag) - - if !isCached { - tags = v.parseTags(tag, name) - tagsCache.Set(tag, tags) - } - var dive bool var diveSubTag string @@ -482,7 +477,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if cTag.tagVals[0][0] == omitempty { - if !hasValue(v, topStruct, currentStruct, current, typ, kind, "") { + if !hasValue(v, topStruct, currentStruct, current, typ, kind, blank) { return } continue @@ -533,7 +528,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. if cTag.isOrVal { - errTag := "" + errTag := blank for _, val := range cTag.tagVals { @@ -549,12 +544,24 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. errTag += orSeparator + val[0] } - errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: errTag[1:], - Value: current.Interface(), - Type: currentType, - Kind: currentKind, + if cTag.isAlias { + errs[errPrefix+name] = &FieldError{ + Field: name, + Tag: cTag.tag, + ActualTag: errTag[1:], + Value: current.Interface(), + Type: currentType, + Kind: currentKind, + } + } else { + errs[errPrefix+name] = &FieldError{ + Field: name, + Tag: errTag[1:], + ActualTag: errTag[1:], + Value: current.Interface(), + Type: currentType, + Kind: currentKind, + } } return true @@ -570,12 +577,13 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. } errs[errPrefix+name] = &FieldError{ - Field: name, - Tag: cTag.tagVals[0][0], - Value: current.Interface(), - Param: cTag.tagVals[0][1], - Type: currentType, - Kind: currentKind, + Field: name, + Tag: cTag.tag, + ActualTag: cTag.tagVals[0][0], + Value: current.Interface(), + Param: cTag.tagVals[0][1], + Type: currentType, + Kind: currentKind, } return true diff --git a/validator_test.go b/validator_test.go index 91d8324..03bf489 100644 --- a/validator_test.go +++ b/validator_test.go @@ -111,7 +111,7 @@ type TestSlice struct { OmitEmpty []int `validate:"omitempty,min=1,max=10"` } -var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, AliasValidators: BakedInAliasValidators}) +var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { @@ -212,12 +212,23 @@ type TestPartial struct { func TestAliasTags(t *testing.T) { + validate.RegisterAliasValidation("iscolor", "hexcolor|rgb|rgba|hsl|hsla") + s := "rgb(255,255,255)" errs := validate.Field(s, "iscolor") Equal(t, errs, nil) + s = "" + errs = validate.Field(s, "omitempty,iscolor") + Equal(t, errs, nil) + + s = "rgb(255,255,0)" + errs = validate.Field(s, "iscolor,len=5") + NotEqual(t, errs, nil) + AssertError(t, errs, "", "", "len") + type Test struct { - Color string `vlidate:"iscolor"` + Color string `validate:"iscolor"` } tst := &Test{ @@ -226,6 +237,12 @@ func TestAliasTags(t *testing.T) { errs = validate.Struct(tst) Equal(t, errs, nil) + + tst.Color = "cfvre" + errs = validate.Struct(tst) + NotEqual(t, errs, nil) + AssertError(t, errs, "Test.Color", "Color", "iscolor") + Equal(t, errs["Test.Color"].ActualTag, "hexcolor|rgb|rgba|hsl|hsla") } func TestStructPartial(t *testing.T) { From a964f5384a87251e7db989326ae721f559f5394b Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 30 Aug 2015 20:29:04 -0400 Subject: [PATCH 03/22] Completed 100% test coverage for alis registration --- baked_in.go | 4 +++- util.go | 27 ++++++++++++++++++--------- validator.go | 10 ++++++++-- validator_test.go | 10 ++++++++++ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/baked_in.go b/baked_in.go index 802804c..c749266 100644 --- a/baked_in.go +++ b/baked_in.go @@ -14,7 +14,9 @@ import ( // defines a common or complex set of validation(s) to simplify // adding validation to structs. i.e. set key "_ageok" and the tags // are "gt=0,lte=130" or key "_preferredname" and tags "omitempty,gt=0,lte=60" -var BakedInAliasValidators = map[string]string{} +var BakedInAliasValidators = map[string]string{ + "iscolor": "hexcolor|rgb|rgba|hsl|hsla", +} // BakedInValidators is the default map of ValidationFunc // you can add, remove or even replace items to suite your needs, diff --git a/util.go b/util.go index eb5d11f..59e4505 100644 --- a/util.go +++ b/util.go @@ -13,8 +13,8 @@ const ( leftBracket = "[" rightBracket = "]" restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}" - restrictedAliasErr = "Alias \"%s\" either contains restricted characters or is the same as a restricted tag needed for normal operation\n" - restrictedTagErr = "Tag \"%s\" either contains restricted characters or is the same as a restricted tag needed for normal operation\n" + 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 ( @@ -234,15 +234,16 @@ func panicIf(err error) { func (v *Validate) parseTags(tag, fieldName string) []*tagCache { - return v.parseTagsRecursive(tag, fieldName, blank, false) + tags, _ := v.parseTagsRecursive(tag, fieldName, blank, false) + return tags } -func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool) []*tagCache { +func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool) ([]*tagCache, bool) { tags := []*tagCache{} if len(tag) == 0 { - return tags + return tags, true } for _, t := range strings.Split(tag, tagSeparator) { @@ -250,14 +251,22 @@ func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool if v.config.hasAliasValidators { // check map for alias and process new tags, otherwise process as usual if tagsVal, ok := v.config.aliasValidators[t]; ok { - tags = append(tags, v.parseTagsRecursive(tagsVal, fieldName, t, true)...) + + aliasTags, leave := v.parseTagsRecursive(tagsVal, fieldName, t, true) + tags = append(tags, aliasTags...) + + if leave { + return tags, leave + } + continue } } if t == diveTag { - tags = append(tags, &tagCache{tagVals: [][]string{{t}}}) - break + tVals := &tagCache{diveTag: tag, tagVals: [][]string{{t}}} + tags = append(tags, tVals) + return tags, true } // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C" @@ -291,5 +300,5 @@ func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool } } - return tags + return tags, false } diff --git a/validator.go b/validator.go index 8dcfa60..1fbba26 100644 --- a/validator.go +++ b/validator.go @@ -55,6 +55,7 @@ type tagCache struct { isOrVal bool isAlias bool tag string + diveTag string // actualTag string } @@ -191,8 +192,12 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // to structs. func (v *Validate) RegisterAliasValidation(alias, tags string) { - if v.config.aliasValidators == nil { + if len(v.config.aliasValidators) == 0 { + // must copy validators for separate validations to be used in each v.config.aliasValidators = map[string]string{} + for k, val := range BakedInAliasValidators { + v.config.aliasValidators[k] = val + } } _, ok := restrictedTags[alias] @@ -471,7 +476,8 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if cTag.tagVals[0][0] == diveTag { dive = true - diveSubTag = strings.TrimLeft(strings.SplitN(tag, diveTag, 2)[1], ",") + // fmt.Println(cTag.diveTag) + diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",") break } diff --git a/validator_test.go b/validator_test.go index 03bf489..45a3e58 100644 --- a/validator_test.go +++ b/validator_test.go @@ -243,6 +243,14 @@ func TestAliasTags(t *testing.T) { NotEqual(t, errs, nil) AssertError(t, errs, "Test.Color", "Color", "iscolor") Equal(t, errs["Test.Color"].ActualTag, "hexcolor|rgb|rgba|hsl|hsla") + + validate.RegisterAliasValidation("req", "required,dive,iscolor") + arr := []string{"val1", "#fff", "#000"} + errs = validate.Field(arr, "req") + NotEqual(t, errs, nil) + AssertError(t, errs, "[0]", "[0]", "iscolor") + + PanicMatches(t, func() { validate.RegisterAliasValidation("exists", "gt=5,lt=10") }, "Alias \"exists\" either contains restricted characters or is the same as a restricted tag needed for normal operation") } func TestStructPartial(t *testing.T) { @@ -3881,6 +3889,8 @@ func TestAddFunctions(t *testing.T) { errs = validate.RegisterValidation("new", fn) Equal(t, errs, nil) + + PanicMatches(t, func() { validate.RegisterValidation("dive", fn) }, "Tag \"dive\" either contains restricted characters or is the same as a restricted tag needed for normal operation") } func TestChangeTag(t *testing.T) { From b4563c2cf653ad1a3ad91010d6af2165c7eb1f58 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 30 Aug 2015 20:56:59 -0400 Subject: [PATCH 04/22] Add alias docs + notes --- doc.go | 12 ++++++++++++ validator.go | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 13bb66e..cc96f2d 100644 --- a/doc.go +++ b/doc.go @@ -445,6 +445,18 @@ Here is a list of the current built in validators: http://golang.org/src/net/mac.go?s=866:918#L29 (Usage: mac) +Alias Validators and Tags + +NOTE: when returning an error the tag returned in FieldError will be +the alias tag unless the dive tag is part of the alias; everything after the +dive tag is not reported as the alias tag. Also the ActualTag in the before case +will be the actual tag within the alias that failed. + +Here is a list of the current built in alias tags: + + iscolor + alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor) + Validator notes: regex diff --git a/validator.go b/validator.go index 1fbba26..7382aa9 100644 --- a/validator.go +++ b/validator.go @@ -189,7 +189,10 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // RegisterAliasValidation registers a mapping of a single validationstag that // defines a common or complex set of validation(s) to simplify adding validation -// to structs. +// to structs. NOTE: when returning an error the tag returned in FieldError will be +// the alias tag unless the dive tag is part of the alias; everything after the +// dive tag is not reported as the alias tag. Also the ActualTag in the before case +// will be the actual tag within the alias that failed. func (v *Validate) RegisterAliasValidation(alias, tags string) { if len(v.config.aliasValidators) == 0 { From b213226b01517e4292d9a357111dcc14863ab019 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 30 Aug 2015 20:59:08 -0400 Subject: [PATCH 05/22] Add Alias to the unique features section of README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 87aff6e..a44a95d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ It has the following **unique** features: - Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated. - Handles type interface by determining it's underlying type prior to validation. - Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) +- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs Installation ------------ From c293315337a324d890ecbdd2a50eaf3be86e44da Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Sun, 30 Aug 2015 21:49:18 -0400 Subject: [PATCH 06/22] updated tag caching for maximum performance --- util.go | 43 +++++++++++++++++--------------- validator.go | 69 +++++++++++++++++++++++++++------------------------- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/util.go b/util.go index 59e4505..6ca21ce 100644 --- a/util.go +++ b/util.go @@ -232,18 +232,18 @@ func panicIf(err error) { } } -func (v *Validate) parseTags(tag, fieldName string) []*tagCache { +func (v *Validate) parseTags(tag, fieldName string) *cachedTag { - tags, _ := v.parseTagsRecursive(tag, fieldName, blank, false) - return tags -} + cTag := &cachedTag{} -func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool) ([]*tagCache, bool) { + v.parseTagsRecursive(cTag, tag, fieldName, blank, false) + return cTag +} - tags := []*tagCache{} +func (v *Validate) parseTagsRecursive(cTag *cachedTag, tag, fieldName, alias string, isAlias bool) bool { if len(tag) == 0 { - return tags, true + return true } for _, t := range strings.Split(tag, tagSeparator) { @@ -252,11 +252,10 @@ func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool // check map for alias and process new tags, otherwise process as usual if tagsVal, ok := v.config.aliasValidators[t]; ok { - aliasTags, leave := v.parseTagsRecursive(tagsVal, fieldName, t, true) - tags = append(tags, aliasTags...) + leave := v.parseTagsRecursive(cTag, tagsVal, fieldName, t, true) if leave { - return tags, leave + return leave } continue @@ -264,16 +263,20 @@ func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool } if t == diveTag { - tVals := &tagCache{diveTag: tag, tagVals: [][]string{{t}}} - tags = append(tags, tVals) - return tags, true + cTag.diveTag = tag + tVals := &tagVals{tagVals: [][]string{{t}}} + cTag.tags = append(cTag.tags, tVals) + return true + } + + if t == omitempty { + cTag.isOmitEmpty = true } // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C" orVals := strings.Split(t, orSeparator) - cTag := &tagCache{isAlias: isAlias, isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))} - - tags = append(tags, cTag) + tagVal := &tagVals{isAlias: isAlias, isOrVal: len(orVals) > 1, tagVals: make([][]string, len(orVals))} + cTag.tags = append(cTag.tags, tagVal) var key string var param string @@ -282,10 +285,10 @@ func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool vals := strings.SplitN(val, tagKeySeparator, 2) key = vals[0] - cTag.tag = key + tagVal.tag = key if isAlias { - cTag.tag = alias + tagVal.tag = alias } if len(key) == 0 { @@ -296,9 +299,9 @@ func (v *Validate) parseTagsRecursive(tag, fieldName, alias string, isAlias bool param = strings.Replace(strings.Replace(vals[1], utf8HexComma, ",", -1), utf8Pipe, "|", -1) } - cTag.tagVals[i] = []string{key, param} + tagVal.tagVals[i] = []string{key, param} } } - return tags, false + return false } diff --git a/validator.go b/validator.go index 7382aa9..52e74ac 100644 --- a/validator.go +++ b/validator.go @@ -41,7 +41,7 @@ var ( timeType = reflect.TypeOf(time.Time{}) timePtrType = reflect.TypeOf(&time.Time{}) errsPool = &sync.Pool{New: newValidationErrors} - tagsCache = &tagCacheMap{m: map[string][]*tagCache{}} + tagsCache = &tagCacheMap{m: map[string]*cachedTag{}} emptyStructPtr = new(struct{}) ) @@ -50,28 +50,32 @@ func newValidationErrors() interface{} { return ValidationErrors{} } -type tagCache struct { +type cachedTag struct { + isOmitEmpty bool + diveTag string + tags []*tagVals +} + +type tagVals struct { tagVals [][]string isOrVal bool isAlias bool tag string - diveTag string - // actualTag string } type tagCacheMap struct { lock sync.RWMutex - m map[string][]*tagCache + m map[string]*cachedTag } -func (s *tagCacheMap) Get(key string) ([]*tagCache, bool) { +func (s *tagCacheMap) Get(key string) (*cachedTag, bool) { s.lock.RLock() defer s.lock.RUnlock() value, ok := s.m[key] return value, ok } -func (s *tagCacheMap) Set(key string, value []*tagCache) { +func (s *tagCacheMap) Set(key string, value *cachedTag) { s.lock.Lock() defer s.lock.Unlock() s.m[key] = value @@ -399,11 +403,11 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - tags, isCached := tagsCache.Get(tag) + cTag, isCached := tagsCache.Get(tag) if !isCached { - tags = v.parseTags(tag, name) - tagsCache.Set(tag, tags) + cTag = v.parseTags(tag, name) + tagsCache.Set(tag, cTag) } current, kind := v.extractType(current) @@ -411,7 +415,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. switch kind { case reflect.Ptr, reflect.Interface, reflect.Invalid: - if strings.Contains(tag, omitempty) { + if cTag.isOmitEmpty { return } @@ -420,9 +424,9 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. if kind == reflect.Invalid { errs[errPrefix+name] = &FieldError{ Field: name, - Tag: tags[0].tag, - ActualTag: tags[0].tagVals[0][0], - Param: tags[0].tagVals[0][1], + Tag: cTag.tags[0].tag, + ActualTag: cTag.tags[0].tagVals[0][0], + Param: cTag.tags[0].tagVals[0][1], Kind: kind, } return @@ -430,9 +434,9 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. errs[errPrefix+name] = &FieldError{ Field: name, - Tag: tags[0].tag, - ActualTag: tags[0].tagVals[0][0], - Param: tags[0].tagVals[0][1], + Tag: cTag.tags[0].tag, + ActualTag: cTag.tags[0].tagVals[0][0], + Param: cTag.tags[0].tagVals[0][1], Value: current.Interface(), Kind: kind, Type: current.Type(), @@ -471,20 +475,19 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. var dive bool var diveSubTag string - for _, cTag := range tags { + for _, valTag := range cTag.tags { - if cTag.tagVals[0][0] == existsTag { + if valTag.tagVals[0][0] == existsTag { continue } - if cTag.tagVals[0][0] == diveTag { + if valTag.tagVals[0][0] == diveTag { dive = true - // fmt.Println(cTag.diveTag) diveSubTag = strings.TrimLeft(strings.SplitN(cTag.diveTag, diveTag, 2)[1], ",") break } - if cTag.tagVals[0][0] == omitempty { + if valTag.tagVals[0][0] == omitempty { if !hasValue(v, topStruct, currentStruct, current, typ, kind, blank) { return @@ -492,7 +495,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. continue } - if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, cTag, name) { + if v.validateField(topStruct, currentStruct, current, typ, kind, errPrefix, errs, valTag, name) { return } } @@ -530,16 +533,16 @@ func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Va } // validateField validates a field based on the provided tag's key and param values and returns true if there is an error or 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, cTag *tagCache, 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, valTag *tagVals, name string) bool { var valFunc Func var ok bool - if cTag.isOrVal { + if valTag.isOrVal { errTag := blank - for _, val := range cTag.tagVals { + for _, val := range valTag.tagVals { valFunc, ok = v.config.ValidationFuncs[val[0]] if !ok { @@ -553,10 +556,10 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. errTag += orSeparator + val[0] } - if cTag.isAlias { + if valTag.isAlias { errs[errPrefix+name] = &FieldError{ Field: name, - Tag: cTag.tag, + Tag: valTag.tag, ActualTag: errTag[1:], Value: current.Interface(), Type: currentType, @@ -576,21 +579,21 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. return true } - valFunc, ok = v.config.ValidationFuncs[cTag.tagVals[0][0]] + valFunc, ok = v.config.ValidationFuncs[valTag.tagVals[0][0]] if !ok { panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } - if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, cTag.tagVals[0][1]) { + if valFunc(v, topStruct, currentStruct, current, currentType, currentKind, valTag.tagVals[0][1]) { return false } errs[errPrefix+name] = &FieldError{ Field: name, - Tag: cTag.tag, - ActualTag: cTag.tagVals[0][0], + Tag: valTag.tag, + ActualTag: valTag.tagVals[0][0], Value: current.Interface(), - Param: cTag.tagVals[0][1], + Param: valTag.tagVals[0][1], Type: currentType, Kind: currentKind, } From 4807bf93bb2f48cdb7ecbf0eceea6cee93bc6aa8 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Mon, 31 Aug 2015 16:40:07 -0400 Subject: [PATCH 07/22] unexpose baked in validators map for safety. --- benchmarks_test.go | 37 ++++++++++++++++++---------------- util.go | 2 +- validator.go | 50 ++++++++++++++++++++++++++++++---------------- validator_test.go | 26 ++++++++++++------------ 4 files changed, 67 insertions(+), 48 deletions(-) diff --git a/benchmarks_test.go b/benchmarks_test.go index 3649bf9..284750d 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -2,7 +2,6 @@ package validator import ( sql "database/sql/driver" - "reflect" "testing" "time" ) @@ -33,11 +32,12 @@ func BenchmarkFieldDiveFailure(b *testing.B) { func BenchmarkFieldCustomTypeSuccess(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + // customTypes := map[reflect.Type]CustomTypeFunc{} + // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate := New(Config{TagName: "validate"}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{ Name: "1", @@ -50,11 +50,12 @@ func BenchmarkFieldCustomTypeSuccess(b *testing.B) { func BenchmarkFieldCustomTypeFailure(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + // customTypes := map[reflect.Type]CustomTypeFunc{} + // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate := New(Config{TagName: "validate"}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{} @@ -77,11 +78,12 @@ func BenchmarkFieldOrTagFailure(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + // customTypes := map[reflect.Type]CustomTypeFunc{} + // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate := New(Config{TagName: "validate"}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{ Name: "1", @@ -101,11 +103,12 @@ func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + // customTypes := map[reflect.Type]CustomTypeFunc{} + // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType + // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate := New(Config{TagName: "validate"}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{} diff --git a/util.go b/util.go index 6ca21ce..372b964 100644 --- a/util.go +++ b/util.go @@ -54,7 +54,7 @@ func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Ki default: if v.config.hasCustomFuncs { - if fn, ok := v.config.CustomTypeFuncs[current.Type()]; ok { + if fn, ok := v.config.customTypeFuncs[current.Type()]; ok { return v.extractType(reflect.ValueOf(fn(current))) } } diff --git a/validator.go b/validator.go index 52e74ac..70a0b34 100644 --- a/validator.go +++ b/validator.go @@ -90,8 +90,8 @@ type Validate struct { // It is passed to the New() function type Config struct { TagName string - ValidationFuncs map[string]Func - CustomTypeFuncs map[reflect.Type]CustomTypeFunc + validationFuncs map[string]Func + customTypeFuncs map[reflect.Type]CustomTypeFunc aliasValidators map[string]string hasCustomFuncs bool hasAliasValidators bool @@ -146,8 +146,24 @@ type FieldError struct { // New creates a new Validate instance for use. func New(config Config) *Validate { - if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { - config.hasCustomFuncs = true + // if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { + // config.hasCustomFuncs = true + // } + + if len(config.aliasValidators) == 0 { + // must copy validators for separate validations to be used in each + config.aliasValidators = map[string]string{} + for k, val := range BakedInAliasValidators { + config.aliasValidators[k] = val + } + } + + if len(config.validationFuncs) == 0 { + // must copy validators for separate validations to be used in each + config.validationFuncs = map[string]Func{} + for k, val := range BakedInValidators { + config.validationFuncs[k] = val + } } return &Validate{config: config} @@ -172,7 +188,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error { panic(fmt.Sprintf(restrictedTagErr, key)) } - v.config.ValidationFuncs[key] = f + v.config.validationFuncs[key] = f return nil } @@ -180,12 +196,12 @@ func (v *Validate) RegisterValidation(key string, f Func) error { // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { - if v.config.CustomTypeFuncs == nil { - v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} + if v.config.customTypeFuncs == nil { + v.config.customTypeFuncs = map[reflect.Type]CustomTypeFunc{} } for _, t := range types { - v.config.CustomTypeFuncs[reflect.TypeOf(t)] = fn + v.config.customTypeFuncs[reflect.TypeOf(t)] = fn } v.config.hasCustomFuncs = true @@ -199,13 +215,13 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // will be the actual tag within the alias that failed. func (v *Validate) RegisterAliasValidation(alias, tags string) { - if len(v.config.aliasValidators) == 0 { - // must copy validators for separate validations to be used in each - v.config.aliasValidators = map[string]string{} - for k, val := range BakedInAliasValidators { - v.config.aliasValidators[k] = val - } - } + // if len(v.config.aliasValidators) == 0 { + // // must copy validators for separate validations to be used in each + // v.config.aliasValidators = map[string]string{} + // for k, val := range BakedInAliasValidators { + // v.config.aliasValidators[k] = val + // } + // } _, ok := restrictedTags[alias] @@ -544,7 +560,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. for _, val := range valTag.tagVals { - valFunc, ok = v.config.ValidationFuncs[val[0]] + valFunc, ok = v.config.validationFuncs[val[0]] if !ok { panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } @@ -579,7 +595,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. return true } - valFunc, ok = v.config.ValidationFuncs[valTag.tagVals[0][0]] + valFunc, ok = v.config.validationFuncs[valTag.tagVals[0][0]] if !ok { panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } diff --git a/validator_test.go b/validator_test.go index 45a3e58..af898f9 100644 --- a/validator_test.go +++ b/validator_test.go @@ -111,7 +111,7 @@ type TestSlice struct { OmitEmpty []int `validate:"omitempty,min=1,max=10"` } -var validate = New(Config{TagName: "validate", ValidationFuncs: BakedInValidators}) +var validate = New(Config{TagName: "validate"}) func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { @@ -1254,8 +1254,7 @@ func TestExistsValidation(t *testing.T) { func TestSQLValue2Validation(t *testing.T) { config := Config{ - TagName: "validate", - ValidationFuncs: BakedInValidators, + TagName: "validate", } validate := New(config) @@ -1311,13 +1310,16 @@ func TestSQLValue2Validation(t *testing.T) { func TestSQLValueValidation(t *testing.T) { - customTypes := map[reflect.Type]CustomTypeFunc{} - customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType - customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType - customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason + // customTypes := map[reflect.Type]CustomTypeFunc{} + // customTypes[reflect.TypeOf((*driver.Valuer)(nil))] = ValidateValuerType + // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType + // customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType + // customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason - validate := New(Config{TagName: "validate", ValidationFuncs: BakedInValidators, CustomTypeFuncs: customTypes}) + validate := New(Config{TagName: "validate"}) + validate.RegisterCustomTypeFunc(ValidateValuerType, (*driver.Valuer)(nil), valuer{}) + validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) + validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1) val := valuer{ Name: "", @@ -3872,8 +3874,7 @@ func TestAddFunctions(t *testing.T) { } config := Config{ - TagName: "validateme", - ValidationFuncs: BakedInValidators, + TagName: "validateme", } validate := New(config) @@ -3896,8 +3897,7 @@ func TestAddFunctions(t *testing.T) { func TestChangeTag(t *testing.T) { config := Config{ - TagName: "val", - ValidationFuncs: BakedInValidators, + TagName: "val", } validate := New(config) From 9988ba3f45f50e8ff1472637fdd0168633cb6179 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Tue, 1 Sep 2015 23:15:30 -0400 Subject: [PATCH 08/22] update New to use register functions to avoid errors --- validator.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/validator.go b/validator.go index 70a0b34..04af642 100644 --- a/validator.go +++ b/validator.go @@ -149,24 +149,25 @@ func New(config Config) *Validate { // if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { // config.hasCustomFuncs = true // } + v := &Validate{config: config} - if len(config.aliasValidators) == 0 { + if len(v.config.aliasValidators) == 0 { // must copy validators for separate validations to be used in each - config.aliasValidators = map[string]string{} + v.config.aliasValidators = map[string]string{} for k, val := range BakedInAliasValidators { - config.aliasValidators[k] = val + v.RegisterAliasValidation(k, val) } } - if len(config.validationFuncs) == 0 { + if len(v.config.validationFuncs) == 0 { // must copy validators for separate validations to be used in each - config.validationFuncs = map[string]Func{} + v.config.validationFuncs = map[string]Func{} for k, val := range BakedInValidators { - config.validationFuncs[k] = val + v.RegisterValidation(k, val) } } - return &Validate{config: config} + return v } // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key From 10cf645b915374136d39f1c5cfaa2d97342d234a Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 08:11:44 -0400 Subject: [PATCH 09/22] reorganization of code + variables to more appropriate structs --- baked_in.go | 4 +- benchmarks_test.go | 21 +------- examples_test.go | 22 +++----- util.go | 8 +-- validator.go | 94 ++++++++++++++++------------------ validator_test.go | 124 +++++++++++++++++++++++---------------------- 6 files changed, 121 insertions(+), 152 deletions(-) diff --git a/baked_in.go b/baked_in.go index c749266..c735046 100644 --- a/baked_in.go +++ b/baked_in.go @@ -14,14 +14,14 @@ import ( // defines a common or complex set of validation(s) to simplify // adding validation to structs. i.e. set key "_ageok" and the tags // are "gt=0,lte=130" or key "_preferredname" and tags "omitempty,gt=0,lte=60" -var BakedInAliasValidators = map[string]string{ +var bakedInAliasValidators = map[string]string{ "iscolor": "hexcolor|rgb|rgba|hsl|hsla", } // BakedInValidators is the default map of ValidationFunc // you can add, remove or even replace items to suite your needs, // or even disregard and use your own map if so desired. -var BakedInValidators = map[string]Func{ +var bakedInValidators = map[string]Func{ "required": hasValue, "len": hasLengthOf, "min": hasMinOf, diff --git a/benchmarks_test.go b/benchmarks_test.go index 284750d..3a2a1fc 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -32,11 +32,6 @@ func BenchmarkFieldDiveFailure(b *testing.B) { func BenchmarkFieldCustomTypeSuccess(b *testing.B) { - // customTypes := map[reflect.Type]CustomTypeFunc{} - // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate"}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{ @@ -50,11 +45,7 @@ func BenchmarkFieldCustomTypeSuccess(b *testing.B) { func BenchmarkFieldCustomTypeFailure(b *testing.B) { - // customTypes := map[reflect.Type]CustomTypeFunc{} - // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate"}) + // validate := New(Config{TagName: "validate"}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{} @@ -78,11 +69,6 @@ func BenchmarkFieldOrTagFailure(b *testing.B) { func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { - // customTypes := map[reflect.Type]CustomTypeFunc{} - // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate"}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{ @@ -103,11 +89,6 @@ func BenchmarkStructSimpleCustomTypeSuccess(b *testing.B) { func BenchmarkStructSimpleCustomTypeFailure(b *testing.B) { - // customTypes := map[reflect.Type]CustomTypeFunc{} - // customTypes[reflect.TypeOf((*sql.Valuer)(nil))] = ValidateValuerType - // customTypes[reflect.TypeOf(valuer{})] = ValidateValuerType - - validate := New(Config{TagName: "validate"}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*sql.Valuer)(nil), valuer{}) val := valuer{} diff --git a/examples_test.go b/examples_test.go index 8605168..2ebf8b3 100644 --- a/examples_test.go +++ b/examples_test.go @@ -3,14 +3,12 @@ package validator_test import ( "fmt" - "gopkg.in/bluesuncorp/validator.v7" + // "gopkg.in/bluesuncorp/validator.v7" + "../validator" ) func ExampleValidate_new() { - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := validator.Config{TagName: "validate"} validator.New(config) } @@ -19,16 +17,13 @@ func ExampleValidate_field() { // This should be stored somewhere globally var validate *validator.Validate - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := validator.Config{TagName: "validate"} validate = validator.New(config) i := 0 errs := validate.Field(i, "gt=1,lte=10") - err := errs[""] + err := errs.(validator.ValidationErrors)[""] fmt.Println(err.Field) fmt.Println(err.Tag) fmt.Println(err.Kind) // NOTE: Kind and Type can be different i.e. time Kind=struct and Type=time.Time @@ -48,10 +43,7 @@ func ExampleValidate_struct() { // This should be stored somewhere globally var validate *validator.Validate - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := validator.Config{TagName: "validate"} validate = validator.New(config) @@ -81,7 +73,7 @@ func ExampleValidate_struct() { } errs := validate.Struct(user) - for _, v := range errs { + for _, v := range errs.(validator.ValidationErrors) { fmt.Println(v.Field) // Phone fmt.Println(v.Tag) // required //... and so forth diff --git a/util.go b/util.go index 372b964..404bb78 100644 --- a/util.go +++ b/util.go @@ -53,8 +53,8 @@ func (v *Validate) extractType(current reflect.Value) (reflect.Value, reflect.Ki default: - if v.config.hasCustomFuncs { - if fn, ok := v.config.customTypeFuncs[current.Type()]; ok { + if v.hasCustomFuncs { + if fn, ok := v.customTypeFuncs[current.Type()]; ok { return v.extractType(reflect.ValueOf(fn(current))) } } @@ -248,9 +248,9 @@ func (v *Validate) parseTagsRecursive(cTag *cachedTag, tag, fieldName, alias str for _, t := range strings.Split(tag, tagSeparator) { - if v.config.hasAliasValidators { + if v.hasAliasValidators { // check map for alias and process new tags, otherwise process as usual - if tagsVal, ok := v.config.aliasValidators[t]; ok { + if tagsVal, ok := v.aliasValidators[t]; ok { leave := v.parseTagsRecursive(cTag, tagsVal, fieldName, t, true) diff --git a/validator.go b/validator.go index 04af642..eb5b7d6 100644 --- a/validator.go +++ b/validator.go @@ -40,9 +40,8 @@ const ( var ( timeType = reflect.TypeOf(time.Time{}) timePtrType = reflect.TypeOf(&time.Time{}) - errsPool = &sync.Pool{New: newValidationErrors} - tagsCache = &tagCacheMap{m: map[string]*cachedTag{}} emptyStructPtr = new(struct{}) + errsPool = &sync.Pool{New: newValidationErrors} ) // returns new ValidationErrors to the pool @@ -83,18 +82,19 @@ func (s *tagCacheMap) Set(key string, value *cachedTag) { // Validate contains the validator settings passed in using the Config struct type Validate struct { - config Config -} - -// Config contains the options that a Validator instance will use. -// It is passed to the New() function -type Config struct { - TagName string + config Config validationFuncs map[string]Func customTypeFuncs map[reflect.Type]CustomTypeFunc aliasValidators map[string]string hasCustomFuncs bool hasAliasValidators bool + tagsCache *tagCacheMap +} + +// Config contains the options that a Validator instance will use. +// It is passed to the New() function +type Config struct { + TagName string } // CustomTypeFunc allows for overriding or adding custom field type handler functions @@ -149,20 +149,20 @@ func New(config Config) *Validate { // if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { // config.hasCustomFuncs = true // } - v := &Validate{config: config} + v := &Validate{config: config, tagsCache: &tagCacheMap{m: map[string]*cachedTag{}}} - if len(v.config.aliasValidators) == 0 { - // must copy validators for separate validations to be used in each - v.config.aliasValidators = map[string]string{} - for k, val := range BakedInAliasValidators { + if len(v.aliasValidators) == 0 { + // must copy validators for separate validations to be used in each validator instance + v.aliasValidators = map[string]string{} + for k, val := range bakedInAliasValidators { v.RegisterAliasValidation(k, val) } } - if len(v.config.validationFuncs) == 0 { - // must copy validators for separate validations to be used in each - v.config.validationFuncs = map[string]Func{} - for k, val := range BakedInValidators { + if len(v.validationFuncs) == 0 { + // must copy validators for separate validations to be used in each instance + v.validationFuncs = map[string]Func{} + for k, val := range bakedInValidators { v.RegisterValidation(k, val) } } @@ -189,7 +189,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error { panic(fmt.Sprintf(restrictedTagErr, key)) } - v.config.validationFuncs[key] = f + v.validationFuncs[key] = f return nil } @@ -197,15 +197,15 @@ func (v *Validate) RegisterValidation(key string, f Func) error { // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { - if v.config.customTypeFuncs == nil { - v.config.customTypeFuncs = map[reflect.Type]CustomTypeFunc{} + if v.customTypeFuncs == nil { + v.customTypeFuncs = map[reflect.Type]CustomTypeFunc{} } for _, t := range types { - v.config.customTypeFuncs[reflect.TypeOf(t)] = fn + v.customTypeFuncs[reflect.TypeOf(t)] = fn } - v.config.hasCustomFuncs = true + v.hasCustomFuncs = true } // RegisterAliasValidation registers a mapping of a single validationstag that @@ -216,28 +216,21 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // will be the actual tag within the alias that failed. func (v *Validate) RegisterAliasValidation(alias, tags string) { - // if len(v.config.aliasValidators) == 0 { - // // must copy validators for separate validations to be used in each - // v.config.aliasValidators = map[string]string{} - // for k, val := range BakedInAliasValidators { - // v.config.aliasValidators[k] = val - // } - // } - _, ok := restrictedTags[alias] if ok || strings.ContainsAny(alias, restrictedTagChars) { panic(fmt.Sprintf(restrictedAliasErr, alias)) } - v.config.aliasValidators[alias] = tags - v.config.hasAliasValidators = true + v.aliasValidators[alias] = tags + v.hasAliasValidators = true } -// Field validates a single field using tag style validation and returns ValidationErrors +// Field validates a single field using tag style validation and returns nil or ValidationErrors as type error. +// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error -func (v *Validate) Field(field interface{}, tag string) ValidationErrors { +func (v *Validate) Field(field interface{}, tag string) error { errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) @@ -252,10 +245,11 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { return errs } -// FieldWithValue validates a single field, against another fields value using tag style validation and returns ValidationErrors +// FieldWithValue validates a single field, against another fields value using tag style validation and returns nil or ValidationErrors. +// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error -func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors { +func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error { errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) @@ -272,10 +266,9 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string // StructPartial validates the fields passed in only, ignoring all others. // Fields may be provided in a namespaced fashion relative to the struct provided -// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name -// NOTE: This is normally not needed, however in some specific cases such as: tied to a -// legacy data structure, it will be useful -func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error +// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. +func (v *Validate) StructPartial(current interface{}, fields ...string) error { sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() @@ -330,10 +323,9 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati // 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 -// NOTE: This is normally not needed, however in some specific cases such as: tied to a -// legacy data structure, it will be useful -func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { +// i.e. NestedStruct.Field or NestedArrayField[0].Struct.Name and returns nil or ValidationErrors as error +// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. +func (v *Validate) StructExcept(current interface{}, fields ...string) error { sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() @@ -356,7 +348,9 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio } // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. -func (v *Validate) Struct(current interface{}) ValidationErrors { +// it returns nil or ValidationErrors as error. +// You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. +func (v *Validate) Struct(current interface{}) error { errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) @@ -420,11 +414,11 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect. return } - cTag, isCached := tagsCache.Get(tag) + cTag, isCached := v.tagsCache.Get(tag) if !isCached { cTag = v.parseTags(tag, name) - tagsCache.Set(tag, cTag) + v.tagsCache.Set(tag, cTag) } current, kind := v.extractType(current) @@ -561,7 +555,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. for _, val := range valTag.tagVals { - valFunc, ok = v.config.validationFuncs[val[0]] + valFunc, ok = v.validationFuncs[val[0]] if !ok { panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } @@ -596,7 +590,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect. return true } - valFunc, ok = v.config.validationFuncs[valTag.tagVals[0][0]] + valFunc, ok = v.validationFuncs[valTag.tagVals[0][0]] if !ok { panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, name))) } diff --git a/validator_test.go b/validator_test.go index af898f9..117d7e0 100644 --- a/validator_test.go +++ b/validator_test.go @@ -113,7 +113,9 @@ type TestSlice struct { var validate = New(Config{TagName: "validate"}) -func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) { +func AssertError(t *testing.T, err error, key, field, expectedTag string) { + + errs := err.(ValidationErrors) val, ok := errs[key] EqualSkip(t, 2, ok, true) @@ -242,7 +244,7 @@ func TestAliasTags(t *testing.T) { errs = validate.Struct(tst) NotEqual(t, errs, nil) AssertError(t, errs, "Test.Color", "Color", "iscolor") - Equal(t, errs["Test.Color"].ActualTag, "hexcolor|rgb|rgba|hsl|hsla") + Equal(t, errs.(ValidationErrors)["Test.Color"].ActualTag, "hexcolor|rgb|rgba|hsl|hsla") validate.RegisterAliasValidation("req", "required,dive,iscolor") arr := []string{"val1", "#fff", "#000"} @@ -397,12 +399,12 @@ func TestStructPartial(t *testing.T) { // these will fail as unset item IS tested errs = validate.StructExcept(tPartial, p1...) AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) errs = validate.StructPartial(tPartial, p2...) NotEqual(t, errs, nil) AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) // Unset second slice member concurrently to test dive behavior: tPartial.SubSlice[1].Test = "" @@ -417,13 +419,13 @@ func TestStructPartial(t *testing.T) { AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") errs = validate.StructExcept(tPartial, p1...) - Equal(t, len(errs), 2) + Equal(t, len(errs.(ValidationErrors)), 2) AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") errs = validate.StructPartial(tPartial, p2...) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required") // reset struct in slice, and unset struct in slice in unset posistion @@ -439,7 +441,7 @@ func TestStructPartial(t *testing.T) { // testing for missing item by exception, yes it dives and fails errs = validate.StructExcept(tPartial, p1...) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required") errs = validate.StructExcept(tPartial, p2...) @@ -1303,7 +1305,7 @@ func TestSQLValue2Validation(t *testing.T) { errs = validate.Struct(c) NotEqual(t, errs, nil) - Equal(t, len(errs), 2) + Equal(t, len(errs.(ValidationErrors)), 2) AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") } @@ -1362,7 +1364,7 @@ func TestSQLValueValidation(t *testing.T) { errs = validate.Struct(c) NotEqual(t, errs, nil) - Equal(t, len(errs), 2) + Equal(t, len(errs.(ValidationErrors)), 2) AssertError(t, errs, "CustomMadeUpStruct.MadeUp", "MadeUp", "required") AssertError(t, errs, "CustomMadeUpStruct.OverriddenInt", "OverriddenInt", "gt") } @@ -1393,7 +1395,7 @@ func TestMACValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d mac failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "mac" { t.Fatalf("Index: %d mac failed Error: %s", i, errs) } @@ -1431,7 +1433,7 @@ func TestIPValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ip failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ip" { t.Fatalf("Index: %d ip failed Error: %s", i, errs) } @@ -1469,7 +1471,7 @@ func TestIPv6Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ipv6" { t.Fatalf("Index: %d ipv6 failed Error: %s", i, errs) } @@ -1507,7 +1509,7 @@ func TestIPv4Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ipv4" { t.Fatalf("Index: %d ipv4 failed Error: %s", i, errs) } @@ -1664,7 +1666,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD.Data", "Data", "required") type ExternalCMD2 struct { @@ -1681,7 +1683,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s2) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") s3 := &ExternalCMD2{ @@ -1692,7 +1694,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s3) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD2.Data", "Data", "len") type Inner struct { @@ -1711,7 +1713,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(s4) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "ExternalCMD.Data.Name", "Name", "required") type TestMapStructPtr struct { @@ -1726,7 +1728,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(msp) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "len") type TestMultiDimensionalStructs struct { @@ -1744,7 +1746,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(tms) NotEqual(t, errs, nil) - Equal(t, len(errs), 4) + Equal(t, len(errs.(ValidationErrors)), 4) AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "required") @@ -1766,7 +1768,7 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Struct(tmsp2) NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "required") @@ -1778,24 +1780,24 @@ func TestInterfaceErrValidation(t *testing.T) { errs = validate.Field(m, "len=3,dive,len=2") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[3]", "[3]", "len") errs = validate.Field(m, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") arr := []interface{}{"ok", "", "ok"} errs = validate.Field(arr, "len=3,dive,len=2") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[1]", "[1]", "len") errs = validate.Field(arr, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") type MyStruct struct { @@ -1822,12 +1824,12 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Field(m, "len=3,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[3]", "[3]", "required") errs = validate.Field(m, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") type Inner struct { @@ -1846,7 +1848,7 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(ms) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestMapStruct.Errs[3].Name", "Name", "required") // for full test coverage @@ -1867,7 +1869,7 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(mt) NotEqual(t, errs, nil) - Equal(t, len(errs), 2) + Equal(t, len(errs.(ValidationErrors)), 2) AssertError(t, errs, "TestMapTimeStruct.Errs[3]", "Errs[3]", "required") AssertError(t, errs, "TestMapTimeStruct.Errs[4]", "Errs[4]", "required") @@ -1883,7 +1885,7 @@ func TestMapDiveValidation(t *testing.T) { errs = validate.Struct(msp) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "TestMapStructPtr.Errs[3]", "Errs[3]", "required") type TestMapStructPtr2 struct { @@ -1906,12 +1908,12 @@ func TestArrayDiveValidation(t *testing.T) { errs := validate.Field(arr, "len=3,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "[1]", "[1]", "required") errs = validate.Field(arr, "len=2,dive,required") NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "", "", "len") type BadDive struct { @@ -1934,7 +1936,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(test) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "Test.Errs[1]", "Errs[1]", "required") test = &Test{ @@ -1943,7 +1945,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(test) NotEqual(t, errs, nil) - Equal(t, len(errs), 1) + Equal(t, len(errs.(ValidationErrors)), 1) AssertError(t, errs, "Test.Errs[2]", "Errs[2]", "required") type TestMultiDimensional struct { @@ -1961,7 +1963,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tm) NotEqual(t, errs, nil) - Equal(t, len(errs), 4) + Equal(t, len(errs.(ValidationErrors)), 4) AssertError(t, errs, "TestMultiDimensional.Errs[0][1]", "Errs[0][1]", "required") AssertError(t, errs, "TestMultiDimensional.Errs[0][2]", "Errs[0][2]", "required") AssertError(t, errs, "TestMultiDimensional.Errs[1][1]", "Errs[1][1]", "required") @@ -1986,7 +1988,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tms) NotEqual(t, errs, nil) - Equal(t, len(errs), 4) + Equal(t, len(errs.(ValidationErrors)), 4) AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructs.Errs[1][1].Name", "Name", "required") @@ -2008,7 +2010,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmsp) NotEqual(t, errs, nil) - Equal(t, len(errs), 5) + Equal(t, len(errs.(ValidationErrors)), 5) AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr.Errs[1][1].Name", "Name", "required") @@ -2035,7 +2037,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmsp2) NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr2.Errs[1][1].Name", "Name", "required") @@ -2059,7 +2061,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmsp3) NotEqual(t, errs, nil) - Equal(t, len(errs), 5) + Equal(t, len(errs.(ValidationErrors)), 5) AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][1].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[0][2].Name", "Name", "required") AssertError(t, errs, "TestMultiDimensionalStructsPtr3.Errs[1][1].Name", "Name", "required") @@ -2086,7 +2088,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmtp3) NotEqual(t, errs, nil) - Equal(t, len(errs), 3) + Equal(t, len(errs.(ValidationErrors)), 3) AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[1][2]", "Errs[1][2]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][1]", "Errs[2][1]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime.Errs[2][2]", "Errs[2][2]", "required") @@ -2111,7 +2113,7 @@ func TestArrayDiveValidation(t *testing.T) { errs = validate.Struct(tmtp) NotEqual(t, errs, nil) - Equal(t, len(errs), 3) + Equal(t, len(errs.(ValidationErrors)), 3) AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[1][2]", "Errs[1][2]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][1]", "Errs[2][1]", "required") AssertError(t, errs, "TestMultiDimensionalTimeTime2.Errs[2][2]", "Errs[2][2]", "required") @@ -2234,7 +2236,7 @@ func TestSSNValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d SSN failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ssn" { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } @@ -2268,7 +2270,7 @@ func TestLongitudeValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Longitude failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "longitude" { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } @@ -2302,7 +2304,7 @@ func TestLatitudeValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "latitude" { t.Fatalf("Index: %d Latitude failed Error: %s", i, errs) } @@ -2342,7 +2344,7 @@ func TestDataURIValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "datauri" { t.Fatalf("Index: %d DataURI failed Error: %s", i, errs) } @@ -2380,7 +2382,7 @@ func TestMultibyteValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "multibyte" { t.Fatalf("Index: %d Multibyte failed Error: %s", i, errs) } @@ -2419,7 +2421,7 @@ func TestPrintableASCIIValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "printascii" { t.Fatalf("Index: %d Printable ASCII failed Error: %s", i, errs) } @@ -2457,7 +2459,7 @@ func TestASCIIValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "ascii" { t.Fatalf("Index: %d ASCII failed Error: %s", i, errs) } @@ -2492,7 +2494,7 @@ func TestUUID5Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid5" { t.Fatalf("Index: %d UUID5 failed Error: %s", i, errs) } @@ -2526,7 +2528,7 @@ func TestUUID4Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid4" { t.Fatalf("Index: %d UUID4 failed Error: %s", i, errs) } @@ -2559,7 +2561,7 @@ func TestUUID3Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid3" { t.Fatalf("Index: %d UUID3 failed Error: %s", i, errs) } @@ -2595,7 +2597,7 @@ func TestUUIDValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uuid" { t.Fatalf("Index: %d UUID failed Error: %s", i, errs) } @@ -2633,7 +2635,7 @@ func TestISBNValidation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "isbn" { t.Fatalf("Index: %d ISBN failed Error: %s", i, errs) } @@ -2670,7 +2672,7 @@ func TestISBN13Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "isbn13" { t.Fatalf("Index: %d ISBN13 failed Error: %s", i, errs) } @@ -2708,7 +2710,7 @@ func TestISBN10Validation(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "isbn10" { t.Fatalf("Index: %d ISBN10 failed Error: %s", i, errs) } @@ -4177,7 +4179,7 @@ func TestUrl(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "url" { t.Fatalf("Index: %d URL failed Error: %s", i, errs) } @@ -4241,7 +4243,7 @@ func TestUri(t *testing.T) { if IsEqual(errs, nil) { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } else { - val := errs[""] + val := errs.(ValidationErrors)[""] if val.Tag != "uri" { t.Fatalf("Index: %d URI failed Error: %s", i, errs) } @@ -4707,7 +4709,7 @@ func TestStructStringValidation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 13) + Equal(t, len(errs.(ValidationErrors)), 13) // Assert Fields AssertError(t, errs, "TestString.Required", "Required", "required") @@ -4762,7 +4764,7 @@ func TestStructInt32Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 10) + Equal(t, len(errs.(ValidationErrors)), 10) // Assert Fields AssertError(t, errs, "TestInt32.Required", "Required", "required") @@ -4804,7 +4806,7 @@ func TestStructUint64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Fields AssertError(t, errs, "TestUint64.Required", "Required", "required") @@ -4842,7 +4844,7 @@ func TestStructFloat64Validation(t *testing.T) { // Assert Top Level NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Fields AssertError(t, errs, "TestFloat64.Required", "Required", "required") @@ -4878,7 +4880,7 @@ func TestStructSliceValidation(t *testing.T) { errs = validate.Struct(tFail) NotEqual(t, errs, nil) - Equal(t, len(errs), 6) + Equal(t, len(errs.(ValidationErrors)), 6) // Assert Field Errors AssertError(t, errs, "TestSlice.Required", "Required", "required") From 70cb087d5ac37008dc7edf011b7f30f34d3aa771 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 08:25:26 -0400 Subject: [PATCH 10/22] update how config is passed in and assigned to validator * makes it safer as options passed in by reference, in the future, will not be manipulatable externally. --- examples_test.go | 6 +++--- validator.go | 8 ++++---- validator_test.go | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples_test.go b/examples_test.go index 2ebf8b3..3144bae 100644 --- a/examples_test.go +++ b/examples_test.go @@ -8,7 +8,7 @@ import ( ) func ExampleValidate_new() { - config := validator.Config{TagName: "validate"} + config := &validator.Config{TagName: "validate"} validator.New(config) } @@ -17,7 +17,7 @@ func ExampleValidate_field() { // This should be stored somewhere globally var validate *validator.Validate - config := validator.Config{TagName: "validate"} + config := &validator.Config{TagName: "validate"} validate = validator.New(config) @@ -43,7 +43,7 @@ func ExampleValidate_struct() { // This should be stored somewhere globally var validate *validator.Validate - config := validator.Config{TagName: "validate"} + config := &validator.Config{TagName: "validate"} validate = validator.New(config) diff --git a/validator.go b/validator.go index eb5b7d6..bef479d 100644 --- a/validator.go +++ b/validator.go @@ -82,7 +82,7 @@ func (s *tagCacheMap) Set(key string, value *cachedTag) { // Validate contains the validator settings passed in using the Config struct type Validate struct { - config Config + tagName string validationFuncs map[string]Func customTypeFuncs map[reflect.Type]CustomTypeFunc aliasValidators map[string]string @@ -144,12 +144,12 @@ type FieldError struct { } // New creates a new Validate instance for use. -func New(config Config) *Validate { +func New(config *Config) *Validate { // if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { // config.hasCustomFuncs = true // } - v := &Validate{config: config, tagsCache: &tagCacheMap{m: map[string]*cachedTag{}}} + v := &Validate{tagName: config.TagName, tagsCache: &tagCacheMap{m: map[string]*cachedTag{}}} if len(v.aliasValidators) == 0 { // must copy validators for separate validations to be used in each validator instance @@ -403,7 +403,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec } } - v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name, partial, exclude, includeExclude) + v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.tagName), fld.Name, partial, exclude, includeExclude) } } diff --git a/validator_test.go b/validator_test.go index 117d7e0..12c19ff 100644 --- a/validator_test.go +++ b/validator_test.go @@ -111,7 +111,7 @@ type TestSlice struct { OmitEmpty []int `validate:"omitempty,min=1,max=10"` } -var validate = New(Config{TagName: "validate"}) +var validate = New(&Config{TagName: "validate"}) func AssertError(t *testing.T, err error, key, field, expectedTag string) { @@ -1255,7 +1255,7 @@ func TestExistsValidation(t *testing.T) { func TestSQLValue2Validation(t *testing.T) { - config := Config{ + config := &Config{ TagName: "validate", } @@ -1318,7 +1318,7 @@ func TestSQLValueValidation(t *testing.T) { // customTypes[reflect.TypeOf(MadeUpCustomType{})] = ValidateCustomType // customTypes[reflect.TypeOf(1)] = OverrideIntTypeForSomeReason - validate := New(Config{TagName: "validate"}) + validate := New(&Config{TagName: "validate"}) validate.RegisterCustomTypeFunc(ValidateValuerType, (*driver.Valuer)(nil), valuer{}) validate.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) validate.RegisterCustomTypeFunc(OverrideIntTypeForSomeReason, 1) @@ -3875,7 +3875,7 @@ func TestAddFunctions(t *testing.T) { return true } - config := Config{ + config := &Config{ TagName: "validateme", } @@ -3898,7 +3898,7 @@ func TestAddFunctions(t *testing.T) { func TestChangeTag(t *testing.T) { - config := Config{ + config := &Config{ TagName: "val", } validate := New(config) From 0b51279b056ac945de2653a3647674c879873197 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 08:31:16 -0400 Subject: [PATCH 11/22] Update Error Pool * made error pool per validator instance to reduce contention is multiple validators are in play. --- validator.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/validator.go b/validator.go index bef479d..1dae1d6 100644 --- a/validator.go +++ b/validator.go @@ -41,14 +41,8 @@ var ( timeType = reflect.TypeOf(time.Time{}) timePtrType = reflect.TypeOf(&time.Time{}) emptyStructPtr = new(struct{}) - errsPool = &sync.Pool{New: newValidationErrors} ) -// returns new ValidationErrors to the pool -func newValidationErrors() interface{} { - return ValidationErrors{} -} - type cachedTag struct { isOmitEmpty bool diveTag string @@ -89,6 +83,7 @@ type Validate struct { hasCustomFuncs bool hasAliasValidators bool tagsCache *tagCacheMap + errsPool *sync.Pool } // Config contains the options that a Validator instance will use. @@ -149,7 +144,12 @@ func New(config *Config) *Validate { // if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { // config.hasCustomFuncs = true // } - v := &Validate{tagName: config.TagName, tagsCache: &tagCacheMap{m: map[string]*cachedTag{}}} + v := &Validate{ + tagName: config.TagName, + tagsCache: &tagCacheMap{m: map[string]*cachedTag{}}, + errsPool: &sync.Pool{New: func() interface{} { + return ValidationErrors{} + }}} if len(v.aliasValidators) == 0 { // must copy validators for separate validations to be used in each validator instance @@ -232,13 +232,13 @@ func (v *Validate) RegisterAliasValidation(alias, tags string) { // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) Field(field interface{}, tag string) error { - errs := errsPool.Get().(ValidationErrors) + errs := v.errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) v.traverseField(fieldVal, fieldVal, fieldVal, blank, errs, false, tag, blank, false, false, nil) if len(errs) == 0 { - errsPool.Put(errs) + v.errsPool.Put(errs) return nil } @@ -251,13 +251,13 @@ func (v *Validate) Field(field interface{}, tag string) error { // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) error { - errs := errsPool.Get().(ValidationErrors) + errs := v.errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) v.traverseField(topVal, topVal, reflect.ValueOf(field), blank, errs, false, tag, blank, false, false, nil) if len(errs) == 0 { - errsPool.Put(errs) + v.errsPool.Put(errs) return nil } @@ -309,12 +309,12 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) error { } } - errs := errsPool.Get().(ValidationErrors) + errs := v.errsPool.Get().(ValidationErrors) v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, false, m) if len(errs) == 0 { - errsPool.Put(errs) + v.errsPool.Put(errs) return nil } @@ -335,12 +335,12 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) error { m[name+"."+key] = emptyStructPtr } - errs := errsPool.Get().(ValidationErrors) + errs := v.errsPool.Get().(ValidationErrors) v.tranverseStruct(sv, sv, sv, blank, errs, true, len(m) != 0, true, m) if len(errs) == 0 { - errsPool.Put(errs) + v.errsPool.Put(errs) return nil } @@ -352,13 +352,13 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) error { // You will need to assert the error if it's not nil i.e. err.(validator.ValidationErrors) to access the map of errors. func (v *Validate) Struct(current interface{}) error { - errs := errsPool.Get().(ValidationErrors) + errs := v.errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) v.tranverseStruct(sv, sv, sv, blank, errs, true, false, false, nil) if len(errs) == 0 { - errsPool.Put(errs) + v.errsPool.Put(errs) return nil } From 9990e055a3e091bc5751c789cad0a577bb691df3 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 08:34:22 -0400 Subject: [PATCH 12/22] update comments --- validator.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/validator.go b/validator.go index 1dae1d6..85dcdaf 100644 --- a/validator.go +++ b/validator.go @@ -141,9 +141,6 @@ type FieldError struct { // New creates a new Validate instance for use. func New(config *Config) *Validate { - // if config.CustomTypeFuncs != nil && len(config.CustomTypeFuncs) > 0 { - // config.hasCustomFuncs = true - // } v := &Validate{ tagName: config.TagName, tagsCache: &tagCacheMap{m: map[string]*cachedTag{}}, @@ -152,7 +149,7 @@ func New(config *Config) *Validate { }}} if len(v.aliasValidators) == 0 { - // must copy validators for separate validations to be used in each validator instance + // must copy alias validators for separate validations to be used in each validator instance v.aliasValidators = map[string]string{} for k, val := range bakedInAliasValidators { v.RegisterAliasValidation(k, val) From b2d8b4186c6db4b61e0b4498e410eedcfbd2a103 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 08:35:38 -0400 Subject: [PATCH 13/22] update more comments --- validator.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/validator.go b/validator.go index 85dcdaf..19904d4 100644 --- a/validator.go +++ b/validator.go @@ -169,7 +169,7 @@ func New(config *Config) *Validate { // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key // NOTE: if the key already exists, the previous validation function will be replaced. -// NOTE: this method is not thread-safe +// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterValidation(key string, f Func) error { if len(key) == 0 { @@ -192,6 +192,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error { } // 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.customTypeFuncs == nil { @@ -211,6 +212,7 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // the alias tag unless the dive tag is part of the alias; everything after the // dive tag is not reported as the alias tag. Also the ActualTag in the before case // will be the actual tag within the alias that failed. +// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation func (v *Validate) RegisterAliasValidation(alias, tags string) { _, ok := restrictedTags[alias] From 19b2f07d980eb46528c5024ea38ccc5d05051604 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 08:42:33 -0400 Subject: [PATCH 14/22] Update README for v8 --- README.md | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a44a95d..892db3d 100644 --- a/README.md +++ b/README.md @@ -21,20 +21,20 @@ Installation Use go get. - go get gopkg.in/bluesuncorp/validator.v7 + go get gopkg.in/bluesuncorp/validator.v8 or to update - go get -u gopkg.in/bluesuncorp/validator.v7 + go get -u gopkg.in/bluesuncorp/validator.v8 Then import the validator package into your own code. - import "gopkg.in/bluesuncorp/validator.v7" + import "gopkg.in/bluesuncorp/validator.v8" Usage and documentation ------ -Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v7 for detailed usage docs. +Please see http://godoc.org/gopkg.in/bluesuncorp/validator.v8 for detailed usage docs. ##### Examples: @@ -45,7 +45,7 @@ package main import ( "fmt" - "gopkg.in/bluesuncorp/validator.v7" + "gopkg.in/bluesuncorp/validator.v8" ) // User contains user information @@ -70,10 +70,7 @@ var validate *validator.Validate func main() { - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := &validator.Config{TagName: "validate"} validate = validator.New(config) @@ -99,13 +96,13 @@ func validateStruct() { } // returns nil or ValidationErrors ( map[string]*FieldError ) - errs := validate.Struct(user) + err := validate.Struct(user) if errs != nil { fmt.Println(errs) // output: Key: "User.Age" Error:Field validation for "Age" failed on the "lte" tag // Key: "User.Addresses[0].City" Error:Field validation for "City" failed on the "required" tag - err := errs["User.Addresses[0].City"] + err := errs.(validator.ValidationErrors)["User.Addresses[0].City"] fmt.Println(err.Field) // output: City fmt.Println(err.Tag) // output: required fmt.Println(err.Kind) // output: string @@ -144,7 +141,7 @@ import ( "fmt" "reflect" - "gopkg.in/bluesuncorp/validator.v7" + "gopkg.in/bluesuncorp/validator.v8" ) // DbBackedUser User struct @@ -155,10 +152,7 @@ type DbBackedUser struct { func main() { - config := validator.Config{ - TagName: "validate", - ValidationFuncs: validator.BakedInValidators, - } + config := &validator.Config{TagName: "validate"} validate := validator.New(config) @@ -168,7 +162,7 @@ func main() { x := DbBackedUser{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{Int64: 0, Valid: false}} errs := validate.Struct(x) - if len(errs) > 0 { + if len(errs.(validator.ValidationErrors)) > 0 { fmt.Printf("Errs:\n%+v\n", errs) } } From d792c5a5789ecdfbe19eda6a94790b0fa8cfe2a9 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 08:59:36 -0400 Subject: [PATCH 15/22] Add validation error info to DOC --- doc.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc.go b/doc.go index cc96f2d..843b00d 100644 --- a/doc.go +++ b/doc.go @@ -16,6 +16,16 @@ I needed to know the field and what validation failed so that I could provide an return "Translated string based on field" } +Validation functions return type error + +They return type error to avoid the issue discussed in the following, where err is always != nil: +http://stackoverflow.com/a/29138676/3158232 +https://github.com/bluesuncorp/validator/issues/134 + +validator only returns nil or ValidationErrors as type error; so in you code all you need to do +is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors +like so err.(validator.ValidationErrors) + Custom Functions Custom functions can be added From 22a5f70a0ec1864d85218111236094f456adce48 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 09:05:05 -0400 Subject: [PATCH 16/22] Add Error handling info to README --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 892db3d..6be6371 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,25 @@ Then import the validator package into your own code. import "gopkg.in/bluesuncorp/validator.v8" +Error Return Value +------- + +Validation functions return type error + +They return type error to avoid the issue discussed in the following, where err is always != nil: + +* http://stackoverflow.com/a/29138676/3158232 +* https://github.com/bluesuncorp/validator/issues/134 + +validator only returns nil or ValidationErrors as type error; so in you code all you need to do +is check if the error returned is not nil, and if it's not type cast it to type ValidationErrors +like so: + +```go +err := validate.Struct(mystruct) +validationErrors := err.(validator.ValidationErrors) + ``` + Usage and documentation ------ From 715aa553c8a1f4d76186a793b7d86c14b10301e5 Mon Sep 17 00:00:00 2001 From: Joel Williams Date: Wed, 2 Sep 2015 23:42:58 +0200 Subject: [PATCH 17/22] Added checking for nil receiver on Validator methods --- README.md | 6 +++--- validator.go | 26 +++++++++++++++++++++----- validator_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6bf0949..d9e66c2 100644 --- a/README.md +++ b/README.md @@ -225,9 +225,9 @@ How to Contribute There will always be a development branch for each version i.e. `v1-development`. In order to contribute, please make your pull requests against those branches. -If the changes being proposed or requested are breaking changes, please create an issue, for discussion -or create a pull request against the highest development branch for example this package has a -v1 and v1-development branch however, there will also be a v2-development brach even though v2 doesn't exist yet. +If the changes being proposed or requested are breaking changes, please create an issue, for discussion +or create a pull request against the highest development branch for example this package has a +v1 and v1-development branch however, there will also be a v2-development branch even though v2 doesn't exist yet. I strongly encourage everyone whom creates a custom validation function to contribute them and help make this package even better. diff --git a/validator.go b/validator.go index 3542569..c5436db 100644 --- a/validator.go +++ b/validator.go @@ -146,7 +146,9 @@ func New(config Config) *Validate { // NOTE: if the key already exists, the previous validation function will be replaced. // NOTE: this method is not thread-safe func (v *Validate) RegisterValidation(key string, f Func) error { - + if v == nil { + panic("Validate.RegisterValidation called with nil receiver") + } if len(key) == 0 { return errors.New("Function Key cannot be empty") } @@ -162,6 +164,9 @@ func (v *Validate) RegisterValidation(key string, f Func) error { // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { + if v == nil { + panic("Validate.RegisterCustomTypeFunc called with nil receiver") + } if v.config.CustomTypeFuncs == nil { v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} @@ -178,7 +183,9 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) Field(field interface{}, tag string) ValidationErrors { - + if v == nil { + panic("Validate.Field called with nil receiver") + } errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) @@ -196,6 +203,9 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors { + if v == nil { + panic("Validate.FieldWithValue called with nil receiver") + } errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) @@ -216,7 +226,9 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string // NOTE: This is normally not needed, however in some specific cases such as: tied to a // legacy data structure, it will be useful func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { - + if v == nil { + panic("Validate.StructPartial called with nil receiver") + } sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() m := map[string]*struct{}{} @@ -274,7 +286,9 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati // NOTE: This is normally not needed, however in some specific cases such as: tied to a // legacy data structure, it will be useful func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { - + if v == nil { + panic("Validate.StructExcept called with nil receiver") + } sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() m := map[string]*struct{}{} @@ -297,7 +311,9 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. func (v *Validate) Struct(current interface{}) ValidationErrors { - + if v == nil { + panic("Validate.Struct called with nil receiver") + } errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) diff --git a/validator_test.go b/validator_test.go index 22084b4..0a12d50 100644 --- a/validator_test.go +++ b/validator_test.go @@ -210,6 +210,31 @@ type TestPartial struct { } } +func TestNilValidator(t *testing.T) { + + type TestStruct struct { + Test string `validate:"required"` + } + + ts := TestStruct{} + + var val *Validate + + fn := func(v *Validate, topStruct reflect.Value, current reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { + + return current.String() == field.String() + } + + PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, "Validate.RegisterCustomTypeFunc called with nil receiver") + PanicMatches(t, func() { val.RegisterValidation("something", fn) }, "Validate.RegisterValidation called with nil receiver") + PanicMatches(t, func() { val.Field(ts.Test, "required") }, "Validate.Field called with nil receiver") + PanicMatches(t, func() { val.FieldWithValue("test", ts.Test, "required") }, "Validate.FieldWithValue called with nil receiver") + PanicMatches(t, func() { val.Struct(ts) }, "Validate.Struct called with nil receiver") + PanicMatches(t, func() { val.StructExcept(ts, "Test") }, "Validate.StructExcept called with nil receiver") + PanicMatches(t, func() { val.StructPartial(ts, "Test") }, "Validate.StructPartial called with nil receiver") + +} + func TestStructPartial(t *testing.T) { p1 := []string{ From 14df41612656da974670e1b3849367e0c91d2e4a Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 20:13:22 -0400 Subject: [PATCH 18/22] Update Validator instance check * change for one validator instance method and message --- validator.go | 70 +++++++++++++++++++++++------------------------ validator_test.go | 14 +++++----- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/validator.go b/validator.go index c5436db..25a1bd2 100644 --- a/validator.go +++ b/validator.go @@ -20,21 +20,22 @@ import ( ) const ( - utf8HexComma = "0x2C" - utf8Pipe = "0x7C" - tagSeparator = "," - orSeparator = "|" - tagKeySeparator = "=" - structOnlyTag = "structonly" - omitempty = "omitempty" - skipValidationTag = "-" - diveTag = "dive" - existsTag = "exists" - fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" - arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket - mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket - invalidValidation = "Invalid validation tag on field %s" - undefinedValidation = "Undefined validation function on field %s" + utf8HexComma = "0x2C" + utf8Pipe = "0x7C" + tagSeparator = "," + orSeparator = "|" + tagKeySeparator = "=" + structOnlyTag = "structonly" + omitempty = "omitempty" + skipValidationTag = "-" + diveTag = "dive" + existsTag = "exists" + fieldErrMsg = "Key: \"%s\" Error:Field validation for \"%s\" failed on the \"%s\" tag" + arrayIndexFieldName = "%s" + leftBracket + "%d" + rightBracket + mapIndexFieldName = "%s" + leftBracket + "%v" + rightBracket + invalidValidation = "Invalid validation tag on field %s" + undefinedValidation = "Undefined validation function on field %s" + validatorNotInitialized = "Validator instance not initialized" ) var ( @@ -78,6 +79,12 @@ type Validate struct { config Config } +func (v *Validate) initCheck() { + if v == nil { + panic(validatorNotInitialized) + } +} + // Config contains the options that a Validator instance will use. // It is passed to the New() function type Config struct { @@ -146,9 +153,8 @@ func New(config Config) *Validate { // NOTE: if the key already exists, the previous validation function will be replaced. // NOTE: this method is not thread-safe func (v *Validate) RegisterValidation(key string, f Func) error { - if v == nil { - panic("Validate.RegisterValidation called with nil receiver") - } + v.initCheck() + if len(key) == 0 { return errors.New("Function Key cannot be empty") } @@ -164,9 +170,7 @@ func (v *Validate) RegisterValidation(key string, f Func) error { // RegisterCustomTypeFunc registers a CustomTypeFunc against a number of types func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { - if v == nil { - panic("Validate.RegisterCustomTypeFunc called with nil receiver") - } + v.initCheck() if v.config.CustomTypeFuncs == nil { v.config.CustomTypeFuncs = map[reflect.Type]CustomTypeFunc{} @@ -183,9 +187,8 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{ // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) Field(field interface{}, tag string) ValidationErrors { - if v == nil { - panic("Validate.Field called with nil receiver") - } + v.initCheck() + errs := errsPool.Get().(ValidationErrors) fieldVal := reflect.ValueOf(field) @@ -203,9 +206,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors { // NOTE: it returns ValidationErrors instead of a single FieldError because this can also // validate Array, Slice and maps fields which may contain more than one error func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string) ValidationErrors { - if v == nil { - panic("Validate.FieldWithValue called with nil receiver") - } + v.initCheck() errs := errsPool.Get().(ValidationErrors) topVal := reflect.ValueOf(val) @@ -226,9 +227,8 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string // NOTE: This is normally not needed, however in some specific cases such as: tied to a // legacy data structure, it will be useful func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors { - if v == nil { - panic("Validate.StructPartial called with nil receiver") - } + v.initCheck() + sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() m := map[string]*struct{}{} @@ -286,9 +286,8 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati // NOTE: This is normally not needed, however in some specific cases such as: tied to a // legacy data structure, it will be useful func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors { - if v == nil { - panic("Validate.StructExcept called with nil receiver") - } + v.initCheck() + sv, _ := v.extractType(reflect.ValueOf(current)) name := sv.Type().Name() m := map[string]*struct{}{} @@ -311,9 +310,8 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio // Struct validates a structs exposed fields, and automatically validates nested structs, unless otherwise specified. func (v *Validate) Struct(current interface{}) ValidationErrors { - if v == nil { - panic("Validate.Struct called with nil receiver") - } + v.initCheck() + errs := errsPool.Get().(ValidationErrors) sv := reflect.ValueOf(current) diff --git a/validator_test.go b/validator_test.go index 0a12d50..36e2724 100644 --- a/validator_test.go +++ b/validator_test.go @@ -225,13 +225,13 @@ func TestNilValidator(t *testing.T) { return current.String() == field.String() } - PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, "Validate.RegisterCustomTypeFunc called with nil receiver") - PanicMatches(t, func() { val.RegisterValidation("something", fn) }, "Validate.RegisterValidation called with nil receiver") - PanicMatches(t, func() { val.Field(ts.Test, "required") }, "Validate.Field called with nil receiver") - PanicMatches(t, func() { val.FieldWithValue("test", ts.Test, "required") }, "Validate.FieldWithValue called with nil receiver") - PanicMatches(t, func() { val.Struct(ts) }, "Validate.Struct called with nil receiver") - PanicMatches(t, func() { val.StructExcept(ts, "Test") }, "Validate.StructExcept called with nil receiver") - PanicMatches(t, func() { val.StructPartial(ts, "Test") }, "Validate.StructPartial called with nil receiver") + PanicMatches(t, func() { val.RegisterCustomTypeFunc(ValidateCustomType, MadeUpCustomType{}) }, validatorNotInitialized) + PanicMatches(t, func() { val.RegisterValidation("something", fn) }, validatorNotInitialized) + PanicMatches(t, func() { val.Field(ts.Test, "required") }, validatorNotInitialized) + PanicMatches(t, func() { val.FieldWithValue("test", ts.Test, "required") }, validatorNotInitialized) + PanicMatches(t, func() { val.Struct(ts) }, validatorNotInitialized) + PanicMatches(t, func() { val.StructExcept(ts, "Test") }, validatorNotInitialized) + PanicMatches(t, func() { val.StructPartial(ts, "Test") }, validatorNotInitialized) } From b366564cbe17088f9040d0dd557d76999813cb89 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 20:35:05 -0400 Subject: [PATCH 19/22] update to v8 benchmarks --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ef89f36..1c0f48f 100644 --- a/README.md +++ b/README.md @@ -205,32 +205,32 @@ Benchmarks ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 5000000 285 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 284 ns/op 16 B/op 1 allocs/op -BenchmarkFieldDiveSuccess-4 500000 2501 ns/op 384 B/op 19 allocs/op -BenchmarkFieldDiveFailure-4 500000 3022 ns/op 752 B/op 23 allocs/op -BenchmarkFieldCustomTypeSuccess-4 3000000 445 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 788 ns/op 416 B/op 6 allocs/op -BenchmarkFieldOrTagSuccess-4 1000000 1377 ns/op 32 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1201 ns/op 400 B/op 6 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1257 ns/op 80 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1776 ns/op 608 B/op 13 allocs/op -BenchmarkStructPartialSuccess-4 1000000 1354 ns/op 400 B/op 11 allocs/op -BenchmarkStructPartialFailure-4 1000000 1813 ns/op 784 B/op 16 allocs/op -BenchmarkStructExceptSuccess-4 2000000 916 ns/op 368 B/op 9 allocs/op -BenchmarkStructExceptFailure-4 1000000 1369 ns/op 400 B/op 11 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1033 ns/op 128 B/op 6 allocs/op -BenchmarkStructSimpleCrossFieldFailure-4 1000000 1569 ns/op 528 B/op 11 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1371 ns/op 160 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 1935 ns/op 560 B/op 13 allocs/op -BenchmarkStructSimpleSuccess-4 1000000 1161 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1720 ns/op 560 B/op 11 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 329 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 625 ns/op 560 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 6636 ns/op 432 B/op 27 allocs/op -BenchmarkStructComplexFailure-4 200000 11327 ns/op 2919 B/op 69 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 1991 ns/op 432 B/op 27 allocs/op -BenchmarkStructComplexFailureParallel-4 500000 3854 ns/op 2920 B/op 69 allocs/op +BenchmarkFieldSuccess-4 5000000 292 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 291 ns/op 16 B/op 1 allocs/op +BenchmarkFieldDiveSuccess-4 500000 2518 ns/op 384 B/op 19 allocs/op +BenchmarkFieldDiveFailure-4 500000 3059 ns/op 768 B/op 23 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 444 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 766 ns/op 384 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-4 1000000 1344 ns/op 32 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1176 ns/op 416 B/op 6 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1181 ns/op 80 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1733 ns/op 592 B/op 11 allocs/op +BenchmarkStructPartialSuccess-4 1000000 1345 ns/op 400 B/op 11 allocs/op +BenchmarkStructPartialFailure-4 1000000 1905 ns/op 800 B/op 16 allocs/op +BenchmarkStructExceptSuccess-4 2000000 902 ns/op 368 B/op 9 allocs/op +BenchmarkStructExceptFailure-4 1000000 1344 ns/op 400 B/op 11 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1205 ns/op 128 B/op 6 allocs/op +BenchmarkStructSimpleCrossFieldFailure-4 1000000 1779 ns/op 544 B/op 11 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1782 ns/op 160 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2348 ns/op 576 B/op 13 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1172 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1810 ns/op 592 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 344 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 644 ns/op 592 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 7575 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailure-4 100000 12463 ns/op 3128 B/op 69 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2348 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4369 ns/op 3128 B/op 69 allocs/op ``` How to Contribute From 9e4314f9ab09b9e2a848c7418109429d536f647c Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 20:46:03 -0400 Subject: [PATCH 20/22] add more docs to doc.go --- doc.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc.go b/doc.go index 843b00d..fd8ab91 100644 --- a/doc.go +++ b/doc.go @@ -18,6 +18,9 @@ I needed to know the field and what validation failed so that I could provide an Validation functions return type error +Doing things this way is actually the way the standard library does, see the file.Open +method here: https://golang.org/pkg/os/#Open. + They return type error to avoid the issue discussed in the following, where err is always != nil: http://stackoverflow.com/a/29138676/3158232 https://github.com/bluesuncorp/validator/issues/134 From 8119861695130050a43f8be280333d263b1e34e3 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 20:57:31 -0400 Subject: [PATCH 21/22] inline Regex calls --- baked_in.go | 50 +++++++++++++++++++++++++------------------------- regexes.go | 4 ---- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/baked_in.go b/baked_in.go index c735046..59746e3 100644 --- a/baked_in.go +++ b/baked_in.go @@ -115,15 +115,15 @@ func isSSN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Va return false } - return matchesRegex(sSNRegex, field.String()) + return sSNRegex.MatchString(field.String()) } func isLongitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(longitudeRegex, field.String()) + return longitudeRegex.MatchString(field.String()) } func isLatitude(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(latitudeRegex, field.String()) + return latitudeRegex.MatchString(field.String()) } func isDataURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { @@ -134,7 +134,7 @@ func isDataURI(v *Validate, topStruct reflect.Value, currentStructOrField reflec return false } - if !matchesRegex(dataURIRegex, uri[0]) { + if !dataURIRegex.MatchString(uri[0]) { return false } @@ -149,31 +149,31 @@ func hasMultiByteCharacter(v *Validate, topStruct reflect.Value, currentStructOr return true } - return matchesRegex(multibyteRegex, field.String()) + return multibyteRegex.MatchString(field.String()) } func isPrintableASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(printableASCIIRegex, field.String()) + return printableASCIIRegex.MatchString(field.String()) } func isASCII(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(aSCIIRegex, field.String()) + return aSCIIRegex.MatchString(field.String()) } func isUUID5(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(uUID5Regex, field.String()) + return uUID5Regex.MatchString(field.String()) } func isUUID4(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(uUID4Regex, field.String()) + return uUID4Regex.MatchString(field.String()) } func isUUID3(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(uUID3Regex, field.String()) + return uUID3Regex.MatchString(field.String()) } func isUUID(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(uUIDRegex, field.String()) + return uUIDRegex.MatchString(field.String()) } func isISBN(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { @@ -184,7 +184,7 @@ func isISBN13(v *Validate, topStruct reflect.Value, currentStructOrField reflect s := strings.Replace(strings.Replace(field.String(), "-", "", 4), " ", "", 4) - if !matchesRegex(iSBN13Regex, s) { + if !iSBN13Regex.MatchString(s) { return false } @@ -208,7 +208,7 @@ func isISBN10(v *Validate, topStruct reflect.Value, currentStructOrField reflect s := strings.Replace(strings.Replace(field.String(), "-", "", 3), " ", "", 3) - if !matchesRegex(iSBN10Regex, s) { + if !iSBN10Regex.MatchString(s) { return false } @@ -625,7 +625,7 @@ func isEq(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Val } func isBase64(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(base64Regex, field.String()) + return base64Regex.MatchString(field.String()) } func isURI(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { @@ -663,47 +663,47 @@ func isURL(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Va } func isEmail(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(emailRegex, field.String()) + return emailRegex.MatchString(field.String()) } func isHsla(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(hslaRegex, field.String()) + return hslaRegex.MatchString(field.String()) } func isHsl(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(hslRegex, field.String()) + return hslRegex.MatchString(field.String()) } func isRgba(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(rgbaRegex, field.String()) + return rgbaRegex.MatchString(field.String()) } func isRgb(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(rgbRegex, field.String()) + return rgbRegex.MatchString(field.String()) } func isHexcolor(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(hexcolorRegex, field.String()) + return hexcolorRegex.MatchString(field.String()) } func isHexadecimal(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(hexadecimalRegex, field.String()) + return hexadecimalRegex.MatchString(field.String()) } func isNumber(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(numberRegex, field.String()) + return numberRegex.MatchString(field.String()) } func isNumeric(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(numericRegex, field.String()) + return numericRegex.MatchString(field.String()) } func isAlphanum(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(alphaNumericRegex, field.String()) + return alphaNumericRegex.MatchString(field.String()) } func isAlpha(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { - return matchesRegex(alphaRegex, field.String()) + return alphaRegex.MatchString(field.String()) } func hasValue(v *Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool { diff --git a/regexes.go b/regexes.go index d061b03..83ae198 100644 --- a/regexes.go +++ b/regexes.go @@ -57,7 +57,3 @@ var ( longitudeRegex = regexp.MustCompile(longitudeRegexString) sSNRegex = regexp.MustCompile(sSNRegexString) ) - -func matchesRegex(regex *regexp.Regexp, value string) bool { - return regex.MatchString(value) -} From aa72515d758a8a63d1191f599a195b364f3a5e16 Mon Sep 17 00:00:00 2001 From: joeybloggs Date: Wed, 2 Sep 2015 21:12:06 -0400 Subject: [PATCH 22/22] update benchmarks --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 1c0f48f..a316982 100644 --- a/README.md +++ b/README.md @@ -205,32 +205,32 @@ Benchmarks ```go $ go test -cpu=4 -bench=. -benchmem=true PASS -BenchmarkFieldSuccess-4 5000000 292 ns/op 16 B/op 1 allocs/op -BenchmarkFieldFailure-4 5000000 291 ns/op 16 B/op 1 allocs/op -BenchmarkFieldDiveSuccess-4 500000 2518 ns/op 384 B/op 19 allocs/op -BenchmarkFieldDiveFailure-4 500000 3059 ns/op 768 B/op 23 allocs/op -BenchmarkFieldCustomTypeSuccess-4 3000000 444 ns/op 32 B/op 2 allocs/op -BenchmarkFieldCustomTypeFailure-4 2000000 766 ns/op 384 B/op 4 allocs/op -BenchmarkFieldOrTagSuccess-4 1000000 1344 ns/op 32 B/op 2 allocs/op -BenchmarkFieldOrTagFailure-4 1000000 1176 ns/op 416 B/op 6 allocs/op -BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1181 ns/op 80 B/op 5 allocs/op -BenchmarkStructSimpleCustomTypeFailure-4 1000000 1733 ns/op 592 B/op 11 allocs/op -BenchmarkStructPartialSuccess-4 1000000 1345 ns/op 400 B/op 11 allocs/op -BenchmarkStructPartialFailure-4 1000000 1905 ns/op 800 B/op 16 allocs/op -BenchmarkStructExceptSuccess-4 2000000 902 ns/op 368 B/op 9 allocs/op -BenchmarkStructExceptFailure-4 1000000 1344 ns/op 400 B/op 11 allocs/op -BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1205 ns/op 128 B/op 6 allocs/op -BenchmarkStructSimpleCrossFieldFailure-4 1000000 1779 ns/op 544 B/op 11 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1782 ns/op 160 B/op 8 allocs/op -BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2348 ns/op 576 B/op 13 allocs/op -BenchmarkStructSimpleSuccess-4 1000000 1172 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailure-4 1000000 1810 ns/op 592 B/op 11 allocs/op -BenchmarkStructSimpleSuccessParallel-4 5000000 344 ns/op 48 B/op 3 allocs/op -BenchmarkStructSimpleFailureParallel-4 2000000 644 ns/op 592 B/op 11 allocs/op -BenchmarkStructComplexSuccess-4 200000 7575 ns/op 432 B/op 27 allocs/op -BenchmarkStructComplexFailure-4 100000 12463 ns/op 3128 B/op 69 allocs/op -BenchmarkStructComplexSuccessParallel-4 1000000 2348 ns/op 432 B/op 27 allocs/op -BenchmarkStructComplexFailureParallel-4 300000 4369 ns/op 3128 B/op 69 allocs/op +BenchmarkFieldSuccess-4 5000000 296 ns/op 16 B/op 1 allocs/op +BenchmarkFieldFailure-4 5000000 294 ns/op 16 B/op 1 allocs/op +BenchmarkFieldDiveSuccess-4 500000 2529 ns/op 384 B/op 19 allocs/op +BenchmarkFieldDiveFailure-4 500000 3056 ns/op 768 B/op 23 allocs/op +BenchmarkFieldCustomTypeSuccess-4 3000000 443 ns/op 32 B/op 2 allocs/op +BenchmarkFieldCustomTypeFailure-4 2000000 753 ns/op 384 B/op 4 allocs/op +BenchmarkFieldOrTagSuccess-4 1000000 1334 ns/op 32 B/op 2 allocs/op +BenchmarkFieldOrTagFailure-4 1000000 1172 ns/op 416 B/op 6 allocs/op +BenchmarkStructSimpleCustomTypeSuccess-4 1000000 1206 ns/op 80 B/op 5 allocs/op +BenchmarkStructSimpleCustomTypeFailure-4 1000000 1737 ns/op 592 B/op 11 allocs/op +BenchmarkStructPartialSuccess-4 1000000 1367 ns/op 400 B/op 11 allocs/op +BenchmarkStructPartialFailure-4 1000000 1914 ns/op 800 B/op 16 allocs/op +BenchmarkStructExceptSuccess-4 2000000 909 ns/op 368 B/op 9 allocs/op +BenchmarkStructExceptFailure-4 1000000 1350 ns/op 400 B/op 11 allocs/op +BenchmarkStructSimpleCrossFieldSuccess-4 1000000 1218 ns/op 128 B/op 6 allocs/op +BenchmarkStructSimpleCrossFieldFailure-4 1000000 1783 ns/op 544 B/op 11 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldSuccess-4 1000000 1806 ns/op 160 B/op 8 allocs/op +BenchmarkStructSimpleCrossStructCrossFieldFailure-4 1000000 2369 ns/op 576 B/op 13 allocs/op +BenchmarkStructSimpleSuccess-4 1000000 1161 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailure-4 1000000 1813 ns/op 592 B/op 11 allocs/op +BenchmarkStructSimpleSuccessParallel-4 5000000 353 ns/op 48 B/op 3 allocs/op +BenchmarkStructSimpleFailureParallel-4 2000000 656 ns/op 592 B/op 11 allocs/op +BenchmarkStructComplexSuccess-4 200000 7637 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailure-4 100000 12775 ns/op 3128 B/op 69 allocs/op +BenchmarkStructComplexSuccessParallel-4 1000000 2270 ns/op 432 B/op 27 allocs/op +BenchmarkStructComplexFailureParallel-4 300000 4328 ns/op 3128 B/op 69 allocs/op ``` How to Contribute