diff --git a/examples/_logger/logrus/go.mod b/examples/_logger/logrus/go.mod index 57916ac0d0..8dc619cf68 100644 --- a/examples/_logger/logrus/go.mod +++ b/examples/_logger/logrus/go.mod @@ -19,7 +19,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/examples/_logger/logrus/go.sum b/examples/_logger/logrus/go.sum index d8eeaeeca5..6613a0e476 100644 --- a/examples/_logger/logrus/go.sum +++ b/examples/_logger/logrus/go.sum @@ -37,8 +37,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/examples/_logger/zap/go.mod b/examples/_logger/zap/go.mod index fbb8955d8d..bd187a3f18 100644 --- a/examples/_logger/zap/go.mod +++ b/examples/_logger/zap/go.mod @@ -21,6 +21,6 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/examples/_logger/zap/go.sum b/examples/_logger/zap/go.sum index 0c12968e79..b0578e6a22 100644 --- a/examples/_logger/zap/go.sum +++ b/examples/_logger/zap/go.sum @@ -58,8 +58,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/examples/_logger/zerolog/go.mod b/examples/_logger/zerolog/go.mod index 415b19c7b3..fa062410f1 100644 --- a/examples/_logger/zerolog/go.mod +++ b/examples/_logger/zerolog/go.mod @@ -21,7 +21,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/examples/_logger/zerolog/go.sum b/examples/_logger/zerolog/go.sum index 4ae84a6883..047aade802 100644 --- a/examples/_logger/zerolog/go.sum +++ b/examples/_logger/zerolog/go.sum @@ -39,8 +39,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/cmd/benchmark/go.mod b/internal/cmd/benchmark/go.mod index 83a664c9c8..d615bf9cae 100644 --- a/internal/cmd/benchmark/go.mod +++ b/internal/cmd/benchmark/go.mod @@ -23,7 +23,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.24.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.16.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/cmd/benchmark/go.sum b/internal/cmd/benchmark/go.sum index 16d8304718..17b4c22f2e 100644 --- a/internal/cmd/benchmark/go.sum +++ b/internal/cmd/benchmark/go.sum @@ -30,8 +30,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/cmd/compilecheck/go.mod b/internal/cmd/compilecheck/go.mod index 1d73def083..57e96e3955 100644 --- a/internal/cmd/compilecheck/go.mod +++ b/internal/cmd/compilecheck/go.mod @@ -16,6 +16,6 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/internal/cmd/compilecheck/go.sum b/internal/cmd/compilecheck/go.sum index eaea7583ba..6c81d8d87f 100644 --- a/internal/cmd/compilecheck/go.sum +++ b/internal/cmd/compilecheck/go.sum @@ -23,8 +23,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/cmd/faas/awslambda/mongodb/go.mod b/internal/cmd/faas/awslambda/mongodb/go.mod index 2d582f9a9c..2df2be8f73 100644 --- a/internal/cmd/faas/awslambda/mongodb/go.mod +++ b/internal/cmd/faas/awslambda/mongodb/go.mod @@ -17,7 +17,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/crypto v0.22.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/internal/cmd/faas/awslambda/mongodb/go.sum b/internal/cmd/faas/awslambda/mongodb/go.sum index 3264bc331b..c0db4e8da9 100644 --- a/internal/cmd/faas/awslambda/mongodb/go.sum +++ b/internal/cmd/faas/awslambda/mongodb/go.sum @@ -37,8 +37,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/codecutil/encoding.go b/internal/codecutil/encoding.go index 5fa031efb9..aafb1e997c 100644 --- a/internal/codecutil/encoding.go +++ b/internal/codecutil/encoding.go @@ -33,7 +33,7 @@ func (e MarshalError) Error() string { } // EncoderFn is used to functionally construct an encoder for marshaling values. -type EncoderFn func(io.Writer) (*bson.Encoder, error) +type EncoderFn func(io.Writer) *bson.Encoder // MarshalValue will attempt to encode the value with the encoder returned by // the encoder function. @@ -49,14 +49,11 @@ func MarshalValue(val interface{}, encFn EncoderFn) (bsoncore.Value, error) { buf := new(bytes.Buffer) - enc, err := encFn(buf) - if err != nil { - return bsoncore.Value{}, err - } + enc := encFn(buf) // Encode the value in a single-element document with an empty key. Use // bsoncore to extract the first element and return the BSON value. - err = enc.Encode(bson.D{{Key: "", Value: val}}) + err := enc.Encode(bson.D{{Key: "", Value: val}}) if err != nil { return bsoncore.Value{}, MarshalError{Value: val, Err: err} } diff --git a/internal/codecutil/encoding_test.go b/internal/codecutil/encoding_test.go index 2050733b28..8103defecc 100644 --- a/internal/codecutil/encoding_test.go +++ b/internal/codecutil/encoding_test.go @@ -17,11 +17,9 @@ import ( func testEncFn(t *testing.T) EncoderFn { t.Helper() - return func(w io.Writer) (*bson.Encoder, error) { + return func(w io.Writer) *bson.Encoder { rw := bson.NewDocumentWriter(w) - enc := bson.NewEncoder(rw) - - return enc, nil + return bson.NewEncoder(rw) } } diff --git a/internal/integration/client_test.go b/internal/integration/client_test.go index 4d58174b64..98d8752e00 100644 --- a/internal/integration/client_test.go +++ b/internal/integration/client_test.go @@ -54,6 +54,12 @@ func (e *negateCodec) DecodeValue(_ bson.DecodeContext, vr bson.ValueReader, val return nil } +type intKey int + +func (i intKey) MarshalKey() (string, error) { + return fmt.Sprintf("key_%d", i), nil +} + var _ options.ContextDialer = &slowConnDialer{} // A slowConnDialer dials connections that delay network round trips by the given delay duration. @@ -724,6 +730,20 @@ func TestClient_BSONOptions(t *testing.T) { C string `json:"y" bson:"3"` } + type omitemptyTest struct { + X jsonTagsTest `bson:"x,omitempty"` + } + + type truncatingDoublesTest struct { + X int + } + + type timeZoneTest struct { + X time.Time + } + + timestamp, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00") + testCases := []struct { name string bsonOpts *options.BSONOptions @@ -766,6 +786,88 @@ func TestClient_BSONOptions(t *testing.T) { AppendInt32("x", 1). Build()), }, + { + name: "NilMapAsEmpty", + bsonOpts: &options.BSONOptions{ + NilMapAsEmpty: true, + }, + doc: bson.D{{Key: "x", Value: map[string]string(nil)}}, + decodeInto: func() interface{} { return &bson.D{} }, + want: &bson.D{{Key: "x", Value: bson.D{}}}, + wantRaw: bson.Raw(bsoncore.NewDocumentBuilder(). + AppendDocument("x", bsoncore.NewDocumentBuilder().Build()). + Build()), + }, + { + name: "NilSliceAsEmpty", + bsonOpts: &options.BSONOptions{ + NilSliceAsEmpty: true, + }, + doc: bson.D{{Key: "x", Value: []int(nil)}}, + decodeInto: func() interface{} { return &bson.D{} }, + want: &bson.D{{Key: "x", Value: bson.A{}}}, + wantRaw: bson.Raw(bsoncore.NewDocumentBuilder(). + AppendArray("x", bsoncore.NewDocumentBuilder().Build()). + Build()), + }, + { + name: "NilByteSliceAsEmpty", + bsonOpts: &options.BSONOptions{ + NilByteSliceAsEmpty: true, + }, + doc: bson.D{{Key: "x", Value: []byte(nil)}}, + decodeInto: func() interface{} { return &bson.D{} }, + want: &bson.D{{Key: "x", Value: bson.Binary{Data: []byte{}}}}, + wantRaw: bson.Raw(bsoncore.NewDocumentBuilder(). + AppendBinary("x", 0, nil). + Build()), + }, + { + name: "OmitZeroStruct", + bsonOpts: &options.BSONOptions{ + OmitZeroStruct: true, + }, + doc: omitemptyTest{}, + decodeInto: func() interface{} { return &bson.D{} }, + want: &bson.D{}, + wantRaw: bson.Raw(bsoncore.NewDocumentBuilder().Build()), + }, + { + name: "StringifyMapKeysWithFmt", + bsonOpts: &options.BSONOptions{ + StringifyMapKeysWithFmt: true, + }, + doc: map[intKey]string{intKey(42): "foo"}, + decodeInto: func() interface{} { return &bson.D{} }, + want: &bson.D{{"42", "foo"}}, + wantRaw: bson.Raw(bsoncore.NewDocumentBuilder(). + AppendString("42", "foo"). + Build()), + }, + { + name: "AllowTruncatingDoubles", + bsonOpts: &options.BSONOptions{ + AllowTruncatingDoubles: true, + }, + doc: bson.D{{Key: "x", Value: 3.14}}, + decodeInto: func() interface{} { return &truncatingDoublesTest{} }, + want: &truncatingDoublesTest{3}, + wantRaw: bson.Raw(bsoncore.NewDocumentBuilder(). + AppendDouble("x", 3.14). + Build()), + }, + { + name: "BinaryAsSlice", + bsonOpts: &options.BSONOptions{ + BinaryAsSlice: true, + }, + doc: bson.D{{Key: "x", Value: []byte{42}}}, + decodeInto: func() interface{} { return &bson.D{} }, + want: &bson.D{{Key: "x", Value: []byte{42}}}, + wantRaw: bson.Raw(bsoncore.NewDocumentBuilder(). + AppendBinary("x", 0, []byte{42}). + Build()), + }, { name: "DefaultDocumentM", bsonOpts: &options.BSONOptions{ @@ -775,6 +877,50 @@ func TestClient_BSONOptions(t *testing.T) { decodeInto: func() interface{} { return &bson.D{} }, want: &bson.D{{Key: "doc", Value: bson.M{"a": int64(1)}}}, }, + { + name: "UseLocalTimeZone", + bsonOpts: &options.BSONOptions{ + UseLocalTimeZone: true, + }, + doc: bson.D{{Key: "x", Value: timestamp}}, + decodeInto: func() interface{} { return &timeZoneTest{} }, + want: &timeZoneTest{timestamp.In(time.Local)}, + }, + { + name: "ZeroMaps", + bsonOpts: &options.BSONOptions{ + ZeroMaps: true, + }, + doc: bson.D{{"a", "apple"}, {"b", "banana"}}, + decodeInto: func() interface{} { + return &map[string]string{ + "b": "berry", + "c": "carrot", + } + }, + want: &map[string]string{ + "a": "apple", + "b": "banana", + }, + }, + { + name: "ZeroStructs", + bsonOpts: &options.BSONOptions{ + ZeroStructs: true, + }, + doc: bson.D{{"a", "apple"}, {"x", "broccoli"}}, + decodeInto: func() interface{} { + return &jsonTagsTest{ + B: "banana", + C: "carrot", + } + }, + want: &jsonTagsTest{ + A: "apple", + B: "broccoli", + C: "", + }, + }, } for _, tc := range testCases { @@ -809,6 +955,35 @@ func TestClient_BSONOptions(t *testing.T) { } opts := mtest.NewOptions().ClientOptions( + options.Client().SetBSONOptions(&options.BSONOptions{ + ObjectIDAsHexString: true, + })) + mt.RunOpts("ObjectIDAsHexString", opts, func(mt *mtest.T) { + res, err := mt.Coll.InsertOne(context.Background(), bson.D{{"x", 42}}) + require.NoError(mt, err, "InsertOne error") + + sr := mt.Coll.FindOne( + context.Background(), + bson.D{{Key: "_id", Value: res.InsertedID}}, + ) + + type data struct { + ID string `bson:"_id"` + X int `bson:"x"` + } + var got data + + err = sr.Decode(&got) + require.NoError(mt, err, "Decode error") + + want := data{ + ID: res.InsertedID.(bson.ObjectID).Hex(), + X: 42, + } + assert.Equal(mt, want, got, "expected and actual decoded result are different") + }) + + opts = mtest.NewOptions().ClientOptions( options.Client().SetBSONOptions(&options.BSONOptions{ ErrorOnInlineDuplicates: true, })) diff --git a/mongo/cursor.go b/mongo/cursor.go index f3bfa91bf1..7bc34b501a 100644 --- a/mongo/cursor.go +++ b/mongo/cursor.go @@ -250,6 +250,9 @@ func getDecoder( if opts.DefaultDocumentM { dec.DefaultDocumentM() } + if opts.ObjectIDAsHexString { + dec.ObjectIDAsHexString() + } if opts.UseJSONStructTags { dec.UseJSONStructTags() } diff --git a/mongo/cursor_test.go b/mongo/cursor_test.go index 4ce9c463a5..787e5c38bc 100644 --- a/mongo/cursor_test.go +++ b/mongo/cursor_test.go @@ -9,6 +9,7 @@ package mongo import ( "context" "fmt" + "reflect" "testing" "time" @@ -264,6 +265,38 @@ func TestNewCursorFromDocuments(t *testing.T) { }) } +func TestGetDecoder(t *testing.T) { + t.Parallel() + + decT := reflect.TypeOf((*bson.Decoder)(nil)) + ctxT := reflect.TypeOf(bson.DecodeContext{}) + for i := 0; i < decT.NumMethod(); i++ { + m := decT.Method(i) + // Test methods with no input/output parameter. + if m.Type.NumIn() != 1 || m.Type.NumOut() != 0 { + continue + } + t.Run(m.Name, func(t *testing.T) { + var opts options.BSONOptions + optsV := reflect.ValueOf(&opts).Elem() + f, ok := optsV.Type().FieldByName(m.Name) + require.True(t, ok, "expected %s field in %s", m.Name, optsV.Type()) + + wantDec := reflect.ValueOf(bson.NewDecoder(nil)) + _ = wantDec.Method(i).Call(nil) + wantCtx := wantDec.Elem().Field(0) + require.Equal(t, ctxT, wantCtx.Type()) + + optsV.FieldByIndex(f.Index).SetBool(true) + gotDec := getDecoder(nil, &opts, nil) + gotCtx := reflect.ValueOf(gotDec).Elem().Field(0) + require.Equal(t, ctxT, gotCtx.Type()) + + assert.True(t, gotCtx.Equal(wantCtx), "expected %v: %v, got: %v", ctxT, wantCtx, gotCtx) + }) + } +} + func BenchmarkNewCursorFromDocuments(b *testing.B) { // Prepare sample data documents := []interface{}{ diff --git a/mongo/mongo.go b/mongo/mongo.go index b22694d291..260576bd1e 100644 --- a/mongo/mongo.go +++ b/mongo/mongo.go @@ -60,7 +60,7 @@ func getEncoder( w io.Writer, opts *options.BSONOptions, reg *bson.Registry, -) (*bson.Encoder, error) { +) *bson.Encoder { vw := bson.NewDocumentWriter(w) enc := bson.NewEncoder(vw) @@ -95,13 +95,13 @@ func getEncoder( enc.SetRegistry(reg) } - return enc, nil + return enc } // newEncoderFn will return a function for constructing an encoder based on the // provided codec options. func newEncoderFn(opts *options.BSONOptions, registry *bson.Registry) codecutil.EncoderFn { - return func(w io.Writer) (*bson.Encoder, error) { + return func(w io.Writer) *bson.Encoder { return getEncoder(w, opts, registry) } } @@ -128,12 +128,8 @@ func marshal( } buf := new(bytes.Buffer) - enc, err := getEncoder(buf, bsonOpts, registry) - if err != nil { - return nil, fmt.Errorf("error configuring BSON encoder: %w", err) - } - - err = enc.Encode(val) + enc := getEncoder(buf, bsonOpts, registry) + err := enc.Encode(val) if err != nil { return nil, MarshalError{Value: val, Err: err} } diff --git a/mongo/mongo_test.go b/mongo/mongo_test.go index 08388933f2..96be905cb5 100644 --- a/mongo/mongo_test.go +++ b/mongo/mongo_test.go @@ -9,6 +9,7 @@ package mongo import ( "errors" "fmt" + "reflect" "testing" "go.mongodb.org/mongo-driver/v2/bson" @@ -608,6 +609,38 @@ func TestMarshalValue(t *testing.T) { } } +func TestGetEncoder(t *testing.T) { + t.Parallel() + + encT := reflect.TypeOf((*bson.Encoder)(nil)) + ctxT := reflect.TypeOf(bson.EncodeContext{}) + for i := 0; i < encT.NumMethod(); i++ { + m := encT.Method(i) + // Test methods with no input/output parameter. + if m.Type.NumIn() != 1 || m.Type.NumOut() != 0 { + continue + } + t.Run(m.Name, func(t *testing.T) { + var opts options.BSONOptions + optsV := reflect.ValueOf(&opts).Elem() + f, ok := optsV.Type().FieldByName(m.Name) + require.True(t, ok, "expected %s field in %s", m.Name, optsV.Type()) + + wantEnc := reflect.ValueOf(bson.NewEncoder(nil)) + _ = wantEnc.Method(i).Call(nil) + wantCtx := wantEnc.Elem().Field(0) + require.Equal(t, ctxT, wantCtx.Type()) + + optsV.FieldByIndex(f.Index).SetBool(true) + gotEnc := getEncoder(nil, &opts, nil) + gotCtx := reflect.ValueOf(gotEnc).Elem().Field(0) + require.Equal(t, ctxT, gotCtx.Type()) + + assert.True(t, gotCtx.Equal(wantCtx), "expected %v: %v, got: %v", ctxT, wantCtx, gotCtx) + }) + } +} + var _ bson.ValueMarshaler = bvMarsh{} type bvMarsh struct { diff --git a/mongo/options/clientoptions.go b/mongo/options/clientoptions.go index 9fe4e36c1f..22aa009463 100644 --- a/mongo/options/clientoptions.go +++ b/mongo/options/clientoptions.go @@ -205,6 +205,10 @@ type BSONOptions struct { // "interface{}" or "map[string]interface{}". DefaultDocumentM bool + // ObjectIDAsHexString causes the Decoder to decode object IDs to their hex + // representation. + ObjectIDAsHexString bool + // UseLocalTimeZone causes the driver to unmarshal time.Time values in the // local timezone instead of the UTC timezone. UseLocalTimeZone bool diff --git a/x/mongo/driver/batch_cursor.go b/x/mongo/driver/batch_cursor.go index ddf4913ef5..47abe3e5f2 100644 --- a/x/mongo/driver/batch_cursor.go +++ b/x/mongo/driver/batch_cursor.go @@ -163,7 +163,7 @@ type CursorOptions struct { CommandMonitor *event.CommandMonitor Crypt Crypt ServerAPI *ServerAPIOptions - MarshalValueEncoderFn func(io.Writer) (*bson.Encoder, error) + MarshalValueEncoderFn func(io.Writer) *bson.Encoder // MaxAwaitTime is only valid for tailable awaitData cursors. If this option // is set, it will be used as the "maxTimeMS" field on getMore commands.