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-2796 Update ObjectIDAsHexString behavior. #1694

Merged
merged 5 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 9 additions & 125 deletions bson/bsoncodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,10 @@ func (vde ValueDecoderError) Error() string {
type EncodeContext struct {
*Registry

// MinSize causes the Encoder to marshal Go integer values (int, int8, int16, int32, int64,
// minSize causes the Encoder to marshal Go integer values (int, int8, int16, int32, int64,
// uint, uint8, uint16, uint32, or uint64) as the minimum BSON int size (either 32 or 64 bits)
// that can represent the integer value.
//
// Deprecated: Use bson.Encoder.IntMinSize instead.
MinSize bool
minSize bool

errorOnInlineDuplicates bool
stringifyMapKeysWithFmt bool
Expand All @@ -93,148 +91,34 @@ type EncodeContext struct {
useJSONStructTags bool
}

// ErrorOnInlineDuplicates causes the Encoder to return an error if there is a duplicate field in
// the marshaled BSON when the "inline" struct tag option is set.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Encoder.ErrorOnInlineDuplicates] instead.
func (ec *EncodeContext) ErrorOnInlineDuplicates() {
ec.errorOnInlineDuplicates = true
}

// StringifyMapKeysWithFmt causes the Encoder to convert Go map keys to BSON document field name
// strings using fmt.Sprintf() instead of the default string conversion logic.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Encoder.StringifyMapKeysWithFmt] instead.
func (ec *EncodeContext) StringifyMapKeysWithFmt() {
ec.stringifyMapKeysWithFmt = true
}

// NilMapAsEmpty causes the Encoder to marshal nil Go maps as empty BSON documents instead of BSON
// null.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Encoder.NilMapAsEmpty] instead.
func (ec *EncodeContext) NilMapAsEmpty() {
ec.nilMapAsEmpty = true
}

// NilSliceAsEmpty causes the Encoder to marshal nil Go slices as empty BSON arrays instead of BSON
// null.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Encoder.NilSliceAsEmpty] instead.
func (ec *EncodeContext) NilSliceAsEmpty() {
ec.nilSliceAsEmpty = true
}

// NilByteSliceAsEmpty causes the Encoder to marshal nil Go byte slices as empty BSON binary values
// instead of BSON null.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Encoder.NilByteSliceAsEmpty] instead.
func (ec *EncodeContext) NilByteSliceAsEmpty() {
ec.nilByteSliceAsEmpty = true
}

// OmitZeroStruct causes the Encoder to consider the zero value for a struct (e.g. MyStruct{})
// as empty and omit it from the marshaled BSON when the "omitempty" struct tag option is set.
//
// Note that the Encoder only examines exported struct fields when determining if a struct is the
// zero value. It considers pointers to a zero struct value (e.g. &MyStruct{}) not empty.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Encoder.OmitZeroStruct] instead.
func (ec *EncodeContext) OmitZeroStruct() {
ec.omitZeroStruct = true
}

// UseJSONStructTags causes the Encoder to fall back to using the "json" struct tag if a "bson"
// struct tag is not specified.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Encoder.UseJSONStructTags] instead.
func (ec *EncodeContext) UseJSONStructTags() {
ec.useJSONStructTags = true
}

// DecodeContext is the contextual information required for a Codec to decode a
// value.
type DecodeContext struct {
*Registry

// Truncate, if true, instructs decoders to to truncate the fractional part of BSON "double"
// truncate, if true, instructs decoders to to truncate the fractional part of BSON "double"
// values when attempting to unmarshal them into a Go integer (int, int8, int16, int32, int64,
// uint, uint8, uint16, uint32, or uint64) struct field. The truncation logic does not apply to
// BSON "decimal128" values.
//
// Deprecated: Use bson.Decoder.AllowTruncatingDoubles instead.
Truncate bool
truncate bool

// defaultDocumentType specifies the Go type to decode top-level and nested BSON documents into. In particular, the
// usage for this field is restricted to data typed as "interface{}" or "map[string]interface{}". If DocumentType is
// set to a type that a BSON document cannot be unmarshaled into (e.g. "string"), unmarshalling will result in an
// error.
defaultDocumentType reflect.Type

binaryAsSlice bool
binaryAsSlice bool

// a false value results in a decoding error.
objectIDAsHexString bool

useJSONStructTags bool
useLocalTimeZone bool
zeroMaps bool
zeroStructs bool
}

// BinaryAsSlice causes the Decoder to unmarshal BSON binary field values that are the "Generic" or
// "Old" BSON binary subtype as a Go byte slice instead of a Binary.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Decoder.BinaryAsSlice] instead.
func (dc *DecodeContext) BinaryAsSlice() {
dc.binaryAsSlice = true
}

// UseJSONStructTags causes the Decoder to fall back to using the "json" struct tag if a "bson"
// struct tag is not specified.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Decoder.UseJSONStructTags] instead.
func (dc *DecodeContext) UseJSONStructTags() {
dc.useJSONStructTags = true
}

// UseLocalTimeZone causes the Decoder to unmarshal time.Time values in the local timezone instead
// of the UTC timezone.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Decoder.UseLocalTimeZone] instead.
func (dc *DecodeContext) UseLocalTimeZone() {
dc.useLocalTimeZone = true
}

// ZeroMaps causes the Decoder to delete any existing values from Go maps in the destination value
// passed to Decode before unmarshaling BSON documents into them.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Decoder.ZeroMaps] instead.
func (dc *DecodeContext) ZeroMaps() {
dc.zeroMaps = true
}

// ZeroStructs causes the Decoder to delete any existing values from Go structs in the destination
// value passed to Decode before unmarshaling BSON documents into them.
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Decoder.ZeroStructs] instead.
func (dc *DecodeContext) ZeroStructs() {
dc.zeroStructs = true
}

// DefaultDocumentM causes the Decoder to always unmarshal documents into the M type. This
// behavior is restricted to data typed as "interface{}" or "map[string]interface{}".
//
// Deprecated: Use [go.mongodb.org/mongo-driver/bson.Decoder.DefaultDocumentM] instead.
func (dc *DecodeContext) DefaultDocumentM() {
dc.defaultDocumentType = reflect.TypeOf(M{})
}

// ValueCodec is an interface for encoding and decoding a reflect.Value.
// values.
//
// Deprecated: Use [ValueEncoder] and [ValueDecoder] instead.
type ValueCodec interface {
ValueEncoder
ValueDecoder
}

// ValueEncoder is the interface implemented by types that can encode a provided Go type to BSON.
// The value to encode is provided as a reflect.Value and a bson.ValueWriter is used within the
// EncodeValue method to actually create the BSON representation. For convenience, ValueEncoderFunc
Expand Down
46 changes: 12 additions & 34 deletions bson/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ var decPool = sync.Pool{
type Decoder struct {
dc DecodeContext
vr ValueReader

defaultDocumentM bool

binaryAsSlice bool
useJSONStructTags bool
useLocalTimeZone bool
zeroMaps bool
zeroStructs bool
}

// NewDecoder returns a new decoder that uses the DefaultRegistry to read from vr.
Expand Down Expand Up @@ -81,25 +73,6 @@ func (d *Decoder) Decode(val interface{}) error {
return err
}

if d.defaultDocumentM {
d.dc.DefaultDocumentM()
}
if d.binaryAsSlice {
d.dc.BinaryAsSlice()
}
if d.useJSONStructTags {
d.dc.UseJSONStructTags()
}
if d.useLocalTimeZone {
d.dc.UseLocalTimeZone()
}
if d.zeroMaps {
d.dc.ZeroMaps()
}
if d.zeroStructs {
d.dc.ZeroStructs()
}

return decoder.DecodeValue(d.dc, d.vr, rval)
}

Expand All @@ -117,42 +90,47 @@ func (d *Decoder) SetRegistry(r *Registry) {
// DefaultDocumentM causes the Decoder to always unmarshal documents into the primitive.M type. This
// behavior is restricted to data typed as "interface{}" or "map[string]interface{}".
func (d *Decoder) DefaultDocumentM() {
d.defaultDocumentM = true
d.dc.defaultDocumentType = reflect.TypeOf(M{})
}

// AllowTruncatingDoubles causes the Decoder to truncate the fractional part of BSON "double" values
// when attempting to unmarshal them into a Go integer (int, int8, int16, int32, or int64) struct
// field. The truncation logic does not apply to BSON "decimal128" values.
func (d *Decoder) AllowTruncatingDoubles() {
d.dc.Truncate = true
d.dc.truncate = true
}

// BinaryAsSlice causes the Decoder to unmarshal BSON binary field values that are the "Generic" or
// "Old" BSON binary subtype as a Go byte slice instead of a primitive.Binary.
func (d *Decoder) BinaryAsSlice() {
d.binaryAsSlice = true
d.dc.binaryAsSlice = true
}

// ObjectIDAsHexString causes the Decoder to decode object IDs to their hex representation.
func (d *Decoder) ObjectIDAsHexString() {
d.dc.objectIDAsHexString = true
}

// UseJSONStructTags causes the Decoder to fall back to using the "json" struct tag if a "bson"
// struct tag is not specified.
func (d *Decoder) UseJSONStructTags() {
d.useJSONStructTags = true
d.dc.useJSONStructTags = true
}

// UseLocalTimeZone causes the Decoder to unmarshal time.Time values in the local timezone instead
// of the UTC timezone.
func (d *Decoder) UseLocalTimeZone() {
d.useLocalTimeZone = true
d.dc.useLocalTimeZone = true
}

// ZeroMaps causes the Decoder to delete any existing values from Go maps in the destination value
// passed to Decode before unmarshaling BSON documents into them.
func (d *Decoder) ZeroMaps() {
d.zeroMaps = true
d.dc.zeroMaps = true
}

// ZeroStructs causes the Decoder to delete any existing values from Go structs in the destination
// value passed to Decode before unmarshaling BSON documents into them.
func (d *Decoder) ZeroStructs() {
d.zeroStructs = true
d.dc.zeroStructs = true
}
39 changes: 39 additions & 0 deletions bson/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,10 @@ func TestDecoderConfiguration(t *testing.T) {
MyUint64 uint64
}

type objectIDTest struct {
ID string
}

type jsonStructTest struct {
StructFieldName string `json:"jsonFieldName"`
}
Expand Down Expand Up @@ -510,6 +514,21 @@ func TestDecoderConfiguration(t *testing.T) {
{Key: "myDocument", Value: M{"myString": "test value"}},
},
},
// Test that ObjectIDAsHexString causes the Decoder to decode object ID to hex.
{
description: "ObjectIDAsHexString",
configure: func(dec *Decoder) {
dec.ObjectIDAsHexString()
},
input: bsoncore.NewDocumentBuilder().
AppendObjectID("id", func() ObjectID {
id, _ := ObjectIDFromHex("5ef7fdd91c19e3222b41b839")
return id
}()).
Build(),
decodeInto: func() interface{} { return &objectIDTest{} },
want: &objectIDTest{ID: "5ef7fdd91c19e3222b41b839"},
},
// Test that UseJSONStructTags causes the Decoder to fall back to "json" struct tags if
// "bson" struct tags are not available.
{
Expand Down Expand Up @@ -588,6 +607,26 @@ func TestDecoderConfiguration(t *testing.T) {
})
}

