Merge pull request #119 from colincross/extend

Relax type requirements when extending properties
This commit is contained in:
colincross 2016-08-22 16:01:27 -07:00 committed by GitHub
commit b9fb0ad53d
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)
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")
}
}

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
@ -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