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
// 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,

@ -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)))
}

@ -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

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

Loading…
Cancel
Save