t.Run("Decoding an object ID to string", func(t *testing.T) {
t.Parallel()

type objectIDTest struct {
ID string
}

doc := bsoncore.NewDocumentBuilder().
AppendObjectID("id", func() ObjectID {
id, _ := ObjectIDFromHex("5ef7fdd91c19e3222b41b839")
return id
}()).
Build()

dec := NewDecoder(NewValueReader(doc))

var got objectIDTest
err := dec.Decode(&got)
assert.EqualError(t, err, "error decoding key id: decoding an object ID to a non-hexadecimal string representation is not supported")
})
t.Run("DefaultDocumentM top-level", func(t *testing.T) {
t.Parallel()

Expand Down
4 changes: 2 additions & 2 deletions bson/default_value_decoders.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func intDecodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Va
if err != nil {
return emptyValue, err
}
if !dc.Truncate && math.Floor(f64) != f64 {
if !dc.truncate && math.Floor(f64) != f64 {
return emptyValue, errCannotTruncate
}
if f64 > float64(math.MaxInt64) {
Expand Down Expand Up @@ -368,7 +368,7 @@ func floatDecodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.

switch t.Kind() {
case reflect.Float32:
if !dc.Truncate && float64(float32(f)) != f {
if !dc.truncate && float64(float32(f)) != f {
return emptyValue, errCannotTruncate
}

Expand Down
8 changes: 4 additions & 4 deletions bson/default_value_decoders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func TestDefaultValueDecoders(t *testing.T) {
nil,
},
{
"ReadDouble (truncate)", int64(3), &DecodeContext{Truncate: true},
"ReadDouble (truncate)", int64(3), &DecodeContext{truncate: true},
&valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble,
nil,
},
Expand Down Expand Up @@ -419,7 +419,7 @@ func TestDefaultValueDecoders(t *testing.T) {
nil,
},
{
"ReadDouble (truncate)", uint64(3), &DecodeContext{Truncate: true},
"ReadDouble (truncate)", uint64(3), &DecodeContext{truncate: true},
&valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble,
nil,
},
Expand Down Expand Up @@ -670,7 +670,7 @@ func TestDefaultValueDecoders(t *testing.T) {
nil,
},
{
"float32/fast path (truncate)", float32(3.14), &DecodeContext{Truncate: true},
"float32/fast path (truncate)", float32(3.14), &DecodeContext{truncate: true},
&valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble,
nil,
},
Expand Down Expand Up @@ -708,7 +708,7 @@ func TestDefaultValueDecoders(t *testing.T) {
nil,
},
{
"float32/reflection path (truncate)", myfloat32(3.14), &DecodeContext{Truncate: true},
"float32/reflection path (truncate)", myfloat32(3.14), &DecodeContext{truncate: true},
&valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble,
nil,
},
Expand Down
2 changes: 1 addition & 1 deletion bson/default_value_encoders.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func intEncodeValue(ec EncodeContext, vw ValueWriter, val reflect.Value) error {
return vw.WriteInt64(i64)
case reflect.Int64:
i64 := val.Int()
if ec.MinSize && fitsIn32Bits(i64) {
if ec.minSize && fitsIn32Bits(i64) {
return vw.WriteInt32(int32(i64))
}
return vw.WriteInt64(i64)
Expand Down
Loading
Loading