Relax type requirements when extending properties

Allow using ExtendMatchingProperties to extend pointer to a struct or an
interface containing a pointer to a struct using a struct, and
vice-versa.

Also fixes a pre-existing bug where extending a nested structure could
fail if there were multiple possible destnations and some of them did
not have a matching nested property.

Change-Id: I6e69d78eb6595ba7dd2603e3aa7dd8de3f292744
This commit is contained in:
Colin Cross 2016-08-19 18:16:33 -07:00
parent 7fda24e54b
commit bf2adbfee2
2 changed files with 159 additions and 45 deletions

View file

@ -253,7 +253,30 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
propertyName := prefix + PropertyNameForField(srcField.Name) propertyName := prefix + PropertyNameForField(srcField.Name)
srcFieldValue := srcValue.Field(i) srcFieldValue := srcValue.Field(i)
// Step into source interfaces
if srcFieldValue.Kind() == reflect.Interface {
if srcFieldValue.IsNil() {
continue
}
srcFieldValue = srcFieldValue.Elem()
if srcFieldValue.Kind() != reflect.Ptr {
return extendPropertyErrorf(propertyName, "interface not a pointer")
}
}
// Step into source pointers to structs
if srcFieldValue.Kind() == reflect.Ptr && srcFieldValue.Type().Elem().Kind() == reflect.Struct {
if srcFieldValue.IsNil() {
continue
}
srcFieldValue = srcFieldValue.Elem()
}
found := false found := false
var recurse []reflect.Value
for _, dstValue := range dstValues { for _, dstValue := range dstValues {
dstType := dstValue.Type() dstType := dstValue.Type()
var dstField reflect.StructField var dstField reflect.StructField
@ -279,53 +302,30 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
dstFieldValue := dstValue.FieldByIndex(dstField.Index) dstFieldValue := dstValue.FieldByIndex(dstField.Index)
origDstFieldValue := dstFieldValue origDstFieldValue := dstFieldValue
if srcFieldValue.Kind() != dstFieldValue.Kind() { // Step into destination interfaces
return extendPropertyErrorf(propertyName, "mismatched types %s and %s", if dstFieldValue.Kind() == reflect.Interface {
dstFieldValue.Type(), srcFieldValue.Type())
}
switch srcFieldValue.Kind() {
case reflect.Interface:
if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
return extendPropertyErrorf(propertyName, "nilitude mismatch")
}
if dstFieldValue.IsNil() { if dstFieldValue.IsNil() {
continue return extendPropertyErrorf(propertyName, "nilitude mismatch")
} }
dstFieldValue = dstFieldValue.Elem() dstFieldValue = dstFieldValue.Elem()
srcFieldValue = srcFieldValue.Elem()
if srcFieldValue.Kind() != reflect.Ptr || dstFieldValue.Kind() != reflect.Ptr { if dstFieldValue.Kind() != reflect.Ptr {
return extendPropertyErrorf(propertyName, "interface not a pointer") return extendPropertyErrorf(propertyName, "interface not a pointer")
} }
}
fallthrough // Step into destination pointers to structs
case reflect.Ptr: if dstFieldValue.Kind() == reflect.Ptr && dstFieldValue.Type().Elem().Kind() == reflect.Struct {
ptrKind := srcFieldValue.Type().Elem().Kind()
if ptrKind == reflect.Bool || ptrKind == reflect.String {
if srcFieldValue.Type() != dstFieldValue.Type() {
return extendPropertyErrorf(propertyName, "mismatched pointer types %s and %s",
dstFieldValue.Type(), srcFieldValue.Type())
}
break
} else if ptrKind != reflect.Struct {
return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind)
}
// Pointer to a struct
if srcFieldValue.IsNil() {
continue
}
if dstFieldValue.IsNil() { if dstFieldValue.IsNil() {
dstFieldValue = reflect.New(srcFieldValue.Type().Elem()) dstFieldValue = reflect.New(dstFieldValue.Type().Elem())
origDstFieldValue.Set(dstFieldValue) origDstFieldValue.Set(dstFieldValue)
} }
dstFieldValue = dstFieldValue.Elem() dstFieldValue = dstFieldValue.Elem()
srcFieldValue = srcFieldValue.Elem() }
fallthrough switch srcFieldValue.Kind() {
case reflect.Struct: case reflect.Struct:
if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() { if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() {
return extendPropertyErrorf(propertyName, "mismatched types %s and %s", return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
@ -333,17 +333,24 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
} }
// Recursively extend the struct's fields. // Recursively extend the struct's fields.
err := extendPropertiesRecursive([]reflect.Value{dstFieldValue}, srcFieldValue, recurse = append(recurse, dstFieldValue)
propertyName+".", filter, sameTypes, order)
if err != nil {
return err
}
continue continue
case reflect.Bool, reflect.String, reflect.Slice: case reflect.Bool, reflect.String, reflect.Slice:
if srcFieldValue.Type() != dstFieldValue.Type() { if srcFieldValue.Type() != dstFieldValue.Type() {
return extendPropertyErrorf(propertyName, "mismatched types %s and %s", return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
dstFieldValue.Type(), srcFieldValue.Type()) dstFieldValue.Type(), srcFieldValue.Type())
} }
case reflect.Ptr:
if srcFieldValue.Type() != dstFieldValue.Type() {
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
dstFieldValue.Type(), srcFieldValue.Type())
}
switch ptrKind := srcFieldValue.Type().Elem().Kind(); ptrKind {
case reflect.Bool, reflect.String, reflect.Struct:
// Nothing
default:
return extendPropertyErrorf(propertyName, "pointer is a %s", ptrKind)
}
default: default:
return extendPropertyErrorf(propertyName, "unsupported kind %s", return extendPropertyErrorf(propertyName, "unsupported kind %s",
srcFieldValue.Kind()) srcFieldValue.Kind())
@ -436,7 +443,13 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
} }
} }
} }
if !found { if len(recurse) > 0 {
err := extendPropertiesRecursive(recurse, srcFieldValue,
propertyName+".", filter, sameTypes, order)
if err != nil {
return err
}
} else if !found {
return extendPropertyErrorf(propertyName, "failed to find property to extend") return extendPropertyErrorf(propertyName, "failed to find property to extend")
} }
} }

