Support embedded anonymous property structs

Allow property structs to contain anonymous embedded structs and
interfaces.  Properties in an anonymous embedded struct or interface are
treated as if they were properties in the embedding struct.
This commit is contained in:
Colin Cross 2015-11-20 17:03:25 -08:00
parent 83cedbec85
commit 9d1469d559
4 changed files with 290 additions and 14 deletions

View file

@ -181,8 +181,69 @@ var clonePropertiesTestCases = []struct {
S: nil,
},
},
{
// Anonymous struct
in: &struct {
EmbeddedStruct
Nested struct{ EmbeddedStruct }
}{
EmbeddedStruct: EmbeddedStruct{
S: "string1",
},
Nested: struct{ EmbeddedStruct }{
EmbeddedStruct: EmbeddedStruct{
S: "string2",
},
},
},
out: &struct {
EmbeddedStruct
Nested struct{ EmbeddedStruct }
}{
EmbeddedStruct: EmbeddedStruct{
S: "string1",
},
Nested: struct{ EmbeddedStruct }{
EmbeddedStruct: EmbeddedStruct{
S: "string2",
},
},
},
},
{
// Anonymous interface
in: &struct {
EmbeddedInterface
Nested struct{ EmbeddedInterface }
}{
EmbeddedInterface: &struct{ S string }{
S: "string1",
},
Nested: struct{ EmbeddedInterface }{
EmbeddedInterface: &struct{ S string }{
S: "string2",
},
},
},
out: &struct {
EmbeddedInterface
Nested struct{ EmbeddedInterface }
}{
EmbeddedInterface: &struct{ S string }{
S: "string1",
},
Nested: struct{ EmbeddedInterface }{
EmbeddedInterface: &struct{ S string }{
S: "string2",
},
},
},
},
}
type EmbeddedStruct struct{ S string }
type EmbeddedInterface interface{}
func TestCloneProperties(t *testing.T) {
for _, testCase := range clonePropertiesTestCases {
testString := fmt.Sprintf("%s", testCase.in)

View file

@ -409,6 +409,90 @@ var appendPropertiesTestCases = []struct {
S: nil,
},
},
{
// Anonymous struct
in1: &struct {
EmbeddedStruct
Nested struct{ EmbeddedStruct }
}{
EmbeddedStruct: EmbeddedStruct{
S: "string1",
},
Nested: struct{ EmbeddedStruct }{
EmbeddedStruct: EmbeddedStruct{
S: "string2",
},
},
},
in2: &struct {
EmbeddedStruct
Nested struct{ EmbeddedStruct }
}{
EmbeddedStruct: EmbeddedStruct{
S: "string3",
},
Nested: struct{ EmbeddedStruct }{
EmbeddedStruct: EmbeddedStruct{
S: "string4",
},
},
},
out: &struct {
EmbeddedStruct
Nested struct{ EmbeddedStruct }
}{
EmbeddedStruct: EmbeddedStruct{
S: "string1string3",
},
Nested: struct{ EmbeddedStruct }{
EmbeddedStruct: EmbeddedStruct{
S: "string2string4",
},
},
},
},
{
// Anonymous interface
in1: &struct {
EmbeddedInterface
Nested struct{ EmbeddedInterface }
}{
EmbeddedInterface: &struct{ S string }{
S: "string1",
},
Nested: struct{ EmbeddedInterface }{
EmbeddedInterface: &struct{ S string }{
S: "string2",
},
},
},
in2: &struct {
EmbeddedInterface
Nested struct{ EmbeddedInterface }
}{
EmbeddedInterface: &struct{ S string }{
S: "string3",
},
Nested: struct{ EmbeddedInterface }{
EmbeddedInterface: &struct{ S string }{
S: "string4",
},
},
},
out: &struct {
EmbeddedInterface
Nested struct{ EmbeddedInterface }
}{
EmbeddedInterface: &struct{ S string }{
S: "string1string3",
},
Nested: struct{ EmbeddedInterface }{
EmbeddedInterface: &struct{ S string }{
S: "string2string4",
},
},
},
},
// Errors

View file

@ -134,8 +134,10 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
continue
}
propertyName := namePrefix + proptools.PropertyNameForField(field.Name)
if !fieldValue.CanSet() {
panic(fmt.Errorf("field %s is not settable", field.Name))
panic(fmt.Errorf("field %s is not settable", propertyName))
}
// To make testing easier we validate the struct field's type regardless
@ -146,47 +148,47 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
case reflect.Slice:
elemType := field.Type.Elem()
if elemType.Kind() != reflect.String {
panic(fmt.Errorf("field %s is a non-string slice", field.Name))
panic(fmt.Errorf("field %s is a non-string slice", propertyName))
}
case reflect.Interface:
if fieldValue.IsNil() {
panic(fmt.Errorf("field %s contains a nil interface",
field.Name))
panic(fmt.Errorf("field %s contains a nil interface", propertyName))
}
fieldValue = fieldValue.Elem()
elemType := fieldValue.Type()
if elemType.Kind() != reflect.Ptr {
panic(fmt.Errorf("field %s contains a non-pointer interface",
field.Name))
panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName))
}
fallthrough
case reflect.Ptr:
switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
case reflect.Struct:
if fieldValue.IsNil() {
panic(fmt.Errorf("field %s contains a nil pointer",
field.Name))
panic(fmt.Errorf("field %s contains a nil pointer", propertyName))
}
fieldValue = fieldValue.Elem()
case reflect.Bool, reflect.String:
// Nothing
default:
panic(fmt.Errorf("field %s contains a pointer to %s",
field.Name, ptrKind))
panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind))
}
case reflect.Int, reflect.Uint:
if !proptools.HasTag(field, "blueprint", "mutated") {
panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, field.Name))
panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName))
}
default:
panic(fmt.Errorf("unsupported kind for field %s: %s",
field.Name, kind))
panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
}
if field.Anonymous && fieldValue.Kind() == reflect.Struct {
newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue)
errs = append(errs, newErrs...)
continue
}
// Get the property value if it was specified.
propertyName := namePrefix + proptools.PropertyNameForField(field.Name)
packedProperty, ok := propertyMap[propertyName]
if !ok {
// This property wasn't specified.

View file

@ -228,8 +228,137 @@ var validUnpackTestCases = []struct {
},
},
},
// Anonymous struct
{`
m {
name: "abc",
nested: {
name: "def",
},
}
`,
struct {
EmbeddedStruct
Nested struct {
EmbeddedStruct
}
}{
EmbeddedStruct: EmbeddedStruct{
Name: "abc",
},
Nested: struct {
EmbeddedStruct
}{
EmbeddedStruct: EmbeddedStruct{
Name: "def",
},
},
},
nil,
},
// Anonymous interface
{`
m {
name: "abc",
nested: {
name: "def",
},
}
`,
struct {
EmbeddedInterface
Nested struct {
EmbeddedInterface
}
}{
EmbeddedInterface: &struct{ Name string }{
Name: "abc",
},
Nested: struct {
EmbeddedInterface
}{
EmbeddedInterface: &struct{ Name string }{
Name: "def",
},
},
},
nil,
},
// Anonymous struct with name collision
{`
m {
name: "abc",
nested: {
name: "def",
},
}
`,
struct {
Name string
EmbeddedStruct
Nested struct {
Name string
EmbeddedStruct
}
}{
Name: "abc",
EmbeddedStruct: EmbeddedStruct{
Name: "abc",
},
Nested: struct {
Name string
EmbeddedStruct
}{
Name: "def",
EmbeddedStruct: EmbeddedStruct{
Name: "def",
},
},
},
nil,
},
// Anonymous interface with name collision
{`
m {
name: "abc",
nested: {
name: "def",
},
}
`,
struct {
Name string
EmbeddedInterface
Nested struct {
Name string
EmbeddedInterface
}
}{
Name: "abc",
EmbeddedInterface: &struct{ Name string }{
Name: "abc",
},
Nested: struct {
Name string
EmbeddedInterface
}{
Name: "def",
EmbeddedInterface: &struct{ Name string }{
Name: "def",
},
},
},
nil,
},
}
type EmbeddedStruct struct{ Name string }
type EmbeddedInterface interface{}
func TestUnpackProperties(t *testing.T) {
for _, testCase := range validUnpackTestCases {
r := bytes.NewBufferString(testCase.input)