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:
parent
7fda24e54b
commit
bf2adbfee2
2 changed files with 159 additions and 45 deletions
|
@ -253,7 +253,30 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
|
|||
propertyName := prefix + PropertyNameForField(srcField.Name)
|
||||
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
|
||||
var recurse []reflect.Value
|
||||
for _, dstValue := range dstValues {
|
||||
dstType := dstValue.Type()
|
||||
var dstField reflect.StructField
|
||||
|
@ -279,53 +302,30 @@ func extendPropertiesRecursive(dstValues []reflect.Value, srcValue reflect.Value
|
|||
dstFieldValue := dstValue.FieldByIndex(dstField.Index)
|
||||
origDstFieldValue := dstFieldValue
|
||||
|
||||
if srcFieldValue.Kind() != dstFieldValue.Kind() {
|
||||
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
|
||||
dstFieldValue.Type(), srcFieldValue.Type())
|
||||
}
|
||||
|
||||
switch srcFieldValue.Kind() {
|
||||
case reflect.Interface:
|
||||
if dstFieldValue.IsNil() != srcFieldValue.IsNil() {
|
||||
return extendPropertyErrorf(propertyName, "nilitude mismatch")
|
||||
}
|
||||
// Step into destination interfaces
|
||||
if dstFieldValue.Kind() == reflect.Interface {
|
||||
if dstFieldValue.IsNil() {
|
||||
continue
|
||||
return extendPropertyErrorf(propertyName, "nilitude mismatch")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case reflect.Ptr:
|
||||
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
|
||||
}
|
||||
// Step into destination pointers to structs
|
||||
if dstFieldValue.Kind() == reflect.Ptr && dstFieldValue.Type().Elem().Kind() == reflect.Struct {
|
||||
if dstFieldValue.IsNil() {
|
||||
dstFieldValue = reflect.New(srcFieldValue.Type().Elem())
|
||||
dstFieldValue = reflect.New(dstFieldValue.Type().Elem())
|
||||
origDstFieldValue.Set(dstFieldValue)
|
||||
}
|
||||
|
||||
dstFieldValue = dstFieldValue.Elem()
|
||||
srcFieldValue = srcFieldValue.Elem()
|
||||
}
|
||||
|
||||
fallthrough
|
||||
switch srcFieldValue.Kind() {
|
||||
case reflect.Struct:
|
||||
if sameTypes && dstFieldValue.Type() != srcFieldValue.Type() {
|
||||
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.
|
||||
err := extendPropertiesRecursive([]reflect.Value{dstFieldValue}, srcFieldValue,
|
||||
propertyName+".", filter, sameTypes, order)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recurse = append(recurse, dstFieldValue)
|
||||
continue
|
||||
case reflect.Bool, reflect.String, reflect.Slice:
|
||||
if srcFieldValue.Type() != dstFieldValue.Type() {
|
||||
return extendPropertyErrorf(propertyName, "mismatched types %s and %s",
|
||||
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:
|
||||
return extendPropertyErrorf(propertyName, "unsupported kind %s",
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -612,18 +628,16 @@ func appendPropertiesTestCases() []appendPropertyTestCase {
|
|||
{
|
||||
// Interface nilitude mismatch
|
||||
in1: &struct{ S interface{} }{
|
||||
S: &struct{ S string }{
|
||||
S: "string1",
|
||||
},
|
||||
},
|
||||
in2: &struct{ S interface{} }{
|
||||
S: nil,
|
||||
},
|
||||
out: &struct{ S interface{} }{
|
||||
in2: &struct{ S interface{} }{
|
||||
S: &struct{ S string }{
|
||||
S: "string1",
|
||||
},
|
||||
},
|
||||
out: &struct{ S interface{} }{
|
||||
S: nil,
|
||||
},
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in a new issue