Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow ptr in inline structs #146

Merged
merged 3 commits into from
Apr 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion bson/bson.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,14 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
}
inlineMap = info.Num
case reflect.Ptr:
// allow only pointer to struct
if kind := field.Type.Elem().Kind(); kind != reflect.Struct {
return nil, errors.New("Option ,inline allows a pointer only to a struct, was given pointer to " + kind.String())
}

field.Type = field.Type.Elem()
fallthrough
case reflect.Struct:
sinfo, err := getStructInfo(field.Type)
if err != nil {
Expand All @@ -765,7 +773,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldsList = append(fieldsList, finfo)
}
default:
panic("Option ,inline needs a struct value or map field")
panic("Option ,inline needs a struct value or a pointer to a struct or map field")
}
continue
}
Expand Down
49 changes: 47 additions & 2 deletions bson/bson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,42 @@ func (s *S) TestMarshalBuffer(c *C) {
c.Assert(data, DeepEquals, buf[:len(data)])
}

func (s *S) TestPtrInline(c *C) {
cases := []struct {
In interface{}
Out bson.M
}{
{
In: inlinePtrStruct{A: 1, MStruct: &MStruct{M: 3}},
Out: bson.M{"a": 1, "m": 3},
},
{ // go deeper
In: inlinePtrPtrStruct{B: 10, inlinePtrStruct: &inlinePtrStruct{A: 20, MStruct: &MStruct{M: 30}}},
Out: bson.M{"b": 10, "a": 20, "m": 30},
},
{
// nil embed struct
In: &inlinePtrStruct{A: 3},
Out: bson.M{"a": 3},
},
{
// nil embed struct
In: &inlinePtrPtrStruct{B: 5},
Out: bson.M{"b": 5},
},
}

for _, cs := range cases {
data, err := bson.Marshal(cs.In)
c.Assert(err, IsNil)
var dataBSON bson.M
err = bson.Unmarshal(data, &dataBSON)
c.Assert(err, IsNil)

c.Assert(dataBSON, DeepEquals, cs.Out)
}
}

// --------------------------------------------------------------------------
// Some one way marshaling operations which would unmarshal differently.

Expand Down Expand Up @@ -713,8 +749,6 @@ var marshalErrorItems = []testItemType{
"Attempted to marshal empty Raw document"},
{bson.M{"w": bson.Raw{Kind: 0x3, Data: []byte{}}},
"Attempted to marshal empty Raw document"},
{&inlineCantPtr{&struct{ A, B int }{1, 2}},
"Option ,inline needs a struct value or map field"},
{&inlineDupName{1, struct{ A, B int }{2, 3}},
"Duplicated key 'a' in struct bson_test.inlineDupName"},
{&inlineDupMap{},
Expand Down Expand Up @@ -1174,6 +1208,17 @@ type inlineUnexported struct {
M map[string]interface{} `bson:",inline"`
unexported `bson:",inline"`
}
type MStruct struct {
M int `bson:"m,omitempty"`
}
type inlinePtrStruct struct {
A int
*MStruct `bson:",inline"`
}
type inlinePtrPtrStruct struct {
B int
*inlinePtrStruct `bson:",inline"`
}
type unexported struct {
A int
}
Expand Down
29 changes: 28 additions & 1 deletion bson/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,18 @@ func (e *encoder) addStruct(v reflect.Value) {
if info.Inline == nil {
value = v.Field(info.Num)
} else {
value = v.FieldByIndex(info.Inline)
// as pointers to struct are allowed here,
// there is no guarantee that pointer won't be nil.
//
// It is expected allowed behaviour
// so info.Inline MAY consist index to a nil pointer
// and that is why we safely call v.FieldByIndex and just continue on panic
field, errField := safeFieldByIndex(v, info.Inline)
if errField != nil {
continue
}

value = field
}
if info.OmitEmpty && isZero(value) {
continue
Expand All @@ -238,6 +249,22 @@ func (e *encoder) addStruct(v reflect.Value) {
}
}

func safeFieldByIndex(v reflect.Value, index []int) (result reflect.Value, err error) {
defer func() {
if recovered := recover(); recovered != nil {
switch r := recovered.(type) {
case string:
err = fmt.Errorf("%s", r)
case error:
err = r
}
}
}()

result = v.FieldByIndex(index)
return
}

func isZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.String:
Expand Down