Skip to content

Commit 52d87da

Browse files
author
Divjot Arora
authored
GODRIVER-952 Add context to BSON decoding errors (#359)
1 parent 7019e82 commit 52d87da

File tree

6 files changed

+271
-14
lines changed

6 files changed

+271
-14
lines changed

bson/bsoncodec/default_value_decoders.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,7 @@ func (dvd DefaultValueDecoders) decodeDefault(dc DecodeContext, vr bsonrw.ValueR
12071207
return nil, err
12081208
}
12091209

1210+
idx := 0
12101211
for {
12111212
vr, err := ar.ReadValue()
12121213
if err == bsonrw.ErrEOA {
@@ -1220,9 +1221,10 @@ func (dvd DefaultValueDecoders) decodeDefault(dc DecodeContext, vr bsonrw.ValueR
12201221

12211222
err = decoder.DecodeValue(dc, vr, elem)
12221223
if err != nil {
1223-
return nil, err
1224+
return nil, newDecodeError(strconv.Itoa(idx), err)
12241225
}
12251226
elems = append(elems, elem)
1227+
idx++
12261228
}
12271229

12281230
return elems, nil
@@ -1306,7 +1308,7 @@ func (DefaultValueDecoders) decodeElemsFromDocumentReader(dc DecodeContext, dr b
13061308
val := reflect.New(tEmpty).Elem()
13071309
err = decoder.DecodeValue(dc, vr, val)
13081310
if err != nil {
1309-
return nil, err
1311+
return nil, newDecodeError(key, err)
13101312
}
13111313

13121314
elems = append(elems, reflect.ValueOf(primitive.E{Key: key, Value: val.Interface()}))

bson/bsoncodec/default_value_decoders_test.go

Lines changed: 200 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"math"
1515
"net/url"
1616
"reflect"
17+
"strings"
1718
"testing"
1819
"time"
1920

@@ -22,6 +23,7 @@ import (
2223
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
2324
"go.mongodb.org/mongo-driver/bson/bsontype"
2425
"go.mongodb.org/mongo-driver/bson/primitive"
26+
"go.mongodb.org/mongo-driver/internal/testutil/assert"
2527
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
2628
)
2729

@@ -924,7 +926,7 @@ func TestDefaultValueDecoders(t *testing.T) {
924926
&DecodeContext{Registry: buildDefaultRegistry()},
925927
&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
926928
bsonrwtest.ReadValue,
927-
errors.New("cannot decode array into a string type"),
929+
&DecodeError{keys: []string{"0"}, wrapped: errors.New("cannot decode array into a string type")},
928930
},
929931
{
930932
"Document but not D",
@@ -1018,7 +1020,7 @@ func TestDefaultValueDecoders(t *testing.T) {
10181020
&DecodeContext{Registry: buildDefaultRegistry()},
10191021
&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
10201022
bsonrwtest.ReadValue,
1021-
errors.New("cannot decode array into a string type"),
1023+
&DecodeError{keys: []string{"0"}, wrapped: errors.New("cannot decode array into a string type")},
10221024
},
10231025
{
10241026
"Document but not D",
@@ -3353,6 +3355,202 @@ func TestDefaultValueDecoders(t *testing.T) {
33533355
}
33543356
})
33553357
})
3358+
3359+
t.Run("decode errors contain key information", func(t *testing.T) {
3360+
decodeValueError := errors.New("decode value error")
3361+
emptyInterfaceErrorDecode := func(DecodeContext, bsonrw.ValueReader, reflect.Value) error {
3362+
return decodeValueError
3363+
}
3364+
emptyInterfaceErrorRegistry := NewRegistryBuilder().
3365+
RegisterTypeDecoder(tEmpty, ValueDecoderFunc(emptyInterfaceErrorDecode)).Build()
3366+
3367+
// Set up a document {foo: 10} and an error that would happen if the value were decoded into interface{}
3368+
// using the registry defined above.
3369+
docBytes := bsoncore.BuildDocumentFromElements(
3370+
nil,
3371+
bsoncore.AppendInt32Element(nil, "foo", 10),
3372+
)
3373+
docEmptyInterfaceErr := &DecodeError{
3374+
keys: []string{"foo"},
3375+
wrapped: decodeValueError,
3376+
}
3377+
3378+
// Set up struct definitions where Foo maps to interface{} and string. When decoded using the registry defined
3379+
// above, the interface{} struct will get an error when calling DecodeValue and the string struct will get an
3380+
// error when looking up a decoder.
3381+
type emptyInterfaceStruct struct {
3382+
Foo interface{}
3383+
}
3384+
type stringStruct struct {
3385+
Foo string
3386+
}
3387+
emptyInterfaceStructErr := &DecodeError{
3388+
keys: []string{"foo(field Foo)"},
3389+
wrapped: decodeValueError,
3390+
}
3391+
stringStructErr := &DecodeError{
3392+
keys: []string{"foo(field Foo)"},
3393+
wrapped: ErrNoDecoder{reflect.TypeOf("")},
3394+
}
3395+
3396+
// Test a deeply nested struct mixed with maps and slices.
3397+
// Build document {"first": {"second": {"randomKey": {"third": [{}, {"fourth": "value"}]}}}}
3398+
type inner3 struct{ Fourth interface{} }
3399+
type inner2 struct{ Third []inner3 }
3400+
type inner1 struct{ Second map[string]inner2 }
3401+
type outer struct{ First inner1 }
3402+
inner3EmptyDoc := buildDocument(nil)
3403+
inner3Doc := buildDocument(bsoncore.AppendStringElement(nil, "fourth", "value"))
3404+
inner3Array := buildArray(
3405+
// buildArray takes []byte so we first append() all of the values into a single []byte
3406+
append(
3407+
bsoncore.AppendDocumentElement(nil, "0", inner3EmptyDoc),
3408+
bsoncore.AppendDocumentElement(nil, "1", inner3Doc)...,
3409+
),
3410+
)
3411+
inner2Doc := buildDocument(bsoncore.AppendArrayElement(nil, "third", inner3Array))
3412+
inner2Map := buildDocument(bsoncore.AppendDocumentElement(nil, "randomKey", inner2Doc))
3413+
inner1Doc := buildDocument(bsoncore.AppendDocumentElement(nil, "second", inner2Map))
3414+
outerDoc := buildDocument(bsoncore.AppendDocumentElement(nil, "first", inner1Doc))
3415+
3416+
// Use a registry that has all default decoders with the custom interface{} decoder that always errors.
3417+
nestedRegistryBuilder := NewRegistryBuilder()
3418+
defaultValueDecoders.RegisterDefaultDecoders(nestedRegistryBuilder)
3419+
nestedRegistry := nestedRegistryBuilder.
3420+
RegisterTypeDecoder(tEmpty, ValueDecoderFunc(emptyInterfaceErrorDecode)).
3421+
Build()
3422+
nestedErr := &DecodeError{
3423+
keys: []string{"fourth(field Fourth)", "1", "third(field Third)", "randomKey", "second(field Second)", "first(field First)"},
3424+
wrapped: decodeValueError,
3425+
}
3426+
3427+
testCases := []struct {
3428+
name string
3429+
val interface{}
3430+
vr bsonrw.ValueReader
3431+
registry *Registry // buildDefaultRegistry will be used if this is nil
3432+
decoder ValueDecoder
3433+
err error
3434+
}{
3435+
{
3436+
// DecodeValue error when decoding into a primitive.D.
3437+
"primitive.D slice",
3438+
primitive.D{},
3439+
bsonrw.NewBSONDocumentReader(docBytes),
3440+
emptyInterfaceErrorRegistry,
3441+
defaultSliceCodec,
3442+
docEmptyInterfaceErr,
3443+
},
3444+
{
3445+
// DecodeValue error when decoding into a []string.
3446+
"string slice",
3447+
[]string{},
3448+
&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
3449+
nil,
3450+
defaultSliceCodec,
3451+
&DecodeError{
3452+
keys: []string{"0"},
3453+
wrapped: errors.New("cannot decode array into a string type"),
3454+
},
3455+
},
3456+
{
3457+
// DecodeValue error when decoding into a primitive.E array. This should have the same behavior as
3458+
// the "primitive.D slice" test above because both the defaultSliceCodec and ArrayDecodeValue use
3459+
// the decodeD helper function.
3460+
"primitive.D array",
3461+
[1]primitive.E{},
3462+
bsonrw.NewBSONDocumentReader(docBytes),
3463+
emptyInterfaceErrorRegistry,
3464+
ValueDecoderFunc(dvd.ArrayDecodeValue),
3465+
docEmptyInterfaceErr,
3466+
},
3467+
{
3468+
// DecodeValue error when decoding into a string array. This should have the same behavior as
3469+
// the "primitive.D slice" test above because both the defaultSliceCodec and ArrayDecodeValue use
3470+
// the decodeDefault helper function.
3471+
"string array",
3472+
[1]string{},
3473+
&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
3474+
nil,
3475+
ValueDecoderFunc(dvd.ArrayDecodeValue),
3476+
&DecodeError{
3477+
keys: []string{"0"},
3478+
wrapped: errors.New("cannot decode array into a string type"),
3479+
},
3480+
},
3481+
{
3482+
// DecodeValue error when decoding into a map.
3483+
"map",
3484+
map[string]interface{}{},
3485+
bsonrw.NewBSONDocumentReader(docBytes),
3486+
emptyInterfaceErrorRegistry,
3487+
defaultMapCodec,
3488+
docEmptyInterfaceErr,
3489+
},
3490+
{
3491+
// DecodeValue error when decoding into a struct.
3492+
"struct - DecodeValue error",
3493+
emptyInterfaceStruct{},
3494+
bsonrw.NewBSONDocumentReader(docBytes),
3495+
emptyInterfaceErrorRegistry,
3496+
defaultStructCodec,
3497+
emptyInterfaceStructErr,
3498+
},
3499+
{
3500+
// ErrNoDecoder when decoding into a struct.
3501+
// This test uses NewRegistryBuilder().Build rather than buildDefaultRegistry to ensure that there is
3502+
// no decoder for strings.
3503+
"struct - no decoder found",
3504+
stringStruct{},
3505+
bsonrw.NewBSONDocumentReader(docBytes),
3506+
NewRegistryBuilder().Build(),
3507+
defaultStructCodec,
3508+
stringStructErr,
3509+
},
3510+
{
3511+
"deeply nested struct",
3512+
outer{},
3513+
bsonrw.NewBSONDocumentReader(outerDoc),
3514+
nestedRegistry,
3515+
defaultStructCodec,
3516+
nestedErr,
3517+
},
3518+
}
3519+
for _, tc := range testCases {
3520+
t.Run(tc.name, func(t *testing.T) {
3521+
dc := DecodeContext{Registry: tc.registry}
3522+
if dc.Registry == nil {
3523+
dc.Registry = buildDefaultRegistry()
3524+
}
3525+
3526+
var val reflect.Value
3527+
if rtype := reflect.TypeOf(tc.val); rtype != nil {
3528+
val = reflect.New(rtype).Elem()
3529+
}
3530+
err := tc.decoder.DecodeValue(dc, tc.vr, val)
3531+
assert.Equal(t, tc.err, err, "expected error %v, got %v", tc.err, err)
3532+
})
3533+
}
3534+
3535+
t.Run("keys are correctly reversed", func(t *testing.T) {
3536+
innerBytes := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendInt32Element(nil, "bar", 10))
3537+
outerBytes := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendDocumentElement(nil, "foo", innerBytes))
3538+
3539+
type inner struct{ Bar string }
3540+
type outer struct{ Foo inner }
3541+
3542+
dc := DecodeContext{Registry: buildDefaultRegistry()}
3543+
vr := bsonrw.NewBSONDocumentReader(outerBytes)
3544+
val := reflect.New(reflect.TypeOf(outer{})).Elem()
3545+
err := defaultStructCodec.DecodeValue(dc, vr, val)
3546+
3547+
decodeErr, ok := err.(*DecodeError)
3548+
assert.True(t, ok, "expected DecodeError, got %v of type %T", err, err)
3549+
keyPattern := "foo(field Foo).bar(field Bar)"
3550+
assert.True(t, strings.Contains(decodeErr.Error(), keyPattern),
3551+
"expected error %v to contain key pattern %s", decodeErr, keyPattern)
3552+
})
3553+
})
33563554
}
33573555

33583556
type testValueUnmarshaler struct {

bson/bsoncodec/map_codec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func (mc *MapCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val ref
193193
elem := reflect.New(eType).Elem()
194194
err = decoder.DecodeValue(dc, vr, elem)
195195
if err != nil {
196-
return err
196+
return newDecodeError(key, err)
197197
}
198198

199199
val.SetMapIndex(k, elem)

bson/bsoncodec/slice_codec.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ func (sc *SliceCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val r
152152
}
153153
return nil
154154
case bsontype.String:
155-
if val.Type().Elem() != tByte {
156-
return fmt.Errorf("SliceDecodeValue can only decode a string into a byte array, got %v", vrType)
155+
if sliceType := val.Type().Elem(); sliceType != tByte {
156+
return fmt.Errorf("SliceDecodeValue can only decode a string into a byte array, got %v", sliceType)
157157
}
158158
str, err := vr.ReadString()
159159
if err != nil {

0 commit comments

Comments
 (0)