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

GODRIVER-1808 Fix BSON unmarshaling into an interface containing a concrete value. #1584

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 3 additions & 10 deletions bson/bsoncodec.go
Expand Up @@ -309,17 +309,10 @@ type decodeAdapter struct {
var _ ValueDecoder = decodeAdapter{}
var _ typeDecoder = decodeAdapter{}

// decodeTypeOrValue calls decoder.decodeType is decoder is a typeDecoder. Otherwise, it allocates a new element of type
// t and calls decoder.DecodeValue on it.
func decodeTypeOrValue(decoder ValueDecoder, dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) {
td, _ := decoder.(typeDecoder)
return decodeTypeOrValueWithInfo(decoder, td, dc, vr, t, true)
}

func decodeTypeOrValueWithInfo(vd ValueDecoder, td typeDecoder, dc DecodeContext, vr ValueReader, t reflect.Type, convert bool) (reflect.Value, error) {
if td != nil {
func decodeTypeOrValueWithInfo(vd ValueDecoder, dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) {
if td, _ := vd.(typeDecoder); td != nil {
val, err := td.decodeType(dc, vr, t)
if err == nil && convert && val.Type() != t {
if err == nil && val.Type() != t {
// This conversion step is necessary for slices and maps. If a user declares variables like:
//
// type myBool bool
Expand Down
156 changes: 156 additions & 0 deletions bson/decoder_test.go
Expand Up @@ -39,6 +39,162 @@ func TestBasicDecode(t *testing.T) {
}
}

func TestDecodingInterfaces(t *testing.T) {
t.Parallel()

type testCase struct {
name string
stub func() ([]byte, interface{}, func(*testing.T))
}
testCases := []testCase{
{
name: "struct with interface containing a concrete value",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Value interface{}
}
var value string

data := docToBytes(struct {
Value string
}{
Value: "foo",
})

receiver := testStruct{&value}

check := func(t *testing.T) {
t.Helper()
assert.Equal(t, "foo", value)
}

return data, &receiver, check
},
},
{
name: "struct with interface containing a struct",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type demo struct {
Data string
}

type testStruct struct {
Value interface{}
}
var value demo

data := docToBytes(struct {
Value demo
}{
Value: demo{"foo"},
})

receiver := testStruct{&value}

check := func(t *testing.T) {
t.Helper()
assert.Equal(t, "foo", value.Data)
}

return data, &receiver, check
},
},
{
name: "struct with interface containing a slice",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Values interface{}
}
var values []string

data := docToBytes(struct {
Values []string
}{
Values: []string{"foo", "bar"},
})

receiver := testStruct{&values}

check := func(t *testing.T) {
t.Helper()
assert.Equal(t, []string{"foo", "bar"}, values)
}

return data, &receiver, check
},
},
{
name: "struct with interface containing an array",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Values interface{}
}
var values [2]string

data := docToBytes(struct {
Values []string
}{
Values: []string{"foo", "bar"},
})

receiver := testStruct{&values}

check := func(t *testing.T) {
t.Helper()
assert.Equal(t, [2]string{"foo", "bar"}, values)
}

return data, &receiver, check
},
},
{
name: "struct with interface array containing concrete values",
stub: func() ([]byte, interface{}, func(*testing.T)) {
type testStruct struct {
Values [3]interface{}
}
var str string
var i, j int

data := docToBytes(struct {
Values []interface{}
}{
Values: []interface{}{"foo", 42, nil},
})

receiver := testStruct{[3]interface{}{&str, &i, &j}}

check := func(t *testing.T) {
t.Helper()
assert.Equal(t, "foo", str)
assert.Equal(t, 42, i)
assert.Equal(t, 0, j)
assert.Equal(t, testStruct{[3]interface{}{&str, &i, nil}}, receiver)
}

return data, &receiver, check
},
},
}
for _, tc := range testCases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
t.Parallel()

data, receiver, check := tc.stub()
got := reflect.ValueOf(receiver).Elem()
vr := NewValueReader(data)
reg := DefaultRegistry
decoder, err := reg.LookupDecoder(got.Type())
noerr(t, err)
err = decoder.DecodeValue(DecodeContext{Registry: reg}, vr, got)
noerr(t, err)
check(t)
})
}
}

func TestDecoderv2(t *testing.T) {
t.Parallel()

Expand Down