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)
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue