Merge pull request #83 from colincross/embed

Support embedded anonymous property structs
This commit is contained in:
colincross 2015-11-24 11:23:39 -08:00
commit 6afb72ff1b
6 changed files with 394 additions and 21 deletions

View file

@ -240,8 +240,15 @@ func newDocs(t *doc.Type) (*PropertyStructDocs, error) {
func structProperties(structType *ast.StructType) (props []PropertyDocs, err error) {
for _, f := range structType.Fields.List {
//fmt.Printf("%T %#v\n", f, f)
for _, n := range f.Names {
names := f.Names
if names == nil {
// Anonymous fields have no name, use the type as the name
// TODO: hide the name and make the properties show up in the embedding struct
if t, ok := f.Type.(*ast.Ident); ok {
names = append(names, t)
}
}
for _, n := range names {
var name, typ, tag, text string
var innerProps []PropertyDocs
if n != nil {

View file

@ -140,7 +140,7 @@ func ZeroProperties(structValue reflect.Value) {
fieldValue := structValue.Field(i)
switch fieldValue.Kind() {
case reflect.Bool, reflect.String, reflect.Struct, reflect.Slice, reflect.Int, reflect.Uint:
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
fieldValue.Set(reflect.Zero(fieldValue.Type()))
case reflect.Interface:
if fieldValue.IsNil() {
@ -172,7 +172,8 @@ func ZeroProperties(structValue reflect.Value) {
panic(fmt.Errorf("can't zero field %q: points to a %s",
field.Name, fieldValue.Elem().Kind()))
}
case reflect.Struct:
ZeroProperties(fieldValue)
default:
panic(fmt.Errorf("unexpected kind for property struct field %q: %s",
field.Name, fieldValue.Kind()))

View file

@ -130,6 +130,26 @@ var clonePropertiesTestCases = []struct {
},
},
{
// Clone nested interface
in: &struct {
Nested struct{ S interface{} }
}{
Nested: struct{ S interface{} }{
S: &struct{ S string }{
S: "string1",
},
},
},
out: &struct {
Nested struct{ S interface{} }
}{
Nested: struct{ S interface{} }{
S: &struct{ S string }{
S: "string1",
},
},
},
}, {
// Empty struct
in: &struct{}{},
out: &struct{}{},
@ -161,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)
@ -267,6 +348,25 @@ var cloneEmptyPropertiesTestCases = []struct {
S: &struct{ S string }{},
},
},
{
// Clone nested interface
in: &struct {
Nested struct{ S interface{} }
}{
Nested: struct{ S interface{} }{
S: &struct{ S string }{
S: "string1",
},
},
},
out: &struct {
Nested struct{ S interface{} }
}{
Nested: struct{ S interface{} }{
S: &struct{ S string }{},
},
},
},
{
// Empty struct
in: &struct{}{},
@ -295,11 +395,61 @@ var cloneEmptyPropertiesTestCases = []struct {
},
out: &struct{ S *struct{} }{},
},
{
// 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{},
Nested: struct{ EmbeddedStruct }{
EmbeddedStruct: EmbeddedStruct{},
},
},
},
{
// 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 }{},
Nested: struct{ EmbeddedInterface }{
EmbeddedInterface: &struct{ S string }{},
},
},
},
}
func TestCloneEmptyProperties(t *testing.T) {
for _, testCase := range cloneEmptyPropertiesTestCases {
testString := fmt.Sprintf("%s", testCase.in)
testString := fmt.Sprintf("%#v", testCase.in)
got := CloneEmptyProperties(reflect.ValueOf(testCase.in).Elem()).Interface()
@ -314,9 +464,9 @@ func TestCloneEmptyProperties(t *testing.T) {
func TestZeroProperties(t *testing.T) {
for _, testCase := range cloneEmptyPropertiesTestCases {
testString := fmt.Sprintf("%s", testCase.in)
testString := fmt.Sprintf("%#v", testCase.in)
got := CloneEmptyProperties(reflect.ValueOf(testCase.in).Elem()).Interface()
got := CloneProperties(reflect.ValueOf(testCase.in).Elem()).Interface()
ZeroProperties(reflect.ValueOf(got).Elem())
if !reflect.DeepEqual(testCase.out, got) {

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)