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-1235 Skip embedded documents and arrays correctly in extJSONValueReader #544

Merged
merged 10 commits into from
Dec 9, 2020
58 changes: 13 additions & 45 deletions bson/bsonrw/extjson_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,29 +159,18 @@ func (ejvr *extJSONValueReader) pop() {
}
}

func (ejvr *extJSONValueReader) skipDocument() error {
// read entire document until ErrEOD (using readKey and readValue)
_, typ, err := ejvr.p.readKey()
for err == nil {
_, err = ejvr.p.readValue(typ)
kevinAlbs marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
break
func (ejvr *extJSONValueReader) skipObject() {
// read entire object until depth returns to 0 (last ending } or ] seen)
depth := 1
for depth > 0 {
ejvr.p.advanceState()
switch ejvr.p.s {
case jpsSawBeginObject, jpsSawBeginArray:
depth++
case jpsSawEndObject, jpsSawEndArray:
depth--
}

_, typ, err = ejvr.p.readKey()
}

return err
}

func (ejvr *extJSONValueReader) skipArray() error {
// read entire array until ErrEOA (using peekType)
_, err := ejvr.p.peekType()
for err == nil {
_, err = ejvr.p.peekType()
}

return err
}

func (ejvr *extJSONValueReader) invalidTransitionErr(destination mode, name string, modes []mode) error {
Expand Down Expand Up @@ -234,30 +223,9 @@ func (ejvr *extJSONValueReader) Skip() error {

t := ejvr.stack[ejvr.frame].vType
switch t {
case bsontype.Array:
// read entire array until ErrEOA
err := ejvr.skipArray()
if err != ErrEOA {
return err
}
case bsontype.EmbeddedDocument:
// read entire doc until ErrEOD
err := ejvr.skipDocument()
if err != ErrEOD {
return err
}
case bsontype.CodeWithScope:
// read the code portion and set up parser in document mode
_, err := ejvr.p.readValue(t)
if err != nil {
return err
}

// read until ErrEOD
err = ejvr.skipDocument()
if err != ErrEOD {
return err
}
case bsontype.Array, bsontype.EmbeddedDocument, bsontype.CodeWithScope:
// read entire array, doc or CodeWithScope
ejvr.skipObject()
default:
_, err := ejvr.p.readValue(t)
if err != nil {
Expand Down
112 changes: 112 additions & 0 deletions bson/unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
Expand Down Expand Up @@ -166,3 +167,114 @@ func TestCachingDecodersNotSharedAcrossRegistries(t *testing.T) {
assert.Equal(t, int32(-1), *second.X, "expected X value to be -1, got %v", *second.X)
})
}

type expectedResponse struct {
DefinedField string
}

func unmarshalExtJSONHelper(extJSON string) (*expectedResponse, error) {
divjotarora marked this conversation as resolved.
Show resolved Hide resolved
responseDoc := expectedResponse{}
err := UnmarshalExtJSON([]byte(extJSON), false, &responseDoc)
return &responseDoc, err
}

func TestUnmarshalExtJSONWithUndefinedField(t *testing.T) {
// When unmarshalling, fields that are undefined in the destination struct are skipped.
// This process must not skip other, defined fields and must not raise errors.
testCases := []struct {
name string
testJSON string
}{
{
"no array",
`{
"UndefinedField": {"key": 1},
"DefinedField": "value"
}`,
},
{
"outer array",
`{
"UndefinedField": [{"key": 1}],
"DefinedField": "value"
}`,
},
{
"embedded array",
`{
"UndefinedField": {"keys": [2]},
"DefinedField": "value"
}`,
},
{
"outer array and embedded array",
`{
"UndefinedField": [{"keys": [2]}],
"DefinedField": "value"
}`,
},
{
"embedded document",
`{
"UndefinedField": {"key": {"one": "two"}},
"DefinedField": "value"
}`,
},
{
"doubly embedded document",
`{
"UndefinedField": {"key": {"one": {"two": "three"}}},
"DefinedField": "value"
}`,
},
{
"embedded document and embedded array",
`{
"UndefinedField": {"key": {"one": {"two": [3]}}},
"DefinedField": "value"
}`,
},
{
"embedded document and embedded array in outer array",
`{
"UndefinedField": [{"key": {"one": [3]}}],
"DefinedField": "value"
}`,
kevinAlbs marked this conversation as resolved.
Show resolved Hide resolved
},
{
"code with scope",
`{
"UndefinedField": {"logic": {"$code": "foo", "$scope": {"bar": 1}}},
"DefinedField": "value"
}`,
},
{
"embedded array of code with scope",
`{
"UndefinedField": {"logic": [{"$code": "foo", "$scope": {"bar": 1}}]},
"DefinedField": "value"
}`,
},
{
"type definition embedded document",
`{
"UndefinedField": {"myDouble": {"$numberDouble": "1.24"}},
"DefinedField": "value"
}`,
},
{
"empty embedded document",
`{
"UndefinedField": {"empty": {}},
"DefinedField": "value"
}`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
responseDoc, err := unmarshalExtJSONHelper(tc.testJSON)
require.NoError(t, err)
divjotarora marked this conversation as resolved.
Show resolved Hide resolved
require.Equal(t, "value", responseDoc.DefinedField)
})
}
}