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.
pull/169/head
joeybloggs 9 years ago
parent 2cda50fb41
commit 407aac458f
  1. 4
      baked_in.go
  2. 26
      util.go
  3. 126
      validator.go
  4. 21
      validator_test.go

@ -14,9 +14,7 @@ import (
// defines a common or complex set of validation(s) to simplify // defines a common or complex set of validation(s) to simplify
// adding validation to structs. i.e. set key "_ageok" and the tags // 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" // 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 // BakedInValidators is the default map of ValidationFunc
// you can add, remove or even replace items to suite your needs, // you can add, remove or even replace items to suite your needs,

@ -8,6 +8,7 @@ import (
) )
const ( const (
blank = ""
namespaceSeparator = "." namespaceSeparator = "."
leftBracket = "[" leftBracket = "["
rightBracket = "]" rightBracket = "]"
@ -94,7 +95,7 @@ func (v *Validate) getStructFieldOK(current reflect.Value, namespace string) (re
fld = namespace[:idx] fld = namespace[:idx]
ns = namespace[idx+1:] ns = namespace[idx+1:]
} else { } else {
ns = "" ns = blank
idx = len(namespace) idx = len(namespace)
} }
@ -233,14 +234,24 @@ func panicIf(err error) {
func (v *Validate) parseTags(tag, fieldName string) []*tagCache { 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{} tags := []*tagCache{}
if len(tag) == 0 {
return tags
}
for _, t := range strings.Split(tag, tagSeparator) { for _, t := range strings.Split(tag, tagSeparator) {
if v.config.hasAliasValidators { if v.config.hasAliasValidators {
// check map for alias and process new tags, otherwise process as usual // check map for alias and process new tags, otherwise process as usual
if tagsVal, ok := v.config.AliasValidators[t]; ok { if tagsVal, ok := v.config.aliasValidators[t]; ok {
return v.parseTags(tagsVal, fieldName) 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" // if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator) 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) tags = append(tags, cTag)
var key string var key string
@ -261,6 +273,12 @@ func (v *Validate) parseTags(tag, fieldName string) []*tagCache {
vals := strings.SplitN(val, tagKeySeparator, 2) vals := strings.SplitN(val, tagKeySeparator, 2)
key = vals[0] key = vals[0]
cTag.tag = key
if isAlias {
cTag.tag = alias
}
if len(key) == 0 { if len(key) == 0 {
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName))) panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
} }

@ -53,6 +53,9 @@ func newValidationErrors() interface{} {
type tagCache struct { type tagCache struct {
tagVals [][]string tagVals [][]string
isOrVal bool isOrVal bool
isAlias bool
tag string
// actualTag string
} }
type tagCacheMap struct { type tagCacheMap struct {
@ -84,7 +87,7 @@ type Config struct {
TagName string TagName string
ValidationFuncs map[string]Func ValidationFuncs map[string]Func
CustomTypeFuncs map[reflect.Type]CustomTypeFunc CustomTypeFuncs map[reflect.Type]CustomTypeFunc
AliasValidators map[string]string aliasValidators map[string]string
hasCustomFuncs bool hasCustomFuncs bool
hasAliasValidators bool hasAliasValidators bool
} }
@ -113,7 +116,7 @@ type ValidationErrors map[string]*FieldError
// the FieldError found within the ValidationErrors map // the FieldError found within the ValidationErrors map
func (ve ValidationErrors) Error() string { func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("") buff := bytes.NewBufferString(blank)
for key, err := range ve { for key, err := range ve {
buff.WriteString(fmt.Sprintf(fieldErrMsg, key, err.Field, err.Tag)) 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 // FieldError contains a single field's validation error along
// with other properties that may be needed for error message creation // with other properties that may be needed for error message creation
type FieldError struct { type FieldError struct {
Field string Field string
Tag string Tag string
Kind reflect.Kind ActualTag string
Type reflect.Type Kind reflect.Kind
Param string Type reflect.Type
Value interface{} Param string
Value interface{}
} }
// New creates a new Validate instance for use. // New creates a new Validate instance for use.
@ -141,10 +145,6 @@ func New(config Config) *Validate {
config.hasCustomFuncs = true config.hasCustomFuncs = true
} }
if config.AliasValidators != nil && len(config.AliasValidators) > 0 {
config.hasAliasValidators = true
}
return &Validate{config: config} return &Validate{config: config}
} }
@ -191,8 +191,8 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{
// to structs. // to structs.
func (v *Validate) RegisterAliasValidation(alias, tags string) { func (v *Validate) RegisterAliasValidation(alias, tags string) {
if v.config.AliasValidators == nil { if v.config.aliasValidators == nil {
v.config.AliasValidators = map[string]string{} v.config.aliasValidators = map[string]string{}
} }
_, ok := restrictedTags[alias] _, ok := restrictedTags[alias]
@ -201,7 +201,7 @@ func (v *Validate) RegisterAliasValidation(alias, tags string) {
panic(fmt.Sprintf(restrictedAliasErr, alias)) panic(fmt.Sprintf(restrictedAliasErr, alias))
} }
v.config.AliasValidators[alias] = tags v.config.aliasValidators[alias] = tags
v.config.hasAliasValidators = true v.config.hasAliasValidators = true
} }
@ -213,7 +213,7 @@ func (v *Validate) Field(field interface{}, tag string) ValidationErrors {
errs := errsPool.Get().(ValidationErrors) errs := errsPool.Get().(ValidationErrors)
fieldVal := reflect.ValueOf(field) 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 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -231,7 +231,7 @@ func (v *Validate) FieldWithValue(val interface{}, field interface{}, tag string
errs := errsPool.Get().(ValidationErrors) errs := errsPool.Get().(ValidationErrors)
topVal := reflect.ValueOf(val) 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 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -289,7 +289,7 @@ func (v *Validate) StructPartial(current interface{}, fields ...string) Validati
errs := errsPool.Get().(ValidationErrors) 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 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -316,7 +316,7 @@ func (v *Validate) StructExcept(current interface{}, fields ...string) Validatio
errs := errsPool.Get().(ValidationErrors) 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 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -332,7 +332,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
errs := errsPool.Get().(ValidationErrors) errs := errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current) 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 { if len(errs) == 0 {
errsPool.Put(errs) errsPool.Put(errs)
@ -391,6 +391,13 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
return return
} }
tags, isCached := tagsCache.Get(tag)
if !isCached {
tags = v.parseTags(tag, name)
tagsCache.Set(tag, tags)
}
current, kind := v.extractType(current) current, kind := v.extractType(current)
var typ reflect.Type var typ reflect.Type
@ -402,35 +409,30 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
if len(tag) > 0 { 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 { if kind == reflect.Invalid {
errs[errPrefix+name] = &FieldError{ errs[errPrefix+name] = &FieldError{
Field: name, Field: name,
Tag: vals[0], Tag: tags[0].tag,
Param: param, ActualTag: tags[0].tagVals[0][0],
Kind: kind, Param: tags[0].tagVals[0][1],
Kind: kind,
} }
return return
} }
errs[errPrefix+name] = &FieldError{ errs[errPrefix+name] = &FieldError{
Field: name, Field: name,
Tag: vals[0], Tag: tags[0].tag,
Param: param, ActualTag: tags[0].tagVals[0][0],
Value: current.Interface(), Param: tags[0].tagVals[0][1],
Kind: kind, Value: current.Interface(),
Type: current.Type(), Kind: kind,
Type: current.Type(),
} }
return return
} }
// if we get here tag length is zero and we can leave // if we get here tag length is zero and we can leave
if kind == reflect.Invalid { if kind == reflect.Invalid {
return return
@ -458,13 +460,6 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
typ = current.Type() typ = current.Type()
tags, isCached := tagsCache.Get(tag)
if !isCached {
tags = v.parseTags(tag, name)
tagsCache.Set(tag, tags)
}
var dive bool var dive bool
var diveSubTag string var diveSubTag string
@ -482,7 +477,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
if cTag.tagVals[0][0] == omitempty { if cTag.tagVals[0][0] == omitempty {
if !hasValue(v, topStruct, currentStruct, current, typ, kind, "") { if !hasValue(v, topStruct, currentStruct, current, typ, kind, blank) {
return return
} }
continue continue
@ -533,7 +528,7 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.
if cTag.isOrVal { if cTag.isOrVal {
errTag := "" errTag := blank
for _, val := range cTag.tagVals { for _, val := range cTag.tagVals {
@ -549,12 +544,24 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.
errTag += orSeparator + val[0] errTag += orSeparator + val[0]
} }
errs[errPrefix+name] = &FieldError{ if cTag.isAlias {
Field: name, errs[errPrefix+name] = &FieldError{
Tag: errTag[1:], Field: name,
Value: current.Interface(), Tag: cTag.tag,
Type: currentType, ActualTag: errTag[1:],
Kind: currentKind, 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 return true
@ -570,12 +577,13 @@ func (v *Validate) validateField(topStruct reflect.Value, currentStruct reflect.
} }
errs[errPrefix+name] = &FieldError{ errs[errPrefix+name] = &FieldError{
Field: name, Field: name,
Tag: cTag.tagVals[0][0], Tag: cTag.tag,
Value: current.Interface(), ActualTag: cTag.tagVals[0][0],
Param: cTag.tagVals[0][1], Value: current.Interface(),
Type: currentType, Param: cTag.tagVals[0][1],
Kind: currentKind, Type: currentType,
Kind: currentKind,
} }
return true return true

@ -111,7 +111,7 @@ type TestSlice struct {
OmitEmpty []int `validate:"omitempty,min=1,max=10"` 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) { func AssertError(t *testing.T, errs ValidationErrors, key, field, expectedTag string) {
@ -212,12 +212,23 @@ type TestPartial struct {
func TestAliasTags(t *testing.T) { func TestAliasTags(t *testing.T) {
validate.RegisterAliasValidation("iscolor", "hexcolor|rgb|rgba|hsl|hsla")
s := "rgb(255,255,255)" s := "rgb(255,255,255)"
errs := validate.Field(s, "iscolor") errs := validate.Field(s, "iscolor")
Equal(t, errs, nil) 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 { type Test struct {
Color string `vlidate:"iscolor"` Color string `validate:"iscolor"`
} }
tst := &Test{ tst := &Test{
@ -226,6 +237,12 @@ func TestAliasTags(t *testing.T) {
errs = validate.Struct(tst) errs = validate.Struct(tst)
Equal(t, errs, nil) 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) { func TestStructPartial(t *testing.T) {

Loading…
Cancel
Save