bson/unmarshaling_cases_test.go
2025-03-17 20:58:26 +01:00

370 lines
9.9 KiB
Go

// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bytes"
"reflect"
)
type unmarshalingTestCase struct {
name string
sType reflect.Type
want interface{}
data []byte
}
func unmarshalingTestCases() []unmarshalingTestCase {
var zeroPtrStruct unmarshalerPtrStruct
{
i := myInt64(0)
m := myMap{}
b := myBytes{}
s := myString("")
zeroPtrStruct = unmarshalerPtrStruct{I: &i, M: &m, B: &b, S: &s}
}
var zeroNonPtrStruct unmarshalerNonPtrStruct
{
i := myInt64(0)
m := myMap{}
b := myBytes{}
s := myString("")
zeroNonPtrStruct = unmarshalerNonPtrStruct{I: i, M: m, B: b, S: s}
}
var valPtrStruct unmarshalerPtrStruct
{
i := myInt64(5)
m := myMap{"key": "value"}
b := myBytes{0x00, 0x01}
s := myString("test")
valPtrStruct = unmarshalerPtrStruct{I: &i, M: &m, B: &b, S: &s}
}
var valNonPtrStruct unmarshalerNonPtrStruct
{
i := myInt64(5)
m := myMap{"key": "value"}
b := myBytes{0x00, 0x01}
s := myString("test")
valNonPtrStruct = unmarshalerNonPtrStruct{I: i, M: m, B: b, S: s}
}
type fooBytes struct {
Foo []byte
}
return []unmarshalingTestCase{
{
name: "small struct",
sType: reflect.TypeOf(struct {
Foo bool
}{}),
want: &struct {
Foo bool
}{Foo: true},
data: docToBytes(D{{"foo", true}}),
},
{
name: "nested document",
sType: reflect.TypeOf(struct {
Foo struct {
Bar bool
}
}{}),
want: &struct {
Foo struct {
Bar bool
}
}{
Foo: struct {
Bar bool
}{Bar: true},
},
data: docToBytes(D{{"foo", D{{"bar", true}}}}),
},
{
name: "simple array",
sType: reflect.TypeOf(struct {
Foo []bool
}{}),
want: &struct {
Foo []bool
}{
Foo: []bool{true},
},
data: docToBytes(D{{"foo", A{true}}}),
},
{
name: "struct with mixed case fields",
sType: reflect.TypeOf(struct {
FooBar int32
}{}),
want: &struct {
FooBar int32
}{
FooBar: 10,
},
data: docToBytes(D{{"fooBar", int32(10)}}),
},
// GODRIVER-2252
// Test that a struct of pointer types with UnmarshalBSON functions defined marshal and
// unmarshal to the same Go values when the pointer values are "nil".
{
name: "nil pointer fields with UnmarshalBSON function should marshal and unmarshal to the same values",
sType: reflect.TypeOf(unmarshalerPtrStruct{}),
want: &unmarshalerPtrStruct{},
data: docToBytes(unmarshalerPtrStruct{}),
},
// GODRIVER-2252
// Test that a struct of pointer types with UnmarshalBSON functions defined marshal and
// unmarshal to the same Go values when the pointer values are the respective zero values.
{
name: "zero-value pointer fields with UnmarshalBSON function should marshal and unmarshal to the same values",
sType: reflect.TypeOf(unmarshalerPtrStruct{}),
want: &zeroPtrStruct,
data: docToBytes(zeroPtrStruct),
},
// GODRIVER-2252
// Test that a struct of pointer types with UnmarshalBSON functions defined marshal and
// unmarshal to the same Go values when the pointer values are non-zero values.
{
name: "non-zero-value pointer fields with UnmarshalBSON function should marshal and unmarshal to the same values",
sType: reflect.TypeOf(unmarshalerPtrStruct{}),
want: &valPtrStruct,
data: docToBytes(valPtrStruct),
},
// GODRIVER-2311
// Test that an unmarshaled struct that has a byte slice value does not reference the same
// underlying array as the input.
{
name: "struct with byte slice",
sType: reflect.TypeOf(fooBytes{}),
want: &fooBytes{
Foo: []byte{0, 1, 2, 3, 4, 5},
},
data: docToBytes(fooBytes{
Foo: []byte{0, 1, 2, 3, 4, 5},
}),
},
// GODRIVER-2427
// Test that a struct of non-pointer types with UnmarshalBSON functions defined for the pointer of the field
// will marshal and unmarshal to the same Go values when the non-pointer values are the respctive zero values.
{
name: `zero-value non-pointer fields with pointer UnmarshalBSON function should marshal and unmarshal to
the same values`,
sType: reflect.TypeOf(unmarshalerNonPtrStruct{}),
want: &zeroNonPtrStruct,
data: docToBytes(zeroNonPtrStruct),
},
// GODRIVER-2427
// Test that a struct of non-pointer types with UnmarshalBSON functions defined for the pointer of the field
// unmarshal to the same Go values when the non-pointer values are non-zero values.
{
name: `non-zero-value non-pointer fields with pointer UnmarshalBSON function should marshal and unmarshal
to the same values`,
sType: reflect.TypeOf(unmarshalerNonPtrStruct{}),
want: &valNonPtrStruct,
data: docToBytes(valNonPtrStruct),
},
{
name: "nil pointer and non-pointer type with literal null BSON",
sType: reflect.TypeOf(unmarshalBehaviorTestCase{}),
want: &unmarshalBehaviorTestCase{
BSONValueTracker: unmarshalBSONValueCallTracker{
called: true,
},
BSONValuePtrTracker: nil,
BSONTracker: unmarshalBSONCallTracker{
called: true,
},
BSONPtrTracker: nil,
},
data: docToBytes(D{
{Key: "bv_tracker", Value: nil},
{Key: "bv_ptr_tracker", Value: nil},
{Key: "b_tracker", Value: nil},
{Key: "b_ptr_tracker", Value: nil},
}),
},
{
name: "nil pointer and non-pointer type with BSON minkey",
sType: reflect.TypeOf(unmarshalBehaviorTestCase{}),
want: &unmarshalBehaviorTestCase{
BSONValueTracker: unmarshalBSONValueCallTracker{
called: true,
},
BSONValuePtrTracker: &unmarshalBSONValueCallTracker{
called: true,
},
BSONTracker: unmarshalBSONCallTracker{
called: true,
},
BSONPtrTracker: nil,
},
data: docToBytes(D{
{Key: "bv_tracker", Value: MinKey{}},
{Key: "bv_ptr_tracker", Value: MinKey{}},
{Key: "b_tracker", Value: MinKey{}},
{Key: "b_ptr_tracker", Value: MinKey{}},
}),
},
{
name: "nil pointer and non-pointer type with BSON maxkey",
sType: reflect.TypeOf(unmarshalBehaviorTestCase{}),
want: &unmarshalBehaviorTestCase{
BSONValueTracker: unmarshalBSONValueCallTracker{
called: true,
},
BSONValuePtrTracker: &unmarshalBSONValueCallTracker{
called: true,
},
BSONTracker: unmarshalBSONCallTracker{
called: true,
},
BSONPtrTracker: nil,
},
data: docToBytes(D{
{Key: "bv_tracker", Value: MaxKey{}},
{Key: "bv_ptr_tracker", Value: MaxKey{}},
{Key: "b_tracker", Value: MaxKey{}},
{Key: "b_ptr_tracker", Value: MaxKey{}},
}),
},
}
}
// unmarshalerPtrStruct contains a collection of fields that are all pointers to custom types that
// implement the bson.Unmarshaler interface. It is used to test the BSON unmarshal behavior for
// pointer types with custom UnmarshalBSON functions.
type unmarshalerPtrStruct struct {
I *myInt64
M *myMap
B *myBytes
S *myString
}
// unmarshalerNonPtrStruct contains a collection of non-pointer fields that are all to custom types that implement the
// bson.Unmarshaler interface. It is used to test the BSON unmarshal behavior for types with custom UnmarshalBSON
// functions.
type unmarshalerNonPtrStruct struct {
I myInt64
M myMap
B myBytes
S myString
}
type myInt64 int64
var _ ValueUnmarshaler = (*myInt64)(nil)
func (mi *myInt64) UnmarshalBSONValue(t byte, b []byte) error {
if len(b) == 0 {
return nil
}
if Type(t) == TypeInt64 {
i, err := newValueReader(TypeInt64, bytes.NewReader(b)).ReadInt64()
if err != nil {
return err
}
*mi = myInt64(i)
}
return nil
}
func (mi *myInt64) UnmarshalBSON(b []byte) error {
if len(b) == 0 {
return nil
}
i, err := newValueReader(TypeInt64, bytes.NewReader(b)).ReadInt64()
if err != nil {
return err
}
*mi = myInt64(i)
return nil
}
type myMap map[string]string
func (mm *myMap) UnmarshalBSON(bytes []byte) error {
if len(bytes) == 0 {
return nil
}
var m map[string]string
err := Unmarshal(bytes, &m)
*mm = myMap(m)
return err
}
type myBytes []byte
func (mb *myBytes) UnmarshalBSON(b []byte) error {
if len(b) == 0 {
return nil
}
b, _, err := newValueReader(TypeBinary, bytes.NewReader(b)).ReadBinary()
if err != nil {
return err
}
*mb = b
return nil
}
type myString string
func (ms *myString) UnmarshalBSON(b []byte) error {
if len(b) == 0 {
return nil
}
s, err := newValueReader(TypeString, bytes.NewReader(b)).ReadString()
if err != nil {
return err
}
*ms = myString(s)
return nil
}
// unmarshalBSONValueCallTracker is a test struct that tracks whether the
// UnmarshalBSONValue method has been called.
type unmarshalBSONValueCallTracker struct {
called bool // called is set to true when UnmarshalBSONValue is invoked.
}
var _ ValueUnmarshaler = &unmarshalBSONValueCallTracker{}
// unmarshalBSONCallTracker is a test struct that tracks whether the
// UnmarshalBSON method has been called.
type unmarshalBSONCallTracker struct {
called bool // called is set to true when UnmarshalBSON is invoked.
}
// Ensure unmarshalBSONCallTracker implements the Unmarshaler interface.
var _ Unmarshaler = &unmarshalBSONCallTracker{}
// unmarshalBehaviorTestCase holds instances of call trackers for testing BSON
// unmarshaling behavior.
type unmarshalBehaviorTestCase struct {
BSONValueTracker unmarshalBSONValueCallTracker `bson:"bv_tracker"` // BSON value unmarshaling by value.
BSONValuePtrTracker *unmarshalBSONValueCallTracker `bson:"bv_ptr_tracker"` // BSON value unmarshaling by pointer.
BSONTracker unmarshalBSONCallTracker `bson:"b_tracker"` // BSON unmarshaling by value.
BSONPtrTracker *unmarshalBSONCallTracker `bson:"b_ptr_tracker"` // BSON unmarshaling by pointer.
}
func (tracker *unmarshalBSONValueCallTracker) UnmarshalBSONValue(byte, []byte) error {
tracker.called = true
return nil
}
func (tracker *unmarshalBSONCallTracker) UnmarshalBSON([]byte) error {
tracker.called = true
return nil
}