goext/mongo/bson/bsoncodec/default_value_encoders_test.go

1910 lines
53 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 bsoncodec
import (
"encoding/json"
"errors"
"fmt"
"math"
"net/url"
"reflect"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"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"
)
type myInterface interface {
Foo() int
}
type myStruct struct {
Val int
}
func (ms myStruct) Foo() int {
return ms.Val
}
func TestDefaultValueEncoders(t *testing.T) {
var dve DefaultValueEncoders
var wrong = func(string, string) string { return "wrong" }
type mybool bool
type myint8 int8
type myint16 int16
type myint32 int32
type myint64 int64
type myint int
type myuint8 uint8
type myuint16 uint16
type myuint32 uint32
type myuint64 uint64
type myuint uint
type myfloat32 float32
type myfloat64 float64
now := time.Now().Truncate(time.Millisecond)
pjsnum := new(json.Number)
*pjsnum = json.Number("3.14159")
d128 := primitive.NewDecimal128(12345, 67890)
var nilValueMarshaler *testValueMarshaler
var nilMarshaler *testMarshaler
var nilProxy *testProxy
vmStruct := struct{ V testValueMarshalPtr }{testValueMarshalPtr{t: bsontype.String, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}}}
mStruct := struct{ V testMarshalPtr }{testMarshalPtr{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))}}
pStruct := struct{ V testProxyPtr }{testProxyPtr{ret: int64(1234567890)}}
type subtest struct {
name string
val interface{}
ectx *EncodeContext
llvrw *bsonrwtest.ValueReaderWriter
invoke bsonrwtest.Invoked
err error
}
testCases := []struct {
name string
ve ValueEncoder
subtests []subtest
}{
{
"BooleanEncodeValue",
ValueEncoderFunc(dve.BooleanEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "BooleanEncodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.ValueOf(wrong)},
},
{"fast path", bool(true), nil, nil, bsonrwtest.WriteBoolean, nil},
{"reflection path", mybool(true), nil, nil, bsonrwtest.WriteBoolean, nil},
},
},
{
"IntEncodeValue",
ValueEncoderFunc(dve.IntEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "IntEncodeValue",
Kinds: []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
Received: reflect.ValueOf(wrong),
},
},
{"int8/fast path", int8(127), nil, nil, bsonrwtest.WriteInt32, nil},
{"int16/fast path", int16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
{"int32/fast path", int32(2147483647), nil, nil, bsonrwtest.WriteInt32, nil},
{"int64/fast path", int64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
{"int64/fast path - minsize", int64(math.MaxInt32), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"int64/fast path - minsize too large", int64(math.MaxInt32 + 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"int64/fast path - minsize too small", int64(math.MinInt32 - 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"int/fast path - positive int32", int(math.MaxInt32 - 1), nil, nil, bsonrwtest.WriteInt32, nil},
{"int/fast path - negative int32", int(math.MinInt32 + 1), nil, nil, bsonrwtest.WriteInt32, nil},
{"int/fast path - MaxInt32", int(math.MaxInt32), nil, nil, bsonrwtest.WriteInt32, nil},
{"int/fast path - MinInt32", int(math.MinInt32), nil, nil, bsonrwtest.WriteInt32, nil},
{"int8/reflection path", myint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
{"int16/reflection path", myint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
{"int32/reflection path", myint32(2147483647), nil, nil, bsonrwtest.WriteInt32, nil},
{"int64/reflection path", myint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
{"int64/reflection path - minsize", myint64(math.MaxInt32), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"int64/reflection path - minsize too large", myint64(math.MaxInt32 + 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"int64/reflection path - minsize too small", myint64(math.MinInt32 - 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"int/reflection path - positive int32", myint(math.MaxInt32 - 1), nil, nil, bsonrwtest.WriteInt32, nil},
{"int/reflection path - negative int32", myint(math.MinInt32 + 1), nil, nil, bsonrwtest.WriteInt32, nil},
{"int/reflection path - MaxInt32", myint(math.MaxInt32), nil, nil, bsonrwtest.WriteInt32, nil},
{"int/reflection path - MinInt32", myint(math.MinInt32), nil, nil, bsonrwtest.WriteInt32, nil},
},
},
{
"UintEncodeValue",
defaultUIntCodec,
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "UintEncodeValue",
Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
Received: reflect.ValueOf(wrong),
},
},
{"uint8/fast path", uint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
{"uint16/fast path", uint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
{"uint32/fast path", uint32(2147483647), nil, nil, bsonrwtest.WriteInt64, nil},
{"uint64/fast path", uint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
{"uint/fast path", uint(1234567), nil, nil, bsonrwtest.WriteInt64, nil},
{"uint32/fast path - minsize", uint32(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"uint64/fast path - minsize", uint64(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"uint/fast path - minsize", uint(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"uint32/fast path - minsize too large", uint32(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"uint64/fast path - minsize too large", uint64(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"uint/fast path - minsize too large", uint(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"uint64/fast path - overflow", uint64(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint64(1<<63))},
{"uint8/reflection path", myuint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
{"uint16/reflection path", myuint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
{"uint32/reflection path", myuint32(2147483647), nil, nil, bsonrwtest.WriteInt64, nil},
{"uint64/reflection path", myuint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
{"uint32/reflection path - minsize", myuint32(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"uint64/reflection path - minsize", myuint64(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"uint/reflection path - minsize", myuint(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
{"uint32/reflection path - minsize too large", myuint(1 << 31), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"uint64/reflection path - minsize too large", myuint64(1 << 31), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"uint/reflection path - minsize too large", myuint(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
{"uint64/reflection path - overflow", myuint64(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint64(1<<63))},
},
},
{
"FloatEncodeValue",
ValueEncoderFunc(dve.FloatEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "FloatEncodeValue",
Kinds: []reflect.Kind{reflect.Float32, reflect.Float64},
Received: reflect.ValueOf(wrong),
},
},
{"float32/fast path", float32(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
{"float64/fast path", float64(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
{"float32/reflection path", myfloat32(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
{"float64/reflection path", myfloat64(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
},
},
{
"TimeEncodeValue",
defaultTimeCodec,
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "TimeEncodeValue", Types: []reflect.Type{tTime}, Received: reflect.ValueOf(wrong)},
},
{"time.Time", now, nil, nil, bsonrwtest.WriteDateTime, nil},
},
},
{
"MapEncodeValue",
defaultMapCodec,
[]subtest{
{
"wrong kind",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "MapEncodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: reflect.ValueOf(wrong)},
},
{
"WriteDocument Error",
map[string]interface{}{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
bsonrwtest.WriteDocument,
errors.New("wd error"),
},
{
"Lookup Error",
map[string]int{"foo": 1},
&EncodeContext{Registry: NewRegistryBuilder().Build()},
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteDocument,
fmt.Errorf("no encoder found for int"),
},
{
"WriteDocumentElement Error",
map[string]interface{}{"foo": "bar"},
&EncodeContext{Registry: buildDefaultRegistry()},
&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
bsonrwtest.WriteDocumentElement,
errors.New("wde error"),
},
{
"EncodeValue Error",
map[string]interface{}{"foo": "bar"},
&EncodeContext{Registry: buildDefaultRegistry()},
&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
bsonrwtest.WriteString,
errors.New("ev error"),
},
{
"empty map/success",
map[string]interface{}{},
&EncodeContext{Registry: NewRegistryBuilder().Build()},
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"with interface/success",
map[string]myInterface{"foo": myStruct{1}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"with interface/nil/success",
map[string]myInterface{"foo": nil},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"non-string key success",
map[int]interface{}{
1: "foobar",
},
&EncodeContext{Registry: buildDefaultRegistry()},
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteDocumentEnd,
nil,
},
},
},
{
"ArrayEncodeValue",
ValueEncoderFunc(dve.ArrayEncodeValue),
[]subtest{
{
"wrong kind",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "ArrayEncodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: reflect.ValueOf(wrong)},
},
{
"WriteArray Error",
[1]string{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wa error"), ErrAfter: bsonrwtest.WriteArray},
bsonrwtest.WriteArray,
errors.New("wa error"),
},
{
"Lookup Error",
[1]int{1},
&EncodeContext{Registry: NewRegistryBuilder().Build()},
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteArray,
fmt.Errorf("no encoder found for int"),
},
{
"WriteArrayElement Error",
[1]string{"foo"},
&EncodeContext{Registry: buildDefaultRegistry()},
&bsonrwtest.ValueReaderWriter{Err: errors.New("wae error"), ErrAfter: bsonrwtest.WriteArrayElement},
bsonrwtest.WriteArrayElement,
errors.New("wae error"),
},
{
"EncodeValue Error",
[1]string{"foo"},
&EncodeContext{Registry: buildDefaultRegistry()},
&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
bsonrwtest.WriteString,
errors.New("ev error"),
},
{
"[1]primitive.E/success",
[1]primitive.E{{"hello", "world"}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"[1]primitive.E/success",
[1]primitive.E{{"hello", nil}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"[1]interface/success",
[1]myInterface{myStruct{1}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteArrayEnd,
nil,
},
{
"[1]interface/nil/success",
[1]myInterface{nil},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteArrayEnd,
nil,
},
},
},
{
"SliceEncodeValue",
defaultSliceCodec,
[]subtest{
{
"wrong kind",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "SliceEncodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(wrong)},
},
{
"WriteArray Error",
[]string{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wa error"), ErrAfter: bsonrwtest.WriteArray},
bsonrwtest.WriteArray,
errors.New("wa error"),
},
{
"Lookup Error",
[]int{1},
&EncodeContext{Registry: NewRegistryBuilder().Build()},
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteArray,
fmt.Errorf("no encoder found for int"),
},
{
"WriteArrayElement Error",
[]string{"foo"},
&EncodeContext{Registry: buildDefaultRegistry()},
&bsonrwtest.ValueReaderWriter{Err: errors.New("wae error"), ErrAfter: bsonrwtest.WriteArrayElement},
bsonrwtest.WriteArrayElement,
errors.New("wae error"),
},
{
"EncodeValue Error",
[]string{"foo"},
&EncodeContext{Registry: buildDefaultRegistry()},
&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
bsonrwtest.WriteString,
errors.New("ev error"),
},
{
"D/success",
primitive.D{{"hello", "world"}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"D/success",
primitive.D{{"hello", nil}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"empty slice/success",
[]interface{}{},
&EncodeContext{Registry: NewRegistryBuilder().Build()},
&bsonrwtest.ValueReaderWriter{},
bsonrwtest.WriteArrayEnd,
nil,
},
{
"interface/success",
[]myInterface{myStruct{1}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteArrayEnd,
nil,
},
{
"interface/success",
[]myInterface{nil},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteArrayEnd,
nil,
},
},
},
{
"ObjectIDEncodeValue",
ValueEncoderFunc(dve.ObjectIDEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "ObjectIDEncodeValue", Types: []reflect.Type{tOID}, Received: reflect.ValueOf(wrong)},
},
{
"primitive.ObjectID/success",
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
nil, nil, bsonrwtest.WriteObjectID, nil,
},
},
},
{
"Decimal128EncodeValue",
ValueEncoderFunc(dve.Decimal128EncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "Decimal128EncodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.ValueOf(wrong)},
},
{"Decimal128/success", d128, nil, nil, bsonrwtest.WriteDecimal128, nil},
},
},
{
"JSONNumberEncodeValue",
ValueEncoderFunc(dve.JSONNumberEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "JSONNumberEncodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.ValueOf(wrong)},
},
{
"json.Number/invalid",
json.Number("hello world"),
nil, nil, bsonrwtest.Nothing, errors.New(`strconv.ParseFloat: parsing "hello world": invalid syntax`),
},
{
"json.Number/int64/success",
json.Number("1234567890"),
nil, nil, bsonrwtest.WriteInt64, nil,
},
{
"json.Number/float64/success",
json.Number("3.14159"),
nil, nil, bsonrwtest.WriteDouble, nil,
},
},
},
{
"URLEncodeValue",
ValueEncoderFunc(dve.URLEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "URLEncodeValue", Types: []reflect.Type{tURL}, Received: reflect.ValueOf(wrong)},
},
{"url.URL", url.URL{Scheme: "http", Host: "example.com"}, nil, nil, bsonrwtest.WriteString, nil},
},
},
{
"ByteSliceEncodeValue",
defaultByteSliceCodec,
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.ValueOf(wrong)},
},
{"[]byte", []byte{0x01, 0x02, 0x03}, nil, nil, bsonrwtest.WriteBinary, nil},
{"[]byte/nil", []byte(nil), nil, nil, bsonrwtest.WriteNull, nil},
},
},
{
"EmptyInterfaceEncodeValue",
defaultEmptyInterfaceCodec,
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(wrong)},
},
},
},
{
"ValueMarshalerEncodeValue",
ValueEncoderFunc(dve.ValueMarshalerEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "ValueMarshalerEncodeValue",
Types: []reflect.Type{tValueMarshaler},
Received: reflect.ValueOf(wrong),
},
},
{
"MarshalBSONValue error",
testValueMarshaler{err: errors.New("mbsonv error")},
nil,
nil,
bsonrwtest.Nothing,
errors.New("mbsonv error"),
},
{
"Copy error",
testValueMarshaler{},
nil,
nil,
bsonrwtest.Nothing,
fmt.Errorf("Cannot copy unknown BSON type %s", bsontype.Type(0)),
},
{
"success struct implementation",
testValueMarshaler{t: bsontype.String, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}},
nil,
nil,
bsonrwtest.WriteString,
nil,
},
{
"success ptr to struct implementation",
&testValueMarshaler{t: bsontype.String, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}},
nil,
nil,
bsonrwtest.WriteString,
nil,
},
{
"success nil ptr to struct implementation",
nilValueMarshaler,
nil,
nil,
bsonrwtest.WriteNull,
nil,
},
{
"success ptr to ptr implementation",
&testValueMarshalPtr{t: bsontype.String, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}},
nil,
nil,
bsonrwtest.WriteString,
nil,
},
{
"unaddressable ptr implementation",
testValueMarshalPtr{t: bsontype.String, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}},
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "ValueMarshalerEncodeValue",
Types: []reflect.Type{tValueMarshaler},
Received: reflect.ValueOf(testValueMarshalPtr{}),
},
},
},
},
{
"MarshalerEncodeValue",
ValueEncoderFunc(dve.MarshalerEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: reflect.ValueOf(wrong)},
},
{
"MarshalBSON error",
testMarshaler{err: errors.New("mbson error")},
nil,
nil,
bsonrwtest.Nothing,
errors.New("mbson error"),
},
{
"success struct implementation",
testMarshaler{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))},
nil,
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"success ptr to struct implementation",
&testMarshaler{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))},
nil,
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"success nil ptr to struct implementation",
nilMarshaler,
nil,
nil,
bsonrwtest.WriteNull,
nil,
},
{
"success ptr to ptr implementation",
&testMarshalPtr{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))},
nil,
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"unaddressable ptr implementation",
testMarshalPtr{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))},
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: reflect.ValueOf(testMarshalPtr{})},
},
},
},
{
"ProxyEncodeValue",
ValueEncoderFunc(dve.ProxyEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "ProxyEncodeValue", Types: []reflect.Type{tProxy}, Received: reflect.ValueOf(wrong)},
},
{
"Proxy error",
testProxy{err: errors.New("proxy error")},
nil,
nil,
bsonrwtest.Nothing,
errors.New("proxy error"),
},
{
"Lookup error",
testProxy{ret: nil},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.Nothing,
ErrNoEncoder{Type: nil},
},
{
"success struct implementation",
testProxy{ret: int64(1234567890)},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteInt64,
nil,
},
{
"success ptr to struct implementation",
&testProxy{ret: int64(1234567890)},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteInt64,
nil,
},
{
"success nil ptr to struct implementation",
nilProxy,
nil,
nil,
bsonrwtest.WriteNull,
nil,
},
{
"success ptr to ptr implementation",
&testProxyPtr{ret: int64(1234567890)},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteInt64,
nil,
},
{
"unaddressable ptr implementation",
testProxyPtr{ret: int64(1234567890)},
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "ProxyEncodeValue", Types: []reflect.Type{tProxy}, Received: reflect.ValueOf(testProxyPtr{})},
},
},
},
{
"PointerCodec.EncodeValue",
NewPointerCodec(),
[]subtest{
{
"nil",
nil,
nil,
nil,
bsonrwtest.WriteNull,
nil,
},
{
"not pointer",
int32(123456),
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "PointerCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: reflect.ValueOf(int32(123456))},
},
{
"typed nil",
(*int32)(nil),
nil,
nil,
bsonrwtest.WriteNull,
nil,
},
{
"no encoder",
&wrong,
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.Nothing,
ErrNoEncoder{Type: reflect.TypeOf(wrong)},
},
},
},
{
"pointer implementation addressable interface",
NewPointerCodec(),
[]subtest{
{
"ValueMarshaler",
&vmStruct,
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"Marshaler",
&mStruct,
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"Proxy",
&pStruct,
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
},
},
{
"JavaScriptEncodeValue",
ValueEncoderFunc(dve.JavaScriptEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "JavaScriptEncodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.ValueOf(wrong)},
},
{"JavaScript", primitive.JavaScript("foobar"), nil, nil, bsonrwtest.WriteJavascript, nil},
},
},
{
"SymbolEncodeValue",
ValueEncoderFunc(dve.SymbolEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "SymbolEncodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.ValueOf(wrong)},
},
{"Symbol", primitive.Symbol("foobar"), nil, nil, bsonrwtest.WriteSymbol, nil},
},
},
{
"BinaryEncodeValue",
ValueEncoderFunc(dve.BinaryEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "BinaryEncodeValue", Types: []reflect.Type{tBinary}, Received: reflect.ValueOf(wrong)},
},
{"Binary/success", primitive.Binary{Data: []byte{0x01, 0x02}, Subtype: 0xFF}, nil, nil, bsonrwtest.WriteBinaryWithSubtype, nil},
},
},
{
"UndefinedEncodeValue",
ValueEncoderFunc(dve.UndefinedEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "UndefinedEncodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.ValueOf(wrong)},
},
{"Undefined/success", primitive.Undefined{}, nil, nil, bsonrwtest.WriteUndefined, nil},
},
},
{
"DateTimeEncodeValue",
ValueEncoderFunc(dve.DateTimeEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.ValueOf(wrong)},
},
{"DateTime/success", primitive.DateTime(1234567890), nil, nil, bsonrwtest.WriteDateTime, nil},
},
},
{
"NullEncodeValue",
ValueEncoderFunc(dve.NullEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "NullEncodeValue", Types: []reflect.Type{tNull}, Received: reflect.ValueOf(wrong)},
},
{"Null/success", primitive.Null{}, nil, nil, bsonrwtest.WriteNull, nil},
},
},
{
"RegexEncodeValue",
ValueEncoderFunc(dve.RegexEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "RegexEncodeValue", Types: []reflect.Type{tRegex}, Received: reflect.ValueOf(wrong)},
},
{"Regex/success", primitive.Regex{Pattern: "foo", Options: "bar"}, nil, nil, bsonrwtest.WriteRegex, nil},
},
},
{
"DBPointerEncodeValue",
ValueEncoderFunc(dve.DBPointerEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "DBPointerEncodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.ValueOf(wrong)},
},
{
"DBPointer/success",
primitive.DBPointer{
DB: "foobar",
Pointer: primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
},
nil, nil, bsonrwtest.WriteDBPointer, nil,
},
},
},
{
"TimestampEncodeValue",
ValueEncoderFunc(dve.TimestampEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "TimestampEncodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.ValueOf(wrong)},
},
{"Timestamp/success", primitive.Timestamp{T: 12345, I: 67890}, nil, nil, bsonrwtest.WriteTimestamp, nil},
},
},
{
"MinKeyEncodeValue",
ValueEncoderFunc(dve.MinKeyEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "MinKeyEncodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.ValueOf(wrong)},
},
{"MinKey/success", primitive.MinKey{}, nil, nil, bsonrwtest.WriteMinKey, nil},
},
},
{
"MaxKeyEncodeValue",
ValueEncoderFunc(dve.MaxKeyEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{Name: "MaxKeyEncodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.ValueOf(wrong)},
},
{"MaxKey/success", primitive.MaxKey{}, nil, nil, bsonrwtest.WriteMaxKey, nil},
},
},
{
"CoreDocumentEncodeValue",
ValueEncoderFunc(dve.CoreDocumentEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "CoreDocumentEncodeValue",
Types: []reflect.Type{tCoreDocument},
Received: reflect.ValueOf(wrong),
},
},
{
"WriteDocument Error",
bsoncore.Document{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
bsonrwtest.WriteDocument,
errors.New("wd error"),
},
{
"bsoncore.Document.Elements Error",
bsoncore.Document{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",
bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))),
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
bsonrwtest.WriteDocumentElement,
errors.New("wde error"),
},
{
"encodeValue error",
bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))),
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteNull},
bsonrwtest.WriteNull,
errors.New("ev error"),
},
{
"iterator error",
bsoncore.Document{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"),
},
},
},
{
"StructEncodeValue",
defaultTestStructCodec,
[]subtest{
{
"interface value",
struct{ Foo myInterface }{Foo: myStruct{1}},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
{
"nil interface value",
struct{ Foo myInterface }{Foo: nil},
&EncodeContext{Registry: buildDefaultRegistry()},
nil,
bsonrwtest.WriteDocumentEnd,
nil,
},
},
},
{
"CodeWithScopeEncodeValue",
ValueEncoderFunc(dve.CodeWithScopeEncodeValue),
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "CodeWithScopeEncodeValue",
Types: []reflect.Type{tCodeWithScope},
Received: reflect.ValueOf(wrong),
},
},
{
"WriteCodeWithScope error",
primitive.CodeWithScope{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wcws error"), ErrAfter: bsonrwtest.WriteCodeWithScope},
bsonrwtest.WriteCodeWithScope,
errors.New("wcws error"),
},
{
"CodeWithScope/success",
primitive.CodeWithScope{
Code: "var hello = 'world';",
Scope: primitive.D{},
},
&EncodeContext{Registry: buildDefaultRegistry()},
nil, bsonrwtest.WriteDocumentEnd, nil,
},
},
},
{
"CoreArrayEncodeValue",
defaultArrayCodec,
[]subtest{
{
"wrong type",
wrong,
nil,
nil,
bsonrwtest.Nothing,
ValueEncoderError{
Name: "CoreArrayEncodeValue",
Types: []reflect.Type{tCoreArray},
Received: reflect.ValueOf(wrong),
},
},
{
"WriteArray Error",
bsoncore.Array{},
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wa error"), ErrAfter: bsonrwtest.WriteArray},
bsonrwtest.WriteArray,
errors.New("wa error"),
},
{
"WriteArrayElement Error",
bsoncore.Array(buildDocumentArray(func(doc []byte) []byte {
return bsoncore.AppendNullElement(nil, "foo")
})),
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("wae error"), ErrAfter: bsonrwtest.WriteArrayElement},
bsonrwtest.WriteArrayElement,
errors.New("wae error"),
},
{
"encodeValue error",
bsoncore.Array(buildDocumentArray(func(doc []byte) []byte {
return bsoncore.AppendNullElement(nil, "foo")
})),
nil,
&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteNull},
bsonrwtest.WriteNull,
errors.New("ev error"),
},
},
},
}
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 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
}{
{
"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},
buildDocument(bsoncore.AppendObjectIDElement(nil, "foo", oid)),
nil,
},
{
"map[string][]int32",
map[string][]int32{"Z": {1, 2, 3}},
buildDocumentArray(func(doc []byte) []byte {
doc = bsoncore.AppendInt32Element(doc, "0", 1)
doc = bsoncore.AppendInt32Element(doc, "1", 2)
return bsoncore.AppendInt32Element(doc, "2", 3)
}),
nil,
},
{
"map[string][]primitive.ObjectID",
map[string][]primitive.ObjectID{"Z": oids},
buildDocumentArray(func(doc []byte) []byte {
doc = bsoncore.AppendObjectIDElement(doc, "0", oids[0])
doc = bsoncore.AppendObjectIDElement(doc, "1", oids[1])
return bsoncore.AppendObjectIDElement(doc, "2", oids[2])
}),
nil,
},
{
"map[string][]json.Number(int64)",
map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
buildDocumentArray(func(doc []byte) []byte {
doc = bsoncore.AppendInt64Element(doc, "0", 5)
return bsoncore.AppendInt64Element(doc, "1", 10)
}),
nil,
},
{
"map[string][]json.Number(float64)",
map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
buildDocumentArray(func(doc []byte) []byte {
doc = bsoncore.AppendInt64Element(doc, "0", 5)
return bsoncore.AppendDoubleElement(doc, "1", 10.1)
}),
nil,
},
{
"map[string][]*url.URL",
map[string][]*url.URL{"Z": {murl}},
buildDocumentArray(func(doc []byte) []byte {
return bsoncore.AppendStringElement(doc, "0", murl.String())
}),
nil,
},
{
"map[string][]primitive.Decimal128",
map[string][]primitive.Decimal128{"Z": {decimal128}},
buildDocumentArray(func(doc []byte) []byte {
return bsoncore.AppendDecimal128Element(doc, "0", decimal128)
}),
nil,
},
{
"-",
struct {
A string `bson:"-"`
}{
A: "",
},
[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
nil,
},
{
"omitempty",
struct {
A string `bson:",omitempty"`
}{
A: "",
},
[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
nil,
},
{
"omitempty, empty time",
struct {
A time.Time `bson:",omitempty"`
}{
A: time.Time{},
},
[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
nil,
},
{
"no private fields",
noPrivateFields{a: "should be empty"},
[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
nil,
},
{
"minsize",
struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
nil,
},
{
"inline",
struct {
Foo struct {
A int64 `bson:",minsize"`
} `bson:",inline"`
}{
Foo: struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
},
buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
nil,
},
{
"inline struct pointer",
struct {
Foo *struct {
A int64 `bson:",minsize"`
} `bson:",inline"`
Bar *struct {
B int64
} `bson:",inline"`
}{
Foo: &struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
Bar: nil,
},
buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
nil,
},
{
"nested inline struct pointer",
struct {
Foo *struct {
Bar *struct {
A int64 `bson:",minsize"`
} `bson:",inline"`
} `bson:",inline"`
}{
Foo: &struct {
Bar *struct {
A int64 `bson:",minsize"`
} `bson:",inline"`
}{
Bar: &struct {
A int64 `bson:",minsize"`
}{
A: 12345,
},
},
},
buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
nil,
},
{
"inline nil struct pointer",
struct {
Foo *struct {
A int64 `bson:",minsize"`
} `bson:",inline"`
}{
Foo: nil,
},
buildDocument([]byte{}),
nil,
},
{
"inline overwrite",
struct {
Foo struct {
A int32
B string
} `bson:",inline"`
A int64
}{
Foo: struct {
A int32
B string
}{
A: 0,
B: "foo",
},
A: 54321,
},
buildDocument(func(doc []byte) []byte {
doc = bsoncore.AppendStringElement(doc, "b", "foo")
doc = bsoncore.AppendInt64Element(doc, "a", 54321)
return doc
}(nil)),
nil,
},
{
"inline overwrite respects ordering",
struct {
A int64
Foo struct {
A int32
B string
} `bson:",inline"`
}{
A: 54321,
Foo: struct {
A int32
B string
}{
A: 0,
B: "foo",
},
},
buildDocument(func(doc []byte) []byte {
doc = bsoncore.AppendInt64Element(doc, "a", 54321)
doc = bsoncore.AppendStringElement(doc, "b", "foo")
return doc
}(nil)),
nil,
},
{
"inline overwrite with nested structs",
struct {
Foo struct {
A int32
} `bson:",inline"`
Bar struct {
A int32
} `bson:",inline"`
A int64
}{
Foo: struct {
A int32
}{},
Bar: struct {
A int32
}{},
A: 54321,
},
buildDocument(bsoncore.AppendInt64Element(nil, "a", 54321)),
nil,
},
{
"inline map",
struct {
Foo map[string]string `bson:",inline"`
}{
Foo: map[string]string{"foo": "bar"},
},
buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
nil,
},
{
"alternate name bson:name",
struct {
A string `bson:"foo"`
}{
A: "bar",
},
buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
nil,
},
{
"alternate name",
struct {
A string `bson:"foo"`
}{
A: "bar",
},
buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
nil,
},
{
"inline, omitempty",
struct {
A string
Foo zeroTest `bson:"omitempty,inline"`
}{
A: "bar",
Foo: zeroTest{true},
},
buildDocument(bsoncore.AppendStringElement(nil, "a", "bar")),
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
}
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 Proxy
AG testProxy
AH map[string]interface{}
AI primitive.CodeWithScope
}{
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",
},
Q: oid,
T: nil,
Y: json.Number("5"),
Z: now,
AA: json.Number("10.1"),
AB: murl,
AC: decimal128,
AD: &now,
AE: testValueMarshaler{t: bsontype.String, buf: bsoncore.AppendString(nil, "hello, world")},
AF: testProxy{ret: struct{ Hello string }{Hello: "world!"}},
AG: testProxy{ret: struct{ Pi float64 }{Pi: 3.14159}},
AH: nil,
AI: primitive.CodeWithScope{Code: "var hello = 'world';", Scope: primitive.D{{"pi", 3.14159}}},
},
buildDocument(func(doc []byte) []byte {
doc = bsoncore.AppendBooleanElement(doc, "a", true)
doc = bsoncore.AppendInt32Element(doc, "b", 123)
doc = bsoncore.AppendInt64Element(doc, "c", 456)
doc = bsoncore.AppendInt32Element(doc, "d", 789)
doc = bsoncore.AppendInt64Element(doc, "e", 101112)
doc = bsoncore.AppendDoubleElement(doc, "f", 3.14159)
doc = bsoncore.AppendStringElement(doc, "g", "Hello, world")
doc = bsoncore.AppendDocumentElement(doc, "h", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")))
doc = bsoncore.AppendBinaryElement(doc, "i", 0x00, []byte{0x01, 0x02, 0x03})
doc = bsoncore.AppendArrayElement(doc, "k",
buildArray(bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
)
doc = bsoncore.AppendDocumentElement(doc, "l", buildDocument(bsoncore.AppendStringElement(nil, "m", "foobar")))
doc = bsoncore.AppendObjectIDElement(doc, "q", oid)
doc = bsoncore.AppendNullElement(doc, "t")
doc = bsoncore.AppendInt64Element(doc, "y", 5)
doc = bsoncore.AppendDateTimeElement(doc, "z", now.UnixNano()/int64(time.Millisecond))
doc = bsoncore.AppendDoubleElement(doc, "aa", 10.1)
doc = bsoncore.AppendStringElement(doc, "ab", murl.String())
doc = bsoncore.AppendDecimal128Element(doc, "ac", decimal128)
doc = bsoncore.AppendDateTimeElement(doc, "ad", now.UnixNano()/int64(time.Millisecond))
doc = bsoncore.AppendStringElement(doc, "ae", "hello, world")
doc = bsoncore.AppendDocumentElement(doc, "af", buildDocument(bsoncore.AppendStringElement(nil, "hello", "world!")))
doc = bsoncore.AppendDocumentElement(doc, "ag", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)))
doc = bsoncore.AppendNullElement(doc, "ah")
doc = bsoncore.AppendCodeWithScopeElement(doc, "ai",
"var hello = 'world';", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
)
return doc
}(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
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 []Proxy
AG []testProxy
}{
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"}},
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: bsontype.String, buf: bsoncore.AppendString(nil, "hello")},
{t: bsontype.String, buf: bsoncore.AppendString(nil, "world")},
},
AF: []Proxy{
testProxy{ret: struct{ Hello string }{Hello: "world!"}},
testProxy{ret: struct{ Foo string }{Foo: "bar"}},
},
AG: []testProxy{
{ret: struct{ One int64 }{One: 1234567890}},
{ret: struct{ Pi float64 }{Pi: 3.14159}},
},
},
buildDocument(func(doc []byte) []byte {
doc = appendArrayElement(doc, "a", bsoncore.AppendBooleanElement(nil, "0", true))
doc = appendArrayElement(doc, "b", bsoncore.AppendInt32Element(nil, "0", 123))
doc = appendArrayElement(doc, "c", bsoncore.AppendInt64Element(nil, "0", 456))
doc = appendArrayElement(doc, "d", bsoncore.AppendInt32Element(nil, "0", 789))
doc = appendArrayElement(doc, "e", bsoncore.AppendInt64Element(nil, "0", 101112))
doc = appendArrayElement(doc, "f", bsoncore.AppendDoubleElement(nil, "0", 3.14159))
doc = appendArrayElement(doc, "g", bsoncore.AppendStringElement(nil, "0", "Hello, world"))
doc = appendArrayElement(doc, "h", bsoncore.BuildDocumentElement(nil, "0", bsoncore.AppendStringElement(nil, "foo", "bar")))
doc = appendArrayElement(doc, "i", bsoncore.AppendBinaryElement(nil, "0", 0x00, []byte{0x01, 0x02, 0x03}))
doc = appendArrayElement(doc, "k",
appendArrayElement(nil, "0",
bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
)
doc = appendArrayElement(doc, "l", bsoncore.BuildDocumentElement(nil, "0", bsoncore.AppendStringElement(nil, "m", "foobar")))
doc = appendArrayElement(doc, "n",
appendArrayElement(nil, "0",
bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar")),
)
doc = appendArrayElement(doc, "r",
bsoncore.AppendObjectIDElement(
bsoncore.AppendObjectIDElement(
bsoncore.AppendObjectIDElement(nil,
"0", oids[0]),
"1", oids[1]),
"2", oids[2]),
)
doc = bsoncore.AppendNullElement(doc, "t")
doc = bsoncore.AppendNullElement(doc, "w")
doc = appendArrayElement(doc, "x", nil)
doc = appendArrayElement(doc, "y", bsoncore.BuildDocumentElement(nil, "0", nil))
doc = appendArrayElement(doc, "z",
bsoncore.AppendDateTimeElement(
bsoncore.AppendDateTimeElement(
nil, "0", now.UnixNano()/int64(time.Millisecond)),
"1", now.UnixNano()/int64(time.Millisecond)),
)
doc = appendArrayElement(doc, "aa", bsoncore.AppendDoubleElement(bsoncore.AppendInt64Element(nil, "0", 5), "1", 10.10))
doc = appendArrayElement(doc, "ab", bsoncore.AppendStringElement(nil, "0", murl.String()))
doc = appendArrayElement(doc, "ac", bsoncore.AppendDecimal128Element(nil, "0", decimal128))
doc = appendArrayElement(doc, "ad",
bsoncore.AppendDateTimeElement(
bsoncore.AppendDateTimeElement(nil, "0", now.UnixNano()/int64(time.Millisecond)),
"1", now.UnixNano()/int64(time.Millisecond)),
)
doc = appendArrayElement(doc, "ae",
bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "hello"), "1", "world"),
)
doc = appendArrayElement(doc, "af",
bsoncore.AppendDocumentElement(
bsoncore.AppendDocumentElement(nil, "0",
bsoncore.BuildDocument(nil, bsoncore.AppendStringElement(nil, "hello", "world!")),
), "1",
bsoncore.BuildDocument(nil, bsoncore.AppendStringElement(nil, "foo", "bar")),
),
)
doc = appendArrayElement(doc, "ag",
bsoncore.AppendDocumentElement(
bsoncore.AppendDocumentElement(nil, "0",
bsoncore.BuildDocument(nil, bsoncore.AppendInt64Element(nil, "one", 1234567890)),
), "1",
bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
),
)
return doc
}(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)
reg := buildDefaultRegistry()
enc, err := reg.LookupEncoder(reflect.TypeOf(tc.value))
noerr(t, err)
err = enc.EncodeValue(EncodeContext{Registry: reg}, vw, reflect.ValueOf(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", bsoncore.Document(b), bsoncore.Document(tc.b))
}
})
}
})
t.Run("error path", func(t *testing.T) {
testCases := []struct {
name string
value interface{}
err error
}{
{
"duplicate name struct",
struct {
A int64
B int64 `bson:"a"`
}{
A: 0,
B: 54321,
},
fmt.Errorf("duplicated key a"),
},
{
"inline map",
struct {
Foo map[string]string `bson:",inline"`
Baz string
}{
Foo: map[string]string{"baz": "bar"},
Baz: "hi",
},
fmt.Errorf("Key baz of inlined map conflicts with a struct field name"),
},
}
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)
reg := buildDefaultRegistry()
enc, err := reg.LookupEncoder(reflect.TypeOf(tc.value))
noerr(t, err)
err = enc.EncodeValue(EncodeContext{Registry: reg}, vw, reflect.ValueOf(tc.value))
if err == nil || !strings.Contains(err.Error(), tc.err.Error()) {
t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err)
}
})
}
})
t.Run("EmptyInterfaceEncodeValue/nil", func(t *testing.T) {
val := reflect.New(tEmpty).Elem()
llvrw := new(bsonrwtest.ValueReaderWriter)
err := dve.EmptyInterfaceEncodeValue(EncodeContext{Registry: NewRegistryBuilder().Build()}, llvrw, val)
noerr(t, err)
if llvrw.Invoked != bsonrwtest.WriteNull {
t.Errorf("Incorrect method called. got %v; want %v", llvrw.Invoked, bsonrwtest.WriteNull)
}
})
t.Run("EmptyInterfaceEncodeValue/LookupEncoder error", func(t *testing.T) {
val := reflect.New(tEmpty).Elem()
val.Set(reflect.ValueOf(int64(1234567890)))
llvrw := new(bsonrwtest.ValueReaderWriter)
got := dve.EmptyInterfaceEncodeValue(EncodeContext{Registry: NewRegistryBuilder().Build()}, llvrw, val)
want := ErrNoEncoder{Type: tInt64}
if !compareErrors(got, want) {
t.Errorf("Did not receive expected error. got %v; want %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 testValueMarshalPtr struct {
t bsontype.Type
buf []byte
err error
}
func (tvm *testValueMarshalPtr) MarshalBSONValue() (bsontype.Type, []byte, error) {
return tvm.t, tvm.buf, tvm.err
}
type testMarshaler struct {
buf []byte
err error
}
func (tvm testMarshaler) MarshalBSON() ([]byte, error) {
return tvm.buf, tvm.err
}
type testMarshalPtr struct {
buf []byte
err error
}
func (tvm *testMarshalPtr) MarshalBSON() ([]byte, error) {
return tvm.buf, tvm.err
}
type testProxy struct {
ret interface{}
err error
}
func (tp testProxy) ProxyBSON() (interface{}, error) { return tp.ret, tp.err }
type testProxyPtr struct {
ret interface{}
err error
}
func (tp *testProxyPtr) ProxyBSON() (interface{}, error) { return tp.ret, tp.err }