Skip to content

Commit

Permalink
Merge pull request #14 from skybet/GS-953
Browse files Browse the repository at this point in the history
[refs GS-953] Allow tags to work on fields that are structs
  • Loading branch information
andrewmunro authored Feb 5, 2019
2 parents 8d2a534 + af5478b commit ddb45dd
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
12 changes: 12 additions & 0 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -990,10 +990,12 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options

var customTypeErrors Errors
optionsOrder := options.orderedKeys()
var processedTagsOnStruct bool
for _, validatorName := range optionsOrder {
validatorStruct := options[validatorName]
if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok {
delete(options, validatorName)
processedTagsOnStruct = true

if result := validatefunc(v.Interface(), o.Interface()); !result {
if len(validatorStruct.customErrorMessage) > 0 {
Expand Down Expand Up @@ -1182,8 +1184,18 @@ func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options
if v.IsNil() {
return true, nil
}
if v.Elem().Kind() == reflect.Struct && processedTagsOnStruct {
return true, nil
}
return typeCheck(v.Elem(), t, o, options)
case reflect.Struct:
// If a field which is a struct has validation tags, they will be custom tags because all built-in tags
// apply to non-struct types. processedTagsOnStruct will have been set to true in this case.
// The struct's fields will already have been validated on earlier, so return
// here so we don't validate those fields again and duplicate any validation errors.
if processedTagsOnStruct {
return true, nil
}
return ValidateStruct(v.Interface())
default:
return false, &UnsupportedTypeError{v.Type()}
Expand Down
114 changes: 114 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3535,3 +3535,117 @@ func TestSplitTag(t *testing.T) {
}

}

type NestedStruct struct {
AString string `valid:"alpha~AString: not alpha"`
}

type MainStruct struct {
Valid bool
Nested NestedStruct `valid:"structtag~Nested: invalid struct"`
}

type MainStructPtr struct {
Valid bool
NestedPtr *NestedStruct `valid:"structtagptr~Nested: invalid struct"`
}

func StructTag(v interface{}, o interface{}) bool {
main, ok := o.(MainStruct)
if !ok {
return false
}

return main.Valid
}

func StructTagPtr(v interface{}, o interface{}) bool {
main, ok := o.(MainStructPtr)
if !ok {
return false
}

return main.Valid
}

func TestValidatorTagOnAStructField(t *testing.T) {
var tt = []struct {
name string
main interface{}
expected bool
expectedError string
expectedErrorCount int
}{
{
name: "child tags not recursively run when parent struct tag passes validation",
main: MainStruct{
Valid: true, //this will make mainStruct return true
Nested: NestedStruct{
AString: "!!!", // this will return false from the alpha tag on the field in the struct
},
},
expected: false,
expectedError: "AString: not alpha",
expectedErrorCount: 1,
},
{
name: "child tags not recursively when a parent pointer-to-a-struct tag passes validation",
main: MainStructPtr{
Valid: true, //this will make mainStruct return true
NestedPtr: &NestedStruct{
AString: "!!!", // this will return false from the alpha tag on the field in the struct
},
},
expected: false,
expectedError: "AString: not alpha",
expectedErrorCount: 1,
},
{
name: "existing behaviour not broken - child tags not run recursively when tag on struct field is invalid",
main: MainStruct{
Valid: false, //this will make mainStruct return false
Nested: NestedStruct{
AString: "!!!", // this will return false from the alpha tag on the field in the struct
},
},
expected: false,
expectedError: "AString: not alpha;Nested: invalid struct",
expectedErrorCount: 2,
},
{
name: "all fields valid",
main: MainStruct{
Valid: true, //this will make mainStruct return true
Nested: NestedStruct{
AString: "aaa",
},
},
expected: true,
expectedError: "",
expectedErrorCount: 0,
},
}

CustomTypeTagMap.Set("structtag", StructTag)
CustomTypeTagMap.Set("structtagptr", StructTagPtr)

for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {

actual, actualErrors := ValidateStruct(tc.main)
if actual != tc.expected {
t.Errorf("returned boolean unexpected. Want:\n%v\nHave\n%v", tc.expected, actual)
}

validatorErrors, _ := actualErrors.(Errors)

if validatorErrors.Error() != tc.expectedError {
t.Errorf("errors incorrect. Want\n%v\nHave\n%v", tc.expectedError, validatorErrors.Error())
}

if len(validatorErrors) != tc.expectedErrorCount {
t.Errorf("error count unexpected. Want\n%v\nHave\n%v", tc.expectedErrorCount, len(validatorErrors))
}
})
}
}

0 comments on commit ddb45dd

Please sign in to comment.