Skip to content

Commit 9a3d363

Browse files
authored
Merge pull request #146 from larrycinnabar/development
allow ptr in inline structs
2 parents 3cf9ed9 + 46c4b47 commit 9a3d363

File tree

3 files changed

+84
-4
lines changed

3 files changed

+84
-4
lines changed

bson/bson.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,14 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
746746
return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
747747
}
748748
inlineMap = info.Num
749+
case reflect.Ptr:
750+
// allow only pointer to struct
751+
if kind := field.Type.Elem().Kind(); kind != reflect.Struct {
752+
return nil, errors.New("Option ,inline allows a pointer only to a struct, was given pointer to " + kind.String())
753+
}
754+
755+
field.Type = field.Type.Elem()
756+
fallthrough
749757
case reflect.Struct:
750758
sinfo, err := getStructInfo(field.Type)
751759
if err != nil {
@@ -765,7 +773,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
765773
fieldsList = append(fieldsList, finfo)
766774
}
767775
default:
768-
panic("Option ,inline needs a struct value or map field")
776+
panic("Option ,inline needs a struct value or a pointer to a struct or map field")
769777
}
770778
continue
771779
}

bson/bson_test.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,42 @@ func (s *S) TestMarshalBuffer(c *C) {
271271
c.Assert(data, DeepEquals, buf[:len(data)])
272272
}
273273

274+
func (s *S) TestPtrInline(c *C) {
275+
cases := []struct {
276+
In interface{}
277+
Out bson.M
278+
}{
279+
{
280+
In: inlinePtrStruct{A: 1, MStruct: &MStruct{M: 3}},
281+
Out: bson.M{"a": 1, "m": 3},
282+
},
283+
{ // go deeper
284+
In: inlinePtrPtrStruct{B: 10, inlinePtrStruct: &inlinePtrStruct{A: 20, MStruct: &MStruct{M: 30}}},
285+
Out: bson.M{"b": 10, "a": 20, "m": 30},
286+
},
287+
{
288+
// nil embed struct
289+
In: &inlinePtrStruct{A: 3},
290+
Out: bson.M{"a": 3},
291+
},
292+
{
293+
// nil embed struct
294+
In: &inlinePtrPtrStruct{B: 5},
295+
Out: bson.M{"b": 5},
296+
},
297+
}
298+
299+
for _, cs := range cases {
300+
data, err := bson.Marshal(cs.In)
301+
c.Assert(err, IsNil)
302+
var dataBSON bson.M
303+
err = bson.Unmarshal(data, &dataBSON)
304+
c.Assert(err, IsNil)
305+
306+
c.Assert(dataBSON, DeepEquals, cs.Out)
307+
}
308+
}
309+
274310
// --------------------------------------------------------------------------
275311
// Some one way marshaling operations which would unmarshal differently.
276312

@@ -713,8 +749,6 @@ var marshalErrorItems = []testItemType{
713749
"Attempted to marshal empty Raw document"},
714750
{bson.M{"w": bson.Raw{Kind: 0x3, Data: []byte{}}},
715751
"Attempted to marshal empty Raw document"},
716-
{&inlineCantPtr{&struct{ A, B int }{1, 2}},
717-
"Option ,inline needs a struct value or map field"},
718752
{&inlineDupName{1, struct{ A, B int }{2, 3}},
719753
"Duplicated key 'a' in struct bson_test.inlineDupName"},
720754
{&inlineDupMap{},
@@ -1174,6 +1208,17 @@ type inlineUnexported struct {
11741208
M map[string]interface{} `bson:",inline"`
11751209
unexported `bson:",inline"`
11761210
}
1211+
type MStruct struct {
1212+
M int `bson:"m,omitempty"`
1213+
}
1214+
type inlinePtrStruct struct {
1215+
A int
1216+
*MStruct `bson:",inline"`
1217+
}
1218+
type inlinePtrPtrStruct struct {
1219+
B int
1220+
*inlinePtrStruct `bson:",inline"`
1221+
}
11771222
type unexported struct {
11781223
A int
11791224
}

bson/encode.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,18 @@ func (e *encoder) addStruct(v reflect.Value) {
229229
if info.Inline == nil {
230230
value = v.Field(info.Num)
231231
} else {
232-
value = v.FieldByIndex(info.Inline)
232+
// as pointers to struct are allowed here,
233+
// there is no guarantee that pointer won't be nil.
234+
//
235+
// It is expected allowed behaviour
236+
// so info.Inline MAY consist index to a nil pointer
237+
// and that is why we safely call v.FieldByIndex and just continue on panic
238+
field, errField := safeFieldByIndex(v, info.Inline)
239+
if errField != nil {
240+
continue
241+
}
242+
243+
value = field
233244
}
234245
if info.OmitEmpty && isZero(value) {
235246
continue
@@ -238,6 +249,22 @@ func (e *encoder) addStruct(v reflect.Value) {
238249
}
239250
}
240251

252+
func safeFieldByIndex(v reflect.Value, index []int) (result reflect.Value, err error) {
253+
defer func() {
254+
if recovered := recover(); recovered != nil {
255+
switch r := recovered.(type) {
256+
case string:
257+
err = fmt.Errorf("%s", r)
258+
case error:
259+
err = r
260+
}
261+
}
262+
}()
263+
264+
result = v.FieldByIndex(index)
265+
return
266+
}
267+
241268
func isZero(v reflect.Value) bool {
242269
switch v.Kind() {
243270
case reflect.String:

0 commit comments

Comments
 (0)