goext/mongo/bson/primitive_codecs_test.go

1073 lines
26 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"
"encoding/json"
"errors"
"fmt"
"net/url"
"reflect"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
func bytesFromDoc(doc interface{}) []byte {
b, err := Marshal(doc)
if err != nil {
panic(fmt.Errorf("Couldn't marshal BSON document: %v", err))
}
return b
}
func compareDecimal128(d1, d2 primitive.Decimal128) bool {
d1H, d1L := d1.GetBytes()
d2H, d2L := d2.GetBytes()
if d1H != d2H {
return false
}
if d1L != d2L {
return false
}
return true
}
func compareErrors(err1, err2 error) bool {
if err1 == nil && err2 == nil {
return true
}
if err1 == nil || err2 == nil {
return false
}
if err1.Error() != err2.Error() {
return false
}
return true
}
func TestDefaultValueEncoders(t *testing.T) {
var pc PrimitiveCodecs
var wrong = func(string, string) string { return "wrong" }
type subtest struct {
name string
val interface{}
ectx *bsoncodec.EncodeContext
llvrw *bsonrwtest.ValueReaderWriter
invoke bsonrwtest.Invoked
err error
}
testCases := []struct {
name string
ve bsoncodec.ValueEncoder
subtests []subtest
}{
{
"RawValueEncodeValue",
bsoncodec.ValueEncoderFunc(pc.RawValueEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
bsoncodec.ValueEncoderError{
Name: "RawValueEncodeValue",
Types: []reflect.Type{tRawValue},
Received: reflect.ValueOf(wrong),
},
},
{
"RawValue/success",
RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
nil,
nil,
bsonrwtest.WriteDouble,
nil,
},
},
},
{
"RawEncodeValue",
bsoncodec.ValueEncoderFunc(pc.RawEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
bsoncodec.ValueEncoderError{Name: "RawEncodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
},
{
"WriteDocument Error",
Raw{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
bsonrwtest.WriteDocument,
errors.New("wd error"),
},
{
"Raw.Elements Error",
Raw{0xFF, 0x00, 0x00, 0x00, 0x00},
nil,
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteDocument,
errors.New("length read exceeds number of bytes available. length=5 bytes=255"),
},
{
"WriteDocumentElement Error",
Raw(bytesFromDoc(D{{"foo", nil}})),
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
bsonrwtest.WriteDocumentElement,
errors.New("wde error"),
},
{
"encodeValue error",
Raw(bytesFromDoc(D{{"foo", nil}})),
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteNull},
bsonrwtest.WriteNull,
errors.New("ev error"),
},
{
"iterator error",
Raw{0x0C, 0x00, 0x00, 0x00, 0x01, 'f', 'o', 'o', 0x00, 0x01, 0x02, 0x03},
nil,
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteDocumentElement,
errors.New("not enough bytes available to read type. bytes=3 type=double"),
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, subtest := range tc.subtests {
t.Run(subtest.name, func(t *testing.T) {
var ec bsoncodec.EncodeContext
if subtest.ectx != nil {
ec = *subtest.ectx
}
llvrw := new(bsonrwtest.ValueReaderWriter)
if subtest.llvrw != nil {
llvrw = subtest.llvrw
}
llvrw.T = t
err := tc.ve.EncodeValue(ec, llvrw, reflect.ValueOf(subtest.val))
if !compareErrors(err, subtest.err) {
t.Errorf("Errors do not match. got %v; want %v", err, subtest.err)
}
invoked := llvrw.Invoked
if !cmp.Equal(invoked, subtest.invoke) {
t.Errorf("Incorrect method invoked. got %v; want %v", invoked, subtest.invoke)
}
})
}
})
}
t.Run("success path", func(t *testing.T) {
oid := primitive.NewObjectID()
oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
var str = new(string)
*str = "bar"
now := time.Now().Truncate(time.Millisecond)
murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
if err != nil {
t.Errorf("Error parsing URL: %v", err)
t.FailNow()
}
decimal128, err := primitive.ParseDecimal128("1.5e10")
if err != nil {
t.Errorf("Error parsing decimal128: %v", err)
t.FailNow()
}
testCases := []struct {
name string
value interface{}
b []byte
err error
}{
{
"D to JavaScript",
D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}},
docToBytes(D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}}),
nil,
},
{
"D to Symbol",
D{{"a", primitive.Symbol("foobarbaz")}},
docToBytes(D{{"a", primitive.Symbol("foobarbaz")}}),
nil,
},
{
"struct{}",
struct {
A bool
B int32
C int64
D uint16
E uint64
F float64
G string
H map[string]string
I []byte
K [2]string
L struct {
M string
}
P Raw
Q primitive.ObjectID
T []struct{}
Y json.Number
Z time.Time
AA json.Number
AB *url.URL
AC primitive.Decimal128
AD *time.Time
AE testValueMarshaler
AF RawValue
AG *RawValue
AH D
AI *D
AJ *D
}{
A: true,
B: 123,
C: 456,
D: 789,
E: 101112,
F: 3.14159,
G: "Hello, world",
H: map[string]string{"foo": "bar"},
I: []byte{0x01, 0x02, 0x03},
K: [2]string{"baz", "qux"},
L: struct {
M string
}{
M: "foobar",
},
P: Raw{0x05, 0x00, 0x00, 0x00, 0x00},
Q: oid,
T: nil,
Y: json.Number("5"),
Z: now,
AA: json.Number("10.1"),
AB: murl,
AC: decimal128,
AD: &now,
AE: testValueMarshaler{t: TypeString, buf: bsoncore.AppendString(nil, "hello, world")},
AF: RawValue{Type: bsontype.String, Value: bsoncore.AppendString(nil, "hello, raw value")},
AG: &RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
AH: D{{"foo", "bar"}},
AI: &D{{"pi", 3.14159}},
AJ: nil,
},
docToBytes(D{
{"a", true},
{"b", int32(123)},
{"c", int64(456)},
{"d", int32(789)},
{"e", int64(101112)},
{"f", float64(3.14159)},
{"g", "Hello, world"},
{"h", D{{"foo", "bar"}}},
{"i", primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}},
{"k", A{"baz", "qux"}},
{"l", D{{"m", "foobar"}}},
{"p", D{}},
{"q", oid},
{"t", nil},
{"y", int64(5)},
{"z", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
{"aa", float64(10.1)},
{"ab", murl.String()},
{"ac", decimal128},
{"ad", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
{"ae", "hello, world"},
{"af", "hello, raw value"},
{"ag", 3.14159},
{"ah", D{{"foo", "bar"}}},
{"ai", D{{"pi", float64(3.14159)}}},
{"aj", nil},
}),
nil,
},
{
"struct{[]interface{}}",
struct {
A []bool
B []int32
C []int64
D []uint16
E []uint64
F []float64
G []string
H []map[string]string
I [][]byte
K [1][2]string
L []struct {
M string
}
N [][]string
Q []Raw
R []primitive.ObjectID
T []struct{}
W []map[string]struct{}
X []map[string]struct{}
Y []map[string]struct{}
Z []time.Time
AA []json.Number
AB []*url.URL
AC []primitive.Decimal128
AD []*time.Time
AE []testValueMarshaler
AF []D
AG []*D
}{
A: []bool{true},
B: []int32{123},
C: []int64{456},
D: []uint16{789},
E: []uint64{101112},
F: []float64{3.14159},
G: []string{"Hello, world"},
H: []map[string]string{{"foo": "bar"}},
I: [][]byte{{0x01, 0x02, 0x03}},
K: [1][2]string{{"baz", "qux"}},
L: []struct {
M string
}{
{
M: "foobar",
},
},
N: [][]string{{"foo", "bar"}},
Q: []Raw{{0x05, 0x00, 0x00, 0x00, 0x00}},
R: oids,
T: nil,
W: nil,
X: []map[string]struct{}{}, // Should be empty BSON Array
Y: []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
Z: []time.Time{now, now},
AA: []json.Number{json.Number("5"), json.Number("10.1")},
AB: []*url.URL{murl},
AC: []primitive.Decimal128{decimal128},
AD: []*time.Time{&now, &now},
AE: []testValueMarshaler{
{t: TypeString, buf: bsoncore.AppendString(nil, "hello")},
{t: TypeString, buf: bsoncore.AppendString(nil, "world")},
},
AF: []D{{{"foo", "bar"}}, {{"hello", "world"}, {"number", 12345}}},
AG: []*D{{{"pi", 3.14159}}, nil},
},
docToBytes(D{
{"a", A{true}},
{"b", A{int32(123)}},
{"c", A{int64(456)}},
{"d", A{int32(789)}},
{"e", A{int64(101112)}},
{"f", A{float64(3.14159)}},
{"g", A{"Hello, world"}},
{"h", A{D{{"foo", "bar"}}}},
{"i", A{primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}}},
{"k", A{A{"baz", "qux"}}},
{"l", A{D{{"m", "foobar"}}}},
{"n", A{A{"foo", "bar"}}},
{"q", A{D{}}},
{"r", A{oids[0], oids[1], oids[2]}},
{"t", nil},
{"w", nil},
{"x", A{}},
{"y", A{D{}}},
{"z", A{
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"aa", A{int64(5), float64(10.10)}},
{"ab", A{murl.String()}},
{"ac", A{decimal128}},
{"ad", A{
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"ae", A{"hello", "world"}},
{"af", A{D{{"foo", "bar"}}, D{{"hello", "world"}, {"number", int32(12345)}}}},
{"ag", A{D{{"pi", float64(3.14159)}}, nil}},
}),
nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b := make(bsonrw.SliceWriter, 0, 512)
vw, err := bsonrw.NewBSONValueWriter(&b)
noerr(t, err)
enc, err := NewEncoder(vw)
noerr(t, err)
err = enc.Encode(tc.value)
if err != tc.err {
t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err)
}
if diff := cmp.Diff([]byte(b), tc.b); diff != "" {
t.Errorf("Bytes written differ: (-got +want)\n%s", diff)
t.Errorf("Bytes\ngot: %v\nwant:%v\n", b, tc.b)
t.Errorf("Readers\ngot: %v\nwant:%v\n", Raw(b), Raw(tc.b))
}
})
}
})
}
func TestDefaultValueDecoders(t *testing.T) {
var pc PrimitiveCodecs
var wrong = func(string, string) string { return "wrong" }
const cansetreflectiontest = "cansetreflectiontest"
type subtest struct {
name string
val interface{}
dctx *bsoncodec.DecodeContext
llvrw *bsonrwtest.ValueReaderWriter
invoke bsonrwtest.Invoked
err error
}
testCases := []struct {
name string
vd bsoncodec.ValueDecoder
subtests []subtest
}{
{
"RawValueDecodeValue",
bsoncodec.ValueDecoderFunc(pc.RawValueDecodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.Nothing,
bsoncodec.ValueDecoderError{
Name: "RawValueDecodeValue",
Types: []reflect.Type{tRawValue},
Received: reflect.ValueOf(wrong),
},
},
{
"ReadValue Error",
RawValue{},
nil,
&bsonrwtest.ValueReaderWriter{
BSONType: bsontype.Binary,
Err: errors.New("rb error"),
ErrAfter: bsonrwtest.ReadBinary,
},
bsonrwtest.ReadBinary,
errors.New("rb error"),
},
{
"RawValue/success",
RawValue{Type: bsontype.Binary, Value: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
nil,
&bsonrwtest.ValueReaderWriter{
BSONType: bsontype.Binary,
Return: bsoncore.Value{
Type: bsontype.Binary,
Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
},
},
bsonrwtest.ReadBinary,
nil,
},
},
},
{
"RawDecodeValue",
bsoncodec.ValueDecoderFunc(pc.RawDecodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.Nothing,
bsoncodec.ValueDecoderError{Name: "RawDecodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
},
{
"*Raw is nil",
(*Raw)(nil),
nil,
nil,
bsonrwtest.Nothing,
bsoncodec.ValueDecoderError{
Name: "RawDecodeValue",
Types: []reflect.Type{tRaw},
Received: reflect.ValueOf((*Raw)(nil)),
},
},
{
"Copy error",
Raw{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadDocument},
bsonrwtest.ReadDocument,
errors.New("copy error"),
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for _, rc := range tc.subtests {
t.Run(rc.name, func(t *testing.T) {
var dc bsoncodec.DecodeContext
if rc.dctx != nil {
dc = *rc.dctx
}
llvrw := new(bsonrwtest.ValueReaderWriter)
if rc.llvrw != nil {
llvrw = rc.llvrw
}
llvrw.T = t
// var got interface{}
if rc.val == cansetreflectiontest { // We're doing a CanSet reflection test
err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
if !compareErrors(err, rc.err) {
t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
}
val := reflect.New(reflect.TypeOf(rc.val)).Elem()
err = tc.vd.DecodeValue(dc, llvrw, val)
if !compareErrors(err, rc.err) {
t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
}
return
}
var val reflect.Value
if rtype := reflect.TypeOf(rc.val); rtype != nil {
val = reflect.New(rtype).Elem()
}
want := rc.val
defer func() {
if err := recover(); err != nil {
fmt.Println(t.Name())
panic(err)
}
}()
err := tc.vd.DecodeValue(dc, llvrw, val)
if !compareErrors(err, rc.err) {
t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
}
invoked := llvrw.Invoked
if !cmp.Equal(invoked, rc.invoke) {
t.Errorf("Incorrect method invoked. got %v; want %v", invoked, rc.invoke)
}
var got interface{}
if val.IsValid() && val.CanInterface() {
got = val.Interface()
}
if rc.err == nil && !cmp.Equal(got, want) {
t.Errorf("Values do not match. got (%T)%v; want (%T)%v", got, got, want, want)
}
})
}
})
}
t.Run("success path", func(t *testing.T) {
oid := primitive.NewObjectID()
oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
var str = new(string)
*str = "bar"
now := time.Now().Truncate(time.Millisecond)
murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
if err != nil {
t.Errorf("Error parsing URL: %v", err)
t.FailNow()
}
decimal128, err := primitive.ParseDecimal128("1.5e10")
if err != nil {
t.Errorf("Error parsing decimal128: %v", err)
t.FailNow()
}
testCases := []struct {
name string
value interface{}
b []byte
err error
}{
{
"map[string]int",
map[string]int32{"foo": 1},
[]byte{
0x0E, 0x00, 0x00, 0x00,
0x10, 'f', 'o', 'o', 0x00,
0x01, 0x00, 0x00, 0x00,
0x00,
},
nil,
},
{
"map[string]primitive.ObjectID",
map[string]primitive.ObjectID{"foo": oid},
docToBytes(D{{"foo", oid}}),
nil,
},
{
"map[string]Reader",
map[string]Raw{"Z": {0x05, 0x00, 0x00, 0x00, 0x00}},
docToBytes(D{{"Z", Raw{0x05, 0x00, 0x00, 0x00, 0x00}}}),
nil,
},
{
"map[string][]int32",
map[string][]int32{"Z": {1, 2, 3}},
docToBytes(D{{"Z", A{int32(1), int32(2), int32(3)}}}),
nil,
},
{
"map[string][]primitive.ObjectID",
map[string][]primitive.ObjectID{"Z": oids},
docToBytes(D{{"Z", A{oids[0], oids[1], oids[2]}}}),
nil,
},
{
"map[string][]json.Number(int64)",
map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
docToBytes(D{{"Z", A{int64(5), int64(10)}}}),
nil,
},
{
"map[string][]json.Number(float64)",
map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
docToBytes(D{{"Z", A{int64(5), float64(10.1)}}}),
nil,
},
{
"map[string][]*url.URL",
map[string][]*url.URL{"Z": {murl}},
docToBytes(D{{"Z", A{murl.String()}}}),
nil,
},
{
"map[string][]primitive.Decimal128",
map[string][]primitive.Decimal128{"Z": {decimal128}},
docToBytes(D{{"Z", A{decimal128}}}),
nil,
},
{
"-",
struct {
A string `bson:"-"`
}{
A: "",
},
docToBytes(D{}),
nil,
},
{
"omitempty",
struct {
A string `bson:",omitempty"`
}{
A: "",
},
docToBytes(D{}),
nil,
},
{
"omitempty, empty time",
struct {
A time.Time `bson:",omitempty"`
}{
A: time.Time{},
},
docToBytes(D{}),
nil,
},
{
"no private fields",
noPrivateFields{a: "should be empty"},
docToBytes(D{}),
nil,
},
{
"minsize",
struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
docToBytes(D{{"a", int32(12345)}}),
nil,
},
{
"inline",
struct {
Foo struct {
A int64 `bson:",minsize"`
} `bson:",inline"`
}{
Foo: struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
},
docToBytes(D{{"a", int32(12345)}}),
nil,
},
{
"inline map",
struct {
Foo map[string]string `bson:",inline"`
}{
Foo: map[string]string{"foo": "bar"},
},
docToBytes(D{{"foo", "bar"}}),
nil,
},
{
"alternate name bson:name",
struct {
A string `bson:"foo"`
}{
A: "bar",
},
docToBytes(D{{"foo", "bar"}}),
nil,
},
{
"alternate name",
struct {
A string `bson:"foo"`
}{
A: "bar",
},
docToBytes(D{{"foo", "bar"}}),
nil,
},
{
"inline, omitempty",
struct {
A string
Foo zeroTest `bson:"omitempty,inline"`
}{
A: "bar",
Foo: zeroTest{true},
},
docToBytes(D{{"a", "bar"}}),
nil,
},
{
"JavaScript to D",
D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}},
docToBytes(D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}}),
nil,
},
{
"Symbol to D",
D{{"a", primitive.Symbol("foobarbaz")}},
docToBytes(D{{"a", primitive.Symbol("foobarbaz")}}),
nil,
},
{
"struct{}",
struct {
A bool
B int32
C int64
D uint16
E uint64
F float64
G string
H map[string]string
I []byte
K [2]string
L struct {
M string
}
P Raw
Q primitive.ObjectID
T []struct{}
Y json.Number
Z time.Time
AA json.Number
AB *url.URL
AC primitive.Decimal128
AD *time.Time
AE *testValueUnmarshaler
AF RawValue
AG *RawValue
AH D
AI *D
AJ *D
}{
A: true,
B: 123,
C: 456,
D: 789,
E: 101112,
F: 3.14159,
G: "Hello, world",
H: map[string]string{"foo": "bar"},
I: []byte{0x01, 0x02, 0x03},
K: [2]string{"baz", "qux"},
L: struct {
M string
}{
M: "foobar",
},
P: Raw{0x05, 0x00, 0x00, 0x00, 0x00},
Q: oid,
T: nil,
Y: json.Number("5"),
Z: now,
AA: json.Number("10.1"),
AB: murl,
AC: decimal128,
AD: &now,
AE: &testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world!")},
AF: RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
AG: &RawValue{Type: bsontype.Binary, Value: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
AH: D{{"foo", "bar"}},
AI: &D{{"pi", 3.14159}},
AJ: nil,
},
docToBytes(D{
{"a", true},
{"b", int32(123)},
{"c", int64(456)},
{"d", int32(789)},
{"e", int64(101112)},
{"f", float64(3.14159)},
{"g", "Hello, world"},
{"h", D{{"foo", "bar"}}},
{"i", primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}},
{"k", A{"baz", "qux"}},
{"l", D{{"m", "foobar"}}},
{"p", D{}},
{"q", oid},
{"t", nil},
{"y", int64(5)},
{"z", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
{"aa", float64(10.1)},
{"ab", murl.String()},
{"ac", decimal128},
{"ad", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
{"ae", "hello, world!"},
{"af", float64(3.14159)},
{"ag", primitive.Binary{Subtype: 0xFF, Data: []byte{0x01, 0x02, 0x03}}},
{"ah", D{{"foo", "bar"}}},
{"ai", D{{"pi", float64(3.14159)}}},
{"aj", nil},
}),
nil,
},
{
"struct{[]interface{}}",
struct {
A []bool
B []int32
C []int64
D []uint16
E []uint64
F []float64
G []string
H []map[string]string
I [][]byte
K [1][2]string
L []struct {
M string
}
N [][]string
Q []Raw
R []primitive.ObjectID
T []struct{}
W []map[string]struct{}
X []map[string]struct{}
Y []map[string]struct{}
Z []time.Time
AA []json.Number
AB []*url.URL
AC []primitive.Decimal128
AD []*time.Time
AE []*testValueUnmarshaler
AF []D
AG []*D
}{
A: []bool{true},
B: []int32{123},
C: []int64{456},
D: []uint16{789},
E: []uint64{101112},
F: []float64{3.14159},
G: []string{"Hello, world"},
H: []map[string]string{{"foo": "bar"}},
I: [][]byte{{0x01, 0x02, 0x03}},
K: [1][2]string{{"baz", "qux"}},
L: []struct {
M string
}{
{
M: "foobar",
},
},
N: [][]string{{"foo", "bar"}},
Q: []Raw{{0x05, 0x00, 0x00, 0x00, 0x00}},
R: oids,
T: nil,
W: nil,
X: []map[string]struct{}{}, // Should be empty BSON Array
Y: []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
Z: []time.Time{now, now},
AA: []json.Number{json.Number("5"), json.Number("10.1")},
AB: []*url.URL{murl},
AC: []primitive.Decimal128{decimal128},
AD: []*time.Time{&now, &now},
AE: []*testValueUnmarshaler{
{t: bsontype.String, val: bsoncore.AppendString(nil, "hello")},
{t: bsontype.String, val: bsoncore.AppendString(nil, "world")},
},
AF: []D{{{"foo", "bar"}}, {{"hello", "world"}, {"number", int64(12345)}}},
AG: []*D{{{"pi", 3.14159}}, nil},
},
docToBytes(D{
{"a", A{true}},
{"b", A{int32(123)}},
{"c", A{int64(456)}},
{"d", A{int32(789)}},
{"e", A{int64(101112)}},
{"f", A{float64(3.14159)}},
{"g", A{"Hello, world"}},
{"h", A{D{{"foo", "bar"}}}},
{"i", A{primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}}},
{"k", A{A{"baz", "qux"}}},
{"l", A{D{{"m", "foobar"}}}},
{"n", A{A{"foo", "bar"}}},
{"q", A{D{}}},
{"r", A{oids[0], oids[1], oids[2]}},
{"t", nil},
{"w", nil},
{"x", A{}},
{"y", A{D{}}},
{"z", A{
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"aa", A{int64(5), float64(10.10)}},
{"ab", A{murl.String()}},
{"ac", A{decimal128}},
{"ad", A{
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
}},
{"ae", A{"hello", "world"}},
{"af", A{
D{{"foo", "bar"}},
D{{"hello", "world"}, {"number", int64(12345)}},
}},
{"ag", A{D{{"pi", float64(3.14159)}}, nil}},
}),
nil,
},
}
t.Run("Decode", func(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
vr := bsonrw.NewBSONDocumentReader(tc.b)
dec, err := NewDecoder(vr)
noerr(t, err)
gotVal := reflect.New(reflect.TypeOf(tc.value))
err = dec.Decode(gotVal.Interface())
noerr(t, err)
got := gotVal.Elem().Interface()
want := tc.value
if diff := cmp.Diff(
got, want,
cmp.Comparer(compareDecimal128),
cmp.Comparer(compareNoPrivateFields),
cmp.Comparer(compareZeroTest),
); diff != "" {
t.Errorf("difference:\n%s", diff)
t.Errorf("Values are not equal.\ngot: %#v\nwant:%#v", got, want)
}
})
}
})
})
}
type testValueMarshaler struct {
t bsontype.Type
buf []byte
err error
}
func (tvm testValueMarshaler) MarshalBSONValue() (bsontype.Type, []byte, error) {
return tvm.t, tvm.buf, tvm.err
}
type testValueUnmarshaler struct {
t bsontype.Type
val []byte
err error
}
func (tvu *testValueUnmarshaler) UnmarshalBSONValue(t bsontype.Type, val []byte) error {
tvu.t, tvu.val = t, val
return tvu.err
}
func (tvu testValueUnmarshaler) Equal(tvu2 testValueUnmarshaler) bool {
return tvu.t == tvu2.t && bytes.Equal(tvu.val, tvu2.val)
}
type noPrivateFields struct {
a string
}
func compareNoPrivateFields(npf1, npf2 noPrivateFields) bool {
return npf1.a != npf2.a // We don't want these to be equal
}
type zeroTest struct {
reportZero bool
}
func (z zeroTest) IsZero() bool { return z.reportZero }
func compareZeroTest(_, _ zeroTest) bool { return true }