Copied mongo repo (to patch it)
This commit is contained in:
164
mongo/x/bsonx/bsoncore/array.go
Normal file
164
mongo/x/bsonx/bsoncore/array.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// NewArrayLengthError creates and returns an error for when the length of an array exceeds the
|
||||
// bytes available.
|
||||
func NewArrayLengthError(length, rem int) error {
|
||||
return lengthError("array", length, rem)
|
||||
}
|
||||
|
||||
// Array is a raw bytes representation of a BSON array.
|
||||
type Array []byte
|
||||
|
||||
// NewArrayFromReader reads an array from r. This function will only validate the length is
|
||||
// correct and that the array ends with a null byte.
|
||||
func NewArrayFromReader(r io.Reader) (Array, error) {
|
||||
return newBufferFromReader(r)
|
||||
}
|
||||
|
||||
// Index searches for and retrieves the value at the given index. This method will panic if
|
||||
// the array is invalid or if the index is out of bounds.
|
||||
func (a Array) Index(index uint) Value {
|
||||
value, err := a.IndexErr(index)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// IndexErr searches for and retrieves the value at the given index.
|
||||
func (a Array) IndexErr(index uint) (Value, error) {
|
||||
elem, err := indexErr(a, index)
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
return elem.Value(), err
|
||||
}
|
||||
|
||||
// DebugString outputs a human readable version of Array. It will attempt to stringify the
|
||||
// valid components of the array even if the entire array is not valid.
|
||||
func (a Array) DebugString() string {
|
||||
if len(a) < 5 {
|
||||
return "<malformed>"
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("Array")
|
||||
length, rem, _ := ReadLength(a) // We know we have enough bytes to read the length
|
||||
buf.WriteByte('(')
|
||||
buf.WriteString(strconv.Itoa(int(length)))
|
||||
length -= 4
|
||||
buf.WriteString(")[")
|
||||
var elem Element
|
||||
var ok bool
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
buf.WriteString(fmt.Sprintf("<malformed (%d)>", length))
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s", elem.Value().DebugString())
|
||||
if length != 1 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// String outputs an ExtendedJSON version of Array. If the Array is not valid, this method
|
||||
// returns an empty string.
|
||||
func (a Array) String() string {
|
||||
if len(a) < 5 {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('[')
|
||||
|
||||
length, rem, _ := ReadLength(a) // We know we have enough bytes to read the length
|
||||
|
||||
length -= 4
|
||||
|
||||
var elem Element
|
||||
var ok bool
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s", elem.Value().String())
|
||||
if length > 1 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
}
|
||||
if length != 1 { // Missing final null byte or inaccurate length
|
||||
return ""
|
||||
}
|
||||
|
||||
buf.WriteByte(']')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Values returns this array as a slice of values. The returned slice will contain valid values.
|
||||
// If the array is not valid, the values up to the invalid point will be returned along with an
|
||||
// error.
|
||||
func (a Array) Values() ([]Value, error) {
|
||||
return values(a)
|
||||
}
|
||||
|
||||
// Validate validates the array and ensures the elements contained within are valid.
|
||||
func (a Array) Validate() error {
|
||||
length, rem, ok := ReadLength(a)
|
||||
if !ok {
|
||||
return NewInsufficientBytesError(a, rem)
|
||||
}
|
||||
if int(length) > len(a) {
|
||||
return NewArrayLengthError(int(length), len(a))
|
||||
}
|
||||
if a[length-1] != 0x00 {
|
||||
return ErrMissingNull
|
||||
}
|
||||
|
||||
length -= 4
|
||||
var elem Element
|
||||
|
||||
var keyNum int64
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return NewInsufficientBytesError(a, rem)
|
||||
}
|
||||
|
||||
// validate element
|
||||
err := elem.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate keys increase numerically
|
||||
if fmt.Sprint(keyNum) != elem.Key() {
|
||||
return fmt.Errorf("array key %q is out of order or invalid", elem.Key())
|
||||
}
|
||||
keyNum++
|
||||
}
|
||||
|
||||
if len(rem) < 1 || rem[0] != 0x00 {
|
||||
return ErrMissingNull
|
||||
}
|
||||
return nil
|
||||
}
|
||||
349
mongo/x/bsonx/bsoncore/array_test.go
Normal file
349
mongo/x/bsonx/bsoncore/array_test.go
Normal file
@@ -0,0 +1,349 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
)
|
||||
|
||||
func TestArray(t *testing.T) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
t.Run("TooShort", func(t *testing.T) {
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
got := Array{'\x00', '\x00'}.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("InvalidLength", func(t *testing.T) {
|
||||
want := NewArrayLengthError(200, 5)
|
||||
r := make(Array, 5)
|
||||
binary.LittleEndian.PutUint32(r[0:4], 200)
|
||||
got := r.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Invalid Element", func(t *testing.T) {
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
r := make(Array, 7)
|
||||
binary.LittleEndian.PutUint32(r[0:4], 7)
|
||||
r[4], r[5], r[6] = 0x02, 'f', 0x00
|
||||
got := r.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Missing Null Terminator", func(t *testing.T) {
|
||||
want := ErrMissingNull
|
||||
r := make(Array, 6)
|
||||
binary.LittleEndian.PutUint32(r[0:4], 6)
|
||||
r[4], r[5] = 0x0A, '0'
|
||||
got := r.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
testCases := []struct {
|
||||
name string
|
||||
r Array
|
||||
want error
|
||||
}{
|
||||
{"array null", Array{'\x08', '\x00', '\x00', '\x00', '\x0A', '0', '\x00', '\x00'}, nil},
|
||||
{"array",
|
||||
Array{
|
||||
'\x1B', '\x00', '\x00', '\x00',
|
||||
'\x02',
|
||||
'0', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x72', '\x00',
|
||||
'\x02',
|
||||
'1', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x7a', '\x00',
|
||||
'\x00',
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{"subarray",
|
||||
Array{
|
||||
'\x13', '\x00', '\x00', '\x00',
|
||||
'\x04',
|
||||
'0', '\x00',
|
||||
'\x0B', '\x00', '\x00', '\x00', '\x0A', '0', '\x00',
|
||||
'\x0A', '1', '\x00', '\x00', '\x00',
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{"invalid key order",
|
||||
Array{
|
||||
'\x0B', '\x00', '\x00', '\x00', '\x0A', '2', '\x00',
|
||||
'\x0A', '0', '\x00', '\x00', '\x00',
|
||||
},
|
||||
errors.New(`array key "2" is out of order or invalid`),
|
||||
},
|
||||
{"invalid key type",
|
||||
Array{
|
||||
'\x0B', '\x00', '\x00', '\x00', '\x0A', 'p', '\x00',
|
||||
'\x0A', 'q', '\x00', '\x00', '\x00',
|
||||
},
|
||||
errors.New(`array key "p" is out of order or invalid`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.r.Validate()
|
||||
if !compareErrors(got, tc.want) {
|
||||
t.Errorf("Returned error does not match. got %v; want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Index", func(t *testing.T) {
|
||||
t.Run("Out of bounds", func(t *testing.T) {
|
||||
rdr := Array{0xe, 0x0, 0x0, 0x0, 0xa, '0', 0x0, 0xa, '1', 0x0, 0xa, 0x7a, 0x0, 0x0}
|
||||
_, err := rdr.IndexErr(3)
|
||||
if err != ErrOutOfBounds {
|
||||
t.Errorf("Out of bounds should be returned when accessing element beyond end of Array. got %v; want %v", err, ErrOutOfBounds)
|
||||
}
|
||||
})
|
||||
t.Run("Validation Error", func(t *testing.T) {
|
||||
rdr := Array{0x07, 0x00, 0x00, 0x00, 0x00}
|
||||
_, got := rdr.IndexErr(1)
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not receive expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
testArray := Array{
|
||||
'\x26', '\x00', '\x00', '\x00',
|
||||
'\x02',
|
||||
'0', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x72', '\x00',
|
||||
'\x02',
|
||||
'1', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x7a', '\x00',
|
||||
'\x02',
|
||||
'2', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x71', '\x75', '\x78', '\x00',
|
||||
'\x00',
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
index uint
|
||||
want Value
|
||||
}{
|
||||
{"first",
|
||||
0,
|
||||
Value{
|
||||
Type: bsontype.String,
|
||||
Data: []byte{0x04, 0x00, 0x00, 0x00, 0x62, 0x61, 0x72, 0x00},
|
||||
},
|
||||
},
|
||||
{"second",
|
||||
1,
|
||||
Value{
|
||||
Type: bsontype.String,
|
||||
Data: []byte{0x04, 0x00, 0x00, 0x00, 0x62, 0x61, 0x7a, 0x00},
|
||||
},
|
||||
},
|
||||
{"third",
|
||||
2,
|
||||
Value{
|
||||
Type: bsontype.String,
|
||||
Data: []byte{0x04, 0x00, 0x00, 0x00, 0x71, 0x75, 0x78, 0x00},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("IndexErr", func(t *testing.T) {
|
||||
got, err := testArray.IndexErr(tc.index)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error from IndexErr: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(got, tc.want); diff != "" {
|
||||
t.Errorf("Arrays differ: (-got +want)\n%s", diff)
|
||||
}
|
||||
})
|
||||
t.Run("Index", func(t *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}()
|
||||
got := testArray.Index(tc.index)
|
||||
if diff := cmp.Diff(got, tc.want); diff != "" {
|
||||
t.Errorf("Arrays differ: (-got +want)\n%s", diff)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("NewArrayFromReader", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ioReader io.Reader
|
||||
arr Array
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"nil reader",
|
||||
nil,
|
||||
nil,
|
||||
ErrNilReader,
|
||||
},
|
||||
{
|
||||
"premature end of reader",
|
||||
bytes.NewBuffer([]byte{}),
|
||||
nil,
|
||||
io.EOF,
|
||||
},
|
||||
{
|
||||
"empty Array",
|
||||
bytes.NewBuffer([]byte{5, 0, 0, 0, 0}),
|
||||
[]byte{5, 0, 0, 0, 0},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"non-empty Array",
|
||||
bytes.NewBuffer([]byte{
|
||||
'\x1B', '\x00', '\x00', '\x00',
|
||||
'\x02',
|
||||
'0', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x72', '\x00',
|
||||
'\x02',
|
||||
'1', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x7a', '\x00',
|
||||
'\x00',
|
||||
}),
|
||||
[]byte{
|
||||
'\x1B', '\x00', '\x00', '\x00',
|
||||
'\x02',
|
||||
'0', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x72', '\x00',
|
||||
'\x02',
|
||||
'1', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x7a', '\x00',
|
||||
'\x00',
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
arr, err := NewArrayFromReader(tc.ioReader)
|
||||
if !compareErrors(err, tc.err) {
|
||||
t.Errorf("errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
if !bytes.Equal(tc.arr, arr) {
|
||||
t.Errorf("Arrays differ. got %v; want %v", tc.arr, arr)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("DebugString", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
arr Array
|
||||
arrayString string
|
||||
arrayDebugString string
|
||||
}{
|
||||
{
|
||||
"array",
|
||||
Array{
|
||||
'\x1B', '\x00', '\x00', '\x00',
|
||||
'\x02',
|
||||
'0', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x72', '\x00',
|
||||
'\x02',
|
||||
'1', '\x00',
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x62', '\x61', '\x7a', '\x00',
|
||||
'\x00',
|
||||
},
|
||||
`["bar","baz"]`,
|
||||
`Array(27)["bar","baz"]`,
|
||||
},
|
||||
{
|
||||
"subarray",
|
||||
Array{
|
||||
'\x13', '\x00', '\x00', '\x00',
|
||||
'\x04',
|
||||
'0', '\x00',
|
||||
'\x0B', '\x00', '\x00', '\x00',
|
||||
'\x0A', '0', '\x00',
|
||||
'\x0A', '1', '\x00',
|
||||
'\x00', '\x00',
|
||||
},
|
||||
`[[null,null]]`,
|
||||
`Array(19)[Array(11)[null,null]]`,
|
||||
},
|
||||
{
|
||||
"malformed--length too small",
|
||||
Array{
|
||||
'\x04', '\x00', '\x00', '\x00',
|
||||
'\x00',
|
||||
},
|
||||
``,
|
||||
`Array(4)[]`,
|
||||
},
|
||||
{
|
||||
"malformed--length too large",
|
||||
Array{
|
||||
'\x13', '\x00', '\x00', '\x00',
|
||||
'\x00',
|
||||
},
|
||||
``,
|
||||
`Array(19)[<malformed (15)>]`,
|
||||
},
|
||||
{
|
||||
"malformed--missing null byte",
|
||||
Array{
|
||||
'\x06', '\x00', '\x00', '\x00',
|
||||
'\x02', '0',
|
||||
},
|
||||
``,
|
||||
`Array(6)[<malformed (2)>]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
arrayString := tc.arr.String()
|
||||
if arrayString != tc.arrayString {
|
||||
t.Errorf("array strings do not match. got %q; want %q",
|
||||
arrayString, tc.arrayString)
|
||||
}
|
||||
|
||||
arrayDebugString := tc.arr.DebugString()
|
||||
if arrayDebugString != tc.arrayDebugString {
|
||||
t.Errorf("array debug strings do not match. got %q; want %q",
|
||||
arrayDebugString, tc.arrayDebugString)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
201
mongo/x/bsonx/bsoncore/bson_arraybuilder.go
Normal file
201
mongo/x/bsonx/bsoncore/bson_arraybuilder.go
Normal file
@@ -0,0 +1,201 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// ArrayBuilder builds a bson array
|
||||
type ArrayBuilder struct {
|
||||
arr []byte
|
||||
indexes []int32
|
||||
keys []int
|
||||
}
|
||||
|
||||
// NewArrayBuilder creates a new ArrayBuilder
|
||||
func NewArrayBuilder() *ArrayBuilder {
|
||||
return (&ArrayBuilder{}).startArray()
|
||||
}
|
||||
|
||||
// startArray reserves the array's length and sets the index to where the length begins
|
||||
func (a *ArrayBuilder) startArray() *ArrayBuilder {
|
||||
var index int32
|
||||
index, a.arr = AppendArrayStart(a.arr)
|
||||
a.indexes = append(a.indexes, index)
|
||||
a.keys = append(a.keys, 0)
|
||||
return a
|
||||
}
|
||||
|
||||
// Build updates the length of the array and index to the beginning of the documents length
|
||||
// bytes, then returns the array (bson bytes)
|
||||
func (a *ArrayBuilder) Build() Array {
|
||||
lastIndex := len(a.indexes) - 1
|
||||
lastKey := len(a.keys) - 1
|
||||
a.arr, _ = AppendArrayEnd(a.arr, a.indexes[lastIndex])
|
||||
a.indexes = a.indexes[:lastIndex]
|
||||
a.keys = a.keys[:lastKey]
|
||||
return a.arr
|
||||
}
|
||||
|
||||
// incrementKey() increments the value keys and returns the key to be used to a.appendArray* functions
|
||||
func (a *ArrayBuilder) incrementKey() string {
|
||||
idx := len(a.keys) - 1
|
||||
key := strconv.Itoa(a.keys[idx])
|
||||
a.keys[idx]++
|
||||
return key
|
||||
}
|
||||
|
||||
// AppendInt32 will append i32 to ArrayBuilder.arr
|
||||
func (a *ArrayBuilder) AppendInt32(i32 int32) *ArrayBuilder {
|
||||
a.arr = AppendInt32Element(a.arr, a.incrementKey(), i32)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendDocument will append doc to ArrayBuilder.arr
|
||||
func (a *ArrayBuilder) AppendDocument(doc []byte) *ArrayBuilder {
|
||||
a.arr = AppendDocumentElement(a.arr, a.incrementKey(), doc)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendArray will append arr to ArrayBuilder.arr
|
||||
func (a *ArrayBuilder) AppendArray(arr []byte) *ArrayBuilder {
|
||||
a.arr = AppendArrayElement(a.arr, a.incrementKey(), arr)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendDouble will append f to ArrayBuilder.doc
|
||||
func (a *ArrayBuilder) AppendDouble(f float64) *ArrayBuilder {
|
||||
a.arr = AppendDoubleElement(a.arr, a.incrementKey(), f)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendString will append str to ArrayBuilder.doc
|
||||
func (a *ArrayBuilder) AppendString(str string) *ArrayBuilder {
|
||||
a.arr = AppendStringElement(a.arr, a.incrementKey(), str)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendObjectID will append oid to ArrayBuilder.doc
|
||||
func (a *ArrayBuilder) AppendObjectID(oid primitive.ObjectID) *ArrayBuilder {
|
||||
a.arr = AppendObjectIDElement(a.arr, a.incrementKey(), oid)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendBinary will append a BSON binary element using subtype, and
|
||||
// b to a.arr
|
||||
func (a *ArrayBuilder) AppendBinary(subtype byte, b []byte) *ArrayBuilder {
|
||||
a.arr = AppendBinaryElement(a.arr, a.incrementKey(), subtype, b)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendUndefined will append a BSON undefined element using key to a.arr
|
||||
func (a *ArrayBuilder) AppendUndefined() *ArrayBuilder {
|
||||
a.arr = AppendUndefinedElement(a.arr, a.incrementKey())
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendBoolean will append a boolean element using b to a.arr
|
||||
func (a *ArrayBuilder) AppendBoolean(b bool) *ArrayBuilder {
|
||||
a.arr = AppendBooleanElement(a.arr, a.incrementKey(), b)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendDateTime will append datetime element dt to a.arr
|
||||
func (a *ArrayBuilder) AppendDateTime(dt int64) *ArrayBuilder {
|
||||
a.arr = AppendDateTimeElement(a.arr, a.incrementKey(), dt)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendNull will append a null element to a.arr
|
||||
func (a *ArrayBuilder) AppendNull() *ArrayBuilder {
|
||||
a.arr = AppendNullElement(a.arr, a.incrementKey())
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendRegex will append pattern and options to a.arr
|
||||
func (a *ArrayBuilder) AppendRegex(pattern, options string) *ArrayBuilder {
|
||||
a.arr = AppendRegexElement(a.arr, a.incrementKey(), pattern, options)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendDBPointer will append ns and oid to a.arr
|
||||
func (a *ArrayBuilder) AppendDBPointer(ns string, oid primitive.ObjectID) *ArrayBuilder {
|
||||
a.arr = AppendDBPointerElement(a.arr, a.incrementKey(), ns, oid)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendJavaScript will append js to a.arr
|
||||
func (a *ArrayBuilder) AppendJavaScript(js string) *ArrayBuilder {
|
||||
a.arr = AppendJavaScriptElement(a.arr, a.incrementKey(), js)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendSymbol will append symbol to a.arr
|
||||
func (a *ArrayBuilder) AppendSymbol(symbol string) *ArrayBuilder {
|
||||
a.arr = AppendSymbolElement(a.arr, a.incrementKey(), symbol)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendCodeWithScope will append code and scope to a.arr
|
||||
func (a *ArrayBuilder) AppendCodeWithScope(code string, scope Document) *ArrayBuilder {
|
||||
a.arr = AppendCodeWithScopeElement(a.arr, a.incrementKey(), code, scope)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendTimestamp will append t and i to a.arr
|
||||
func (a *ArrayBuilder) AppendTimestamp(t, i uint32) *ArrayBuilder {
|
||||
a.arr = AppendTimestampElement(a.arr, a.incrementKey(), t, i)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendInt64 will append i64 to a.arr
|
||||
func (a *ArrayBuilder) AppendInt64(i64 int64) *ArrayBuilder {
|
||||
a.arr = AppendInt64Element(a.arr, a.incrementKey(), i64)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendDecimal128 will append d128 to a.arr
|
||||
func (a *ArrayBuilder) AppendDecimal128(d128 primitive.Decimal128) *ArrayBuilder {
|
||||
a.arr = AppendDecimal128Element(a.arr, a.incrementKey(), d128)
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendMaxKey will append a max key element to a.arr
|
||||
func (a *ArrayBuilder) AppendMaxKey() *ArrayBuilder {
|
||||
a.arr = AppendMaxKeyElement(a.arr, a.incrementKey())
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendMinKey will append a min key element to a.arr
|
||||
func (a *ArrayBuilder) AppendMinKey() *ArrayBuilder {
|
||||
a.arr = AppendMinKeyElement(a.arr, a.incrementKey())
|
||||
return a
|
||||
}
|
||||
|
||||
// AppendValue appends a BSON value to the array.
|
||||
func (a *ArrayBuilder) AppendValue(val Value) *ArrayBuilder {
|
||||
a.arr = AppendValueElement(a.arr, a.incrementKey(), val)
|
||||
return a
|
||||
}
|
||||
|
||||
// StartArray starts building an inline Array. After this document is completed,
|
||||
// the user must call a.FinishArray
|
||||
func (a *ArrayBuilder) StartArray() *ArrayBuilder {
|
||||
a.arr = AppendHeader(a.arr, bsontype.Array, a.incrementKey())
|
||||
a.startArray()
|
||||
return a
|
||||
}
|
||||
|
||||
// FinishArray builds the most recent array created
|
||||
func (a *ArrayBuilder) FinishArray() *ArrayBuilder {
|
||||
a.arr = a.Build()
|
||||
return a
|
||||
}
|
||||
213
mongo/x/bsonx/bsoncore/bson_arraybuilder_test.go
Normal file
213
mongo/x/bsonx/bsoncore/bson_arraybuilder_test.go
Normal file
@@ -0,0 +1,213 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestArrayBuilder(t *testing.T) {
|
||||
bits := math.Float64bits(3.14159)
|
||||
pi := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(pi, bits)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
fn interface{}
|
||||
params []interface{}
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
"AppendInt32",
|
||||
NewArrayBuilder().AppendInt32,
|
||||
[]interface{}{int32(256)},
|
||||
BuildDocumentFromElements(nil, AppendInt32Element(nil, "0", int32(256))),
|
||||
},
|
||||
{
|
||||
"AppendDouble",
|
||||
NewArrayBuilder().AppendDouble,
|
||||
[]interface{}{float64(3.14159)},
|
||||
BuildDocumentFromElements(nil, AppendDoubleElement(nil, "0", float64(3.14159))),
|
||||
},
|
||||
{
|
||||
"AppendString",
|
||||
NewArrayBuilder().AppendString,
|
||||
[]interface{}{"x"},
|
||||
BuildDocumentFromElements(nil, AppendStringElement(nil, "0", "x")),
|
||||
},
|
||||
{
|
||||
"AppendDocument",
|
||||
NewArrayBuilder().AppendDocument,
|
||||
[]interface{}{[]byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
BuildDocumentFromElements(nil, AppendDocumentElement(nil, "0", []byte{0x05, 0x00, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
"AppendArray",
|
||||
NewArrayBuilder().AppendArray,
|
||||
[]interface{}{[]byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
BuildDocumentFromElements(nil, AppendArrayElement(nil, "0", []byte{0x05, 0x00, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
"AppendBinary",
|
||||
NewArrayBuilder().AppendBinary,
|
||||
[]interface{}{byte(0x02), []byte{0x01, 0x02, 0x03}},
|
||||
BuildDocumentFromElements(nil, AppendBinaryElement(nil, "0", byte(0x02), []byte{0x01, 0x02, 0x03})),
|
||||
},
|
||||
{
|
||||
"AppendObjectID",
|
||||
NewArrayBuilder().AppendObjectID,
|
||||
[]interface{}{
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
},
|
||||
BuildDocumentFromElements(nil, AppendObjectIDElement(nil, "0",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C})),
|
||||
},
|
||||
{
|
||||
"AppendBoolean",
|
||||
NewArrayBuilder().AppendBoolean,
|
||||
[]interface{}{true},
|
||||
BuildDocumentFromElements(nil, AppendBooleanElement(nil, "0", true)),
|
||||
},
|
||||
{
|
||||
"AppendDateTime",
|
||||
NewArrayBuilder().AppendDateTime,
|
||||
[]interface{}{int64(256)},
|
||||
BuildDocumentFromElements(nil, AppendDateTimeElement(nil, "0", int64(256))),
|
||||
},
|
||||
{
|
||||
"AppendNull",
|
||||
NewArrayBuilder().AppendNull,
|
||||
[]interface{}{},
|
||||
BuildDocumentFromElements(nil, AppendNullElement(nil, "0")),
|
||||
},
|
||||
{
|
||||
"AppendRegex",
|
||||
NewArrayBuilder().AppendRegex,
|
||||
[]interface{}{"bar", "baz"},
|
||||
BuildDocumentFromElements(nil, AppendRegexElement(nil, "0", "bar", "baz")),
|
||||
},
|
||||
{
|
||||
"AppendJavaScript",
|
||||
NewArrayBuilder().AppendJavaScript,
|
||||
[]interface{}{"barbaz"},
|
||||
BuildDocumentFromElements(nil, AppendJavaScriptElement(nil, "0", "barbaz")),
|
||||
},
|
||||
{
|
||||
"AppendCodeWithScope",
|
||||
NewArrayBuilder().AppendCodeWithScope,
|
||||
[]interface{}{"barbaz", Document([]byte{0x05, 0x00, 0x00, 0x00, 0x00})},
|
||||
BuildDocumentFromElements(nil, AppendCodeWithScopeElement(nil, "0", "barbaz", Document([]byte{0x05, 0x00, 0x00, 0x00, 0x00}))),
|
||||
},
|
||||
{
|
||||
"AppendTimestamp",
|
||||
NewArrayBuilder().AppendTimestamp,
|
||||
[]interface{}{uint32(65536), uint32(256)},
|
||||
BuildDocumentFromElements(nil, AppendTimestampElement(nil, "0", uint32(65536), uint32(256))),
|
||||
},
|
||||
{
|
||||
"AppendInt64",
|
||||
NewArrayBuilder().AppendInt64,
|
||||
[]interface{}{int64(4294967296)},
|
||||
BuildDocumentFromElements(nil, AppendInt64Element(nil, "0", int64(4294967296))),
|
||||
},
|
||||
{
|
||||
"AppendDecimal128",
|
||||
NewArrayBuilder().AppendDecimal128,
|
||||
[]interface{}{primitive.NewDecimal128(4294967296, 65536)},
|
||||
BuildDocumentFromElements(nil, AppendDecimal128Element(nil, "0", primitive.NewDecimal128(4294967296, 65536))),
|
||||
},
|
||||
{
|
||||
"AppendMaxKey",
|
||||
NewArrayBuilder().AppendMaxKey,
|
||||
[]interface{}{},
|
||||
BuildDocumentFromElements(nil, AppendMaxKeyElement(nil, "0")),
|
||||
},
|
||||
{
|
||||
"AppendMinKey",
|
||||
NewArrayBuilder().AppendMinKey,
|
||||
[]interface{}{},
|
||||
BuildDocumentFromElements(nil, AppendMinKeyElement(nil, "0")),
|
||||
},
|
||||
{
|
||||
"AppendSymbol",
|
||||
NewArrayBuilder().AppendSymbol,
|
||||
[]interface{}{"barbaz"},
|
||||
BuildDocumentFromElements(nil, AppendSymbolElement(nil, "0", "barbaz")),
|
||||
},
|
||||
{
|
||||
"AppendDBPointer",
|
||||
NewArrayBuilder().AppendDBPointer,
|
||||
[]interface{}{"barbaz",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}},
|
||||
BuildDocumentFromElements(nil, AppendDBPointerElement(nil, "0", "barbaz",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C})),
|
||||
},
|
||||
{
|
||||
"AppendUndefined",
|
||||
NewArrayBuilder().AppendUndefined,
|
||||
[]interface{}{},
|
||||
BuildDocumentFromElements(nil, AppendUndefinedElement(nil, "0")),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
fn := reflect.ValueOf(tc.fn)
|
||||
if fn.Kind() != reflect.Func {
|
||||
t.Fatalf("fn must be of kind Func but is a %v", fn.Kind())
|
||||
}
|
||||
if fn.Type().NumIn() != len(tc.params) {
|
||||
t.Fatalf("tc.params must match the number of params in tc.fn. params %d; fn %d", fn.Type().NumIn(), len(tc.params))
|
||||
}
|
||||
if fn.Type().NumOut() != 1 || fn.Type().Out(0) != reflect.TypeOf(&ArrayBuilder{}) {
|
||||
t.Fatalf("fn must have one return parameter and it must be an ArrayBuilder.")
|
||||
}
|
||||
params := make([]reflect.Value, 0, len(tc.params))
|
||||
for _, param := range tc.params {
|
||||
params = append(params, reflect.ValueOf(param))
|
||||
}
|
||||
results := fn.Call(params)
|
||||
got := results[0].Interface().(*ArrayBuilder).Build()
|
||||
want := tc.expected
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("Did not receive expected bytes. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run("TestBuildTwoElementsArray", func(t *testing.T) {
|
||||
intArr := BuildDocumentFromElements(nil, AppendInt32Element(nil, "0", int32(1)))
|
||||
expected := BuildDocumentFromElements(nil, AppendArrayElement(AppendInt32Element(nil, "0", int32(3)), "1", intArr))
|
||||
elem := NewArrayBuilder().AppendInt32(int32(1)).Build()
|
||||
result := NewArrayBuilder().AppendInt32(int32(3)).AppendArray(elem).Build()
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Arrays do not match. got %v; want %v", result, expected)
|
||||
}
|
||||
})
|
||||
t.Run("TestBuildInlineArray", func(t *testing.T) {
|
||||
docElement := BuildDocumentFromElements(nil, AppendInt32Element(nil, "0", int32(256)))
|
||||
expected := Document(BuildDocumentFromElements(nil, AppendArrayElement(nil, "0", docElement)))
|
||||
result := NewArrayBuilder().StartArray().AppendInt32(int32(256)).FinishArray().Build()
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", result, expected)
|
||||
}
|
||||
})
|
||||
t.Run("TestBuildNestedInlineArray", func(t *testing.T) {
|
||||
docElement := BuildDocumentFromElements(nil, AppendDoubleElement(nil, "0", 3.14))
|
||||
docInline := BuildDocumentFromElements(nil, AppendArrayElement(nil, "0", docElement))
|
||||
expected := Document(BuildDocumentFromElements(nil, AppendArrayElement(nil, "0", docInline)))
|
||||
result := NewArrayBuilder().StartArray().StartArray().AppendDouble(3.14).FinishArray().FinishArray().Build()
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", result, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
189
mongo/x/bsonx/bsoncore/bson_documentbuilder.go
Normal file
189
mongo/x/bsonx/bsoncore/bson_documentbuilder.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// DocumentBuilder builds a bson document
|
||||
type DocumentBuilder struct {
|
||||
doc []byte
|
||||
indexes []int32
|
||||
}
|
||||
|
||||
// startDocument reserves the document's length and set the index to where the length begins
|
||||
func (db *DocumentBuilder) startDocument() *DocumentBuilder {
|
||||
var index int32
|
||||
index, db.doc = AppendDocumentStart(db.doc)
|
||||
db.indexes = append(db.indexes, index)
|
||||
return db
|
||||
}
|
||||
|
||||
// NewDocumentBuilder creates a new DocumentBuilder
|
||||
func NewDocumentBuilder() *DocumentBuilder {
|
||||
return (&DocumentBuilder{}).startDocument()
|
||||
}
|
||||
|
||||
// Build updates the length of the document and index to the beginning of the documents length
|
||||
// bytes, then returns the document (bson bytes)
|
||||
func (db *DocumentBuilder) Build() Document {
|
||||
last := len(db.indexes) - 1
|
||||
db.doc, _ = AppendDocumentEnd(db.doc, db.indexes[last])
|
||||
db.indexes = db.indexes[:last]
|
||||
return db.doc
|
||||
}
|
||||
|
||||
// AppendInt32 will append an int32 element using key and i32 to DocumentBuilder.doc
|
||||
func (db *DocumentBuilder) AppendInt32(key string, i32 int32) *DocumentBuilder {
|
||||
db.doc = AppendInt32Element(db.doc, key, i32)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendDocument will append a bson embedded document element using key
|
||||
// and doc to DocumentBuilder.doc
|
||||
func (db *DocumentBuilder) AppendDocument(key string, doc []byte) *DocumentBuilder {
|
||||
db.doc = AppendDocumentElement(db.doc, key, doc)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendArray will append a bson array using key and arr to DocumentBuilder.doc
|
||||
func (db *DocumentBuilder) AppendArray(key string, arr []byte) *DocumentBuilder {
|
||||
db.doc = AppendHeader(db.doc, bsontype.Array, key)
|
||||
db.doc = AppendArray(db.doc, arr)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendDouble will append a double element using key and f to DocumentBuilder.doc
|
||||
func (db *DocumentBuilder) AppendDouble(key string, f float64) *DocumentBuilder {
|
||||
db.doc = AppendDoubleElement(db.doc, key, f)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendString will append str to DocumentBuilder.doc with the given key
|
||||
func (db *DocumentBuilder) AppendString(key string, str string) *DocumentBuilder {
|
||||
db.doc = AppendStringElement(db.doc, key, str)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendObjectID will append oid to DocumentBuilder.doc with the given key
|
||||
func (db *DocumentBuilder) AppendObjectID(key string, oid primitive.ObjectID) *DocumentBuilder {
|
||||
db.doc = AppendObjectIDElement(db.doc, key, oid)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendBinary will append a BSON binary element using key, subtype, and
|
||||
// b to db.doc
|
||||
func (db *DocumentBuilder) AppendBinary(key string, subtype byte, b []byte) *DocumentBuilder {
|
||||
db.doc = AppendBinaryElement(db.doc, key, subtype, b)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendUndefined will append a BSON undefined element using key to db.doc
|
||||
func (db *DocumentBuilder) AppendUndefined(key string) *DocumentBuilder {
|
||||
db.doc = AppendUndefinedElement(db.doc, key)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendBoolean will append a boolean element using key and b to db.doc
|
||||
func (db *DocumentBuilder) AppendBoolean(key string, b bool) *DocumentBuilder {
|
||||
db.doc = AppendBooleanElement(db.doc, key, b)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendDateTime will append a datetime element using key and dt to db.doc
|
||||
func (db *DocumentBuilder) AppendDateTime(key string, dt int64) *DocumentBuilder {
|
||||
db.doc = AppendDateTimeElement(db.doc, key, dt)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendNull will append a null element using key to db.doc
|
||||
func (db *DocumentBuilder) AppendNull(key string) *DocumentBuilder {
|
||||
db.doc = AppendNullElement(db.doc, key)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendRegex will append pattern and options using key to db.doc
|
||||
func (db *DocumentBuilder) AppendRegex(key, pattern, options string) *DocumentBuilder {
|
||||
db.doc = AppendRegexElement(db.doc, key, pattern, options)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendDBPointer will append ns and oid to using key to db.doc
|
||||
func (db *DocumentBuilder) AppendDBPointer(key string, ns string, oid primitive.ObjectID) *DocumentBuilder {
|
||||
db.doc = AppendDBPointerElement(db.doc, key, ns, oid)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendJavaScript will append js using the provided key to db.doc
|
||||
func (db *DocumentBuilder) AppendJavaScript(key, js string) *DocumentBuilder {
|
||||
db.doc = AppendJavaScriptElement(db.doc, key, js)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendSymbol will append a BSON symbol element using key and symbol db.doc
|
||||
func (db *DocumentBuilder) AppendSymbol(key, symbol string) *DocumentBuilder {
|
||||
db.doc = AppendSymbolElement(db.doc, key, symbol)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendCodeWithScope will append code and scope using key to db.doc
|
||||
func (db *DocumentBuilder) AppendCodeWithScope(key string, code string, scope Document) *DocumentBuilder {
|
||||
db.doc = AppendCodeWithScopeElement(db.doc, key, code, scope)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendTimestamp will append t and i to db.doc using provided key
|
||||
func (db *DocumentBuilder) AppendTimestamp(key string, t, i uint32) *DocumentBuilder {
|
||||
db.doc = AppendTimestampElement(db.doc, key, t, i)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendInt64 will append i64 to dst using key to db.doc
|
||||
func (db *DocumentBuilder) AppendInt64(key string, i64 int64) *DocumentBuilder {
|
||||
db.doc = AppendInt64Element(db.doc, key, i64)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendDecimal128 will append d128 to db.doc using provided key
|
||||
func (db *DocumentBuilder) AppendDecimal128(key string, d128 primitive.Decimal128) *DocumentBuilder {
|
||||
db.doc = AppendDecimal128Element(db.doc, key, d128)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendMaxKey will append a max key element using key to db.doc
|
||||
func (db *DocumentBuilder) AppendMaxKey(key string) *DocumentBuilder {
|
||||
db.doc = AppendMaxKeyElement(db.doc, key)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendMinKey will append a min key element using key to db.doc
|
||||
func (db *DocumentBuilder) AppendMinKey(key string) *DocumentBuilder {
|
||||
db.doc = AppendMinKeyElement(db.doc, key)
|
||||
return db
|
||||
}
|
||||
|
||||
// AppendValue will append a BSON element with the provided key and value to the document.
|
||||
func (db *DocumentBuilder) AppendValue(key string, val Value) *DocumentBuilder {
|
||||
db.doc = AppendValueElement(db.doc, key, val)
|
||||
return db
|
||||
}
|
||||
|
||||
// StartDocument starts building an inline document element with the provided key
|
||||
// After this document is completed, the user must call finishDocument
|
||||
func (db *DocumentBuilder) StartDocument(key string) *DocumentBuilder {
|
||||
db.doc = AppendHeader(db.doc, bsontype.EmbeddedDocument, key)
|
||||
db = db.startDocument()
|
||||
return db
|
||||
}
|
||||
|
||||
// FinishDocument builds the most recent document created
|
||||
func (db *DocumentBuilder) FinishDocument() *DocumentBuilder {
|
||||
db.doc = db.Build()
|
||||
return db
|
||||
}
|
||||
215
mongo/x/bsonx/bsoncore/bson_documentbuilder_test.go
Normal file
215
mongo/x/bsonx/bsoncore/bson_documentbuilder_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestDocumentBuilder(t *testing.T) {
|
||||
bits := math.Float64bits(3.14159)
|
||||
pi := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(pi, bits)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
fn interface{}
|
||||
params []interface{}
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
"AppendInt32",
|
||||
NewDocumentBuilder().AppendInt32,
|
||||
[]interface{}{"foobar", int32(256)},
|
||||
BuildDocumentFromElements(nil, AppendInt32Element(nil, "foobar", 256)),
|
||||
},
|
||||
{
|
||||
"AppendDouble",
|
||||
NewDocumentBuilder().AppendDouble,
|
||||
[]interface{}{"foobar", float64(3.14159)},
|
||||
BuildDocumentFromElements(nil, AppendDoubleElement(nil, "foobar", float64(3.14159))),
|
||||
},
|
||||
{
|
||||
"AppendString",
|
||||
NewDocumentBuilder().AppendString,
|
||||
[]interface{}{"foobar", "x"},
|
||||
BuildDocumentFromElements(nil, AppendStringElement(nil, "foobar", "x")),
|
||||
},
|
||||
{
|
||||
"AppendDocument",
|
||||
NewDocumentBuilder().AppendDocument,
|
||||
[]interface{}{"foobar", []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
BuildDocumentFromElements(nil, AppendDocumentElement(nil, "foobar", []byte{0x05, 0x00, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
"AppendArray",
|
||||
NewDocumentBuilder().AppendArray,
|
||||
[]interface{}{"foobar", []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
BuildDocumentFromElements(nil, AppendArrayElement(nil, "foobar", []byte{0x05, 0x00, 0x00, 0x00, 0x00})),
|
||||
},
|
||||
{
|
||||
"AppendBinary",
|
||||
NewDocumentBuilder().AppendBinary,
|
||||
[]interface{}{"foobar", byte(0x02), []byte{0x01, 0x02, 0x03}},
|
||||
BuildDocumentFromElements(nil, AppendBinaryElement(nil, "foobar", byte(0x02), []byte{0x01, 0x02, 0x03})),
|
||||
},
|
||||
{
|
||||
"AppendObjectID",
|
||||
NewDocumentBuilder().AppendObjectID,
|
||||
[]interface{}{
|
||||
"foobar",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
},
|
||||
BuildDocumentFromElements(nil, AppendObjectIDElement(nil, "foobar",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C})),
|
||||
},
|
||||
{
|
||||
"AppendBoolean",
|
||||
NewDocumentBuilder().AppendBoolean,
|
||||
[]interface{}{"foobar", true},
|
||||
BuildDocumentFromElements(nil, AppendBooleanElement(nil, "foobar", true)),
|
||||
},
|
||||
{
|
||||
"AppendDateTime",
|
||||
NewDocumentBuilder().AppendDateTime,
|
||||
[]interface{}{"foobar", int64(256)},
|
||||
BuildDocumentFromElements(nil, AppendDateTimeElement(nil, "foobar", int64(256))),
|
||||
},
|
||||
{
|
||||
"AppendNull",
|
||||
NewDocumentBuilder().AppendNull,
|
||||
[]interface{}{"foobar"},
|
||||
BuildDocumentFromElements(nil, AppendNullElement(nil, "foobar")),
|
||||
},
|
||||
{
|
||||
"AppendRegex",
|
||||
NewDocumentBuilder().AppendRegex,
|
||||
[]interface{}{"foobar", "bar", "baz"},
|
||||
BuildDocumentFromElements(nil, AppendRegexElement(nil, "foobar", "bar", "baz")),
|
||||
},
|
||||
{
|
||||
"AppendJavaScript",
|
||||
NewDocumentBuilder().AppendJavaScript,
|
||||
[]interface{}{"foobar", "barbaz"},
|
||||
BuildDocumentFromElements(nil, AppendJavaScriptElement(nil, "foobar", "barbaz")),
|
||||
},
|
||||
{
|
||||
"AppendCodeWithScope",
|
||||
NewDocumentBuilder().AppendCodeWithScope,
|
||||
[]interface{}{"foobar", "barbaz", Document([]byte{0x05, 0x00, 0x00, 0x00, 0x00})},
|
||||
BuildDocumentFromElements(nil, AppendCodeWithScopeElement(nil, "foobar", "barbaz", Document([]byte{0x05, 0x00, 0x00, 0x00, 0x00}))),
|
||||
},
|
||||
{
|
||||
"AppendTimestamp",
|
||||
NewDocumentBuilder().AppendTimestamp,
|
||||
[]interface{}{"foobar", uint32(65536), uint32(256)},
|
||||
BuildDocumentFromElements(nil, AppendTimestampElement(nil, "foobar", uint32(65536), uint32(256))),
|
||||
},
|
||||
{
|
||||
"AppendInt64",
|
||||
NewDocumentBuilder().AppendInt64,
|
||||
[]interface{}{"foobar", int64(4294967296)},
|
||||
BuildDocumentFromElements(nil, AppendInt64Element(nil, "foobar", int64(4294967296))),
|
||||
},
|
||||
{
|
||||
"AppendDecimal128",
|
||||
NewDocumentBuilder().AppendDecimal128,
|
||||
[]interface{}{"foobar", primitive.NewDecimal128(4294967296, 65536)},
|
||||
BuildDocumentFromElements(nil, AppendDecimal128Element(nil, "foobar", primitive.NewDecimal128(4294967296, 65536))),
|
||||
},
|
||||
{
|
||||
"AppendMaxKey",
|
||||
NewDocumentBuilder().AppendMaxKey,
|
||||
[]interface{}{"foobar"},
|
||||
BuildDocumentFromElements(nil, AppendMaxKeyElement(nil, "foobar")),
|
||||
},
|
||||
{
|
||||
"AppendMinKey",
|
||||
NewDocumentBuilder().AppendMinKey,
|
||||
[]interface{}{"foobar"},
|
||||
BuildDocumentFromElements(nil, AppendMinKeyElement(nil, "foobar")),
|
||||
},
|
||||
{
|
||||
"AppendSymbol",
|
||||
NewDocumentBuilder().AppendSymbol,
|
||||
[]interface{}{"foobar", "barbaz"},
|
||||
BuildDocumentFromElements(nil, AppendSymbolElement(nil, "foobar", "barbaz")),
|
||||
},
|
||||
{
|
||||
"AppendDBPointer",
|
||||
NewDocumentBuilder().AppendDBPointer,
|
||||
[]interface{}{"foobar", "barbaz",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}},
|
||||
BuildDocumentFromElements(nil, AppendDBPointerElement(nil, "foobar", "barbaz",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C})),
|
||||
},
|
||||
{
|
||||
"AppendUndefined",
|
||||
NewDocumentBuilder().AppendUndefined,
|
||||
[]interface{}{"foobar"},
|
||||
BuildDocumentFromElements(nil, AppendUndefinedElement(nil, "foobar")),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
fn := reflect.ValueOf(tc.fn)
|
||||
if fn.Kind() != reflect.Func {
|
||||
t.Fatalf("fn must be of kind Func but is a %v", fn.Kind())
|
||||
}
|
||||
if fn.Type().NumIn() != len(tc.params) {
|
||||
t.Fatalf("tc.params must match the number of params in tc.fn. params %d; fn %d", fn.Type().NumIn(), len(tc.params))
|
||||
}
|
||||
if fn.Type().NumOut() != 1 || fn.Type().Out(0) != reflect.TypeOf(&DocumentBuilder{}) {
|
||||
t.Fatalf("fn must have one return parameter and it must be a DocumentBuilder.")
|
||||
}
|
||||
params := make([]reflect.Value, 0, len(tc.params))
|
||||
for _, param := range tc.params {
|
||||
params = append(params, reflect.ValueOf(param))
|
||||
}
|
||||
results := fn.Call(params)
|
||||
got := results[0].Interface().(*DocumentBuilder)
|
||||
doc := got.Build()
|
||||
want := tc.expected
|
||||
if !bytes.Equal(doc, want) {
|
||||
t.Errorf("Did not receive expected bytes. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run("TestBuildTwoElements", func(t *testing.T) {
|
||||
intArr := BuildDocumentFromElements(nil, AppendInt32Element(nil, "0", int32(1)))
|
||||
expected := BuildDocumentFromElements(nil, AppendArrayElement(AppendInt32Element(nil, "x", int32(3)), "y", intArr))
|
||||
elem := NewArrayBuilder().AppendInt32(int32(1)).Build()
|
||||
result := NewDocumentBuilder().AppendInt32("x", int32(3)).AppendArray("y", elem).Build()
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", result, expected)
|
||||
}
|
||||
})
|
||||
t.Run("TestBuildInlineDocument", func(t *testing.T) {
|
||||
docElement := BuildDocumentFromElements(nil, AppendInt32Element(nil, "x", int32(256)))
|
||||
expected := Document(BuildDocumentFromElements(nil, AppendDocumentElement(nil, "y", docElement)))
|
||||
result := NewDocumentBuilder().StartDocument("y").AppendInt32("x", int32(256)).FinishDocument().Build()
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", result, expected)
|
||||
}
|
||||
})
|
||||
t.Run("TestBuildNestedInlineDocument", func(t *testing.T) {
|
||||
docElement := BuildDocumentFromElements(nil, AppendDoubleElement(nil, "x", 3.14))
|
||||
docInline := BuildDocumentFromElements(nil, AppendDocumentElement(nil, "y", docElement))
|
||||
expected := Document(BuildDocumentFromElements(nil, AppendDocumentElement(nil, "z", docInline)))
|
||||
result := NewDocumentBuilder().StartDocument("z").StartDocument("y").AppendDouble("x", 3.14).FinishDocument().FinishDocument().Build()
|
||||
if !bytes.Equal(result, expected) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", result, expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
862
mongo/x/bsonx/bsoncore/bsoncore.go
Normal file
862
mongo/x/bsonx/bsoncore/bsoncore.go
Normal file
@@ -0,0 +1,862 @@
|
||||
// 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 bsoncore contains functions that can be used to encode and decode BSON
|
||||
// elements and values to or from a slice of bytes. These functions are aimed at
|
||||
// allowing low level manipulation of BSON and can be used to build a higher
|
||||
// level BSON library.
|
||||
//
|
||||
// The Read* functions within this package return the values of the element and
|
||||
// a boolean indicating if the values are valid. A boolean was used instead of
|
||||
// an error because any error that would be returned would be the same: not
|
||||
// enough bytes. This library attempts to do no validation, it will only return
|
||||
// false if there are not enough bytes for an item to be read. For example, the
|
||||
// ReadDocument function checks the length, if that length is larger than the
|
||||
// number of bytes available, it will return false, if there are enough bytes, it
|
||||
// will return those bytes and true. It is the consumers responsibility to
|
||||
// validate those bytes.
|
||||
//
|
||||
// The Append* functions within this package will append the type value to the
|
||||
// given dst slice. If the slice has enough capacity, it will not grow the
|
||||
// slice. The Append*Element functions within this package operate in the same
|
||||
// way, but additionally append the BSON type and the key before the value.
|
||||
package bsoncore // import "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
const (
|
||||
// EmptyDocumentLength is the length of a document that has been started/ended but has no elements.
|
||||
EmptyDocumentLength = 5
|
||||
// nullTerminator is a string version of the 0 byte that is appended at the end of cstrings.
|
||||
nullTerminator = string(byte(0))
|
||||
invalidKeyPanicMsg = "BSON element keys cannot contain null bytes"
|
||||
invalidRegexPanicMsg = "BSON regex values cannot contain null bytes"
|
||||
)
|
||||
|
||||
// AppendType will append t to dst and return the extended buffer.
|
||||
func AppendType(dst []byte, t bsontype.Type) []byte { return append(dst, byte(t)) }
|
||||
|
||||
// AppendKey will append key to dst and return the extended buffer.
|
||||
func AppendKey(dst []byte, key string) []byte { return append(dst, key+nullTerminator...) }
|
||||
|
||||
// AppendHeader will append Type t and key to dst and return the extended
|
||||
// buffer.
|
||||
func AppendHeader(dst []byte, t bsontype.Type, key string) []byte {
|
||||
if !isValidCString(key) {
|
||||
panic(invalidKeyPanicMsg)
|
||||
}
|
||||
|
||||
dst = AppendType(dst, t)
|
||||
dst = append(dst, key...)
|
||||
return append(dst, 0x00)
|
||||
// return append(AppendType(dst, t), key+string(0x00)...)
|
||||
}
|
||||
|
||||
// TODO(skriptble): All of the Read* functions should return src resliced to start just after what was read.
|
||||
|
||||
// ReadType will return the first byte of the provided []byte as a type. If
|
||||
// there is no available byte, false is returned.
|
||||
func ReadType(src []byte) (bsontype.Type, []byte, bool) {
|
||||
if len(src) < 1 {
|
||||
return 0, src, false
|
||||
}
|
||||
return bsontype.Type(src[0]), src[1:], true
|
||||
}
|
||||
|
||||
// ReadKey will read a key from src. The 0x00 byte will not be present
|
||||
// in the returned string. If there are not enough bytes available, false is
|
||||
// returned.
|
||||
func ReadKey(src []byte) (string, []byte, bool) { return readcstring(src) }
|
||||
|
||||
// ReadKeyBytes will read a key from src as bytes. The 0x00 byte will
|
||||
// not be present in the returned string. If there are not enough bytes
|
||||
// available, false is returned.
|
||||
func ReadKeyBytes(src []byte) ([]byte, []byte, bool) { return readcstringbytes(src) }
|
||||
|
||||
// ReadHeader will read a type byte and a key from src. If both of these
|
||||
// values cannot be read, false is returned.
|
||||
func ReadHeader(src []byte) (t bsontype.Type, key string, rem []byte, ok bool) {
|
||||
t, rem, ok = ReadType(src)
|
||||
if !ok {
|
||||
return 0, "", src, false
|
||||
}
|
||||
key, rem, ok = ReadKey(rem)
|
||||
if !ok {
|
||||
return 0, "", src, false
|
||||
}
|
||||
|
||||
return t, key, rem, true
|
||||
}
|
||||
|
||||
// ReadHeaderBytes will read a type and a key from src and the remainder of the bytes
|
||||
// are returned as rem. If either the type or key cannot be red, ok will be false.
|
||||
func ReadHeaderBytes(src []byte) (header []byte, rem []byte, ok bool) {
|
||||
if len(src) < 1 {
|
||||
return nil, src, false
|
||||
}
|
||||
idx := bytes.IndexByte(src[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return nil, src, false
|
||||
}
|
||||
return src[:idx], src[idx+1:], true
|
||||
}
|
||||
|
||||
// ReadElement reads the next full element from src. It returns the element, the remaining bytes in
|
||||
// the slice, and a boolean indicating if the read was successful.
|
||||
func ReadElement(src []byte) (Element, []byte, bool) {
|
||||
if len(src) < 1 {
|
||||
return nil, src, false
|
||||
}
|
||||
t := bsontype.Type(src[0])
|
||||
idx := bytes.IndexByte(src[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return nil, src, false
|
||||
}
|
||||
length, ok := valueLength(src[idx+2:], t) // We add 2 here because we called IndexByte with src[1:]
|
||||
if !ok {
|
||||
return nil, src, false
|
||||
}
|
||||
elemLength := 1 + idx + 1 + int(length)
|
||||
if elemLength > len(src) {
|
||||
return nil, src, false
|
||||
}
|
||||
if elemLength < 0 {
|
||||
return nil, src, false
|
||||
}
|
||||
return src[:elemLength], src[elemLength:], true
|
||||
}
|
||||
|
||||
// AppendValueElement appends value to dst as an element using key as the element's key.
|
||||
func AppendValueElement(dst []byte, key string, value Value) []byte {
|
||||
dst = AppendHeader(dst, value.Type, key)
|
||||
dst = append(dst, value.Data...)
|
||||
return dst
|
||||
}
|
||||
|
||||
// ReadValue reads the next value as the provided types and returns a Value, the remaining bytes,
|
||||
// and a boolean indicating if the read was successful.
|
||||
func ReadValue(src []byte, t bsontype.Type) (Value, []byte, bool) {
|
||||
data, rem, ok := readValue(src, t)
|
||||
if !ok {
|
||||
return Value{}, src, false
|
||||
}
|
||||
return Value{Type: t, Data: data}, rem, true
|
||||
}
|
||||
|
||||
// AppendDouble will append f to dst and return the extended buffer.
|
||||
func AppendDouble(dst []byte, f float64) []byte {
|
||||
return appendu64(dst, math.Float64bits(f))
|
||||
}
|
||||
|
||||
// AppendDoubleElement will append a BSON double element using key and f to dst
|
||||
// and return the extended buffer.
|
||||
func AppendDoubleElement(dst []byte, key string, f float64) []byte {
|
||||
return AppendDouble(AppendHeader(dst, bsontype.Double, key), f)
|
||||
}
|
||||
|
||||
// ReadDouble will read a float64 from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadDouble(src []byte) (float64, []byte, bool) {
|
||||
bits, src, ok := readu64(src)
|
||||
if !ok {
|
||||
return 0, src, false
|
||||
}
|
||||
return math.Float64frombits(bits), src, true
|
||||
}
|
||||
|
||||
// AppendString will append s to dst and return the extended buffer.
|
||||
func AppendString(dst []byte, s string) []byte {
|
||||
return appendstring(dst, s)
|
||||
}
|
||||
|
||||
// AppendStringElement will append a BSON string element using key and val to dst
|
||||
// and return the extended buffer.
|
||||
func AppendStringElement(dst []byte, key, val string) []byte {
|
||||
return AppendString(AppendHeader(dst, bsontype.String, key), val)
|
||||
}
|
||||
|
||||
// ReadString will read a string from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadString(src []byte) (string, []byte, bool) {
|
||||
return readstring(src)
|
||||
}
|
||||
|
||||
// AppendDocumentStart reserves a document's length and returns the index where the length begins.
|
||||
// This index can later be used to write the length of the document.
|
||||
func AppendDocumentStart(dst []byte) (index int32, b []byte) {
|
||||
// TODO(skriptble): We really need AppendDocumentStart and AppendDocumentEnd. AppendDocumentStart would handle calling
|
||||
// TODO ReserveLength and providing the index of the start of the document. AppendDocumentEnd would handle taking that
|
||||
// TODO start index, adding the null byte, calculating the length, and filling in the length at the start of the
|
||||
// TODO document.
|
||||
return ReserveLength(dst)
|
||||
}
|
||||
|
||||
// AppendDocumentStartInline functions the same as AppendDocumentStart but takes a pointer to the
|
||||
// index int32 which allows this function to be used inline.
|
||||
func AppendDocumentStartInline(dst []byte, index *int32) []byte {
|
||||
idx, doc := AppendDocumentStart(dst)
|
||||
*index = idx
|
||||
return doc
|
||||
}
|
||||
|
||||
// AppendDocumentElementStart writes a document element header and then reserves the length bytes.
|
||||
func AppendDocumentElementStart(dst []byte, key string) (index int32, b []byte) {
|
||||
return AppendDocumentStart(AppendHeader(dst, bsontype.EmbeddedDocument, key))
|
||||
}
|
||||
|
||||
// AppendDocumentEnd writes the null byte for a document and updates the length of the document.
|
||||
// The index should be the beginning of the document's length bytes.
|
||||
func AppendDocumentEnd(dst []byte, index int32) ([]byte, error) {
|
||||
if int(index) > len(dst)-4 {
|
||||
return dst, fmt.Errorf("not enough bytes available after index to write length")
|
||||
}
|
||||
dst = append(dst, 0x00)
|
||||
dst = UpdateLength(dst, index, int32(len(dst[index:])))
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// AppendDocument will append doc to dst and return the extended buffer.
|
||||
func AppendDocument(dst []byte, doc []byte) []byte { return append(dst, doc...) }
|
||||
|
||||
// AppendDocumentElement will append a BSON embedded document element using key
|
||||
// and doc to dst and return the extended buffer.
|
||||
func AppendDocumentElement(dst []byte, key string, doc []byte) []byte {
|
||||
return AppendDocument(AppendHeader(dst, bsontype.EmbeddedDocument, key), doc)
|
||||
}
|
||||
|
||||
// BuildDocument will create a document with the given slice of elements and will append
|
||||
// it to dst and return the extended buffer.
|
||||
func BuildDocument(dst []byte, elems ...[]byte) []byte {
|
||||
idx, dst := ReserveLength(dst)
|
||||
for _, elem := range elems {
|
||||
dst = append(dst, elem...)
|
||||
}
|
||||
dst = append(dst, 0x00)
|
||||
dst = UpdateLength(dst, idx, int32(len(dst[idx:])))
|
||||
return dst
|
||||
}
|
||||
|
||||
// BuildDocumentValue creates an Embedded Document value from the given elements.
|
||||
func BuildDocumentValue(elems ...[]byte) Value {
|
||||
return Value{Type: bsontype.EmbeddedDocument, Data: BuildDocument(nil, elems...)}
|
||||
}
|
||||
|
||||
// BuildDocumentElement will append a BSON embedded document elemnt using key and the provided
|
||||
// elements and return the extended buffer.
|
||||
func BuildDocumentElement(dst []byte, key string, elems ...[]byte) []byte {
|
||||
return BuildDocument(AppendHeader(dst, bsontype.EmbeddedDocument, key), elems...)
|
||||
}
|
||||
|
||||
// BuildDocumentFromElements is an alaias for the BuildDocument function.
|
||||
var BuildDocumentFromElements = BuildDocument
|
||||
|
||||
// ReadDocument will read a document from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadDocument(src []byte) (doc Document, rem []byte, ok bool) { return readLengthBytes(src) }
|
||||
|
||||
// AppendArrayStart appends the length bytes to an array and then returns the index of the start
|
||||
// of those length bytes.
|
||||
func AppendArrayStart(dst []byte) (index int32, b []byte) { return ReserveLength(dst) }
|
||||
|
||||
// AppendArrayElementStart appends an array element header and then the length bytes for an array,
|
||||
// returning the index where the length starts.
|
||||
func AppendArrayElementStart(dst []byte, key string) (index int32, b []byte) {
|
||||
return AppendArrayStart(AppendHeader(dst, bsontype.Array, key))
|
||||
}
|
||||
|
||||
// AppendArrayEnd appends the null byte to an array and calculates the length, inserting that
|
||||
// calculated length starting at index.
|
||||
func AppendArrayEnd(dst []byte, index int32) ([]byte, error) { return AppendDocumentEnd(dst, index) }
|
||||
|
||||
// AppendArray will append arr to dst and return the extended buffer.
|
||||
func AppendArray(dst []byte, arr []byte) []byte { return append(dst, arr...) }
|
||||
|
||||
// AppendArrayElement will append a BSON array element using key and arr to dst
|
||||
// and return the extended buffer.
|
||||
func AppendArrayElement(dst []byte, key string, arr []byte) []byte {
|
||||
return AppendArray(AppendHeader(dst, bsontype.Array, key), arr)
|
||||
}
|
||||
|
||||
// BuildArray will append a BSON array to dst built from values.
|
||||
func BuildArray(dst []byte, values ...Value) []byte {
|
||||
idx, dst := ReserveLength(dst)
|
||||
for pos, val := range values {
|
||||
dst = AppendValueElement(dst, strconv.Itoa(pos), val)
|
||||
}
|
||||
dst = append(dst, 0x00)
|
||||
dst = UpdateLength(dst, idx, int32(len(dst[idx:])))
|
||||
return dst
|
||||
}
|
||||
|
||||
// BuildArrayElement will create an array element using the provided values.
|
||||
func BuildArrayElement(dst []byte, key string, values ...Value) []byte {
|
||||
return BuildArray(AppendHeader(dst, bsontype.Array, key), values...)
|
||||
}
|
||||
|
||||
// ReadArray will read an array from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadArray(src []byte) (arr Array, rem []byte, ok bool) { return readLengthBytes(src) }
|
||||
|
||||
// AppendBinary will append subtype and b to dst and return the extended buffer.
|
||||
func AppendBinary(dst []byte, subtype byte, b []byte) []byte {
|
||||
if subtype == 0x02 {
|
||||
return appendBinarySubtype2(dst, subtype, b)
|
||||
}
|
||||
dst = append(appendLength(dst, int32(len(b))), subtype)
|
||||
return append(dst, b...)
|
||||
}
|
||||
|
||||
// AppendBinaryElement will append a BSON binary element using key, subtype, and
|
||||
// b to dst and return the extended buffer.
|
||||
func AppendBinaryElement(dst []byte, key string, subtype byte, b []byte) []byte {
|
||||
return AppendBinary(AppendHeader(dst, bsontype.Binary, key), subtype, b)
|
||||
}
|
||||
|
||||
// ReadBinary will read a subtype and bin from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadBinary(src []byte) (subtype byte, bin []byte, rem []byte, ok bool) {
|
||||
length, rem, ok := ReadLength(src)
|
||||
if !ok {
|
||||
return 0x00, nil, src, false
|
||||
}
|
||||
if len(rem) < 1 { // subtype
|
||||
return 0x00, nil, src, false
|
||||
}
|
||||
subtype, rem = rem[0], rem[1:]
|
||||
|
||||
if len(rem) < int(length) {
|
||||
return 0x00, nil, src, false
|
||||
}
|
||||
|
||||
if subtype == 0x02 {
|
||||
length, rem, ok = ReadLength(rem)
|
||||
if !ok || len(rem) < int(length) {
|
||||
return 0x00, nil, src, false
|
||||
}
|
||||
}
|
||||
|
||||
return subtype, rem[:length], rem[length:], true
|
||||
}
|
||||
|
||||
// AppendUndefinedElement will append a BSON undefined element using key to dst
|
||||
// and return the extended buffer.
|
||||
func AppendUndefinedElement(dst []byte, key string) []byte {
|
||||
return AppendHeader(dst, bsontype.Undefined, key)
|
||||
}
|
||||
|
||||
// AppendObjectID will append oid to dst and return the extended buffer.
|
||||
func AppendObjectID(dst []byte, oid primitive.ObjectID) []byte { return append(dst, oid[:]...) }
|
||||
|
||||
// AppendObjectIDElement will append a BSON ObjectID element using key and oid to dst
|
||||
// and return the extended buffer.
|
||||
func AppendObjectIDElement(dst []byte, key string, oid primitive.ObjectID) []byte {
|
||||
return AppendObjectID(AppendHeader(dst, bsontype.ObjectID, key), oid)
|
||||
}
|
||||
|
||||
// ReadObjectID will read an ObjectID from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadObjectID(src []byte) (primitive.ObjectID, []byte, bool) {
|
||||
if len(src) < 12 {
|
||||
return primitive.ObjectID{}, src, false
|
||||
}
|
||||
var oid primitive.ObjectID
|
||||
copy(oid[:], src[0:12])
|
||||
return oid, src[12:], true
|
||||
}
|
||||
|
||||
// AppendBoolean will append b to dst and return the extended buffer.
|
||||
func AppendBoolean(dst []byte, b bool) []byte {
|
||||
if b {
|
||||
return append(dst, 0x01)
|
||||
}
|
||||
return append(dst, 0x00)
|
||||
}
|
||||
|
||||
// AppendBooleanElement will append a BSON boolean element using key and b to dst
|
||||
// and return the extended buffer.
|
||||
func AppendBooleanElement(dst []byte, key string, b bool) []byte {
|
||||
return AppendBoolean(AppendHeader(dst, bsontype.Boolean, key), b)
|
||||
}
|
||||
|
||||
// ReadBoolean will read a bool from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadBoolean(src []byte) (bool, []byte, bool) {
|
||||
if len(src) < 1 {
|
||||
return false, src, false
|
||||
}
|
||||
|
||||
return src[0] == 0x01, src[1:], true
|
||||
}
|
||||
|
||||
// AppendDateTime will append dt to dst and return the extended buffer.
|
||||
func AppendDateTime(dst []byte, dt int64) []byte { return appendi64(dst, dt) }
|
||||
|
||||
// AppendDateTimeElement will append a BSON datetime element using key and dt to dst
|
||||
// and return the extended buffer.
|
||||
func AppendDateTimeElement(dst []byte, key string, dt int64) []byte {
|
||||
return AppendDateTime(AppendHeader(dst, bsontype.DateTime, key), dt)
|
||||
}
|
||||
|
||||
// ReadDateTime will read an int64 datetime from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadDateTime(src []byte) (int64, []byte, bool) { return readi64(src) }
|
||||
|
||||
// AppendTime will append time as a BSON DateTime to dst and return the extended buffer.
|
||||
func AppendTime(dst []byte, t time.Time) []byte {
|
||||
return AppendDateTime(dst, t.Unix()*1000+int64(t.Nanosecond()/1e6))
|
||||
}
|
||||
|
||||
// AppendTimeElement will append a BSON datetime element using key and dt to dst
|
||||
// and return the extended buffer.
|
||||
func AppendTimeElement(dst []byte, key string, t time.Time) []byte {
|
||||
return AppendTime(AppendHeader(dst, bsontype.DateTime, key), t)
|
||||
}
|
||||
|
||||
// ReadTime will read an time.Time datetime from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadTime(src []byte) (time.Time, []byte, bool) {
|
||||
dt, rem, ok := readi64(src)
|
||||
return time.Unix(dt/1e3, dt%1e3*1e6), rem, ok
|
||||
}
|
||||
|
||||
// AppendNullElement will append a BSON null element using key to dst
|
||||
// and return the extended buffer.
|
||||
func AppendNullElement(dst []byte, key string) []byte { return AppendHeader(dst, bsontype.Null, key) }
|
||||
|
||||
// AppendRegex will append pattern and options to dst and return the extended buffer.
|
||||
func AppendRegex(dst []byte, pattern, options string) []byte {
|
||||
if !isValidCString(pattern) || !isValidCString(options) {
|
||||
panic(invalidRegexPanicMsg)
|
||||
}
|
||||
|
||||
return append(dst, pattern+nullTerminator+options+nullTerminator...)
|
||||
}
|
||||
|
||||
// AppendRegexElement will append a BSON regex element using key, pattern, and
|
||||
// options to dst and return the extended buffer.
|
||||
func AppendRegexElement(dst []byte, key, pattern, options string) []byte {
|
||||
return AppendRegex(AppendHeader(dst, bsontype.Regex, key), pattern, options)
|
||||
}
|
||||
|
||||
// ReadRegex will read a pattern and options from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadRegex(src []byte) (pattern, options string, rem []byte, ok bool) {
|
||||
pattern, rem, ok = readcstring(src)
|
||||
if !ok {
|
||||
return "", "", src, false
|
||||
}
|
||||
options, rem, ok = readcstring(rem)
|
||||
if !ok {
|
||||
return "", "", src, false
|
||||
}
|
||||
return pattern, options, rem, true
|
||||
}
|
||||
|
||||
// AppendDBPointer will append ns and oid to dst and return the extended buffer.
|
||||
func AppendDBPointer(dst []byte, ns string, oid primitive.ObjectID) []byte {
|
||||
return append(appendstring(dst, ns), oid[:]...)
|
||||
}
|
||||
|
||||
// AppendDBPointerElement will append a BSON DBPointer element using key, ns,
|
||||
// and oid to dst and return the extended buffer.
|
||||
func AppendDBPointerElement(dst []byte, key, ns string, oid primitive.ObjectID) []byte {
|
||||
return AppendDBPointer(AppendHeader(dst, bsontype.DBPointer, key), ns, oid)
|
||||
}
|
||||
|
||||
// ReadDBPointer will read a ns and oid from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadDBPointer(src []byte) (ns string, oid primitive.ObjectID, rem []byte, ok bool) {
|
||||
ns, rem, ok = readstring(src)
|
||||
if !ok {
|
||||
return "", primitive.ObjectID{}, src, false
|
||||
}
|
||||
oid, rem, ok = ReadObjectID(rem)
|
||||
if !ok {
|
||||
return "", primitive.ObjectID{}, src, false
|
||||
}
|
||||
return ns, oid, rem, true
|
||||
}
|
||||
|
||||
// AppendJavaScript will append js to dst and return the extended buffer.
|
||||
func AppendJavaScript(dst []byte, js string) []byte { return appendstring(dst, js) }
|
||||
|
||||
// AppendJavaScriptElement will append a BSON JavaScript element using key and
|
||||
// js to dst and return the extended buffer.
|
||||
func AppendJavaScriptElement(dst []byte, key, js string) []byte {
|
||||
return AppendJavaScript(AppendHeader(dst, bsontype.JavaScript, key), js)
|
||||
}
|
||||
|
||||
// ReadJavaScript will read a js string from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadJavaScript(src []byte) (js string, rem []byte, ok bool) { return readstring(src) }
|
||||
|
||||
// AppendSymbol will append symbol to dst and return the extended buffer.
|
||||
func AppendSymbol(dst []byte, symbol string) []byte { return appendstring(dst, symbol) }
|
||||
|
||||
// AppendSymbolElement will append a BSON symbol element using key and symbol to dst
|
||||
// and return the extended buffer.
|
||||
func AppendSymbolElement(dst []byte, key, symbol string) []byte {
|
||||
return AppendSymbol(AppendHeader(dst, bsontype.Symbol, key), symbol)
|
||||
}
|
||||
|
||||
// ReadSymbol will read a symbol string from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadSymbol(src []byte) (symbol string, rem []byte, ok bool) { return readstring(src) }
|
||||
|
||||
// AppendCodeWithScope will append code and scope to dst and return the extended buffer.
|
||||
func AppendCodeWithScope(dst []byte, code string, scope []byte) []byte {
|
||||
length := int32(4 + 4 + len(code) + 1 + len(scope)) // length of cws, length of code, code, 0x00, scope
|
||||
dst = appendLength(dst, length)
|
||||
|
||||
return append(appendstring(dst, code), scope...)
|
||||
}
|
||||
|
||||
// AppendCodeWithScopeElement will append a BSON code with scope element using
|
||||
// key, code, and scope to dst
|
||||
// and return the extended buffer.
|
||||
func AppendCodeWithScopeElement(dst []byte, key, code string, scope []byte) []byte {
|
||||
return AppendCodeWithScope(AppendHeader(dst, bsontype.CodeWithScope, key), code, scope)
|
||||
}
|
||||
|
||||
// ReadCodeWithScope will read code and scope from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadCodeWithScope(src []byte) (code string, scope []byte, rem []byte, ok bool) {
|
||||
length, rem, ok := ReadLength(src)
|
||||
if !ok || len(src) < int(length) {
|
||||
return "", nil, src, false
|
||||
}
|
||||
|
||||
code, rem, ok = readstring(rem)
|
||||
if !ok {
|
||||
return "", nil, src, false
|
||||
}
|
||||
|
||||
scope, rem, ok = ReadDocument(rem)
|
||||
if !ok {
|
||||
return "", nil, src, false
|
||||
}
|
||||
return code, scope, rem, true
|
||||
}
|
||||
|
||||
// AppendInt32 will append i32 to dst and return the extended buffer.
|
||||
func AppendInt32(dst []byte, i32 int32) []byte { return appendi32(dst, i32) }
|
||||
|
||||
// AppendInt32Element will append a BSON int32 element using key and i32 to dst
|
||||
// and return the extended buffer.
|
||||
func AppendInt32Element(dst []byte, key string, i32 int32) []byte {
|
||||
return AppendInt32(AppendHeader(dst, bsontype.Int32, key), i32)
|
||||
}
|
||||
|
||||
// ReadInt32 will read an int32 from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadInt32(src []byte) (int32, []byte, bool) { return readi32(src) }
|
||||
|
||||
// AppendTimestamp will append t and i to dst and return the extended buffer.
|
||||
func AppendTimestamp(dst []byte, t, i uint32) []byte {
|
||||
return appendu32(appendu32(dst, i), t) // i is the lower 4 bytes, t is the higher 4 bytes
|
||||
}
|
||||
|
||||
// AppendTimestampElement will append a BSON timestamp element using key, t, and
|
||||
// i to dst and return the extended buffer.
|
||||
func AppendTimestampElement(dst []byte, key string, t, i uint32) []byte {
|
||||
return AppendTimestamp(AppendHeader(dst, bsontype.Timestamp, key), t, i)
|
||||
}
|
||||
|
||||
// ReadTimestamp will read t and i from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadTimestamp(src []byte) (t, i uint32, rem []byte, ok bool) {
|
||||
i, rem, ok = readu32(src)
|
||||
if !ok {
|
||||
return 0, 0, src, false
|
||||
}
|
||||
t, rem, ok = readu32(rem)
|
||||
if !ok {
|
||||
return 0, 0, src, false
|
||||
}
|
||||
return t, i, rem, true
|
||||
}
|
||||
|
||||
// AppendInt64 will append i64 to dst and return the extended buffer.
|
||||
func AppendInt64(dst []byte, i64 int64) []byte { return appendi64(dst, i64) }
|
||||
|
||||
// AppendInt64Element will append a BSON int64 element using key and i64 to dst
|
||||
// and return the extended buffer.
|
||||
func AppendInt64Element(dst []byte, key string, i64 int64) []byte {
|
||||
return AppendInt64(AppendHeader(dst, bsontype.Int64, key), i64)
|
||||
}
|
||||
|
||||
// ReadInt64 will read an int64 from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadInt64(src []byte) (int64, []byte, bool) { return readi64(src) }
|
||||
|
||||
// AppendDecimal128 will append d128 to dst and return the extended buffer.
|
||||
func AppendDecimal128(dst []byte, d128 primitive.Decimal128) []byte {
|
||||
high, low := d128.GetBytes()
|
||||
return appendu64(appendu64(dst, low), high)
|
||||
}
|
||||
|
||||
// AppendDecimal128Element will append a BSON primitive.28 element using key and
|
||||
// d128 to dst and return the extended buffer.
|
||||
func AppendDecimal128Element(dst []byte, key string, d128 primitive.Decimal128) []byte {
|
||||
return AppendDecimal128(AppendHeader(dst, bsontype.Decimal128, key), d128)
|
||||
}
|
||||
|
||||
// ReadDecimal128 will read a primitive.Decimal128 from src. If there are not enough bytes it
|
||||
// will return false.
|
||||
func ReadDecimal128(src []byte) (primitive.Decimal128, []byte, bool) {
|
||||
l, rem, ok := readu64(src)
|
||||
if !ok {
|
||||
return primitive.Decimal128{}, src, false
|
||||
}
|
||||
|
||||
h, rem, ok := readu64(rem)
|
||||
if !ok {
|
||||
return primitive.Decimal128{}, src, false
|
||||
}
|
||||
|
||||
return primitive.NewDecimal128(h, l), rem, true
|
||||
}
|
||||
|
||||
// AppendMaxKeyElement will append a BSON max key element using key to dst
|
||||
// and return the extended buffer.
|
||||
func AppendMaxKeyElement(dst []byte, key string) []byte {
|
||||
return AppendHeader(dst, bsontype.MaxKey, key)
|
||||
}
|
||||
|
||||
// AppendMinKeyElement will append a BSON min key element using key to dst
|
||||
// and return the extended buffer.
|
||||
func AppendMinKeyElement(dst []byte, key string) []byte {
|
||||
return AppendHeader(dst, bsontype.MinKey, key)
|
||||
}
|
||||
|
||||
// EqualValue will return true if the two values are equal.
|
||||
func EqualValue(t1, t2 bsontype.Type, v1, v2 []byte) bool {
|
||||
if t1 != t2 {
|
||||
return false
|
||||
}
|
||||
v1, _, ok := readValue(v1, t1)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v2, _, ok = readValue(v2, t2)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(v1, v2)
|
||||
}
|
||||
|
||||
// valueLength will determine the length of the next value contained in src as if it
|
||||
// is type t. The returned bool will be false if there are not enough bytes in src for
|
||||
// a value of type t.
|
||||
func valueLength(src []byte, t bsontype.Type) (int32, bool) {
|
||||
var length int32
|
||||
ok := true
|
||||
switch t {
|
||||
case bsontype.Array, bsontype.EmbeddedDocument, bsontype.CodeWithScope:
|
||||
length, _, ok = ReadLength(src)
|
||||
case bsontype.Binary:
|
||||
length, _, ok = ReadLength(src)
|
||||
length += 4 + 1 // binary length + subtype byte
|
||||
case bsontype.Boolean:
|
||||
length = 1
|
||||
case bsontype.DBPointer:
|
||||
length, _, ok = ReadLength(src)
|
||||
length += 4 + 12 // string length + ObjectID length
|
||||
case bsontype.DateTime, bsontype.Double, bsontype.Int64, bsontype.Timestamp:
|
||||
length = 8
|
||||
case bsontype.Decimal128:
|
||||
length = 16
|
||||
case bsontype.Int32:
|
||||
length = 4
|
||||
case bsontype.JavaScript, bsontype.String, bsontype.Symbol:
|
||||
length, _, ok = ReadLength(src)
|
||||
length += 4
|
||||
case bsontype.MaxKey, bsontype.MinKey, bsontype.Null, bsontype.Undefined:
|
||||
length = 0
|
||||
case bsontype.ObjectID:
|
||||
length = 12
|
||||
case bsontype.Regex:
|
||||
regex := bytes.IndexByte(src, 0x00)
|
||||
if regex < 0 {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
pattern := bytes.IndexByte(src[regex+1:], 0x00)
|
||||
if pattern < 0 {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
length = int32(int64(regex) + 1 + int64(pattern) + 1)
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
|
||||
return length, ok
|
||||
}
|
||||
|
||||
func readValue(src []byte, t bsontype.Type) ([]byte, []byte, bool) {
|
||||
length, ok := valueLength(src, t)
|
||||
if !ok || int(length) > len(src) {
|
||||
return nil, src, false
|
||||
}
|
||||
|
||||
return src[:length], src[length:], true
|
||||
}
|
||||
|
||||
// ReserveLength reserves the space required for length and returns the index where to write the length
|
||||
// and the []byte with reserved space.
|
||||
func ReserveLength(dst []byte) (int32, []byte) {
|
||||
index := len(dst)
|
||||
return int32(index), append(dst, 0x00, 0x00, 0x00, 0x00)
|
||||
}
|
||||
|
||||
// UpdateLength updates the length at index with length and returns the []byte.
|
||||
func UpdateLength(dst []byte, index, length int32) []byte {
|
||||
dst[index] = byte(length)
|
||||
dst[index+1] = byte(length >> 8)
|
||||
dst[index+2] = byte(length >> 16)
|
||||
dst[index+3] = byte(length >> 24)
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendLength(dst []byte, l int32) []byte { return appendi32(dst, l) }
|
||||
|
||||
func appendi32(dst []byte, i32 int32) []byte {
|
||||
return append(dst, byte(i32), byte(i32>>8), byte(i32>>16), byte(i32>>24))
|
||||
}
|
||||
|
||||
// ReadLength reads an int32 length from src and returns the length and the remaining bytes. If
|
||||
// there aren't enough bytes to read a valid length, src is returned unomdified and the returned
|
||||
// bool will be false.
|
||||
func ReadLength(src []byte) (int32, []byte, bool) {
|
||||
ln, src, ok := readi32(src)
|
||||
if ln < 0 {
|
||||
return ln, src, false
|
||||
}
|
||||
return ln, src, ok
|
||||
}
|
||||
|
||||
func readi32(src []byte) (int32, []byte, bool) {
|
||||
if len(src) < 4 {
|
||||
return 0, src, false
|
||||
}
|
||||
return (int32(src[0]) | int32(src[1])<<8 | int32(src[2])<<16 | int32(src[3])<<24), src[4:], true
|
||||
}
|
||||
|
||||
func appendi64(dst []byte, i64 int64) []byte {
|
||||
return append(dst,
|
||||
byte(i64), byte(i64>>8), byte(i64>>16), byte(i64>>24),
|
||||
byte(i64>>32), byte(i64>>40), byte(i64>>48), byte(i64>>56),
|
||||
)
|
||||
}
|
||||
|
||||
func readi64(src []byte) (int64, []byte, bool) {
|
||||
if len(src) < 8 {
|
||||
return 0, src, false
|
||||
}
|
||||
i64 := (int64(src[0]) | int64(src[1])<<8 | int64(src[2])<<16 | int64(src[3])<<24 |
|
||||
int64(src[4])<<32 | int64(src[5])<<40 | int64(src[6])<<48 | int64(src[7])<<56)
|
||||
return i64, src[8:], true
|
||||
}
|
||||
|
||||
func appendu32(dst []byte, u32 uint32) []byte {
|
||||
return append(dst, byte(u32), byte(u32>>8), byte(u32>>16), byte(u32>>24))
|
||||
}
|
||||
|
||||
func readu32(src []byte) (uint32, []byte, bool) {
|
||||
if len(src) < 4 {
|
||||
return 0, src, false
|
||||
}
|
||||
|
||||
return (uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24), src[4:], true
|
||||
}
|
||||
|
||||
func appendu64(dst []byte, u64 uint64) []byte {
|
||||
return append(dst,
|
||||
byte(u64), byte(u64>>8), byte(u64>>16), byte(u64>>24),
|
||||
byte(u64>>32), byte(u64>>40), byte(u64>>48), byte(u64>>56),
|
||||
)
|
||||
}
|
||||
|
||||
func readu64(src []byte) (uint64, []byte, bool) {
|
||||
if len(src) < 8 {
|
||||
return 0, src, false
|
||||
}
|
||||
u64 := (uint64(src[0]) | uint64(src[1])<<8 | uint64(src[2])<<16 | uint64(src[3])<<24 |
|
||||
uint64(src[4])<<32 | uint64(src[5])<<40 | uint64(src[6])<<48 | uint64(src[7])<<56)
|
||||
return u64, src[8:], true
|
||||
}
|
||||
|
||||
// keep in sync with readcstringbytes
|
||||
func readcstring(src []byte) (string, []byte, bool) {
|
||||
idx := bytes.IndexByte(src, 0x00)
|
||||
if idx < 0 {
|
||||
return "", src, false
|
||||
}
|
||||
return string(src[:idx]), src[idx+1:], true
|
||||
}
|
||||
|
||||
// keep in sync with readcstring
|
||||
func readcstringbytes(src []byte) ([]byte, []byte, bool) {
|
||||
idx := bytes.IndexByte(src, 0x00)
|
||||
if idx < 0 {
|
||||
return nil, src, false
|
||||
}
|
||||
return src[:idx], src[idx+1:], true
|
||||
}
|
||||
|
||||
func appendstring(dst []byte, s string) []byte {
|
||||
l := int32(len(s) + 1)
|
||||
dst = appendLength(dst, l)
|
||||
dst = append(dst, s...)
|
||||
return append(dst, 0x00)
|
||||
}
|
||||
|
||||
func readstring(src []byte) (string, []byte, bool) {
|
||||
l, rem, ok := ReadLength(src)
|
||||
if !ok {
|
||||
return "", src, false
|
||||
}
|
||||
if len(src[4:]) < int(l) || l == 0 {
|
||||
return "", src, false
|
||||
}
|
||||
|
||||
return string(rem[:l-1]), rem[l:], true
|
||||
}
|
||||
|
||||
// readLengthBytes attempts to read a length and that number of bytes. This
|
||||
// function requires that the length include the four bytes for itself.
|
||||
func readLengthBytes(src []byte) ([]byte, []byte, bool) {
|
||||
l, _, ok := ReadLength(src)
|
||||
if !ok {
|
||||
return nil, src, false
|
||||
}
|
||||
if len(src) < int(l) {
|
||||
return nil, src, false
|
||||
}
|
||||
return src[:l], src[l:], true
|
||||
}
|
||||
|
||||
func appendBinarySubtype2(dst []byte, subtype byte, b []byte) []byte {
|
||||
dst = appendLength(dst, int32(len(b)+4)) // The bytes we'll encode need to be 4 larger for the length bytes
|
||||
dst = append(dst, subtype)
|
||||
dst = appendLength(dst, int32(len(b)))
|
||||
return append(dst, b...)
|
||||
}
|
||||
|
||||
func isValidCString(cs string) bool {
|
||||
return !strings.ContainsRune(cs, '\x00')
|
||||
}
|
||||
959
mongo/x/bsonx/bsoncore/bsoncore_test.go
Normal file
959
mongo/x/bsonx/bsoncore/bsoncore_test.go
Normal file
@@ -0,0 +1,959 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/internal/testutil/assert"
|
||||
)
|
||||
|
||||
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 TestAppend(t *testing.T) {
|
||||
bits := math.Float64bits(3.14159)
|
||||
pi := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(pi, bits)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
fn interface{}
|
||||
params []interface{}
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
"AppendType",
|
||||
AppendType,
|
||||
[]interface{}{make([]byte, 0), bsontype.Null},
|
||||
[]byte{byte(bsontype.Null)},
|
||||
},
|
||||
{
|
||||
"AppendKey",
|
||||
AppendKey,
|
||||
[]interface{}{make([]byte, 0), "foobar"},
|
||||
[]byte{'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendHeader",
|
||||
AppendHeader,
|
||||
[]interface{}{make([]byte, 0), bsontype.Null, "foobar"},
|
||||
[]byte{byte(bsontype.Null), 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendValueElement",
|
||||
AppendValueElement,
|
||||
[]interface{}{make([]byte, 0), "testing", Value{Type: bsontype.Boolean, Data: []byte{0x01}}},
|
||||
[]byte{byte(bsontype.Boolean), 't', 'e', 's', 't', 'i', 'n', 'g', 0x00, 0x01},
|
||||
},
|
||||
{
|
||||
"AppendDouble",
|
||||
AppendDouble,
|
||||
[]interface{}{make([]byte, 0), float64(3.14159)},
|
||||
pi,
|
||||
},
|
||||
{
|
||||
"AppendDoubleElement",
|
||||
AppendDoubleElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", float64(3.14159)},
|
||||
append([]byte{byte(bsontype.Double), 'f', 'o', 'o', 'b', 'a', 'r', 0x00}, pi...),
|
||||
},
|
||||
{
|
||||
"AppendString",
|
||||
AppendString,
|
||||
[]interface{}{make([]byte, 0), "barbaz"},
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendStringElement",
|
||||
AppendStringElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", "barbaz"},
|
||||
[]byte{byte(bsontype.String),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendDocument",
|
||||
AppendDocument,
|
||||
[]interface{}{[]byte{0x05, 0x00, 0x00, 0x00, 0x00}, []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
[]byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendDocumentElement",
|
||||
AppendDocumentElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
[]byte{byte(bsontype.EmbeddedDocument),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x05, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendArray",
|
||||
AppendArray,
|
||||
[]interface{}{[]byte{0x05, 0x00, 0x00, 0x00, 0x00}, []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
[]byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendArrayElement",
|
||||
AppendArrayElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
[]byte{byte(bsontype.Array),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x05, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"BuildArray",
|
||||
BuildArray,
|
||||
[]interface{}{make([]byte, 0), Value{Type: bsontype.Double, Data: AppendDouble(nil, 3.14159)}},
|
||||
[]byte{
|
||||
0x10, 0x00, 0x00, 0x00,
|
||||
byte(bsontype.Double), '0', 0x00,
|
||||
pi[0], pi[1], pi[2], pi[3], pi[4], pi[5], pi[6], pi[7],
|
||||
0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"BuildArrayElement",
|
||||
BuildArrayElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", Value{Type: bsontype.Double, Data: AppendDouble(nil, 3.14159)}},
|
||||
[]byte{byte(bsontype.Array),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x10, 0x00, 0x00, 0x00,
|
||||
byte(bsontype.Double), '0', 0x00,
|
||||
pi[0], pi[1], pi[2], pi[3], pi[4], pi[5], pi[6], pi[7],
|
||||
0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendBinary Subtype2",
|
||||
AppendBinary,
|
||||
[]interface{}{make([]byte, 0), byte(0x02), []byte{0x01, 0x02, 0x03}},
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
|
||||
},
|
||||
{
|
||||
"AppendBinaryElement Subtype 2",
|
||||
AppendBinaryElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", byte(0x02), []byte{0x01, 0x02, 0x03}},
|
||||
[]byte{byte(bsontype.Binary),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x07, 0x00, 0x00, 0x00,
|
||||
0x02,
|
||||
0x03, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendBinary",
|
||||
AppendBinary,
|
||||
[]interface{}{make([]byte, 0), byte(0xFF), []byte{0x01, 0x02, 0x03}},
|
||||
[]byte{0x03, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x02, 0x03},
|
||||
},
|
||||
{
|
||||
"AppendBinaryElement",
|
||||
AppendBinaryElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", byte(0xFF), []byte{0x01, 0x02, 0x03}},
|
||||
[]byte{byte(bsontype.Binary),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x03, 0x00, 0x00, 0x00,
|
||||
0xFF,
|
||||
0x01, 0x02, 0x03,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendUndefinedElement",
|
||||
AppendUndefinedElement,
|
||||
[]interface{}{make([]byte, 0), "foobar"},
|
||||
[]byte{byte(bsontype.Undefined), 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendObjectID",
|
||||
AppendObjectID,
|
||||
[]interface{}{
|
||||
make([]byte, 0),
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
},
|
||||
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
},
|
||||
{
|
||||
"AppendObjectIDElement",
|
||||
AppendObjectIDElement,
|
||||
[]interface{}{
|
||||
make([]byte, 0), "foobar",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
},
|
||||
[]byte{byte(bsontype.ObjectID),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendBoolean (true)",
|
||||
AppendBoolean,
|
||||
[]interface{}{make([]byte, 0), true},
|
||||
[]byte{0x01},
|
||||
},
|
||||
{
|
||||
"AppendBoolean (false)",
|
||||
AppendBoolean,
|
||||
[]interface{}{make([]byte, 0), false},
|
||||
[]byte{0x00},
|
||||
},
|
||||
{
|
||||
"AppendBooleanElement",
|
||||
AppendBooleanElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", true},
|
||||
[]byte{byte(bsontype.Boolean), 'f', 'o', 'o', 'b', 'a', 'r', 0x00, 0x01},
|
||||
},
|
||||
{
|
||||
"AppendDateTime",
|
||||
AppendDateTime,
|
||||
[]interface{}{make([]byte, 0), int64(256)},
|
||||
[]byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendDateTimeElement",
|
||||
AppendDateTimeElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", int64(256)},
|
||||
[]byte{byte(bsontype.DateTime), 'f', 'o', 'o', 'b', 'a', 'r', 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendNullElement",
|
||||
AppendNullElement,
|
||||
[]interface{}{make([]byte, 0), "foobar"},
|
||||
[]byte{byte(bsontype.Null), 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendRegex",
|
||||
AppendRegex,
|
||||
[]interface{}{make([]byte, 0), "bar", "baz"},
|
||||
[]byte{'b', 'a', 'r', 0x00, 'b', 'a', 'z', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendRegexElement",
|
||||
AppendRegexElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", "bar", "baz"},
|
||||
[]byte{byte(bsontype.Regex),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
'b', 'a', 'r', 0x00, 'b', 'a', 'z', 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendDBPointer",
|
||||
AppendDBPointer,
|
||||
[]interface{}{
|
||||
make([]byte, 0),
|
||||
"foobar",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
},
|
||||
[]byte{
|
||||
0x07, 0x00, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendDBPointerElement",
|
||||
AppendDBPointerElement,
|
||||
[]interface{}{
|
||||
make([]byte, 0), "foobar",
|
||||
"barbaz",
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
},
|
||||
[]byte{byte(bsontype.DBPointer),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendJavaScript",
|
||||
AppendJavaScript,
|
||||
[]interface{}{make([]byte, 0), "barbaz"},
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendJavaScriptElement",
|
||||
AppendJavaScriptElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", "barbaz"},
|
||||
[]byte{byte(bsontype.JavaScript),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendSymbol",
|
||||
AppendSymbol,
|
||||
[]interface{}{make([]byte, 0), "barbaz"},
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendSymbolElement",
|
||||
AppendSymbolElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", "barbaz"},
|
||||
[]byte{byte(bsontype.Symbol),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendCodeWithScope",
|
||||
AppendCodeWithScope,
|
||||
[]interface{}{[]byte{0x05, 0x00, 0x00, 0x00, 0x00}, "foobar", []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
[]byte{0x05, 0x00, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x05, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendCodeWithScopeElement",
|
||||
AppendCodeWithScopeElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", "barbaz", []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
[]byte{byte(bsontype.CodeWithScope),
|
||||
'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x14, 0x00, 0x00, 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 'b', 'a', 'r', 'b', 'a', 'z', 0x00,
|
||||
0x05, 0x00, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendInt32",
|
||||
AppendInt32,
|
||||
[]interface{}{make([]byte, 0), int32(256)},
|
||||
[]byte{0x00, 0x01, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendInt32Element",
|
||||
AppendInt32Element,
|
||||
[]interface{}{make([]byte, 0), "foobar", int32(256)},
|
||||
[]byte{byte(bsontype.Int32), 'f', 'o', 'o', 'b', 'a', 'r', 0x00, 0x00, 0x01, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendTimestamp",
|
||||
AppendTimestamp,
|
||||
[]interface{}{make([]byte, 0), uint32(65536), uint32(256)},
|
||||
[]byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendTimestampElement",
|
||||
AppendTimestampElement,
|
||||
[]interface{}{make([]byte, 0), "foobar", uint32(65536), uint32(256)},
|
||||
[]byte{byte(bsontype.Timestamp), 'f', 'o', 'o', 'b', 'a', 'r', 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendInt64",
|
||||
AppendInt64,
|
||||
[]interface{}{make([]byte, 0), int64(4294967296)},
|
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendInt64Element",
|
||||
AppendInt64Element,
|
||||
[]interface{}{make([]byte, 0), "foobar", int64(4294967296)},
|
||||
[]byte{byte(bsontype.Int64), 'f', 'o', 'o', 'b', 'a', 'r', 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
"AppendDecimal128",
|
||||
AppendDecimal128,
|
||||
[]interface{}{make([]byte, 0), primitive.NewDecimal128(4294967296, 65536)},
|
||||
[]byte{
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendDecimal128Element",
|
||||
AppendDecimal128Element,
|
||||
[]interface{}{make([]byte, 0), "foobar", primitive.NewDecimal128(4294967296, 65536)},
|
||||
[]byte{
|
||||
byte(bsontype.Decimal128), 'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
{
|
||||
"AppendMaxKeyElement",
|
||||
AppendMaxKeyElement,
|
||||
[]interface{}{make([]byte, 0), "foobar"},
|
||||
[]byte{byte(bsontype.MaxKey), 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
},
|
||||
{
|
||||
"AppendMinKeyElement",
|
||||
AppendMinKeyElement,
|
||||
[]interface{}{make([]byte, 0), "foobar"},
|
||||
[]byte{byte(bsontype.MinKey), 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fn := reflect.ValueOf(tc.fn)
|
||||
if fn.Kind() != reflect.Func {
|
||||
t.Fatalf("fn must be of kind Func but is a %v", fn.Kind())
|
||||
}
|
||||
if fn.Type().NumIn() != len(tc.params) {
|
||||
t.Fatalf("tc.params must match the number of params in tc.fn. params %d; fn %d", fn.Type().NumIn(), len(tc.params))
|
||||
}
|
||||
if fn.Type().NumOut() != 1 || fn.Type().Out(0) != reflect.TypeOf([]byte{}) {
|
||||
t.Fatalf("fn must have one return parameter and it must be a []byte.")
|
||||
}
|
||||
params := make([]reflect.Value, 0, len(tc.params))
|
||||
for _, param := range tc.params {
|
||||
params = append(params, reflect.ValueOf(param))
|
||||
}
|
||||
results := fn.Call(params)
|
||||
got := results[0].Interface().([]byte)
|
||||
want := tc.expected
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("Did not receive expected bytes. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
bits := math.Float64bits(3.14159)
|
||||
pi := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(pi, bits)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
fn interface{}
|
||||
param []byte
|
||||
expected []interface{}
|
||||
}{
|
||||
{
|
||||
"ReadType/not enough bytes",
|
||||
ReadType,
|
||||
[]byte{},
|
||||
[]interface{}{bsontype.Type(0), []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadType/success",
|
||||
ReadType,
|
||||
[]byte{0x0A},
|
||||
[]interface{}{bsontype.Null, []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadKey/not enough bytes",
|
||||
ReadKey,
|
||||
[]byte{},
|
||||
[]interface{}{"", []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadKey/success",
|
||||
ReadKey,
|
||||
[]byte{'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
[]interface{}{"foobar", []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadHeader/not enough bytes (type)",
|
||||
ReadHeader,
|
||||
[]byte{},
|
||||
[]interface{}{bsontype.Type(0), "", []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadHeader/not enough bytes (key)",
|
||||
ReadHeader,
|
||||
[]byte{0x0A, 'f', 'o', 'o'},
|
||||
[]interface{}{bsontype.Type(0), "", []byte{0x0A, 'f', 'o', 'o'}, false},
|
||||
},
|
||||
{
|
||||
"ReadHeader/success",
|
||||
ReadHeader,
|
||||
[]byte{0x0A, 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
[]interface{}{bsontype.Null, "foobar", []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadDouble/not enough bytes",
|
||||
ReadDouble,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
[]interface{}{float64(0.00), []byte{0x01, 0x02, 0x03, 0x04}, false},
|
||||
},
|
||||
{
|
||||
"ReadDouble/success",
|
||||
ReadDouble,
|
||||
pi,
|
||||
[]interface{}{float64(3.14159), []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadString/not enough bytes (length)",
|
||||
ReadString,
|
||||
[]byte{},
|
||||
[]interface{}{"", []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadString/not enough bytes (value)",
|
||||
ReadString,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00},
|
||||
[]interface{}{"", []byte{0x0F, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadString/success",
|
||||
ReadString,
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
[]interface{}{"foobar", []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadDocument/not enough bytes (length)",
|
||||
ReadDocument,
|
||||
[]byte{},
|
||||
[]interface{}{Document(nil), []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadDocument/not enough bytes (value)",
|
||||
ReadDocument,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00},
|
||||
[]interface{}{Document(nil), []byte{0x0F, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadDocument/success",
|
||||
ReadDocument,
|
||||
[]byte{0x0A, 0x00, 0x00, 0x00, 0x0A, 'f', 'o', 'o', 0x00, 0x00},
|
||||
[]interface{}{Document{0x0A, 0x00, 0x00, 0x00, 0x0A, 'f', 'o', 'o', 0x00, 0x00}, []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadArray/not enough bytes (length)",
|
||||
ReadArray,
|
||||
[]byte{},
|
||||
[]interface{}{Array(nil), []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadArray/not enough bytes (value)",
|
||||
ReadArray,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00},
|
||||
[]interface{}{Array(nil), []byte{0x0F, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadArray/success",
|
||||
ReadArray,
|
||||
[]byte{0x08, 0x00, 0x00, 0x00, 0x0A, '0', 0x00, 0x00},
|
||||
[]interface{}{Array{0x08, 0x00, 0x00, 0x00, 0x0A, '0', 0x00, 0x00}, []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadBinary/not enough bytes (length)",
|
||||
ReadBinary,
|
||||
[]byte{},
|
||||
[]interface{}{byte(0), []byte(nil), []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadBinary/not enough bytes (subtype)",
|
||||
ReadBinary,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00},
|
||||
[]interface{}{byte(0), []byte(nil), []byte{0x0F, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadBinary/not enough bytes (value)",
|
||||
ReadBinary,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00, 0x00},
|
||||
[]interface{}{byte(0), []byte(nil), []byte{0x0F, 0x00, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadBinary/not enough bytes (subtype 2 length)",
|
||||
ReadBinary,
|
||||
[]byte{0x03, 0x00, 0x00, 0x00, 0x02, 0x0F, 0x00, 0x00},
|
||||
[]interface{}{byte(0), []byte(nil), []byte{0x03, 0x00, 0x00, 0x00, 0x02, 0x0F, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadBinary/not enough bytes (subtype 2 value)",
|
||||
ReadBinary,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00, 0x02, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x02},
|
||||
[]interface{}{
|
||||
byte(0), []byte(nil),
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00, 0x02, 0x0F, 0x00, 0x00, 0x00, 0x01, 0x02}, false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ReadBinary/success (subtype 2)",
|
||||
ReadBinary,
|
||||
[]byte{0x06, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x01, 0x02},
|
||||
[]interface{}{byte(0x02), []byte{0x01, 0x02}, []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadBinary/success",
|
||||
ReadBinary,
|
||||
[]byte{0x03, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x02, 0x03},
|
||||
[]interface{}{byte(0xFF), []byte{0x01, 0x02, 0x03}, []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadObjectID/not enough bytes",
|
||||
ReadObjectID,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
|
||||
[]interface{}{primitive.ObjectID{}, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, false},
|
||||
},
|
||||
{
|
||||
"ReadObjectID/success",
|
||||
ReadObjectID,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
[]interface{}{
|
||||
primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
[]byte{}, true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ReadBoolean/not enough bytes",
|
||||
ReadBoolean,
|
||||
[]byte{},
|
||||
[]interface{}{false, []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadBoolean/success",
|
||||
ReadBoolean,
|
||||
[]byte{0x01},
|
||||
[]interface{}{true, []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadDateTime/not enough bytes",
|
||||
ReadDateTime,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
[]interface{}{int64(0), []byte{0x01, 0x02, 0x03, 0x04}, false},
|
||||
},
|
||||
{
|
||||
"ReadDateTime/success",
|
||||
ReadDateTime,
|
||||
[]byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
[]interface{}{int64(65536), []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadRegex/not enough bytes (pattern)",
|
||||
ReadRegex,
|
||||
[]byte{},
|
||||
[]interface{}{"", "", []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadRegex/not enough bytes (options)",
|
||||
ReadRegex,
|
||||
[]byte{'f', 'o', 'o', 0x00},
|
||||
[]interface{}{"", "", []byte{'f', 'o', 'o', 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadRegex/success",
|
||||
ReadRegex,
|
||||
[]byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r', 0x00},
|
||||
[]interface{}{"foo", "bar", []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadDBPointer/not enough bytes (ns)",
|
||||
ReadDBPointer,
|
||||
[]byte{},
|
||||
[]interface{}{"", primitive.ObjectID{}, []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadDBPointer/not enough bytes (objectID)",
|
||||
ReadDBPointer,
|
||||
[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
|
||||
[]interface{}{"", primitive.ObjectID{}, []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadDBPointer/success",
|
||||
ReadDBPointer,
|
||||
[]byte{
|
||||
0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
|
||||
},
|
||||
[]interface{}{
|
||||
"foo", primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
|
||||
[]byte{}, true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ReadJavaScript/not enough bytes (length)",
|
||||
ReadJavaScript,
|
||||
[]byte{},
|
||||
[]interface{}{"", []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadJavaScript/not enough bytes (value)",
|
||||
ReadJavaScript,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00},
|
||||
[]interface{}{"", []byte{0x0F, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadJavaScript/success",
|
||||
ReadJavaScript,
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
[]interface{}{"foobar", []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadSymbol/not enough bytes (length)",
|
||||
ReadSymbol,
|
||||
[]byte{},
|
||||
[]interface{}{"", []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadSymbol/not enough bytes (value)",
|
||||
ReadSymbol,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00},
|
||||
[]interface{}{"", []byte{0x0F, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadSymbol/success",
|
||||
ReadSymbol,
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', 0x00},
|
||||
[]interface{}{"foobar", []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadCodeWithScope/ not enough bytes (length)",
|
||||
ReadCodeWithScope,
|
||||
[]byte{},
|
||||
[]interface{}{"", []byte(nil), []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadCodeWithScope/ not enough bytes (value)",
|
||||
ReadCodeWithScope,
|
||||
[]byte{0x0F, 0x00, 0x00, 0x00},
|
||||
[]interface{}{"", []byte(nil), []byte{0x0F, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadCodeWithScope/not enough bytes (code value)",
|
||||
ReadCodeWithScope,
|
||||
[]byte{
|
||||
0x0C, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00,
|
||||
'f', 'o', 'o', 0x00,
|
||||
},
|
||||
[]interface{}{
|
||||
"", []byte(nil),
|
||||
[]byte{
|
||||
0x0C, 0x00, 0x00, 0x00,
|
||||
0x0F, 0x00, 0x00, 0x00,
|
||||
'f', 'o', 'o', 0x00,
|
||||
},
|
||||
false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ReadCodeWithScope/success",
|
||||
ReadCodeWithScope,
|
||||
[]byte{
|
||||
0x19, 0x00, 0x00, 0x00,
|
||||
0x07, 0x00, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', 0x00,
|
||||
0x0A, 0x00, 0x00, 0x00, 0x0A, 'f', 'o', 'o', 0x00, 0x00,
|
||||
},
|
||||
[]interface{}{
|
||||
"foobar", []byte{0x0A, 0x00, 0x00, 0x00, 0x0A, 'f', 'o', 'o', 0x00, 0x00},
|
||||
[]byte{}, true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"ReadInt32/not enough bytes",
|
||||
ReadInt32,
|
||||
[]byte{0x01},
|
||||
[]interface{}{int32(0), []byte{0x01}, false},
|
||||
},
|
||||
{
|
||||
"ReadInt32/success",
|
||||
ReadInt32,
|
||||
[]byte{0x00, 0x01, 0x00, 0x00},
|
||||
[]interface{}{int32(256), []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadTimestamp/not enough bytes (increment)",
|
||||
ReadTimestamp,
|
||||
[]byte{},
|
||||
[]interface{}{uint32(0), uint32(0), []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadTimestamp/not enough bytes (timestamp)",
|
||||
ReadTimestamp,
|
||||
[]byte{0x00, 0x01, 0x00, 0x00},
|
||||
[]interface{}{uint32(0), uint32(0), []byte{0x00, 0x01, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadTimestamp/success",
|
||||
ReadTimestamp,
|
||||
[]byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00},
|
||||
[]interface{}{uint32(65536), uint32(256), []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadInt64/not enough bytes",
|
||||
ReadInt64,
|
||||
[]byte{0x01},
|
||||
[]interface{}{int64(0), []byte{0x01}, false},
|
||||
},
|
||||
{
|
||||
"ReadInt64/success",
|
||||
ReadInt64,
|
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||
[]interface{}{int64(4294967296), []byte{}, true},
|
||||
},
|
||||
{
|
||||
"ReadDecimal128/not enough bytes (low)",
|
||||
ReadDecimal128,
|
||||
[]byte{},
|
||||
[]interface{}{primitive.Decimal128{}, []byte{}, false},
|
||||
},
|
||||
{
|
||||
"ReadDecimal128/not enough bytes (high)",
|
||||
ReadDecimal128,
|
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||
[]interface{}{primitive.Decimal128{}, []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, false},
|
||||
},
|
||||
{
|
||||
"ReadDecimal128/success",
|
||||
ReadDecimal128,
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
},
|
||||
[]interface{}{primitive.NewDecimal128(4294967296, 16777216), []byte{}, true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fn := reflect.ValueOf(tc.fn)
|
||||
if fn.Kind() != reflect.Func {
|
||||
t.Fatalf("fn must be of kind Func but it is a %v", fn.Kind())
|
||||
}
|
||||
if fn.Type().NumIn() != 1 || fn.Type().In(0) != reflect.TypeOf([]byte{}) {
|
||||
t.Fatalf("fn must have one parameter and it must be a []byte.")
|
||||
}
|
||||
results := fn.Call([]reflect.Value{reflect.ValueOf(tc.param)})
|
||||
if len(results) != len(tc.expected) {
|
||||
t.Fatalf("Length of results does not match. got %d; want %d", len(results), len(tc.expected))
|
||||
}
|
||||
for idx := range results {
|
||||
got := results[idx].Interface()
|
||||
want := tc.expected[idx]
|
||||
if !cmp.Equal(got, want, cmp.Comparer(compareDecimal128)) {
|
||||
t.Errorf("Result %d does not match. got %v; want %v", idx, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
elems [][]byte
|
||||
want []byte
|
||||
}{
|
||||
{
|
||||
"one element",
|
||||
[][]byte{AppendDoubleElement(nil, "pi", 3.14159)},
|
||||
[]byte{0x11, 0x00, 0x00, 0x00, 0x1, 0x70, 0x69, 0x00, 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x9, 0x40, 0x00},
|
||||
},
|
||||
{
|
||||
"two elements",
|
||||
[][]byte{AppendDoubleElement(nil, "pi", 3.14159), AppendStringElement(nil, "hello", "world!!")},
|
||||
[]byte{
|
||||
0x24, 0x00, 0x00, 0x00, 0x01, 0x70, 0x69, 0x00, 0x6e, 0x86, 0x1b, 0xf0,
|
||||
0xf9, 0x21, 0x09, 0x40, 0x02, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x08,
|
||||
0x00, 0x00, 0x00, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x21, 0x00, 0x00,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("BuildDocument", func(t *testing.T) {
|
||||
elems := make([]byte, 0)
|
||||
for _, elem := range tc.elems {
|
||||
elems = append(elems, elem...)
|
||||
}
|
||||
got := BuildDocument(nil, elems)
|
||||
if !bytes.Equal(got, tc.want) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
t.Run("BuildDocumentFromElements", func(t *testing.T) {
|
||||
got := BuildDocumentFromElements(nil, tc.elems...)
|
||||
if !bytes.Equal(got, tc.want) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullBytes(t *testing.T) {
|
||||
// Helper function to execute the provided callback and assert that it panics with the expected message. The
|
||||
// createBSONFn callback should create a BSON document/array/value and return the stringified version.
|
||||
assertBSONCreationPanics := func(t *testing.T, createBSONFn func(), expected string) {
|
||||
t.Helper()
|
||||
|
||||
defer func() {
|
||||
got := recover()
|
||||
assert.Equal(t, expected, got, "expected panic with error %v, got error %v", expected, got)
|
||||
}()
|
||||
createBSONFn()
|
||||
}
|
||||
|
||||
t.Run("element keys", func(t *testing.T) {
|
||||
createDocFn := func() {
|
||||
NewDocumentBuilder().AppendString("a\x00", "foo")
|
||||
}
|
||||
assertBSONCreationPanics(t, createDocFn, invalidKeyPanicMsg)
|
||||
})
|
||||
t.Run("regex values", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
pattern string
|
||||
options string
|
||||
}{
|
||||
{"null bytes in pattern", "a\x00", "i"},
|
||||
{"null bytes in options", "pattern", "i\x00"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name+"-AppendRegexElement", func(t *testing.T) {
|
||||
createDocFn := func() {
|
||||
AppendRegexElement(nil, "foo", tc.pattern, tc.options)
|
||||
}
|
||||
assertBSONCreationPanics(t, createDocFn, invalidRegexPanicMsg)
|
||||
})
|
||||
t.Run(tc.name+"-AppendRegex", func(t *testing.T) {
|
||||
createValFn := func() {
|
||||
AppendRegex(nil, tc.pattern, tc.options)
|
||||
}
|
||||
assertBSONCreationPanics(t, createValFn, invalidRegexPanicMsg)
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("sub document field name", func(t *testing.T) {
|
||||
createDocFn := func() {
|
||||
NewDocumentBuilder().StartDocument("foobar").AppendDocument("a\x00", []byte("foo")).FinishDocument()
|
||||
}
|
||||
assertBSONCreationPanics(t, createDocFn, invalidKeyPanicMsg)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
386
mongo/x/bsonx/bsoncore/document.go
Normal file
386
mongo/x/bsonx/bsoncore/document.go
Normal file
@@ -0,0 +1,386 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
)
|
||||
|
||||
// ValidationError is an error type returned when attempting to validate a document or array.
|
||||
type ValidationError string
|
||||
|
||||
func (ve ValidationError) Error() string { return string(ve) }
|
||||
|
||||
// NewDocumentLengthError creates and returns an error for when the length of a document exceeds the
|
||||
// bytes available.
|
||||
func NewDocumentLengthError(length, rem int) error {
|
||||
return lengthError("document", length, rem)
|
||||
}
|
||||
|
||||
func lengthError(bufferType string, length, rem int) error {
|
||||
return ValidationError(fmt.Sprintf("%v length exceeds available bytes. length=%d remainingBytes=%d",
|
||||
bufferType, length, rem))
|
||||
}
|
||||
|
||||
// InsufficientBytesError indicates that there were not enough bytes to read the next component.
|
||||
type InsufficientBytesError struct {
|
||||
Source []byte
|
||||
Remaining []byte
|
||||
}
|
||||
|
||||
// NewInsufficientBytesError creates a new InsufficientBytesError with the given Document and
|
||||
// remaining bytes.
|
||||
func NewInsufficientBytesError(src, rem []byte) InsufficientBytesError {
|
||||
return InsufficientBytesError{Source: src, Remaining: rem}
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (ibe InsufficientBytesError) Error() string {
|
||||
return "too few bytes to read next component"
|
||||
}
|
||||
|
||||
// Equal checks that err2 also is an ErrTooSmall.
|
||||
func (ibe InsufficientBytesError) Equal(err2 error) bool {
|
||||
switch err2.(type) {
|
||||
case InsufficientBytesError:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidDepthTraversalError is returned when attempting a recursive Lookup when one component of
|
||||
// the path is neither an embedded document nor an array.
|
||||
type InvalidDepthTraversalError struct {
|
||||
Key string
|
||||
Type bsontype.Type
|
||||
}
|
||||
|
||||
func (idte InvalidDepthTraversalError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"attempt to traverse into %s, but it's type is %s, not %s nor %s",
|
||||
idte.Key, idte.Type, bsontype.EmbeddedDocument, bsontype.Array,
|
||||
)
|
||||
}
|
||||
|
||||
// ErrMissingNull is returned when a document or array's last byte is not null.
|
||||
const ErrMissingNull ValidationError = "document or array end is missing null byte"
|
||||
|
||||
// ErrInvalidLength indicates that a length in a binary representation of a BSON document or array
|
||||
// is invalid.
|
||||
const ErrInvalidLength ValidationError = "document or array length is invalid"
|
||||
|
||||
// ErrNilReader indicates that an operation was attempted on a nil io.Reader.
|
||||
var ErrNilReader = errors.New("nil reader")
|
||||
|
||||
// ErrEmptyKey indicates that no key was provided to a Lookup method.
|
||||
var ErrEmptyKey = errors.New("empty key provided")
|
||||
|
||||
// ErrElementNotFound indicates that an Element matching a certain condition does not exist.
|
||||
var ErrElementNotFound = errors.New("element not found")
|
||||
|
||||
// ErrOutOfBounds indicates that an index provided to access something was invalid.
|
||||
var ErrOutOfBounds = errors.New("out of bounds")
|
||||
|
||||
// Document is a raw bytes representation of a BSON document.
|
||||
type Document []byte
|
||||
|
||||
// NewDocumentFromReader reads a document from r. This function will only validate the length is
|
||||
// correct and that the document ends with a null byte.
|
||||
func NewDocumentFromReader(r io.Reader) (Document, error) {
|
||||
return newBufferFromReader(r)
|
||||
}
|
||||
|
||||
func newBufferFromReader(r io.Reader) ([]byte, error) {
|
||||
if r == nil {
|
||||
return nil, ErrNilReader
|
||||
}
|
||||
|
||||
var lengthBytes [4]byte
|
||||
|
||||
// ReadFull guarantees that we will have read at least len(lengthBytes) if err == nil
|
||||
_, err := io.ReadFull(r, lengthBytes[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
length, _, _ := readi32(lengthBytes[:]) // ignore ok since we always have enough bytes to read a length
|
||||
if length < 0 {
|
||||
return nil, ErrInvalidLength
|
||||
}
|
||||
buffer := make([]byte, length)
|
||||
|
||||
copy(buffer, lengthBytes[:])
|
||||
|
||||
_, err = io.ReadFull(r, buffer[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if buffer[length-1] != 0x00 {
|
||||
return nil, ErrMissingNull
|
||||
}
|
||||
|
||||
return buffer, nil
|
||||
}
|
||||
|
||||
// Lookup searches the document, potentially recursively, for the given key. If there are multiple
|
||||
// keys provided, this method will recurse down, as long as the top and intermediate nodes are
|
||||
// either documents or arrays. If an error occurs or if the value doesn't exist, an empty Value is
|
||||
// returned.
|
||||
func (d Document) Lookup(key ...string) Value {
|
||||
val, _ := d.LookupErr(key...)
|
||||
return val
|
||||
}
|
||||
|
||||
// LookupErr is the same as Lookup, except it returns an error in addition to an empty Value.
|
||||
func (d Document) LookupErr(key ...string) (Value, error) {
|
||||
if len(key) < 1 {
|
||||
return Value{}, ErrEmptyKey
|
||||
}
|
||||
length, rem, ok := ReadLength(d)
|
||||
if !ok {
|
||||
return Value{}, NewInsufficientBytesError(d, rem)
|
||||
}
|
||||
|
||||
length -= 4
|
||||
|
||||
var elem Element
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return Value{}, NewInsufficientBytesError(d, rem)
|
||||
}
|
||||
// We use `KeyBytes` rather than `Key` to avoid a needless string alloc.
|
||||
if string(elem.KeyBytes()) != key[0] {
|
||||
continue
|
||||
}
|
||||
if len(key) > 1 {
|
||||
tt := bsontype.Type(elem[0])
|
||||
switch tt {
|
||||
case bsontype.EmbeddedDocument:
|
||||
val, err := elem.Value().Document().LookupErr(key[1:]...)
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
return val, nil
|
||||
case bsontype.Array:
|
||||
// Convert to Document to continue Lookup recursion.
|
||||
val, err := Document(elem.Value().Array()).LookupErr(key[1:]...)
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
return val, nil
|
||||
default:
|
||||
return Value{}, InvalidDepthTraversalError{Key: elem.Key(), Type: tt}
|
||||
}
|
||||
}
|
||||
return elem.ValueErr()
|
||||
}
|
||||
return Value{}, ErrElementNotFound
|
||||
}
|
||||
|
||||
// Index searches for and retrieves the element at the given index. This method will panic if
|
||||
// the document is invalid or if the index is out of bounds.
|
||||
func (d Document) Index(index uint) Element {
|
||||
elem, err := d.IndexErr(index)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return elem
|
||||
}
|
||||
|
||||
// IndexErr searches for and retrieves the element at the given index.
|
||||
func (d Document) IndexErr(index uint) (Element, error) {
|
||||
return indexErr(d, index)
|
||||
}
|
||||
|
||||
func indexErr(b []byte, index uint) (Element, error) {
|
||||
length, rem, ok := ReadLength(b)
|
||||
if !ok {
|
||||
return nil, NewInsufficientBytesError(b, rem)
|
||||
}
|
||||
|
||||
length -= 4
|
||||
|
||||
var current uint
|
||||
var elem Element
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return nil, NewInsufficientBytesError(b, rem)
|
||||
}
|
||||
if current != index {
|
||||
current++
|
||||
continue
|
||||
}
|
||||
return elem, nil
|
||||
}
|
||||
return nil, ErrOutOfBounds
|
||||
}
|
||||
|
||||
// DebugString outputs a human readable version of Document. It will attempt to stringify the
|
||||
// valid components of the document even if the entire document is not valid.
|
||||
func (d Document) DebugString() string {
|
||||
if len(d) < 5 {
|
||||
return "<malformed>"
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("Document")
|
||||
length, rem, _ := ReadLength(d) // We know we have enough bytes to read the length
|
||||
buf.WriteByte('(')
|
||||
buf.WriteString(strconv.Itoa(int(length)))
|
||||
length -= 4
|
||||
buf.WriteString("){")
|
||||
var elem Element
|
||||
var ok bool
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
buf.WriteString(fmt.Sprintf("<malformed (%d)>", length))
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s ", elem.DebugString())
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// String outputs an ExtendedJSON version of Document. If the document is not valid, this method
|
||||
// returns an empty string.
|
||||
func (d Document) String() string {
|
||||
if len(d) < 5 {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('{')
|
||||
|
||||
length, rem, _ := ReadLength(d) // We know we have enough bytes to read the length
|
||||
|
||||
length -= 4
|
||||
|
||||
var elem Element
|
||||
var ok bool
|
||||
first := true
|
||||
for length > 1 {
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s", elem.String())
|
||||
first = false
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Elements returns this document as a slice of elements. The returned slice will contain valid
|
||||
// elements. If the document is not valid, the elements up to the invalid point will be returned
|
||||
// along with an error.
|
||||
func (d Document) Elements() ([]Element, error) {
|
||||
length, rem, ok := ReadLength(d)
|
||||
if !ok {
|
||||
return nil, NewInsufficientBytesError(d, rem)
|
||||
}
|
||||
|
||||
length -= 4
|
||||
|
||||
var elem Element
|
||||
var elems []Element
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return elems, NewInsufficientBytesError(d, rem)
|
||||
}
|
||||
if err := elem.Validate(); err != nil {
|
||||
return elems, err
|
||||
}
|
||||
elems = append(elems, elem)
|
||||
}
|
||||
return elems, nil
|
||||
}
|
||||
|
||||
// Values returns this document as a slice of values. The returned slice will contain valid values.
|
||||
// If the document is not valid, the values up to the invalid point will be returned along with an
|
||||
// error.
|
||||
func (d Document) Values() ([]Value, error) {
|
||||
return values(d)
|
||||
}
|
||||
|
||||
func values(b []byte) ([]Value, error) {
|
||||
length, rem, ok := ReadLength(b)
|
||||
if !ok {
|
||||
return nil, NewInsufficientBytesError(b, rem)
|
||||
}
|
||||
|
||||
length -= 4
|
||||
|
||||
var elem Element
|
||||
var vals []Value
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return vals, NewInsufficientBytesError(b, rem)
|
||||
}
|
||||
if err := elem.Value().Validate(); err != nil {
|
||||
return vals, err
|
||||
}
|
||||
vals = append(vals, elem.Value())
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// Validate validates the document and ensures the elements contained within are valid.
|
||||
func (d Document) Validate() error {
|
||||
length, rem, ok := ReadLength(d)
|
||||
if !ok {
|
||||
return NewInsufficientBytesError(d, rem)
|
||||
}
|
||||
if int(length) > len(d) {
|
||||
return NewDocumentLengthError(int(length), len(d))
|
||||
}
|
||||
if d[length-1] != 0x00 {
|
||||
return ErrMissingNull
|
||||
}
|
||||
|
||||
length -= 4
|
||||
var elem Element
|
||||
|
||||
for length > 1 {
|
||||
elem, rem, ok = ReadElement(rem)
|
||||
length -= int32(len(elem))
|
||||
if !ok {
|
||||
return NewInsufficientBytesError(d, rem)
|
||||
}
|
||||
err := elem.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(rem) < 1 || rem[0] != 0x00 {
|
||||
return ErrMissingNull
|
||||
}
|
||||
return nil
|
||||
}
|
||||
189
mongo/x/bsonx/bsoncore/document_sequence.go
Normal file
189
mongo/x/bsonx/bsoncore/document_sequence.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright (C) MongoDB, Inc. 2022-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 bsoncore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
)
|
||||
|
||||
// DocumentSequenceStyle is used to represent how a document sequence is laid out in a slice of
|
||||
// bytes.
|
||||
type DocumentSequenceStyle uint32
|
||||
|
||||
// These constants are the valid styles for a DocumentSequence.
|
||||
const (
|
||||
_ DocumentSequenceStyle = iota
|
||||
SequenceStyle
|
||||
ArrayStyle
|
||||
)
|
||||
|
||||
// DocumentSequence represents a sequence of documents. The Style field indicates how the documents
|
||||
// are laid out inside of the Data field.
|
||||
type DocumentSequence struct {
|
||||
Style DocumentSequenceStyle
|
||||
Data []byte
|
||||
Pos int
|
||||
}
|
||||
|
||||
// ErrCorruptedDocument is returned when a full document couldn't be read from the sequence.
|
||||
var ErrCorruptedDocument = errors.New("invalid DocumentSequence: corrupted document")
|
||||
|
||||
// ErrNonDocument is returned when a DocumentSequence contains a non-document BSON value.
|
||||
var ErrNonDocument = errors.New("invalid DocumentSequence: a non-document value was found in sequence")
|
||||
|
||||
// ErrInvalidDocumentSequenceStyle is returned when an unknown DocumentSequenceStyle is set on a
|
||||
// DocumentSequence.
|
||||
var ErrInvalidDocumentSequenceStyle = errors.New("invalid DocumentSequenceStyle")
|
||||
|
||||
// DocumentCount returns the number of documents in the sequence.
|
||||
func (ds *DocumentSequence) DocumentCount() int {
|
||||
if ds == nil {
|
||||
return 0
|
||||
}
|
||||
switch ds.Style {
|
||||
case SequenceStyle:
|
||||
var count int
|
||||
var ok bool
|
||||
rem := ds.Data
|
||||
for len(rem) > 0 {
|
||||
_, rem, ok = ReadDocument(rem)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
count++
|
||||
}
|
||||
return count
|
||||
case ArrayStyle:
|
||||
_, rem, ok := ReadLength(ds.Data)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
var count int
|
||||
for len(rem) > 1 {
|
||||
_, rem, ok = ReadElement(rem)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
count++
|
||||
}
|
||||
return count
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Empty returns true if the sequence is empty. It always returns true for unknown sequence styles.
|
||||
func (ds *DocumentSequence) Empty() bool {
|
||||
if ds == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch ds.Style {
|
||||
case SequenceStyle:
|
||||
return len(ds.Data) == 0
|
||||
case ArrayStyle:
|
||||
return len(ds.Data) <= 5
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// ResetIterator resets the iteration point for the Next method to the beginning of the document
|
||||
// sequence.
|
||||
func (ds *DocumentSequence) ResetIterator() {
|
||||
if ds == nil {
|
||||
return
|
||||
}
|
||||
ds.Pos = 0
|
||||
}
|
||||
|
||||
// Documents returns a slice of the documents. If nil either the Data field is also nil or could not
|
||||
// be properly read.
|
||||
func (ds *DocumentSequence) Documents() ([]Document, error) {
|
||||
if ds == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch ds.Style {
|
||||
case SequenceStyle:
|
||||
rem := ds.Data
|
||||
var docs []Document
|
||||
var doc Document
|
||||
var ok bool
|
||||
for {
|
||||
doc, rem, ok = ReadDocument(rem)
|
||||
if !ok {
|
||||
if len(rem) == 0 {
|
||||
break
|
||||
}
|
||||
return nil, ErrCorruptedDocument
|
||||
}
|
||||
docs = append(docs, doc)
|
||||
}
|
||||
return docs, nil
|
||||
case ArrayStyle:
|
||||
if len(ds.Data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
vals, err := Document(ds.Data).Values()
|
||||
if err != nil {
|
||||
return nil, ErrCorruptedDocument
|
||||
}
|
||||
docs := make([]Document, 0, len(vals))
|
||||
for _, v := range vals {
|
||||
if v.Type != bsontype.EmbeddedDocument {
|
||||
return nil, ErrNonDocument
|
||||
}
|
||||
docs = append(docs, v.Data)
|
||||
}
|
||||
return docs, nil
|
||||
default:
|
||||
return nil, ErrInvalidDocumentSequenceStyle
|
||||
}
|
||||
}
|
||||
|
||||
// Next retrieves the next document from this sequence and returns it. This method will return
|
||||
// io.EOF when it has reached the end of the sequence.
|
||||
func (ds *DocumentSequence) Next() (Document, error) {
|
||||
if ds == nil || ds.Pos >= len(ds.Data) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
switch ds.Style {
|
||||
case SequenceStyle:
|
||||
doc, _, ok := ReadDocument(ds.Data[ds.Pos:])
|
||||
if !ok {
|
||||
return nil, ErrCorruptedDocument
|
||||
}
|
||||
ds.Pos += len(doc)
|
||||
return doc, nil
|
||||
case ArrayStyle:
|
||||
if ds.Pos < 4 {
|
||||
if len(ds.Data) < 4 {
|
||||
return nil, ErrCorruptedDocument
|
||||
}
|
||||
ds.Pos = 4 // Skip the length of the document
|
||||
}
|
||||
if len(ds.Data[ds.Pos:]) == 1 && ds.Data[ds.Pos] == 0x00 {
|
||||
return nil, io.EOF // At the end of the document
|
||||
}
|
||||
elem, _, ok := ReadElement(ds.Data[ds.Pos:])
|
||||
if !ok {
|
||||
return nil, ErrCorruptedDocument
|
||||
}
|
||||
ds.Pos += len(elem)
|
||||
val := elem.Value()
|
||||
if val.Type != bsontype.EmbeddedDocument {
|
||||
return nil, ErrNonDocument
|
||||
}
|
||||
return val.Data, nil
|
||||
default:
|
||||
return nil, ErrInvalidDocumentSequenceStyle
|
||||
}
|
||||
}
|
||||
421
mongo/x/bsonx/bsoncore/document_sequence_test.go
Normal file
421
mongo/x/bsonx/bsoncore/document_sequence_test.go
Normal file
@@ -0,0 +1,421 @@
|
||||
// Copyright (C) MongoDB, Inc. 2022-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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestDocumentSequence(t *testing.T) {
|
||||
|
||||
genArrayStyle := func(num int) []byte {
|
||||
idx, seq := AppendDocumentStart(nil)
|
||||
for i := 0; i < num; i++ {
|
||||
seq = AppendDocumentElement(
|
||||
seq, strconv.Itoa(i),
|
||||
BuildDocument(nil, AppendDoubleElement(nil, "pi", 3.14159)),
|
||||
)
|
||||
}
|
||||
seq, _ = AppendDocumentEnd(seq, idx)
|
||||
return seq
|
||||
}
|
||||
genSequenceStyle := func(num int) []byte {
|
||||
var seq []byte
|
||||
for i := 0; i < num; i++ {
|
||||
seq = append(seq, BuildDocument(nil, AppendDoubleElement(nil, "pi", 3.14159))...)
|
||||
}
|
||||
return seq
|
||||
}
|
||||
|
||||
idx, arrayStyle := AppendDocumentStart(nil)
|
||||
idx2, arrayStyle := AppendDocumentElementStart(arrayStyle, "0")
|
||||
arrayStyle = AppendDoubleElement(arrayStyle, "pi", 3.14159)
|
||||
arrayStyle, _ = AppendDocumentEnd(arrayStyle, idx2)
|
||||
idx2, arrayStyle = AppendDocumentElementStart(arrayStyle, "1")
|
||||
arrayStyle = AppendStringElement(arrayStyle, "hello", "world")
|
||||
arrayStyle, _ = AppendDocumentEnd(arrayStyle, idx2)
|
||||
arrayStyle, _ = AppendDocumentEnd(arrayStyle, idx)
|
||||
|
||||
t.Run("Documents", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
style DocumentSequenceStyle
|
||||
data []byte
|
||||
documents []Document
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"SequenceStle/corrupted document",
|
||||
SequenceStyle,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
|
||||
nil,
|
||||
ErrCorruptedDocument,
|
||||
},
|
||||
{
|
||||
"SequenceStyle/success",
|
||||
SequenceStyle,
|
||||
BuildDocument(
|
||||
BuildDocument(
|
||||
nil,
|
||||
AppendStringElement(AppendDoubleElement(nil, "pi", 3.14159), "hello", "world"),
|
||||
),
|
||||
AppendDoubleElement(AppendStringElement(nil, "hello", "world"), "pi", 3.14159),
|
||||
),
|
||||
[]Document{
|
||||
BuildDocument(nil, AppendStringElement(AppendDoubleElement(nil, "pi", 3.14159), "hello", "world")),
|
||||
BuildDocument(nil, AppendDoubleElement(AppendStringElement(nil, "hello", "world"), "pi", 3.14159)),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/insufficient bytes",
|
||||
ArrayStyle,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04, 0x05},
|
||||
nil,
|
||||
ErrCorruptedDocument,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/non-document",
|
||||
ArrayStyle,
|
||||
BuildDocument(nil, AppendDoubleElement(nil, "0", 12345.67890)),
|
||||
nil,
|
||||
ErrNonDocument,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/success",
|
||||
ArrayStyle,
|
||||
arrayStyle,
|
||||
[]Document{
|
||||
BuildDocument(nil, AppendDoubleElement(nil, "pi", 3.14159)),
|
||||
BuildDocument(nil, AppendStringElement(nil, "hello", "world")),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{"Invalid DocumentSequenceStyle", 0, nil, nil, ErrInvalidDocumentSequenceStyle},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ds := &DocumentSequence{
|
||||
Style: tc.style,
|
||||
Data: tc.data,
|
||||
}
|
||||
documents, err := ds.Documents()
|
||||
if !cmp.Equal(documents, tc.documents) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", documents, tc.documents)
|
||||
}
|
||||
if err != tc.err {
|
||||
t.Errorf("Errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Next", func(t *testing.T) {
|
||||
seqDoc := BuildDocument(
|
||||
BuildDocument(
|
||||
nil,
|
||||
AppendDoubleElement(nil, "pi", 3.14159),
|
||||
),
|
||||
AppendStringElement(nil, "hello", "world"),
|
||||
)
|
||||
|
||||
idx, arrayStyle := AppendDocumentStart(nil)
|
||||
idx2, arrayStyle := AppendDocumentElementStart(arrayStyle, "0")
|
||||
arrayStyle = AppendDoubleElement(arrayStyle, "pi", 3.14159)
|
||||
arrayStyle, _ = AppendDocumentEnd(arrayStyle, idx2)
|
||||
idx2, arrayStyle = AppendDocumentElementStart(arrayStyle, "1")
|
||||
arrayStyle = AppendStringElement(arrayStyle, "hello", "world")
|
||||
arrayStyle, _ = AppendDocumentEnd(arrayStyle, idx2)
|
||||
arrayStyle, _ = AppendDocumentEnd(arrayStyle, idx)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
style DocumentSequenceStyle
|
||||
data []byte
|
||||
pos int
|
||||
document Document
|
||||
err error
|
||||
}{
|
||||
{"io.EOF", 0, make([]byte, 10), 10, nil, io.EOF},
|
||||
{
|
||||
"SequenceStyle/corrupted document",
|
||||
SequenceStyle,
|
||||
[]byte{0x01, 0x02, 0x03, 0x04},
|
||||
0,
|
||||
nil,
|
||||
ErrCorruptedDocument,
|
||||
},
|
||||
{
|
||||
"SequenceStyle/success/first",
|
||||
SequenceStyle,
|
||||
seqDoc,
|
||||
0,
|
||||
BuildDocument(nil, AppendDoubleElement(nil, "pi", 3.14159)),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"SequenceStyle/success/second",
|
||||
SequenceStyle,
|
||||
seqDoc,
|
||||
17,
|
||||
BuildDocument(nil, AppendStringElement(nil, "hello", "world")),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/corrupted document/too short",
|
||||
ArrayStyle,
|
||||
[]byte{0x01, 0x02, 0x03},
|
||||
0,
|
||||
nil,
|
||||
ErrCorruptedDocument,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/corrupted document/invalid element",
|
||||
ArrayStyle,
|
||||
[]byte{0x00, 0x00, 0x00, 0x00, 0x01, '0', 0x00, 0x01, 0x02},
|
||||
0,
|
||||
nil,
|
||||
ErrCorruptedDocument,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/non-document",
|
||||
ArrayStyle,
|
||||
BuildDocument(nil, AppendDoubleElement(nil, "0", 12345.67890)),
|
||||
0,
|
||||
nil,
|
||||
ErrNonDocument,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/success/first",
|
||||
ArrayStyle,
|
||||
arrayStyle,
|
||||
0,
|
||||
BuildDocument(nil, AppendDoubleElement(nil, "pi", 3.14159)),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/success/second",
|
||||
ArrayStyle,
|
||||
arrayStyle,
|
||||
24,
|
||||
BuildDocument(nil, AppendStringElement(nil, "hello", "world")),
|
||||
nil,
|
||||
},
|
||||
{"Invalid DocumentSequenceStyle", 0, make([]byte, 4), 0, nil, ErrInvalidDocumentSequenceStyle},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ds := &DocumentSequence{
|
||||
Style: tc.style,
|
||||
Data: tc.data,
|
||||
Pos: tc.pos,
|
||||
}
|
||||
document, err := ds.Next()
|
||||
if !bytes.Equal(document, tc.document) {
|
||||
t.Errorf("Documents do not match. got %v; want %v", document, tc.document)
|
||||
}
|
||||
if err != tc.err {
|
||||
t.Errorf("Errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Full Iteration", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
style DocumentSequenceStyle
|
||||
data []byte
|
||||
count int
|
||||
}{
|
||||
{"SequenceStyle/success/nil", SequenceStyle, nil, 0},
|
||||
{"SequenceStyle/success/0", SequenceStyle, []byte{}, 0},
|
||||
{"SequenceStyle/success/1", SequenceStyle, genSequenceStyle(1), 1},
|
||||
{"SequenceStyle/success/2", SequenceStyle, genSequenceStyle(2), 2},
|
||||
{"SequenceStyle/success/10", SequenceStyle, genSequenceStyle(10), 10},
|
||||
{"SequenceStyle/success/100", SequenceStyle, genSequenceStyle(100), 100},
|
||||
{"ArrayStyle/success/nil", ArrayStyle, nil, 0},
|
||||
{"ArrayStyle/success/0", ArrayStyle, []byte{0x05, 0x00, 0x00, 0x00, 0x00}, 0},
|
||||
{"ArrayStyle/success/1", ArrayStyle, genArrayStyle(1), 1},
|
||||
{"ArrayStyle/success/2", ArrayStyle, genArrayStyle(2), 2},
|
||||
{"ArrayStyle/success/10", ArrayStyle, genArrayStyle(10), 10},
|
||||
{"ArrayStyle/success/100", ArrayStyle, genArrayStyle(100), 100},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("Documents/"+tc.name, func(t *testing.T) {
|
||||
ds := &DocumentSequence{
|
||||
Style: tc.style,
|
||||
Data: tc.data,
|
||||
}
|
||||
docs, err := ds.Documents()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
count := len(docs)
|
||||
if count != tc.count {
|
||||
t.Errorf("Coun't fully iterate documents, wrong count. got %v; want %v", count, tc.count)
|
||||
}
|
||||
})
|
||||
t.Run("Next/"+tc.name, func(t *testing.T) {
|
||||
ds := &DocumentSequence{
|
||||
Style: tc.style,
|
||||
Data: tc.data,
|
||||
}
|
||||
var docs []Document
|
||||
for {
|
||||
doc, err := ds.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
docs = append(docs, doc)
|
||||
}
|
||||
count := len(docs)
|
||||
if count != tc.count {
|
||||
t.Errorf("Coun't fully iterate documents, wrong count. got %v; want %v", count, tc.count)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("DocumentCount", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
style DocumentSequenceStyle
|
||||
data []byte
|
||||
count int
|
||||
}{
|
||||
{
|
||||
"SequenceStyle/corrupt document/first",
|
||||
SequenceStyle,
|
||||
[]byte{0x01, 0x02, 0x03},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"SequenceStyle/corrupt document/second",
|
||||
SequenceStyle,
|
||||
[]byte{0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
|
||||
0,
|
||||
},
|
||||
{"SequenceStyle/success/nil", SequenceStyle, nil, 0},
|
||||
{"SequenceStyle/success/0", SequenceStyle, []byte{}, 0},
|
||||
{"SequenceStyle/success/1", SequenceStyle, genSequenceStyle(1), 1},
|
||||
{"SequenceStyle/success/2", SequenceStyle, genSequenceStyle(2), 2},
|
||||
{"SequenceStyle/success/10", SequenceStyle, genSequenceStyle(10), 10},
|
||||
{"SequenceStyle/success/100", SequenceStyle, genSequenceStyle(100), 100},
|
||||
{
|
||||
"ArrayStyle/corrupt document/length",
|
||||
ArrayStyle,
|
||||
[]byte{0x01, 0x02, 0x03},
|
||||
0,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/corrupt element/first",
|
||||
ArrayStyle,
|
||||
BuildDocument(nil, []byte{0x01, 0x00, 0x03, 0x04, 0x05}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
"ArrayStyle/corrupt element/second",
|
||||
ArrayStyle,
|
||||
BuildDocument(nil, []byte{0x0A, 0x00, 0x01, 0x00, 0x03, 0x04, 0x05}),
|
||||
0,
|
||||
},
|
||||
{"ArrayStyle/success/nil", ArrayStyle, nil, 0},
|
||||
{"ArrayStyle/success/0", ArrayStyle, []byte{0x05, 0x00, 0x00, 0x00, 0x00}, 0},
|
||||
{"ArrayStyle/success/1", ArrayStyle, genArrayStyle(1), 1},
|
||||
{"ArrayStyle/success/2", ArrayStyle, genArrayStyle(2), 2},
|
||||
{"ArrayStyle/success/10", ArrayStyle, genArrayStyle(10), 10},
|
||||
{"ArrayStyle/success/100", ArrayStyle, genArrayStyle(100), 100},
|
||||
{"Invalid DocumentSequenceStyle", 0, nil, 0},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ds := &DocumentSequence{
|
||||
Style: tc.style,
|
||||
Data: tc.data,
|
||||
}
|
||||
count := ds.DocumentCount()
|
||||
if count != tc.count {
|
||||
t.Errorf("Document counts don't match. got %v; want %v", count, tc.count)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ds *DocumentSequence
|
||||
isEmpty bool
|
||||
}{
|
||||
{"ArrayStyle/is empty/nil", nil, true},
|
||||
{"ArrayStyle/is empty/0", &DocumentSequence{Style: ArrayStyle, Data: []byte{0x05, 0x00, 0x00, 0x00, 0x00}}, true},
|
||||
{"ArrayStyle/is not empty/non-0", &DocumentSequence{Style: ArrayStyle, Data: genArrayStyle(10)}, false},
|
||||
{"SequenceStyle/is empty/nil", nil, true},
|
||||
{"SequenceStyle/is empty/0", &DocumentSequence{Style: SequenceStyle, Data: []byte{}}, true},
|
||||
{"SequenceStyle/is not empty/non-0", &DocumentSequence{Style: SequenceStyle, Data: genSequenceStyle(10)}, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
isEmpty := tc.ds.Empty()
|
||||
if isEmpty != tc.isEmpty {
|
||||
t.Errorf("Unexpected Empty result. got %v; want %v", isEmpty, tc.isEmpty)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("ResetIterator", func(t *testing.T) {
|
||||
ds := &DocumentSequence{Pos: 1234567890}
|
||||
want := 0
|
||||
ds.ResetIterator()
|
||||
if ds.Pos != want {
|
||||
t.Errorf("Unexpected position after ResetIterator. got %d; want %d", ds.Pos, want)
|
||||
}
|
||||
})
|
||||
t.Run("no panic on nil", func(t *testing.T) {
|
||||
capturePanic := func() {
|
||||
if err := recover(); err != nil {
|
||||
t.Errorf("Unexpected panic. got %v; want <nil>", err)
|
||||
}
|
||||
}
|
||||
t.Run("DocumentCount", func(t *testing.T) {
|
||||
defer capturePanic()
|
||||
var ds *DocumentSequence
|
||||
_ = ds.DocumentCount()
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
defer capturePanic()
|
||||
var ds *DocumentSequence
|
||||
_ = ds.Empty()
|
||||
})
|
||||
t.Run("ResetIterator", func(t *testing.T) {
|
||||
defer capturePanic()
|
||||
var ds *DocumentSequence
|
||||
ds.ResetIterator()
|
||||
})
|
||||
t.Run("Documents", func(t *testing.T) {
|
||||
defer capturePanic()
|
||||
var ds *DocumentSequence
|
||||
_, _ = ds.Documents()
|
||||
})
|
||||
t.Run("Next", func(t *testing.T) {
|
||||
defer capturePanic()
|
||||
var ds *DocumentSequence
|
||||
_, _ = ds.Next()
|
||||
})
|
||||
})
|
||||
}
|
||||
412
mongo/x/bsonx/bsoncore/document_test.go
Normal file
412
mongo/x/bsonx/bsoncore/document_test.go
Normal file
@@ -0,0 +1,412 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
)
|
||||
|
||||
func ExampleDocument_Validate() {
|
||||
doc := make(Document, 500)
|
||||
doc[250], doc[251], doc[252], doc[253], doc[254] = 0x05, 0x00, 0x00, 0x00, 0x00
|
||||
err := doc[250:].Validate()
|
||||
fmt.Println(err)
|
||||
|
||||
// Output: <nil>
|
||||
}
|
||||
|
||||
func BenchmarkDocumentValidate(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
doc := make(Document, 500)
|
||||
doc[250], doc[251], doc[252], doc[253], doc[254] = 0x05, 0x00, 0x00, 0x00, 0x00
|
||||
_ = doc[250:].Validate()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocument(t *testing.T) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
t.Run("TooShort", func(t *testing.T) {
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
got := Document{'\x00', '\x00'}.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("InvalidLength", func(t *testing.T) {
|
||||
want := NewDocumentLengthError(200, 5)
|
||||
r := make(Document, 5)
|
||||
binary.LittleEndian.PutUint32(r[0:4], 200)
|
||||
got := r.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Invalid Element", func(t *testing.T) {
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
r := make(Document, 9)
|
||||
binary.LittleEndian.PutUint32(r[0:4], 9)
|
||||
r[4], r[5], r[6], r[7], r[8] = 0x02, 'f', 'o', 'o', 0x00
|
||||
got := r.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("Missing Null Terminator", func(t *testing.T) {
|
||||
want := ErrMissingNull
|
||||
r := make(Document, 8)
|
||||
binary.LittleEndian.PutUint32(r[0:4], 8)
|
||||
r[4], r[5], r[6], r[7] = 0x0A, 'f', 'o', 'o'
|
||||
got := r.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not get expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
testCases := []struct {
|
||||
name string
|
||||
r Document
|
||||
want error
|
||||
}{
|
||||
{"null", Document{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}, nil},
|
||||
{"subdocument",
|
||||
Document{
|
||||
'\x15', '\x00', '\x00', '\x00',
|
||||
'\x03',
|
||||
'f', 'o', 'o', '\x00',
|
||||
'\x0B', '\x00', '\x00', '\x00', '\x0A', 'a', '\x00',
|
||||
'\x0A', 'b', '\x00', '\x00', '\x00',
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{"array",
|
||||
Document{
|
||||
'\x15', '\x00', '\x00', '\x00',
|
||||
'\x04',
|
||||
'f', 'o', 'o', '\x00',
|
||||
'\x0B', '\x00', '\x00', '\x00', '\x0A', '1', '\x00',
|
||||
'\x0A', '2', '\x00', '\x00', '\x00',
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.r.Validate()
|
||||
if !compareErrors(got, tc.want) {
|
||||
t.Errorf("Returned error does not match. got %v; want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Lookup", func(t *testing.T) {
|
||||
t.Run("empty-key", func(t *testing.T) {
|
||||
rdr := Document{'\x05', '\x00', '\x00', '\x00', '\x00'}
|
||||
_, err := rdr.LookupErr()
|
||||
if err != ErrEmptyKey {
|
||||
t.Errorf("Empty key lookup did not return expected result. got %v; want %v", err, ErrEmptyKey)
|
||||
}
|
||||
})
|
||||
t.Run("corrupted-subdocument", func(t *testing.T) {
|
||||
rdr := Document{
|
||||
'\x0D', '\x00', '\x00', '\x00',
|
||||
'\x03', 'x', '\x00',
|
||||
'\x06', '\x00', '\x00', '\x00',
|
||||
'\x01',
|
||||
'\x00',
|
||||
'\x00',
|
||||
}
|
||||
_, got := rdr.LookupErr("x", "y")
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
if !cmp.Equal(got, want) {
|
||||
t.Errorf("Empty key lookup did not return expected result. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("corrupted-array", func(t *testing.T) {
|
||||
rdr := Document{
|
||||
'\x0D', '\x00', '\x00', '\x00',
|
||||
'\x04', 'x', '\x00',
|
||||
'\x06', '\x00', '\x00', '\x00',
|
||||
'\x01',
|
||||
'\x00',
|
||||
'\x00',
|
||||
}
|
||||
_, got := rdr.LookupErr("x", "y")
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
if !cmp.Equal(got, want) {
|
||||
t.Errorf("Empty key lookup did not return expected result. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("invalid-traversal", func(t *testing.T) {
|
||||
rdr := Document{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}
|
||||
_, got := rdr.LookupErr("x", "y")
|
||||
want := InvalidDepthTraversalError{Key: "x", Type: bsontype.Null}
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Empty key lookup did not return expected result. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
testCases := []struct {
|
||||
name string
|
||||
r Document
|
||||
key []string
|
||||
want Value
|
||||
err error
|
||||
}{
|
||||
{"first",
|
||||
Document{
|
||||
'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00',
|
||||
},
|
||||
[]string{"x"},
|
||||
Value{Type: bsontype.Null, Data: []byte{}},
|
||||
nil,
|
||||
},
|
||||
{"first-second",
|
||||
Document{
|
||||
'\x15', '\x00', '\x00', '\x00',
|
||||
'\x03',
|
||||
'f', 'o', 'o', '\x00',
|
||||
'\x0B', '\x00', '\x00', '\x00', '\x0A', 'a', '\x00',
|
||||
'\x0A', 'b', '\x00', '\x00', '\x00',
|
||||
},
|
||||
[]string{"foo", "b"},
|
||||
Value{Type: bsontype.Null, Data: []byte{}},
|
||||
nil,
|
||||
},
|
||||
{"first-second-array",
|
||||
Document{
|
||||
'\x15', '\x00', '\x00', '\x00',
|
||||
'\x04',
|
||||
'f', 'o', 'o', '\x00',
|
||||
'\x0B', '\x00', '\x00', '\x00', '\x0A', '1', '\x00',
|
||||
'\x0A', '2', '\x00', '\x00', '\x00',
|
||||
},
|
||||
[]string{"foo", "2"},
|
||||
Value{Type: bsontype.Null, Data: []byte{}},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("Lookup", func(t *testing.T) {
|
||||
got := tc.r.Lookup(tc.key...)
|
||||
if !cmp.Equal(got, tc.want) {
|
||||
t.Errorf("Returned value does not match expected element. got %v; want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
t.Run("LookupErr", func(t *testing.T) {
|
||||
got, err := tc.r.LookupErr(tc.key...)
|
||||
if err != tc.err {
|
||||
t.Errorf("Returned error does not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
if !cmp.Equal(got, tc.want) {
|
||||
t.Errorf("Returned value does not match expected element. got %v; want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Index", func(t *testing.T) {
|
||||
t.Run("Out of bounds", func(t *testing.T) {
|
||||
rdr := Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0}
|
||||
_, err := rdr.IndexErr(3)
|
||||
if err != ErrOutOfBounds {
|
||||
t.Errorf("Out of bounds should be returned when accessing element beyond end of document. got %v; want %v", err, ErrOutOfBounds)
|
||||
}
|
||||
})
|
||||
t.Run("Validation Error", func(t *testing.T) {
|
||||
rdr := Document{0x07, 0x00, 0x00, 0x00, 0x00}
|
||||
_, got := rdr.IndexErr(1)
|
||||
want := NewInsufficientBytesError(nil, nil)
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Did not receive expected error. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
testCases := []struct {
|
||||
name string
|
||||
rdr Document
|
||||
index uint
|
||||
want Element
|
||||
}{
|
||||
{"first",
|
||||
Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
|
||||
0, Element{0x0a, 0x78, 0x00},
|
||||
},
|
||||
{"second",
|
||||
Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
|
||||
1, Element{0x0a, 0x79, 0x00},
|
||||
},
|
||||
{"third",
|
||||
Document{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
|
||||
2, Element{0x0a, 0x7a, 0x00},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("IndexErr", func(t *testing.T) {
|
||||
got, err := tc.rdr.IndexErr(tc.index)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error from IndexErr: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(got, tc.want); diff != "" {
|
||||
t.Errorf("Documents differ: (-got +want)\n%s", diff)
|
||||
}
|
||||
})
|
||||
t.Run("Index", func(t *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
}()
|
||||
got := tc.rdr.Index(tc.index)
|
||||
if diff := cmp.Diff(got, tc.want); diff != "" {
|
||||
t.Errorf("Documents differ: (-got +want)\n%s", diff)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("NewDocumentFromReader", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ioReader io.Reader
|
||||
doc Document
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"nil reader",
|
||||
nil,
|
||||
nil,
|
||||
ErrNilReader,
|
||||
},
|
||||
{
|
||||
"premature end of reader",
|
||||
bytes.NewBuffer([]byte{}),
|
||||
nil,
|
||||
io.EOF,
|
||||
},
|
||||
{
|
||||
"empty document",
|
||||
bytes.NewBuffer([]byte{5, 0, 0, 0, 0}),
|
||||
[]byte{5, 0, 0, 0, 0},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"non-empty document",
|
||||
bytes.NewBuffer([]byte{
|
||||
// length
|
||||
0x17, 0x0, 0x0, 0x0,
|
||||
|
||||
// type - string
|
||||
0x2,
|
||||
// key - "foo"
|
||||
0x66, 0x6f, 0x6f, 0x0,
|
||||
// value - string length
|
||||
0x4, 0x0, 0x0, 0x0,
|
||||
// value - string "bar"
|
||||
0x62, 0x61, 0x72, 0x0,
|
||||
|
||||
// type - null
|
||||
0xa,
|
||||
// key - "baz"
|
||||
0x62, 0x61, 0x7a, 0x0,
|
||||
|
||||
// null terminator
|
||||
0x0,
|
||||
}),
|
||||
[]byte{
|
||||
// length
|
||||
0x17, 0x0, 0x0, 0x0,
|
||||
|
||||
// type - string
|
||||
0x2,
|
||||
// key - "foo"
|
||||
0x66, 0x6f, 0x6f, 0x0,
|
||||
// value - string length
|
||||
0x4, 0x0, 0x0, 0x0,
|
||||
// value - string "bar"
|
||||
0x62, 0x61, 0x72, 0x0,
|
||||
|
||||
// type - null
|
||||
0xa,
|
||||
// key - "baz"
|
||||
0x62, 0x61, 0x7a, 0x0,
|
||||
|
||||
// null terminator
|
||||
0x0,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
doc, err := NewDocumentFromReader(tc.ioReader)
|
||||
if !compareErrors(err, tc.err) {
|
||||
t.Errorf("errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
if !bytes.Equal(tc.doc, doc) {
|
||||
t.Errorf("documents differ. got %v; want %v", tc.doc, doc)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Elements", func(t *testing.T) {
|
||||
invalidElem := BuildDocument(nil, AppendHeader(nil, bsontype.Double, "foo"))
|
||||
invalidTwoElem := BuildDocument(nil,
|
||||
AppendHeader(
|
||||
AppendDoubleElement(nil, "pi", 3.14159),
|
||||
bsontype.Double, "foo",
|
||||
),
|
||||
)
|
||||
oneElem := BuildDocument(nil, AppendDoubleElement(nil, "pi", 3.14159))
|
||||
twoElems := BuildDocument(nil,
|
||||
AppendStringElement(
|
||||
AppendDoubleElement(nil, "pi", 3.14159),
|
||||
"hello", "world!",
|
||||
),
|
||||
)
|
||||
testCases := []struct {
|
||||
name string
|
||||
doc Document
|
||||
elems []Element
|
||||
err error
|
||||
}{
|
||||
{"Insufficient Bytes Length", Document{0x03, 0x00, 0x00}, nil, NewInsufficientBytesError(nil, nil)},
|
||||
{"Insufficient Bytes First Element", invalidElem, nil, NewInsufficientBytesError(nil, nil)},
|
||||
{"Insufficient Bytes Second Element", invalidTwoElem, []Element{AppendDoubleElement(nil, "pi", 3.14159)}, NewInsufficientBytesError(nil, nil)},
|
||||
{"Success One Element", oneElem, []Element{AppendDoubleElement(nil, "pi", 3.14159)}, nil},
|
||||
{"Success Two Elements", twoElems, []Element{AppendDoubleElement(nil, "pi", 3.14159), AppendStringElement(nil, "hello", "world!")}, nil},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
elems, err := tc.doc.Elements()
|
||||
if !compareErrors(err, tc.err) {
|
||||
t.Errorf("errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
if len(elems) != len(tc.elems) {
|
||||
t.Fatalf("number of elements returned does not match. got %d; want %d", len(elems), len(tc.elems))
|
||||
}
|
||||
|
||||
for idx := range elems {
|
||||
got, want := elems[idx], tc.elems[idx]
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("Elements at index %d differ. got %v; want %v", idx, got.DebugString(), want.DebugString())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
152
mongo/x/bsonx/bsoncore/element.go
Normal file
152
mongo/x/bsonx/bsoncore/element.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
)
|
||||
|
||||
// MalformedElementError represents a class of errors that RawElement methods return.
|
||||
type MalformedElementError string
|
||||
|
||||
func (mee MalformedElementError) Error() string { return string(mee) }
|
||||
|
||||
// ErrElementMissingKey is returned when a RawElement is missing a key.
|
||||
const ErrElementMissingKey MalformedElementError = "element is missing key"
|
||||
|
||||
// ErrElementMissingType is returned when a RawElement is missing a type.
|
||||
const ErrElementMissingType MalformedElementError = "element is missing type"
|
||||
|
||||
// Element is a raw bytes representation of a BSON element.
|
||||
type Element []byte
|
||||
|
||||
// Key returns the key for this element. If the element is not valid, this method returns an empty
|
||||
// string. If knowing if the element is valid is important, use KeyErr.
|
||||
func (e Element) Key() string {
|
||||
key, _ := e.KeyErr()
|
||||
return key
|
||||
}
|
||||
|
||||
// KeyBytes returns the key for this element as a []byte. If the element is not valid, this method
|
||||
// returns an empty string. If knowing if the element is valid is important, use KeyErr. This method
|
||||
// will not include the null byte at the end of the key in the slice of bytes.
|
||||
func (e Element) KeyBytes() []byte {
|
||||
key, _ := e.KeyBytesErr()
|
||||
return key
|
||||
}
|
||||
|
||||
// KeyErr returns the key for this element, returning an error if the element is not valid.
|
||||
func (e Element) KeyErr() (string, error) {
|
||||
key, err := e.KeyBytesErr()
|
||||
return string(key), err
|
||||
}
|
||||
|
||||
// KeyBytesErr returns the key for this element as a []byte, returning an error if the element is
|
||||
// not valid.
|
||||
func (e Element) KeyBytesErr() ([]byte, error) {
|
||||
if len(e) <= 0 {
|
||||
return nil, ErrElementMissingType
|
||||
}
|
||||
idx := bytes.IndexByte(e[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return nil, ErrElementMissingKey
|
||||
}
|
||||
return e[1 : idx+1], nil
|
||||
}
|
||||
|
||||
// Validate ensures the element is a valid BSON element.
|
||||
func (e Element) Validate() error {
|
||||
if len(e) < 1 {
|
||||
return ErrElementMissingType
|
||||
}
|
||||
idx := bytes.IndexByte(e[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return ErrElementMissingKey
|
||||
}
|
||||
return Value{Type: bsontype.Type(e[0]), Data: e[idx+2:]}.Validate()
|
||||
}
|
||||
|
||||
// CompareKey will compare this element's key to key. This method makes it easy to compare keys
|
||||
// without needing to allocate a string. The key may be null terminated. If a valid key cannot be
|
||||
// read this method will return false.
|
||||
func (e Element) CompareKey(key []byte) bool {
|
||||
if len(e) < 2 {
|
||||
return false
|
||||
}
|
||||
idx := bytes.IndexByte(e[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return false
|
||||
}
|
||||
if index := bytes.IndexByte(key, 0x00); index > -1 {
|
||||
key = key[:index]
|
||||
}
|
||||
return bytes.Equal(e[1:idx+1], key)
|
||||
}
|
||||
|
||||
// Value returns the value of this element. If the element is not valid, this method returns an
|
||||
// empty Value. If knowing if the element is valid is important, use ValueErr.
|
||||
func (e Element) Value() Value {
|
||||
val, _ := e.ValueErr()
|
||||
return val
|
||||
}
|
||||
|
||||
// ValueErr returns the value for this element, returning an error if the element is not valid.
|
||||
func (e Element) ValueErr() (Value, error) {
|
||||
if len(e) <= 0 {
|
||||
return Value{}, ErrElementMissingType
|
||||
}
|
||||
idx := bytes.IndexByte(e[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return Value{}, ErrElementMissingKey
|
||||
}
|
||||
|
||||
val, rem, exists := ReadValue(e[idx+2:], bsontype.Type(e[0]))
|
||||
if !exists {
|
||||
return Value{}, NewInsufficientBytesError(e, rem)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// String implements the fmt.String interface. The output will be in extended JSON format.
|
||||
func (e Element) String() string {
|
||||
if len(e) <= 0 {
|
||||
return ""
|
||||
}
|
||||
t := bsontype.Type(e[0])
|
||||
idx := bytes.IndexByte(e[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
key, valBytes := []byte(e[1:idx+1]), []byte(e[idx+2:])
|
||||
val, _, valid := ReadValue(valBytes, t)
|
||||
if !valid {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`"%s": %v`, key, val)
|
||||
}
|
||||
|
||||
// DebugString outputs a human readable version of RawElement. It will attempt to stringify the
|
||||
// valid components of the element even if the entire element is not valid.
|
||||
func (e Element) DebugString() string {
|
||||
if len(e) <= 0 {
|
||||
return "<malformed>"
|
||||
}
|
||||
t := bsontype.Type(e[0])
|
||||
idx := bytes.IndexByte(e[1:], 0x00)
|
||||
if idx == -1 {
|
||||
return fmt.Sprintf(`bson.Element{[%s]<malformed>}`, t)
|
||||
}
|
||||
key, valBytes := []byte(e[1:idx+1]), []byte(e[idx+2:])
|
||||
val, _, valid := ReadValue(valBytes, t)
|
||||
if !valid {
|
||||
return fmt.Sprintf(`bson.Element{[%s]"%s": <malformed>}`, t, key)
|
||||
}
|
||||
return fmt.Sprintf(`bson.Element{[%s]"%s": %v}`, t, key, val)
|
||||
}
|
||||
127
mongo/x/bsonx/bsoncore/element_test.go
Normal file
127
mongo/x/bsonx/bsoncore/element_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
)
|
||||
|
||||
func TestElement(t *testing.T) {
|
||||
t.Run("KeyErr", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
elem Element
|
||||
str string
|
||||
err error
|
||||
}{
|
||||
{"No Type", Element{}, "", ErrElementMissingType},
|
||||
{"No Key", Element{0x01, 'f', 'o', 'o'}, "", ErrElementMissingKey},
|
||||
{"Success", AppendHeader(nil, bsontype.Double, "foo"), "foo", nil},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("Key", func(t *testing.T) {
|
||||
str := tc.elem.Key()
|
||||
if str != tc.str {
|
||||
t.Errorf("returned strings do not match. got %s; want %s", str, tc.str)
|
||||
}
|
||||
})
|
||||
t.Run("KeyErr", func(t *testing.T) {
|
||||
str, err := tc.elem.KeyErr()
|
||||
if !compareErrors(err, tc.err) {
|
||||
t.Errorf("errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
if str != tc.str {
|
||||
t.Errorf("returned strings do not match. got %s; want %s", str, tc.str)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
elem Element
|
||||
err error
|
||||
}{
|
||||
{"No Type", Element{}, ErrElementMissingType},
|
||||
{"No Key", Element{0x01, 'f', 'o', 'o'}, ErrElementMissingKey},
|
||||
{"Insufficient Bytes", AppendHeader(nil, bsontype.Double, "foo"), NewInsufficientBytesError(nil, nil)},
|
||||
{"Success", AppendDoubleElement(nil, "foo", 3.14159), nil},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.elem.Validate()
|
||||
if !compareErrors(err, tc.err) {
|
||||
t.Errorf("errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("CompareKey", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
elem Element
|
||||
key []byte
|
||||
equal bool
|
||||
}{
|
||||
{"Element Too Short", Element{0x02}, nil, false},
|
||||
{"Element Invalid Key", Element{0x02, 'f', 'o', 'o'}, nil, false},
|
||||
{"Key With Null Byte", AppendHeader(nil, bsontype.Double, "foo"), []byte{'f', 'o', 'o', 0x00}, true},
|
||||
{"Key Without Null Byte", AppendHeader(nil, bsontype.Double, "pi"), []byte{'p', 'i'}, true},
|
||||
{"Key With Null Byte With Extra", AppendHeader(nil, bsontype.Double, "foo"), []byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r'}, true},
|
||||
{"Prefix Key No Match", AppendHeader(nil, bsontype.Double, "foo"), []byte{'f', 'o', 'o', 'b', 'a', 'r'}, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
equal := tc.elem.CompareKey(tc.key)
|
||||
if equal != tc.equal {
|
||||
t.Errorf("Did not get expected equality result. got %t; want %t", equal, tc.equal)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("Value & ValueErr", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
elem Element
|
||||
val Value
|
||||
err error
|
||||
}{
|
||||
{"No Type", Element{}, Value{}, ErrElementMissingType},
|
||||
{"No Key", Element{0x01, 'f', 'o', 'o'}, Value{}, ErrElementMissingKey},
|
||||
{"Insufficient Bytes", AppendHeader(nil, bsontype.Double, "foo"), Value{}, NewInsufficientBytesError(nil, nil)},
|
||||
{"Success", AppendDoubleElement(nil, "foo", 3.14159), Value{Type: bsontype.Double, Data: AppendDouble(nil, 3.14159)}, nil},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("Value", func(t *testing.T) {
|
||||
val := tc.elem.Value()
|
||||
if !cmp.Equal(val, tc.val) {
|
||||
t.Errorf("Values do not match. got %v; want %v", val, tc.val)
|
||||
}
|
||||
})
|
||||
t.Run("ValueErr", func(t *testing.T) {
|
||||
val, err := tc.elem.ValueErr()
|
||||
if !compareErrors(err, tc.err) {
|
||||
t.Errorf("errors do not match. got %v; want %v", err, tc.err)
|
||||
}
|
||||
if !cmp.Equal(val, tc.val) {
|
||||
t.Errorf("Values do not match. got %v; want %v", val, tc.val)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
223
mongo/x/bsonx/bsoncore/tables.go
Normal file
223
mongo/x/bsonx/bsoncore/tables.go
Normal file
@@ -0,0 +1,223 @@
|
||||
// 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
|
||||
//
|
||||
// Based on github.com/golang/go by The Go Authors
|
||||
// See THIRD-PARTY-NOTICES for original license terms.
|
||||
|
||||
package bsoncore
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// safeSet holds the value true if the ASCII character with the given array
|
||||
// position can be represented inside a JSON string without any further
|
||||
// escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), and the backslash character ("\").
|
||||
var safeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': true,
|
||||
'=': true,
|
||||
'>': true,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
||||
|
||||
// htmlSafeSet holds the value true if the ASCII character with the given
|
||||
// array position can be safely represented inside a JSON string, embedded
|
||||
// inside of HTML <script> tags, without any additional escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), the backslash character ("\"), HTML opening and closing
|
||||
// tags ("<" and ">"), and the ampersand ("&").
|
||||
var htmlSafeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': false,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': false,
|
||||
'=': true,
|
||||
'>': false,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
||||
972
mongo/x/bsonx/bsoncore/value.go
Normal file
972
mongo/x/bsonx/bsoncore/value.go
Normal file
@@ -0,0 +1,972 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// ElementTypeError specifies that a method to obtain a BSON value an incorrect type was called on a bson.Value.
|
||||
type ElementTypeError struct {
|
||||
Method string
|
||||
Type bsontype.Type
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (ete ElementTypeError) Error() string {
|
||||
return "Call of " + ete.Method + " on " + ete.Type.String() + " type"
|
||||
}
|
||||
|
||||
// Value represents a BSON value with a type and raw bytes.
|
||||
type Value struct {
|
||||
Type bsontype.Type
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Validate ensures the value is a valid BSON value.
|
||||
func (v Value) Validate() error {
|
||||
_, _, valid := readValue(v.Data, v.Type)
|
||||
if !valid {
|
||||
return NewInsufficientBytesError(v.Data, v.Data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNumber returns true if the type of v is a numeric BSON type.
|
||||
func (v Value) IsNumber() bool {
|
||||
switch v.Type {
|
||||
case bsontype.Double, bsontype.Int32, bsontype.Int64, bsontype.Decimal128:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// AsInt32 returns a BSON number as an int32. If the BSON type is not a numeric one, this method
|
||||
// will panic.
|
||||
func (v Value) AsInt32() int32 {
|
||||
if !v.IsNumber() {
|
||||
panic(ElementTypeError{"bsoncore.Value.AsInt32", v.Type})
|
||||
}
|
||||
var i32 int32
|
||||
switch v.Type {
|
||||
case bsontype.Double:
|
||||
f64, _, ok := ReadDouble(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
i32 = int32(f64)
|
||||
case bsontype.Int32:
|
||||
var ok bool
|
||||
i32, _, ok = ReadInt32(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
case bsontype.Int64:
|
||||
i64, _, ok := ReadInt64(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
i32 = int32(i64)
|
||||
case bsontype.Decimal128:
|
||||
panic(ElementTypeError{"bsoncore.Value.AsInt32", v.Type})
|
||||
}
|
||||
return i32
|
||||
}
|
||||
|
||||
// AsInt32OK functions the same as AsInt32 but returns a boolean instead of panicking. False
|
||||
// indicates an error.
|
||||
func (v Value) AsInt32OK() (int32, bool) {
|
||||
if !v.IsNumber() {
|
||||
return 0, false
|
||||
}
|
||||
var i32 int32
|
||||
switch v.Type {
|
||||
case bsontype.Double:
|
||||
f64, _, ok := ReadDouble(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
i32 = int32(f64)
|
||||
case bsontype.Int32:
|
||||
var ok bool
|
||||
i32, _, ok = ReadInt32(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
case bsontype.Int64:
|
||||
i64, _, ok := ReadInt64(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
i32 = int32(i64)
|
||||
case bsontype.Decimal128:
|
||||
return 0, false
|
||||
}
|
||||
return i32, true
|
||||
}
|
||||
|
||||
// AsInt64 returns a BSON number as an int64. If the BSON type is not a numeric one, this method
|
||||
// will panic.
|
||||
func (v Value) AsInt64() int64 {
|
||||
if !v.IsNumber() {
|
||||
panic(ElementTypeError{"bsoncore.Value.AsInt64", v.Type})
|
||||
}
|
||||
var i64 int64
|
||||
switch v.Type {
|
||||
case bsontype.Double:
|
||||
f64, _, ok := ReadDouble(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
i64 = int64(f64)
|
||||
case bsontype.Int32:
|
||||
var ok bool
|
||||
i32, _, ok := ReadInt32(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
i64 = int64(i32)
|
||||
case bsontype.Int64:
|
||||
var ok bool
|
||||
i64, _, ok = ReadInt64(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
case bsontype.Decimal128:
|
||||
panic(ElementTypeError{"bsoncore.Value.AsInt64", v.Type})
|
||||
}
|
||||
return i64
|
||||
}
|
||||
|
||||
// AsInt64OK functions the same as AsInt64 but returns a boolean instead of panicking. False
|
||||
// indicates an error.
|
||||
func (v Value) AsInt64OK() (int64, bool) {
|
||||
if !v.IsNumber() {
|
||||
return 0, false
|
||||
}
|
||||
var i64 int64
|
||||
switch v.Type {
|
||||
case bsontype.Double:
|
||||
f64, _, ok := ReadDouble(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
i64 = int64(f64)
|
||||
case bsontype.Int32:
|
||||
var ok bool
|
||||
i32, _, ok := ReadInt32(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
i64 = int64(i32)
|
||||
case bsontype.Int64:
|
||||
var ok bool
|
||||
i64, _, ok = ReadInt64(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
case bsontype.Decimal128:
|
||||
return 0, false
|
||||
}
|
||||
return i64, true
|
||||
}
|
||||
|
||||
// AsFloat64 returns a BSON number as an float64. If the BSON type is not a numeric one, this method
|
||||
// will panic.
|
||||
//
|
||||
// TODO(skriptble): Add support for Decimal128.
|
||||
func (v Value) AsFloat64() float64 { return 0 }
|
||||
|
||||
// AsFloat64OK functions the same as AsFloat64 but returns a boolean instead of panicking. False
|
||||
// indicates an error.
|
||||
//
|
||||
// TODO(skriptble): Add support for Decimal128.
|
||||
func (v Value) AsFloat64OK() (float64, bool) { return 0, false }
|
||||
|
||||
// Add will add this value to another. This is currently only implemented for strings and numbers.
|
||||
// If either value is a string, the other type is coerced into a string and added to the other.
|
||||
//
|
||||
// This method will alter v and will attempt to reuse the []byte of v. If the []byte is too small,
|
||||
// it will be expanded.
|
||||
func (v *Value) Add(v2 Value) error { return nil }
|
||||
|
||||
// Equal compaes v to v2 and returns true if they are equal.
|
||||
func (v Value) Equal(v2 Value) bool {
|
||||
if v.Type != v2.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(v.Data, v2.Data)
|
||||
}
|
||||
|
||||
// String implements the fmt.String interface. This method will return values in extended JSON
|
||||
// format. If the value is not valid, this returns an empty string
|
||||
func (v Value) String() string {
|
||||
switch v.Type {
|
||||
case bsontype.Double:
|
||||
f64, ok := v.DoubleOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$numberDouble":"%s"}`, formatDouble(f64))
|
||||
case bsontype.String:
|
||||
str, ok := v.StringValueOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return escapeString(str)
|
||||
case bsontype.EmbeddedDocument:
|
||||
doc, ok := v.DocumentOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return doc.String()
|
||||
case bsontype.Array:
|
||||
arr, ok := v.ArrayOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return arr.String()
|
||||
case bsontype.Binary:
|
||||
subtype, data, ok := v.BinaryOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$binary":{"base64":"%s","subType":"%02x"}}`, base64.StdEncoding.EncodeToString(data), subtype)
|
||||
case bsontype.Undefined:
|
||||
return `{"$undefined":true}`
|
||||
case bsontype.ObjectID:
|
||||
oid, ok := v.ObjectIDOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$oid":"%s"}`, oid.Hex())
|
||||
case bsontype.Boolean:
|
||||
b, ok := v.BooleanOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatBool(b)
|
||||
case bsontype.DateTime:
|
||||
dt, ok := v.DateTimeOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$date":{"$numberLong":"%d"}}`, dt)
|
||||
case bsontype.Null:
|
||||
return "null"
|
||||
case bsontype.Regex:
|
||||
pattern, options, ok := v.RegexOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
`{"$regularExpression":{"pattern":%s,"options":"%s"}}`,
|
||||
escapeString(pattern), sortStringAlphebeticAscending(options),
|
||||
)
|
||||
case bsontype.DBPointer:
|
||||
ns, pointer, ok := v.DBPointerOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$dbPointer":{"$ref":%s,"$id":{"$oid":"%s"}}}`, escapeString(ns), pointer.Hex())
|
||||
case bsontype.JavaScript:
|
||||
js, ok := v.JavaScriptOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$code":%s}`, escapeString(js))
|
||||
case bsontype.Symbol:
|
||||
symbol, ok := v.SymbolOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$symbol":%s}`, escapeString(symbol))
|
||||
case bsontype.CodeWithScope:
|
||||
code, scope, ok := v.CodeWithScopeOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$code":%s,"$scope":%s}`, code, scope)
|
||||
case bsontype.Int32:
|
||||
i32, ok := v.Int32OK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$numberInt":"%d"}`, i32)
|
||||
case bsontype.Timestamp:
|
||||
t, i, ok := v.TimestampOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$timestamp":{"t":%v,"i":%v}}`, t, i)
|
||||
case bsontype.Int64:
|
||||
i64, ok := v.Int64OK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$numberLong":"%d"}`, i64)
|
||||
case bsontype.Decimal128:
|
||||
d128, ok := v.Decimal128OK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$numberDecimal":"%s"}`, d128.String())
|
||||
case bsontype.MinKey:
|
||||
return `{"$minKey":1}`
|
||||
case bsontype.MaxKey:
|
||||
return `{"$maxKey":1}`
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// DebugString outputs a human readable version of Document. It will attempt to stringify the
|
||||
// valid components of the document even if the entire document is not valid.
|
||||
func (v Value) DebugString() string {
|
||||
switch v.Type {
|
||||
case bsontype.String:
|
||||
str, ok := v.StringValueOK()
|
||||
if !ok {
|
||||
return "<malformed>"
|
||||
}
|
||||
return escapeString(str)
|
||||
case bsontype.EmbeddedDocument:
|
||||
doc, ok := v.DocumentOK()
|
||||
if !ok {
|
||||
return "<malformed>"
|
||||
}
|
||||
return doc.DebugString()
|
||||
case bsontype.Array:
|
||||
arr, ok := v.ArrayOK()
|
||||
if !ok {
|
||||
return "<malformed>"
|
||||
}
|
||||
return arr.DebugString()
|
||||
case bsontype.CodeWithScope:
|
||||
code, scope, ok := v.CodeWithScopeOK()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`{"$code":%s,"$scope":%s}`, code, scope.DebugString())
|
||||
default:
|
||||
str := v.String()
|
||||
if str == "" {
|
||||
return "<malformed>"
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
// Double returns the float64 value for this element.
|
||||
// It panics if e's BSON type is not bsontype.Double.
|
||||
func (v Value) Double() float64 {
|
||||
if v.Type != bsontype.Double {
|
||||
panic(ElementTypeError{"bsoncore.Value.Double", v.Type})
|
||||
}
|
||||
f64, _, ok := ReadDouble(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return f64
|
||||
}
|
||||
|
||||
// DoubleOK is the same as Double, but returns a boolean instead of panicking.
|
||||
func (v Value) DoubleOK() (float64, bool) {
|
||||
if v.Type != bsontype.Double {
|
||||
return 0, false
|
||||
}
|
||||
f64, _, ok := ReadDouble(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return f64, true
|
||||
}
|
||||
|
||||
// StringValue returns the string balue for this element.
|
||||
// It panics if e's BSON type is not bsontype.String.
|
||||
//
|
||||
// NOTE: This method is called StringValue to avoid a collision with the String method which
|
||||
// implements the fmt.Stringer interface.
|
||||
func (v Value) StringValue() string {
|
||||
if v.Type != bsontype.String {
|
||||
panic(ElementTypeError{"bsoncore.Value.StringValue", v.Type})
|
||||
}
|
||||
str, _, ok := ReadString(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// StringValueOK is the same as StringValue, but returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) StringValueOK() (string, bool) {
|
||||
if v.Type != bsontype.String {
|
||||
return "", false
|
||||
}
|
||||
str, _, ok := ReadString(v.Data)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return str, true
|
||||
}
|
||||
|
||||
// Document returns the BSON document the Value represents as a Document. It panics if the
|
||||
// value is a BSON type other than document.
|
||||
func (v Value) Document() Document {
|
||||
if v.Type != bsontype.EmbeddedDocument {
|
||||
panic(ElementTypeError{"bsoncore.Value.Document", v.Type})
|
||||
}
|
||||
doc, _, ok := ReadDocument(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
// DocumentOK is the same as Document, except it returns a boolean
|
||||
// instead of panicking.
|
||||
func (v Value) DocumentOK() (Document, bool) {
|
||||
if v.Type != bsontype.EmbeddedDocument {
|
||||
return nil, false
|
||||
}
|
||||
doc, _, ok := ReadDocument(v.Data)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return doc, true
|
||||
}
|
||||
|
||||
// Array returns the BSON array the Value represents as an Array. It panics if the
|
||||
// value is a BSON type other than array.
|
||||
func (v Value) Array() Array {
|
||||
if v.Type != bsontype.Array {
|
||||
panic(ElementTypeError{"bsoncore.Value.Array", v.Type})
|
||||
}
|
||||
arr, _, ok := ReadArray(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// ArrayOK is the same as Array, except it returns a boolean instead
|
||||
// of panicking.
|
||||
func (v Value) ArrayOK() (Array, bool) {
|
||||
if v.Type != bsontype.Array {
|
||||
return nil, false
|
||||
}
|
||||
arr, _, ok := ReadArray(v.Data)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return arr, true
|
||||
}
|
||||
|
||||
// Binary returns the BSON binary value the Value represents. It panics if the value is a BSON type
|
||||
// other than binary.
|
||||
func (v Value) Binary() (subtype byte, data []byte) {
|
||||
if v.Type != bsontype.Binary {
|
||||
panic(ElementTypeError{"bsoncore.Value.Binary", v.Type})
|
||||
}
|
||||
subtype, data, _, ok := ReadBinary(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return subtype, data
|
||||
}
|
||||
|
||||
// BinaryOK is the same as Binary, except it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) BinaryOK() (subtype byte, data []byte, ok bool) {
|
||||
if v.Type != bsontype.Binary {
|
||||
return 0x00, nil, false
|
||||
}
|
||||
subtype, data, _, ok = ReadBinary(v.Data)
|
||||
if !ok {
|
||||
return 0x00, nil, false
|
||||
}
|
||||
return subtype, data, true
|
||||
}
|
||||
|
||||
// ObjectID returns the BSON objectid value the Value represents. It panics if the value is a BSON
|
||||
// type other than objectid.
|
||||
func (v Value) ObjectID() primitive.ObjectID {
|
||||
if v.Type != bsontype.ObjectID {
|
||||
panic(ElementTypeError{"bsoncore.Value.ObjectID", v.Type})
|
||||
}
|
||||
oid, _, ok := ReadObjectID(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return oid
|
||||
}
|
||||
|
||||
// ObjectIDOK is the same as ObjectID, except it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) ObjectIDOK() (primitive.ObjectID, bool) {
|
||||
if v.Type != bsontype.ObjectID {
|
||||
return primitive.ObjectID{}, false
|
||||
}
|
||||
oid, _, ok := ReadObjectID(v.Data)
|
||||
if !ok {
|
||||
return primitive.ObjectID{}, false
|
||||
}
|
||||
return oid, true
|
||||
}
|
||||
|
||||
// Boolean returns the boolean value the Value represents. It panics if the
|
||||
// value is a BSON type other than boolean.
|
||||
func (v Value) Boolean() bool {
|
||||
if v.Type != bsontype.Boolean {
|
||||
panic(ElementTypeError{"bsoncore.Value.Boolean", v.Type})
|
||||
}
|
||||
b, _, ok := ReadBoolean(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// BooleanOK is the same as Boolean, except it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) BooleanOK() (bool, bool) {
|
||||
if v.Type != bsontype.Boolean {
|
||||
return false, false
|
||||
}
|
||||
b, _, ok := ReadBoolean(v.Data)
|
||||
if !ok {
|
||||
return false, false
|
||||
}
|
||||
return b, true
|
||||
}
|
||||
|
||||
// DateTime returns the BSON datetime value the Value represents as a
|
||||
// unix timestamp. It panics if the value is a BSON type other than datetime.
|
||||
func (v Value) DateTime() int64 {
|
||||
if v.Type != bsontype.DateTime {
|
||||
panic(ElementTypeError{"bsoncore.Value.DateTime", v.Type})
|
||||
}
|
||||
dt, _, ok := ReadDateTime(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return dt
|
||||
}
|
||||
|
||||
// DateTimeOK is the same as DateTime, except it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) DateTimeOK() (int64, bool) {
|
||||
if v.Type != bsontype.DateTime {
|
||||
return 0, false
|
||||
}
|
||||
dt, _, ok := ReadDateTime(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return dt, true
|
||||
}
|
||||
|
||||
// Time returns the BSON datetime value the Value represents. It panics if the value is a BSON
|
||||
// type other than datetime.
|
||||
func (v Value) Time() time.Time {
|
||||
if v.Type != bsontype.DateTime {
|
||||
panic(ElementTypeError{"bsoncore.Value.Time", v.Type})
|
||||
}
|
||||
dt, _, ok := ReadDateTime(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return time.Unix(dt/1000, dt%1000*1000000)
|
||||
}
|
||||
|
||||
// TimeOK is the same as Time, except it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) TimeOK() (time.Time, bool) {
|
||||
if v.Type != bsontype.DateTime {
|
||||
return time.Time{}, false
|
||||
}
|
||||
dt, _, ok := ReadDateTime(v.Data)
|
||||
if !ok {
|
||||
return time.Time{}, false
|
||||
}
|
||||
return time.Unix(dt/1000, dt%1000*1000000), true
|
||||
}
|
||||
|
||||
// Regex returns the BSON regex value the Value represents. It panics if the value is a BSON
|
||||
// type other than regex.
|
||||
func (v Value) Regex() (pattern, options string) {
|
||||
if v.Type != bsontype.Regex {
|
||||
panic(ElementTypeError{"bsoncore.Value.Regex", v.Type})
|
||||
}
|
||||
pattern, options, _, ok := ReadRegex(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return pattern, options
|
||||
}
|
||||
|
||||
// RegexOK is the same as Regex, except it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) RegexOK() (pattern, options string, ok bool) {
|
||||
if v.Type != bsontype.Regex {
|
||||
return "", "", false
|
||||
}
|
||||
pattern, options, _, ok = ReadRegex(v.Data)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
return pattern, options, true
|
||||
}
|
||||
|
||||
// DBPointer returns the BSON dbpointer value the Value represents. It panics if the value is a BSON
|
||||
// type other than DBPointer.
|
||||
func (v Value) DBPointer() (string, primitive.ObjectID) {
|
||||
if v.Type != bsontype.DBPointer {
|
||||
panic(ElementTypeError{"bsoncore.Value.DBPointer", v.Type})
|
||||
}
|
||||
ns, pointer, _, ok := ReadDBPointer(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return ns, pointer
|
||||
}
|
||||
|
||||
// DBPointerOK is the same as DBPoitner, except that it returns a boolean
|
||||
// instead of panicking.
|
||||
func (v Value) DBPointerOK() (string, primitive.ObjectID, bool) {
|
||||
if v.Type != bsontype.DBPointer {
|
||||
return "", primitive.ObjectID{}, false
|
||||
}
|
||||
ns, pointer, _, ok := ReadDBPointer(v.Data)
|
||||
if !ok {
|
||||
return "", primitive.ObjectID{}, false
|
||||
}
|
||||
return ns, pointer, true
|
||||
}
|
||||
|
||||
// JavaScript returns the BSON JavaScript code value the Value represents. It panics if the value is
|
||||
// a BSON type other than JavaScript code.
|
||||
func (v Value) JavaScript() string {
|
||||
if v.Type != bsontype.JavaScript {
|
||||
panic(ElementTypeError{"bsoncore.Value.JavaScript", v.Type})
|
||||
}
|
||||
js, _, ok := ReadJavaScript(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return js
|
||||
}
|
||||
|
||||
// JavaScriptOK is the same as Javascript, excepti that it returns a boolean
|
||||
// instead of panicking.
|
||||
func (v Value) JavaScriptOK() (string, bool) {
|
||||
if v.Type != bsontype.JavaScript {
|
||||
return "", false
|
||||
}
|
||||
js, _, ok := ReadJavaScript(v.Data)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return js, true
|
||||
}
|
||||
|
||||
// Symbol returns the BSON symbol value the Value represents. It panics if the value is a BSON
|
||||
// type other than symbol.
|
||||
func (v Value) Symbol() string {
|
||||
if v.Type != bsontype.Symbol {
|
||||
panic(ElementTypeError{"bsoncore.Value.Symbol", v.Type})
|
||||
}
|
||||
symbol, _, ok := ReadSymbol(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return symbol
|
||||
}
|
||||
|
||||
// SymbolOK is the same as Symbol, excepti that it returns a boolean
|
||||
// instead of panicking.
|
||||
func (v Value) SymbolOK() (string, bool) {
|
||||
if v.Type != bsontype.Symbol {
|
||||
return "", false
|
||||
}
|
||||
symbol, _, ok := ReadSymbol(v.Data)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return symbol, true
|
||||
}
|
||||
|
||||
// CodeWithScope returns the BSON JavaScript code with scope the Value represents.
|
||||
// It panics if the value is a BSON type other than JavaScript code with scope.
|
||||
func (v Value) CodeWithScope() (string, Document) {
|
||||
if v.Type != bsontype.CodeWithScope {
|
||||
panic(ElementTypeError{"bsoncore.Value.CodeWithScope", v.Type})
|
||||
}
|
||||
code, scope, _, ok := ReadCodeWithScope(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return code, scope
|
||||
}
|
||||
|
||||
// CodeWithScopeOK is the same as CodeWithScope, except that it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) CodeWithScopeOK() (string, Document, bool) {
|
||||
if v.Type != bsontype.CodeWithScope {
|
||||
return "", nil, false
|
||||
}
|
||||
code, scope, _, ok := ReadCodeWithScope(v.Data)
|
||||
if !ok {
|
||||
return "", nil, false
|
||||
}
|
||||
return code, scope, true
|
||||
}
|
||||
|
||||
// Int32 returns the int32 the Value represents. It panics if the value is a BSON type other than
|
||||
// int32.
|
||||
func (v Value) Int32() int32 {
|
||||
if v.Type != bsontype.Int32 {
|
||||
panic(ElementTypeError{"bsoncore.Value.Int32", v.Type})
|
||||
}
|
||||
i32, _, ok := ReadInt32(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return i32
|
||||
}
|
||||
|
||||
// Int32OK is the same as Int32, except that it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) Int32OK() (int32, bool) {
|
||||
if v.Type != bsontype.Int32 {
|
||||
return 0, false
|
||||
}
|
||||
i32, _, ok := ReadInt32(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return i32, true
|
||||
}
|
||||
|
||||
// Timestamp returns the BSON timestamp value the Value represents. It panics if the value is a
|
||||
// BSON type other than timestamp.
|
||||
func (v Value) Timestamp() (t, i uint32) {
|
||||
if v.Type != bsontype.Timestamp {
|
||||
panic(ElementTypeError{"bsoncore.Value.Timestamp", v.Type})
|
||||
}
|
||||
t, i, _, ok := ReadTimestamp(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return t, i
|
||||
}
|
||||
|
||||
// TimestampOK is the same as Timestamp, except that it returns a boolean
|
||||
// instead of panicking.
|
||||
func (v Value) TimestampOK() (t, i uint32, ok bool) {
|
||||
if v.Type != bsontype.Timestamp {
|
||||
return 0, 0, false
|
||||
}
|
||||
t, i, _, ok = ReadTimestamp(v.Data)
|
||||
if !ok {
|
||||
return 0, 0, false
|
||||
}
|
||||
return t, i, true
|
||||
}
|
||||
|
||||
// Int64 returns the int64 the Value represents. It panics if the value is a BSON type other than
|
||||
// int64.
|
||||
func (v Value) Int64() int64 {
|
||||
if v.Type != bsontype.Int64 {
|
||||
panic(ElementTypeError{"bsoncore.Value.Int64", v.Type})
|
||||
}
|
||||
i64, _, ok := ReadInt64(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return i64
|
||||
}
|
||||
|
||||
// Int64OK is the same as Int64, except that it returns a boolean instead of
|
||||
// panicking.
|
||||
func (v Value) Int64OK() (int64, bool) {
|
||||
if v.Type != bsontype.Int64 {
|
||||
return 0, false
|
||||
}
|
||||
i64, _, ok := ReadInt64(v.Data)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return i64, true
|
||||
}
|
||||
|
||||
// Decimal128 returns the decimal the Value represents. It panics if the value is a BSON type other than
|
||||
// decimal.
|
||||
func (v Value) Decimal128() primitive.Decimal128 {
|
||||
if v.Type != bsontype.Decimal128 {
|
||||
panic(ElementTypeError{"bsoncore.Value.Decimal128", v.Type})
|
||||
}
|
||||
d128, _, ok := ReadDecimal128(v.Data)
|
||||
if !ok {
|
||||
panic(NewInsufficientBytesError(v.Data, v.Data))
|
||||
}
|
||||
return d128
|
||||
}
|
||||
|
||||
// Decimal128OK is the same as Decimal128, except that it returns a boolean
|
||||
// instead of panicking.
|
||||
func (v Value) Decimal128OK() (primitive.Decimal128, bool) {
|
||||
if v.Type != bsontype.Decimal128 {
|
||||
return primitive.Decimal128{}, false
|
||||
}
|
||||
d128, _, ok := ReadDecimal128(v.Data)
|
||||
if !ok {
|
||||
return primitive.Decimal128{}, false
|
||||
}
|
||||
return d128, true
|
||||
}
|
||||
|
||||
var hexChars = "0123456789abcdef"
|
||||
|
||||
func escapeString(s string) string {
|
||||
escapeHTML := true
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte('"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
buf.WriteString(s[start:i])
|
||||
}
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(b)
|
||||
case '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('n')
|
||||
case '\r':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('r')
|
||||
case '\t':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('t')
|
||||
case '\b':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('b')
|
||||
case '\f':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('f')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||
// If escapeHTML is set, it also escapes <, >, and &
|
||||
// because they can lead to security holes when
|
||||
// user-controlled strings are rendered into JSON
|
||||
// and served to some browsers.
|
||||
buf.WriteString(`\u00`)
|
||||
buf.WriteByte(hexChars[b>>4])
|
||||
buf.WriteByte(hexChars[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRuneInString(s[i:])
|
||||
if c == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
buf.WriteString(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
// U+2028 is LINE SEPARATOR.
|
||||
// U+2029 is PARAGRAPH SEPARATOR.
|
||||
// They are both technically valid characters in JSON strings,
|
||||
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
||||
// and can lead to security holes there. It is valid JSON to
|
||||
// escape them, so we do so unconditionally.
|
||||
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
||||
if c == '\u2028' || c == '\u2029' {
|
||||
if start < i {
|
||||
buf.WriteString(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\u202`)
|
||||
buf.WriteByte(hexChars[c&0xF])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
buf.WriteString(s[start:])
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func formatDouble(f float64) string {
|
||||
var s string
|
||||
if math.IsInf(f, 1) {
|
||||
s = "Infinity"
|
||||
} else if math.IsInf(f, -1) {
|
||||
s = "-Infinity"
|
||||
} else if math.IsNaN(f) {
|
||||
s = "NaN"
|
||||
} else {
|
||||
// Print exactly one decimalType place for integers; otherwise, print as many are necessary to
|
||||
// perfectly represent it.
|
||||
s = strconv.FormatFloat(f, 'G', -1, 64)
|
||||
if !strings.ContainsRune(s, '.') {
|
||||
s += ".0"
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type sortableString []rune
|
||||
|
||||
func (ss sortableString) Len() int {
|
||||
return len(ss)
|
||||
}
|
||||
|
||||
func (ss sortableString) Less(i, j int) bool {
|
||||
return ss[i] < ss[j]
|
||||
}
|
||||
|
||||
func (ss sortableString) Swap(i, j int) {
|
||||
oldI := ss[i]
|
||||
ss[i] = ss[j]
|
||||
ss[j] = oldI
|
||||
}
|
||||
|
||||
func sortStringAlphebeticAscending(s string) string {
|
||||
ss := sortableString([]rune(s))
|
||||
sort.Sort(ss)
|
||||
return string([]rune(ss))
|
||||
}
|
||||
678
mongo/x/bsonx/bsoncore/value_test.go
Normal file
678
mongo/x/bsonx/bsoncore/value_test.go
Normal file
@@ -0,0 +1,678 @@
|
||||
// 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 bsoncore
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
v := Value{Type: bsontype.Double, Data: []byte{0x01, 0x02, 0x03, 0x04}}
|
||||
want := NewInsufficientBytesError(v.Data, v.Data)
|
||||
got := v.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Errors do not match. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
t.Run("value", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
v := Value{Type: bsontype.Double, Data: AppendDouble(nil, 3.14159)}
|
||||
var want error
|
||||
got := v.Validate()
|
||||
if !compareErrors(got, want) {
|
||||
t.Errorf("Errors do not match. got %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("IsNumber", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
val Value
|
||||
isnum bool
|
||||
}{
|
||||
{"double", Value{Type: bsontype.Double}, true},
|
||||
{"int32", Value{Type: bsontype.Int32}, true},
|
||||
{"int64", Value{Type: bsontype.Int64}, true},
|
||||
{"decimal128", Value{Type: bsontype.Decimal128}, true},
|
||||
{"string", Value{Type: bsontype.String}, false},
|
||||
{"regex", Value{Type: bsontype.Regex}, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
isnum := tc.val.IsNumber()
|
||||
if isnum != tc.isnum {
|
||||
t.Errorf("IsNumber did not return the expected boolean. got %t; want %t", isnum, tc.isnum)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
now := time.Now().Truncate(time.Millisecond)
|
||||
oid := primitive.NewObjectID()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
fn interface{}
|
||||
val Value
|
||||
panicErr error
|
||||
ret []interface{}
|
||||
}{
|
||||
{
|
||||
"Double/Not Double", Value.Double, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Double", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Double/Insufficient Bytes", Value.Double, Value{Type: bsontype.Double, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Double/Success", Value.Double, Value{Type: bsontype.Double, Data: AppendDouble(nil, 3.14159)},
|
||||
nil,
|
||||
[]interface{}{float64(3.14159)},
|
||||
},
|
||||
{
|
||||
"DoubleOK/Not Double", Value.DoubleOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{float64(0), false},
|
||||
},
|
||||
{
|
||||
"DoubleOK/Insufficient Bytes", Value.DoubleOK, Value{Type: bsontype.Double, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
nil,
|
||||
[]interface{}{float64(0), false},
|
||||
},
|
||||
{
|
||||
"DoubleOK/Success", Value.DoubleOK, Value{Type: bsontype.Double, Data: AppendDouble(nil, 3.14159)},
|
||||
nil,
|
||||
[]interface{}{float64(3.14159), true},
|
||||
},
|
||||
{
|
||||
"StringValue/Not String", Value.StringValue, Value{Type: bsontype.Double},
|
||||
ElementTypeError{"bsoncore.Value.StringValue", bsontype.Double},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"StringValue/Insufficient Bytes", Value.StringValue, Value{Type: bsontype.String, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"StringValue/Zero Length", Value.StringValue, Value{Type: bsontype.String, Data: []byte{0x00, 0x00, 0x00, 0x00}},
|
||||
NewInsufficientBytesError([]byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00, 0x00, 0x00}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"StringValue/Success", Value.StringValue, Value{Type: bsontype.String, Data: AppendString(nil, "hello, world!")},
|
||||
nil,
|
||||
[]interface{}{"hello, world!"},
|
||||
},
|
||||
{
|
||||
"StringValueOK/Not String", Value.StringValueOK, Value{Type: bsontype.Double},
|
||||
nil,
|
||||
[]interface{}{"", false},
|
||||
},
|
||||
{
|
||||
"StringValueOK/Insufficient Bytes", Value.StringValueOK, Value{Type: bsontype.String, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
nil,
|
||||
[]interface{}{"", false},
|
||||
},
|
||||
{
|
||||
"StringValueOK/Zero Length", Value.StringValueOK, Value{Type: bsontype.String, Data: []byte{0x00, 0x00, 0x00, 0x00}},
|
||||
nil,
|
||||
[]interface{}{"", false},
|
||||
},
|
||||
{
|
||||
"StringValueOK/Success", Value.StringValueOK, Value{Type: bsontype.String, Data: AppendString(nil, "hello, world!")},
|
||||
nil,
|
||||
[]interface{}{"hello, world!", true},
|
||||
},
|
||||
{
|
||||
"Document/Not Document", Value.Document, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Document", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Document/Insufficient Bytes", Value.Document, Value{Type: bsontype.EmbeddedDocument, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Document/Success", Value.Document, Value{Type: bsontype.EmbeddedDocument, Data: []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
nil,
|
||||
[]interface{}{Document{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
},
|
||||
{
|
||||
"DocumentOK/Not Document", Value.DocumentOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{Document(nil), false},
|
||||
},
|
||||
{
|
||||
"DocumentOK/Insufficient Bytes", Value.DocumentOK, Value{Type: bsontype.EmbeddedDocument, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
nil,
|
||||
[]interface{}{Document(nil), false},
|
||||
},
|
||||
{
|
||||
"DocumentOK/Success", Value.DocumentOK, Value{Type: bsontype.EmbeddedDocument, Data: []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
nil,
|
||||
[]interface{}{Document{0x05, 0x00, 0x00, 0x00, 0x00}, true},
|
||||
},
|
||||
{
|
||||
"Array/Not Array", Value.Array, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Array", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Array/Insufficient Bytes", Value.Array, Value{Type: bsontype.Array, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Array/Success", Value.Array, Value{Type: bsontype.Array, Data: []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
nil,
|
||||
[]interface{}{Array{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
},
|
||||
{
|
||||
"ArrayOK/Not Array", Value.ArrayOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{Array(nil), false},
|
||||
},
|
||||
{
|
||||
"ArrayOK/Insufficient Bytes", Value.ArrayOK, Value{Type: bsontype.Array, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
nil,
|
||||
[]interface{}{Array(nil), false},
|
||||
},
|
||||
{
|
||||
"ArrayOK/Success", Value.ArrayOK, Value{Type: bsontype.Array, Data: []byte{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
nil,
|
||||
[]interface{}{Array{0x05, 0x00, 0x00, 0x00, 0x00}, true},
|
||||
},
|
||||
{
|
||||
"Binary/Not Binary", Value.Binary, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Binary", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Binary/Insufficient Bytes", Value.Binary, Value{Type: bsontype.Binary, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Binary/Success", Value.Binary, Value{Type: bsontype.Binary, Data: AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
|
||||
nil,
|
||||
[]interface{}{byte(0xFF), []byte{0x01, 0x02, 0x03}},
|
||||
},
|
||||
{
|
||||
"BinaryOK/Not Binary", Value.BinaryOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{byte(0x00), []byte(nil), false},
|
||||
},
|
||||
{
|
||||
"BinaryOK/Insufficient Bytes", Value.BinaryOK, Value{Type: bsontype.Binary, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
nil,
|
||||
[]interface{}{byte(0x00), []byte(nil), false},
|
||||
},
|
||||
{
|
||||
"BinaryOK/Success", Value.BinaryOK, Value{Type: bsontype.Binary, Data: AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
|
||||
nil,
|
||||
[]interface{}{byte(0xFF), []byte{0x01, 0x02, 0x03}, true},
|
||||
},
|
||||
{
|
||||
"ObjectID/Not ObjectID", Value.ObjectID, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.ObjectID", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ObjectID/Insufficient Bytes", Value.ObjectID, Value{Type: bsontype.ObjectID, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"ObjectID/Success", Value.ObjectID, Value{Type: bsontype.ObjectID, Data: AppendObjectID(nil, primitive.ObjectID{0x01, 0x02})},
|
||||
nil,
|
||||
[]interface{}{primitive.ObjectID{0x01, 0x02}},
|
||||
},
|
||||
{
|
||||
"ObjectIDOK/Not ObjectID", Value.ObjectIDOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{primitive.ObjectID{}, false},
|
||||
},
|
||||
{
|
||||
"ObjectIDOK/Insufficient Bytes", Value.ObjectIDOK, Value{Type: bsontype.ObjectID, Data: []byte{0x01, 0x02, 0x03, 0x04}},
|
||||
nil,
|
||||
[]interface{}{primitive.ObjectID{}, false},
|
||||
},
|
||||
{
|
||||
"ObjectIDOK/Success", Value.ObjectIDOK, Value{Type: bsontype.ObjectID, Data: AppendObjectID(nil, primitive.ObjectID{0x01, 0x02})},
|
||||
nil,
|
||||
[]interface{}{primitive.ObjectID{0x01, 0x02}, true},
|
||||
},
|
||||
{
|
||||
"Boolean/Not Boolean", Value.Boolean, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Boolean", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Boolean/Insufficient Bytes", Value.Boolean, Value{Type: bsontype.Boolean, Data: []byte{}},
|
||||
NewInsufficientBytesError([]byte{}, []byte{}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Boolean/Success", Value.Boolean, Value{Type: bsontype.Boolean, Data: AppendBoolean(nil, true)},
|
||||
nil,
|
||||
[]interface{}{true},
|
||||
},
|
||||
{
|
||||
"BooleanOK/Not Boolean", Value.BooleanOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{false, false},
|
||||
},
|
||||
{
|
||||
"BooleanOK/Insufficient Bytes", Value.BooleanOK, Value{Type: bsontype.Boolean, Data: []byte{}},
|
||||
nil,
|
||||
[]interface{}{false, false},
|
||||
},
|
||||
{
|
||||
"BooleanOK/Success", Value.BooleanOK, Value{Type: bsontype.Boolean, Data: AppendBoolean(nil, true)},
|
||||
nil,
|
||||
[]interface{}{true, true},
|
||||
},
|
||||
{
|
||||
"DateTime/Not DateTime", Value.DateTime, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.DateTime", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"DateTime/Insufficient Bytes", Value.DateTime, Value{Type: bsontype.DateTime, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{}, []byte{}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"DateTime/Success", Value.DateTime, Value{Type: bsontype.DateTime, Data: AppendDateTime(nil, 12345)},
|
||||
nil,
|
||||
[]interface{}{int64(12345)},
|
||||
},
|
||||
{
|
||||
"DateTimeOK/Not DateTime", Value.DateTimeOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{int64(0), false},
|
||||
},
|
||||
{
|
||||
"DateTimeOK/Insufficient Bytes", Value.DateTimeOK, Value{Type: bsontype.DateTime, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{int64(0), false},
|
||||
},
|
||||
{
|
||||
"DateTimeOK/Success", Value.DateTimeOK, Value{Type: bsontype.DateTime, Data: AppendDateTime(nil, 12345)},
|
||||
nil,
|
||||
[]interface{}{int64(12345), true},
|
||||
},
|
||||
{
|
||||
"Time/Not DateTime", Value.Time, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Time", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Time/Insufficient Bytes", Value.Time, Value{Type: bsontype.DateTime, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Time/Success", Value.Time, Value{Type: bsontype.DateTime, Data: AppendTime(nil, now)},
|
||||
nil,
|
||||
[]interface{}{now},
|
||||
},
|
||||
{
|
||||
"TimeOK/Not DateTime", Value.TimeOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{time.Time{}, false},
|
||||
},
|
||||
{
|
||||
"TimeOK/Insufficient Bytes", Value.TimeOK, Value{Type: bsontype.DateTime, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{time.Time{}, false},
|
||||
},
|
||||
{
|
||||
"TimeOK/Success", Value.TimeOK, Value{Type: bsontype.DateTime, Data: AppendTime(nil, now)},
|
||||
nil,
|
||||
[]interface{}{now, true},
|
||||
},
|
||||
{
|
||||
"Regex/Not Regex", Value.Regex, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Regex", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Regex/Insufficient Bytes", Value.Regex, Value{Type: bsontype.Regex, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Regex/Success", Value.Regex, Value{Type: bsontype.Regex, Data: AppendRegex(nil, "/abcdefg/", "hijkl")},
|
||||
nil,
|
||||
[]interface{}{"/abcdefg/", "hijkl"},
|
||||
},
|
||||
{
|
||||
"RegexOK/Not Regex", Value.RegexOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{"", "", false},
|
||||
},
|
||||
{
|
||||
"RegexOK/Insufficient Bytes", Value.RegexOK, Value{Type: bsontype.Regex, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{"", "", false},
|
||||
},
|
||||
{
|
||||
"RegexOK/Success", Value.RegexOK, Value{Type: bsontype.Regex, Data: AppendRegex(nil, "/abcdefg/", "hijkl")},
|
||||
nil,
|
||||
[]interface{}{"/abcdefg/", "hijkl", true},
|
||||
},
|
||||
{
|
||||
"DBPointer/Not DBPointer", Value.DBPointer, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.DBPointer", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"DBPointer/Insufficient Bytes", Value.DBPointer, Value{Type: bsontype.DBPointer, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"DBPointer/Success", Value.DBPointer, Value{Type: bsontype.DBPointer, Data: AppendDBPointer(nil, "foobar", oid)},
|
||||
nil,
|
||||
[]interface{}{"foobar", oid},
|
||||
},
|
||||
{
|
||||
"DBPointerOK/Not DBPointer", Value.DBPointerOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{"", primitive.ObjectID{}, false},
|
||||
},
|
||||
{
|
||||
"DBPointerOK/Insufficient Bytes", Value.DBPointerOK, Value{Type: bsontype.DBPointer, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{"", primitive.ObjectID{}, false},
|
||||
},
|
||||
{
|
||||
"DBPointerOK/Success", Value.DBPointerOK, Value{Type: bsontype.DBPointer, Data: AppendDBPointer(nil, "foobar", oid)},
|
||||
nil,
|
||||
[]interface{}{"foobar", oid, true},
|
||||
},
|
||||
{
|
||||
"JavaScript/Not JavaScript", Value.JavaScript, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.JavaScript", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"JavaScript/Insufficient Bytes", Value.JavaScript, Value{Type: bsontype.JavaScript, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"JavaScript/Success", Value.JavaScript, Value{Type: bsontype.JavaScript, Data: AppendJavaScript(nil, "var hello = 'world';")},
|
||||
nil,
|
||||
[]interface{}{"var hello = 'world';"},
|
||||
},
|
||||
{
|
||||
"JavaScriptOK/Not JavaScript", Value.JavaScriptOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{"", false},
|
||||
},
|
||||
{
|
||||
"JavaScriptOK/Insufficient Bytes", Value.JavaScriptOK, Value{Type: bsontype.JavaScript, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{"", false},
|
||||
},
|
||||
{
|
||||
"JavaScriptOK/Success", Value.JavaScriptOK, Value{Type: bsontype.JavaScript, Data: AppendJavaScript(nil, "var hello = 'world';")},
|
||||
nil,
|
||||
[]interface{}{"var hello = 'world';", true},
|
||||
},
|
||||
{
|
||||
"Symbol/Not Symbol", Value.Symbol, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Symbol", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Symbol/Insufficient Bytes", Value.Symbol, Value{Type: bsontype.Symbol, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Symbol/Success", Value.Symbol, Value{Type: bsontype.Symbol, Data: AppendSymbol(nil, "symbol123456")},
|
||||
nil,
|
||||
[]interface{}{"symbol123456"},
|
||||
},
|
||||
{
|
||||
"SymbolOK/Not Symbol", Value.SymbolOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{"", false},
|
||||
},
|
||||
{
|
||||
"SymbolOK/Insufficient Bytes", Value.SymbolOK, Value{Type: bsontype.Symbol, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{"", false},
|
||||
},
|
||||
{
|
||||
"SymbolOK/Success", Value.SymbolOK, Value{Type: bsontype.Symbol, Data: AppendSymbol(nil, "symbol123456")},
|
||||
nil,
|
||||
[]interface{}{"symbol123456", true},
|
||||
},
|
||||
{
|
||||
"CodeWithScope/Not CodeWithScope", Value.CodeWithScope, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.CodeWithScope", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"CodeWithScope/Insufficient Bytes", Value.CodeWithScope, Value{Type: bsontype.CodeWithScope, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"CodeWithScope/Success", Value.CodeWithScope, Value{Type: bsontype.CodeWithScope, Data: AppendCodeWithScope(nil, "var hello = 'world';", Document{0x05, 0x00, 0x00, 0x00, 0x00})},
|
||||
nil,
|
||||
[]interface{}{"var hello = 'world';", Document{0x05, 0x00, 0x00, 0x00, 0x00}},
|
||||
},
|
||||
{
|
||||
"CodeWithScopeOK/Not CodeWithScope", Value.CodeWithScopeOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{"", Document(nil), false},
|
||||
},
|
||||
{
|
||||
"CodeWithScopeOK/Insufficient Bytes", Value.CodeWithScopeOK, Value{Type: bsontype.CodeWithScope, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{"", Document(nil), false},
|
||||
},
|
||||
{
|
||||
"CodeWithScopeOK/Success", Value.CodeWithScopeOK, Value{Type: bsontype.CodeWithScope, Data: AppendCodeWithScope(nil, "var hello = 'world';", Document{0x05, 0x00, 0x00, 0x00, 0x00})},
|
||||
nil,
|
||||
[]interface{}{"var hello = 'world';", Document{0x05, 0x00, 0x00, 0x00, 0x00}, true},
|
||||
},
|
||||
{
|
||||
"Int32/Not Int32", Value.Int32, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Int32", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Int32/Insufficient Bytes", Value.Int32, Value{Type: bsontype.Int32, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Int32/Success", Value.Int32, Value{Type: bsontype.Int32, Data: AppendInt32(nil, 1234)},
|
||||
nil,
|
||||
[]interface{}{int32(1234)},
|
||||
},
|
||||
{
|
||||
"Int32OK/Not Int32", Value.Int32OK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{int32(0), false},
|
||||
},
|
||||
{
|
||||
"Int32OK/Insufficient Bytes", Value.Int32OK, Value{Type: bsontype.Int32, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{int32(0), false},
|
||||
},
|
||||
{
|
||||
"Int32OK/Success", Value.Int32OK, Value{Type: bsontype.Int32, Data: AppendInt32(nil, 1234)},
|
||||
nil,
|
||||
[]interface{}{int32(1234), true},
|
||||
},
|
||||
{
|
||||
"Timestamp/Not Timestamp", Value.Timestamp, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Timestamp", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Timestamp/Insufficient Bytes", Value.Timestamp, Value{Type: bsontype.Timestamp, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Timestamp/Success", Value.Timestamp, Value{Type: bsontype.Timestamp, Data: AppendTimestamp(nil, 12345, 67890)},
|
||||
nil,
|
||||
[]interface{}{uint32(12345), uint32(67890)},
|
||||
},
|
||||
{
|
||||
"TimestampOK/Not Timestamp", Value.TimestampOK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{uint32(0), uint32(0), false},
|
||||
},
|
||||
{
|
||||
"TimestampOK/Insufficient Bytes", Value.TimestampOK, Value{Type: bsontype.Timestamp, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{uint32(0), uint32(0), false},
|
||||
},
|
||||
{
|
||||
"TimestampOK/Success", Value.TimestampOK, Value{Type: bsontype.Timestamp, Data: AppendTimestamp(nil, 12345, 67890)},
|
||||
nil,
|
||||
[]interface{}{uint32(12345), uint32(67890), true},
|
||||
},
|
||||
{
|
||||
"Int64/Not Int64", Value.Int64, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Int64", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Int64/Insufficient Bytes", Value.Int64, Value{Type: bsontype.Int64, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Int64/Success", Value.Int64, Value{Type: bsontype.Int64, Data: AppendInt64(nil, 1234567890)},
|
||||
nil,
|
||||
[]interface{}{int64(1234567890)},
|
||||
},
|
||||
{
|
||||
"Int64OK/Not Int64", Value.Int64OK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{int64(0), false},
|
||||
},
|
||||
{
|
||||
"Int64OK/Insufficient Bytes", Value.Int64OK, Value{Type: bsontype.Int64, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{int64(0), false},
|
||||
},
|
||||
{
|
||||
"Int64OK/Success", Value.Int64OK, Value{Type: bsontype.Int64, Data: AppendInt64(nil, 1234567890)},
|
||||
nil,
|
||||
[]interface{}{int64(1234567890), true},
|
||||
},
|
||||
{
|
||||
"Decimal128/Not Decimal128", Value.Decimal128, Value{Type: bsontype.String},
|
||||
ElementTypeError{"bsoncore.Value.Decimal128", bsontype.String},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Decimal128/Insufficient Bytes", Value.Decimal128, Value{Type: bsontype.Decimal128, Data: []byte{0x01, 0x02, 0x03}},
|
||||
NewInsufficientBytesError([]byte{0x01, 0x02, 0x03}, []byte{0x01, 0x02, 0x03}),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"Decimal128/Success", Value.Decimal128, Value{Type: bsontype.Decimal128, Data: AppendDecimal128(nil, primitive.NewDecimal128(12345, 67890))},
|
||||
nil,
|
||||
[]interface{}{primitive.NewDecimal128(12345, 67890)},
|
||||
},
|
||||
{
|
||||
"Decimal128OK/Not Decimal128", Value.Decimal128OK, Value{Type: bsontype.String},
|
||||
nil,
|
||||
[]interface{}{primitive.Decimal128{}, false},
|
||||
},
|
||||
{
|
||||
"Decimal128OK/Insufficient Bytes", Value.Decimal128OK, Value{Type: bsontype.Decimal128, Data: []byte{0x01, 0x02, 0x03}},
|
||||
nil,
|
||||
[]interface{}{primitive.Decimal128{}, false},
|
||||
},
|
||||
{
|
||||
"Decimal128OK/Success", Value.Decimal128OK, Value{Type: bsontype.Decimal128, Data: AppendDecimal128(nil, primitive.NewDecimal128(12345, 67890))},
|
||||
nil,
|
||||
[]interface{}{primitive.NewDecimal128(12345, 67890), true},
|
||||
},
|
||||
{
|
||||
"Timestamp.String/Success", Value.String, Value{Type: bsontype.Timestamp, Data: AppendTimestamp(nil, 12345, 67890)},
|
||||
nil,
|
||||
[]interface{}{"{\"$timestamp\":{\"t\":12345,\"i\":67890}}"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if !cmp.Equal(err, tc.panicErr, cmp.Comparer(compareErrors)) {
|
||||
t.Errorf("Did not receive expected panic error. got %v; want %v", err, tc.panicErr)
|
||||
}
|
||||
}()
|
||||
|
||||
fn := reflect.ValueOf(tc.fn)
|
||||
if fn.Kind() != reflect.Func || fn.Type().NumIn() != 1 || fn.Type().In(0) != reflect.TypeOf(Value{}) {
|
||||
t.Fatalf("test case field fn must be a function with 1 parameter that is a Value, but it is %v", fn.Type())
|
||||
}
|
||||
got := fn.Call([]reflect.Value{reflect.ValueOf(tc.val)})
|
||||
want := make([]reflect.Value, 0, len(tc.ret))
|
||||
for _, ret := range tc.ret {
|
||||
want = append(want, reflect.ValueOf(ret))
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("incorrect number of values returned. got %d; want %d", len(got), len(want))
|
||||
}
|
||||
|
||||
for idx := range got {
|
||||
gotv, wantv := got[idx].Interface(), want[idx].Interface()
|
||||
if !cmp.Equal(gotv, wantv, cmp.Comparer(compareDecimal128)) {
|
||||
t.Errorf("return values at index %d are not equal. got %v; want %v", idx, gotv, wantv)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user