@@ -14,6 +14,7 @@ import (
14
14
"math"
15
15
"net/url"
16
16
"reflect"
17
+ "strings"
17
18
"testing"
18
19
"time"
19
20
@@ -22,6 +23,7 @@ import (
22
23
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
23
24
"go.mongodb.org/mongo-driver/bson/bsontype"
24
25
"go.mongodb.org/mongo-driver/bson/primitive"
26
+ "go.mongodb.org/mongo-driver/internal/testutil/assert"
25
27
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
26
28
)
27
29
@@ -924,7 +926,7 @@ func TestDefaultValueDecoders(t *testing.T) {
924
926
& DecodeContext {Registry : buildDefaultRegistry ()},
925
927
& bsonrwtest.ValueReaderWriter {BSONType : bsontype .Array },
926
928
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" )} ,
928
930
},
929
931
{
930
932
"Document but not D" ,
@@ -1018,7 +1020,7 @@ func TestDefaultValueDecoders(t *testing.T) {
1018
1020
& DecodeContext {Registry : buildDefaultRegistry ()},
1019
1021
& bsonrwtest.ValueReaderWriter {BSONType : bsontype .Array },
1020
1022
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" )} ,
1022
1024
},
1023
1025
{
1024
1026
"Document but not D" ,
@@ -3353,6 +3355,202 @@ func TestDefaultValueDecoders(t *testing.T) {
3353
3355
}
3354
3356
})
3355
3357
})
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
+ })
3356
3554
}
3357
3555
3358
3556
type testValueUnmarshaler struct {
0 commit comments