Partially Merged in Partial struct methods + Tests

pull/161/head
joeybloggs 9 years ago
parent 3697be93be
commit 8ae139a445
  1. 121
      validator.go
  2. 237
      validator_test.go

@ -42,6 +42,7 @@ var (
timePtrType = reflect.TypeOf(&time.Time{})
errsPool = &sync.Pool{New: newValidationErrors}
tagsCache = &tagCacheMap{m: map[string][]*tagCache{}}
emptyStructPtr = new(struct{})
)
// returns new ValidationErrors to the pool
@ -180,7 +181,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, "")
v.traverseField(fieldVal, fieldVal, fieldVal, "", errs, false, tag, "", false, false, nil)
if len(errs) == 0 {
errsPool.Put(errs)
@ -198,7 +199,91 @@ 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, "")
v.traverseField(topVal, topVal, reflect.ValueOf(field), "", errs, false, tag, "", false, false, nil)
if len(errs) == 0 {
errsPool.Put(errs)
return nil
}
return errs
}
// StructPartial validates the fields that are listed by name in the map including nested structs, unless otherwise specified. Items in the map that are NOT found in the struct will cause a panic.
func (v *Validate) StructPartial(current interface{}, fields ...string) ValidationErrors {
sv, _ := v.extractType(reflect.ValueOf(current))
name := sv.Type().Name()
m := map[string]*struct{}{}
var i int
if fields != nil {
for _, k := range fields {
flds := strings.Split(k, ".")
if len(flds) > 0 {
key := name
for _, s := range flds {
idx := strings.Index(s, "[")
if idx != -1 {
for idx != -1 {
i++
key += s[:idx]
m[key] = emptyStructPtr
idx2 := strings.Index(s, "]")
idx2++
key += s[idx:idx2]
m[key] = emptyStructPtr
s = s[idx2:]
idx = strings.Index(s, "[")
if i == 10 {
idx = -1
}
}
} else {
key += s
m[key] = emptyStructPtr
}
key += "."
}
}
}
}
errs := errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, false, m)
if len(errs) == 0 {
errsPool.Put(errs)
return nil
}
return errs
}
// StructExcept validates the fields in the struct that are NOT listed by name in the map including nested structs, unless otherwise specified. Items in the map that are NOT found in the struct will cause a panic.
func (v *Validate) StructExcept(current interface{}, fields ...string) ValidationErrors {
sv, _ := v.extractType(reflect.ValueOf(current))
name := sv.Type().Name()
m := map[string]*struct{}{}
for _, key := range fields {
m[name+"."+key] = emptyStructPtr
}
errs := errsPool.Get().(ValidationErrors)
v.tranverseStruct(sv, sv, sv, "", errs, true, len(m) != 0, true, m)
if len(errs) == 0 {
errsPool.Put(errs)
@ -214,7 +299,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
errs := errsPool.Get().(ValidationErrors)
sv := reflect.ValueOf(current)
v.tranverseStruct(sv, sv, sv, "", errs, true)
v.tranverseStruct(sv, sv, sv, "", errs, true, false, false, nil)
if len(errs) == 0 {
errsPool.Put(errs)
@ -225,7 +310,7 @@ func (v *Validate) Struct(current interface{}) ValidationErrors {
}
// tranverseStruct traverses a structs fields and then passes them to be validated by traverseField
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool) {
func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, useStructName bool, partial bool, exclude bool, includeExclude map[string]*struct{}) {
if current.Kind() == reflect.Ptr && !current.IsNil() {
current = current.Elem()
@ -235,6 +320,7 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
panic("value passed for validation is not a struct")
}
var ok bool
typ := current.Type()
if useStructName {
@ -252,12 +338,21 @@ func (v *Validate) tranverseStruct(topStruct reflect.Value, currentStruct reflec
continue
}
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name)
if partial {
_, ok = includeExclude[errPrefix+fld.Name]
if (ok && exclude) || (!ok && !exclude) {
continue
}
}
v.traverseField(topStruct, currentStruct, current.Field(i), errPrefix, errs, true, fld.Tag.Get(v.config.TagName), fld.Name, partial, exclude, includeExclude)
}
}
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string) {
func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, isStructField bool, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) {
if tag == skipValidationTag {
return
@ -319,7 +414,7 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
return
}
v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false)
v.tranverseStruct(topStruct, current, current, errPrefix+name+".", errs, false, partial, exclude, includeExclude)
return
}
}
@ -402,9 +497,9 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
// or panic ;)
switch kind {
case reflect.Slice, reflect.Array:
v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name)
v.traverseSlice(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude)
case reflect.Map:
v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name)
v.traverseMap(topStruct, currentStruct, current, errPrefix, errs, diveSubTag, name, partial, exclude, includeExclude)
default:
// throw error, if not a slice or map then should not have gotten here
// bad dive tag
@ -414,18 +509,18 @@ func (v *Validate) traverseField(topStruct reflect.Value, currentStruct reflect.
}
// traverseSlice traverses a Slice or Array's elements and passes them to traverseField for validation
func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) {
func (v *Validate) traverseSlice(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) {
for i := 0; i < current.Len(); i++ {
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i))
v.traverseField(topStruct, currentStruct, current.Index(i), errPrefix, errs, false, tag, fmt.Sprintf(arrayIndexFieldName, name, i), partial, exclude, includeExclude)
}
}
// traverseMap traverses a map's elements and passes them to traverseField for validation
func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string) {
func (v *Validate) traverseMap(topStruct reflect.Value, currentStruct reflect.Value, current reflect.Value, errPrefix string, errs ValidationErrors, tag string, name string, partial bool, exclude bool, includeExclude map[string]*struct{}) {
for _, key := range current.MapKeys() {
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()))
v.traverseField(topStruct, currentStruct, current.MapIndex(key), errPrefix, errs, false, tag, fmt.Sprintf(mapIndexFieldName, name, key.Interface()), partial, exclude, includeExclude)
}
}

