diff options
author | 2024-03-06 09:05:45 -0800 | |
---|---|---|
committer | 2024-03-06 18:05:45 +0100 | |
commit | fc3741365c27f1d703e8a736af95b95ff811cc45 (patch) | |
tree | 929f1d5e20d1469d63a3dfe81d38d89f9a073c5a /vendor/go.mongodb.org/mongo-driver/x/bsonx | |
parent | [chore/bugfix] Little DB fixes (#2726) (diff) | |
download | gotosocial-fc3741365c27f1d703e8a736af95b95ff811cc45.tar.xz |
[bugfix] Fix Swagger spec and add test script (#2698)
* Add Swagger spec test script
* Fix Swagger spec errors not related to statuses with polls
* Add API tests that post a status with a poll
* Fix creating a status with a poll from form params
* Fix Swagger spec errors related to statuses with polls (this is the last error)
* Fix Swagger spec warnings not related to unused definitions
* Suppress a duplicate list update params definition that was somehow causing wrong param names
* Add Swagger test to CI
- updates Drone config
- vendorizes go-swagger
- fixes a file extension issue that caused the test script to generate JSON instead of YAML with the vendorized version
* Put `Sample: ` on its own line everywhere
* Remove unused id param from emojiCategoriesGet
* Add 5 more pairs of profile fields to account update API Swagger
* Remove Swagger prefix from dummy fields
It makes the generated code look weird
* Manually annotate params for statusCreate operation
* Fix all remaining Swagger spec warnings
- Change some models into operation parameters
- Ignore models that already correspond to manually documented operation parameters but can't be trivially changed (those with file fields)
* Documented that creating a status with scheduled_at isn't implemented yet
* sign drone.yml
* Fix filter API Swagger errors
* fixup! Fix filter API Swagger errors
---------
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Diffstat (limited to 'vendor/go.mongodb.org/mongo-driver/x/bsonx')
9 files changed, 3346 insertions, 0 deletions
diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/array.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/array.go new file mode 100644 index 000000000..8ea60ba3c --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/array.go @@ -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 +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bson_arraybuilder.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bson_arraybuilder.go new file mode 100644 index 000000000..7e6937d89 --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bson_arraybuilder.go @@ -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 +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bson_documentbuilder.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bson_documentbuilder.go new file mode 100644 index 000000000..52162f8aa --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bson_documentbuilder.go @@ -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 +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bsoncore.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bsoncore.go new file mode 100644 index 000000000..17aad6d71 --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/bsoncore.go @@ -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') +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/document.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/document.go new file mode 100644 index 000000000..d6e4bb069 --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/document.go @@ -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 +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/document_sequence.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/document_sequence.go new file mode 100644 index 000000000..e35bd0cd9 --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/document_sequence.go @@ -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 + } +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/element.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/element.go new file mode 100644 index 000000000..3acb4222b --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/element.go @@ -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) +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/tables.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/tables.go new file mode 100644 index 000000000..9fd903fd2 --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/tables.go @@ -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, +} diff --git a/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/value.go b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/value.go new file mode 100644 index 000000000..789d2b982 --- /dev/null +++ b/vendor/go.mongodb.org/mongo-driver/x/bsonx/bsoncore/value.go @@ -0,0 +1,980 @@ +// 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. +// +// TODO(skriptble): Add support for Decimal128. +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. +// +// TODO(skriptble): Add support for Decimal128. +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. +// +// TODO(skriptble): Add support for Decimal128. +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. +// +// TODO(skriptble): Add support for Decimal128. +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)) +} |