View file

@ -552,6 +552,22 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
}, },
}, },
}, },
{
// Interface src nil
in1: &struct{ S interface{} }{
S: &struct{ S string }{
S: "string1",
},
},
in2: &struct{ S interface{} }{
S: nil,
},
out: &struct{ S interface{} }{
S: &struct{ S string }{
S: "string1",
},
},
},
// Errors // Errors
@ -612,18 +628,16 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
{ {
// Interface nilitude mismatch // Interface nilitude mismatch
in1: &struct{ S interface{} }{ in1: &struct{ S interface{} }{
S: &struct{ S string }{
S: "string1",
},
},
in2: &struct{ S interface{} }{
S: nil, S: nil,
}, },
out: &struct{ S interface{} }{ in2: &struct{ S interface{} }{
S: &struct{ S string }{ S: &struct{ S string }{
S: "string1", S: "string1",
}, },
}, },
out: &struct{ S interface{} }{
S: nil,
},
err: extendPropertyErrorf("s", "nilitude mismatch"), err: extendPropertyErrorf("s", "nilitude mismatch"),
}, },
{ {
@ -947,6 +961,93 @@ func appendMatchingPropertiesTestCases() []appendMatchingPropertiesTestCase {
}, },
}}, }},
}, },
{
// Append through mismatched types
in1: []interface{}{
&struct{ B string }{},
&struct{ S interface{} }{
S: &struct{ S, A string }{
S: "string1",
},
},
},
in2: &struct{ S struct{ S string } }{
S: struct{ S string }{
S: "string2",
},
},
out: []interface{}{
&struct{ B string }{},
&struct{ S interface{} }{
S: &struct{ S, A string }{
S: "string1string2",
},
},
},
},
{
// Append through mismatched types and nil
in1: []interface{}{
&struct{ B string }{},
&struct{ S interface{} }{
S: (*struct{ S, A string })(nil),
},
},
in2: &struct{ S struct{ S string } }{
S: struct{ S string }{
S: "string2",
},
},
out: []interface{}{
&struct{ B string }{},
&struct{ S interface{} }{
S: &struct{ S, A string }{
S: "string2",
},
},
},
},
{
// Append through multiple matches
in1: []interface{}{
&struct {
S struct{ S, A string }
}{
S: struct{ S, A string }{
S: "string1",
},
},
&struct {
S struct{ S, B string }
}{
S: struct{ S, B string }{
S: "string2",
},
},
},
in2: &struct{ S struct{ B string } }{
S: struct{ B string }{
B: "string3",
},
},
out: []interface{}{
&struct {
S struct{ S, A string }
}{
S: struct{ S, A string }{
S: "string1",
},
},
&struct {
S struct{ S, B string }
}{
S: struct{ S, B string }{
S: "string2",
B: "string3",
},
},
},
},
// Errors // Errors