@ -192,6 +192,243 @@ func ValidateValuerType(field reflect.Value) interface{} {
return nil
}
type TestPartial struct {
NoTag string
BlankTag string `validate:""`
Required string `validate:"required"`
SubSlice []*SubTest `validate:"required,dive"`
Sub *SubTest
SubIgnore *SubTest `validate:"-"`
Anonymous struct {
A string `validate:"required"`
ASubSlice []*SubTest `validate:"required,dive"`
SubAnonStruct []struct {
Test string `validate:"required"`
OtherTest string `validate:"required"`
} `validate:"required,dive"`
}
}
func TestStructPartial(t *testing.T) {
p1 := []string{
"NoTag",
"Required",
}
p2 := []string{
"SubSlice[0].Test",
"Sub",
"SubIgnore",
"Anonymous.A",
}
p3 := []string{
"SubTest.Test",
}
// p4 := []string{
// "A",
// }
tPartial := &TestPartial{
NoTag: "NoTag",
Required: "Required",
SubSlice: []*SubTest{
{
Test: "Required",
},
{
Test: "Required",
},
},
Sub: &SubTest{
Test: "1",
},
SubIgnore: &SubTest{
Test: "",
},
Anonymous: struct {
A string `validate:"required"`
ASubSlice []*SubTest `validate:"required,dive"`
SubAnonStruct []struct {
Test string `validate:"required"`
OtherTest string `validate:"required"`
} `validate:"required,dive"`
}{
A: "1",
ASubSlice: []*SubTest{
{
Test: "Required",
},
{
Test: "Required",
},
},
SubAnonStruct: []struct {
Test string `validate:"required"`
OtherTest string `validate:"required"`
}{
{"Required", "RequiredOther"},
{"Required", "RequiredOther"},
},
},
}
// the following should all return no errors as everything is valid in
// the default state
errs := validate.StructPartial(tPartial, p1...)
Equal(t, errs, nil)
errs = validate.StructPartial(tPartial, p2...)
Equal(t, errs, nil)
// this isnt really a robust test, but is ment to illustrate the ANON CASE below
errs = validate.StructPartial(tPartial.SubSlice[0], p3...)
Equal(t, errs, nil)
errs = validate.StructExcept(tPartial, p1...)
Equal(t, errs, nil)
errs = validate.StructExcept(tPartial, p2...)
Equal(t, errs, nil)
// mod tParial for required feild and re-test making sure invalid fields are NOT required:
tPartial.Required = ""
errs = validate.StructExcept(tPartial, p1...)
Equal(t, errs, nil)
errs = validate.StructPartial(tPartial, p2...)
Equal(t, errs, nil)
// inversion and retesting Partial to generate failures:
errs = validate.StructPartial(tPartial, p1...)
NotEqual(t, errs, nil)
AssertError(t, errs, "TestPartial.Required", "Required", "required")
// errs = validate.StructExcept(tPartial, p2)
// AssertError(t, errs, "TestPartial.Required", "Required", "required")
// // reset Required field, and set nested struct
// tPartial.Required = "Required"
// tPartial.Anonymous.A = ""
// // will pass as unset feilds is not going to be tested
// errs = validate.StructPartial(tPartial, p1)
// Equal(t, errs, nil)
// errs = validate.StructExcept(tPartial, p2)
// Equal(t, errs, nil)
// // ANON CASE the response here is strange, it clearly does what it is being told to
// errs = validate.StructExcept(tPartial.Anonymous, p4)
// AssertError(t, errs, ".A", "A", "required")
// // will fail as unset feild is tested
// errs = validate.StructPartial(tPartial, p2)
// AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required")
// errs = validate.StructExcept(tPartial, p1)
// AssertError(t, errs, "TestPartial.Anonymous.A", "A", "required")
// // reset nested struct and unset struct in slice
// tPartial.Anonymous.A = "Required"
// tPartial.SubSlice[0].Test = ""
// // these will pass as unset item is NOT tested
// errs = validate.StructPartial(tPartial, p1)
// Equal(t, errs, nil)
// errs = validate.StructExcept(tPartial, p2)
// Equal(t, errs, nil)
// // 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)
// errs = validate.StructPartial(tPartial, p2)
// //Equal(t, errs, nil)
// AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required")
// Equal(t, len(errs), 1)
// // Unset second slice member concurrently to test dive behavior:
// tPartial.SubSlice[1].Test = ""
// errs = validate.StructPartial(tPartial, p1)
// Equal(t, errs, nil)
// // Case note:
// // were bypassing dive here? by setting a single item?
// // im not sure anyone would or should do this, I cant think of a reason
// // why they would but you never know. As for describing this behavior in
// // documentation I would be at a loss as to do it
// // especialy concidering the next test
// errs = validate.StructExcept(tPartial, p2)
// Equal(t, errs, nil)
// //AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required")
// // test sub validation:
// // this is diving
// errs = validate.StructExcept(tPartial, p1)
// AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required")
// AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required")
// Equal(t, len(errs), 2)
// errs = validate.StructPartial(tPartial, p2)
// //Equal(t, errs, nil)
// AssertError(t, errs, "TestPartial.SubSlice[0].Test", "Test", "required")
// Equal(t, len(errs), 1)
// // reset struct in slice, and unset struct in slice in unset posistion
// tPartial.SubSlice[0].Test = "Required"
// // these will pass as the unset item is NOT tested
// errs = validate.StructPartial(tPartial, p1)
// Equal(t, errs, nil)
// errs = validate.StructPartial(tPartial, p2)
// Equal(t, errs, nil)
// // testing for missing item by exception, yes it dives and fails
// errs = validate.StructExcept(tPartial, p1)
// AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required")
// Equal(t, len(errs), 1)
// // See above case note... this is a variation on the above
// // when all taken into account it seems super strange!
// errs = validate.StructExcept(tPartial, p2)
// Equal(t, errs, nil)
// //AssertError(t, errs, "TestPartial.SubSlice[1].Test", "Test", "required")
// tPartial.SubSlice[1].Test = "Required"
// tPartial.Anonymous.SubAnonStruct[0].Test = ""
// // these will pass as the unset item is NOT tested
// errs = validate.StructPartial(tPartial, p1)
// Equal(t, errs, nil)
// errs = validate.StructPartial(tPartial, p2)
// Equal(t, errs, nil)
// errs = validate.StructExcept(tPartial, p1)
// AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required")
// // See above case note... this is a variation on the above
// // when all taken into account it seems super strange!
// errs = validate.StructExcept(tPartial, p2)
// Equal(t, errs, nil)
// //AssertError(t, errs, "TestPartial.Anonymous.SubAnonStruct[0].Test", "Test", "required")
}
func TestCrossStructLteFieldValidation(t *testing.T) {
type Inner struct {

Loading…
Cancel
Save