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) {