Copied mongo repo (to patch it)

This commit is contained in:
Mike Schwörer 2023-06-18 15:50:55 +02:00
parent 21d241f9b1
commit d471d7c396
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
544 changed files with 142039 additions and 1 deletions

View File

@ -1,4 +1,6 @@
.PHONY: run test version update-mongo
run: run:
echo "This is a library - can't be run" && false echo "This is a library - can't be run" && false
@ -8,4 +10,7 @@ test:
gotestsum --format "testname" -- -tags="timetzdata sqlite_fts5 sqlite_foreign_keys" "./test" gotestsum --format "testname" -- -tags="timetzdata sqlite_fts5 sqlite_foreign_keys" "./test"
version: version:
_data/version.sh _data/version.sh
update-mongo:
_data/update-mongo.sh

72
_data/update-mongo.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/bash
set -o nounset # disallow usage of unset vars ( set -u )
set -o errexit # Exit immediately if a pipeline returns non-zero. ( set -e )
set -o errtrace # Allow the above trap be inherited by all functions in the script. ( set -E )
set -o pipefail # Return value of a pipeline is the value of the last (rightmost) command to exit with a non-zero status
IFS=$'\n\t' # Set $IFS to only newline and tab.
dir="/tmp/mongo_repo_$( uuidgen )"
git clone "https://github.com/mongodb/mongo-go-driver" "$dir"
pushd "$dir"
git fetch --tags
latestTag="$( git describe --tags `git rev-list --tags --max-count=1` )"
git -c "advice.detachedHead=false" checkout $latestTag
latestSHA="$( git rev-parse HEAD )"
popd
existingTag=$( cat mongoPatchVersion.go | grep -oP "(?<=const MongoCloneTag = \")([A-Za-z0-9.]+)(?=\")" )
existingSHA=$( cat mongoPatchVersion.go | grep -oP "(?<=const MongoCloneCommit = \")([A-Za-z0-9.]+)(?=\")" )
echo "===================================="
echo "ID (online) $latestSHA"
echo "ID (local) $existingSHA"
echo "Tag (online) $latestTag"
echo "Tag (local) $existingTag"
echo "===================================="
if [[ "$latestTag" == "$existingTag" ]]; then
echo "Nothing to do"
rm -rf "$dir"
exit 0
fi
rm -rf mongo
cp -r "$dir" "mongo"
rm -rf "mongo/.git"
rm -rf "mongo/.evergreen"
rm -rf "mongo/cmd"
rm -rf "mongo/docs"
rm -rf "mongo/etc"
rm -rf "mongo/examples"
rm -rf "mongo/testdata"
rm -rf "mongo/benchmark"
rm -rf "mongo/vendor"
rm -rf "mongo/go.mod"
rm -rf "mongo/go.sum"
{
printf "package goext\n"
printf "\n"
printf "// %s\n" "$( date +"%Y-%m-%d %H:%M:%S%z" )"
printf "\n"
printf "const MongoCloneTag = \"%s\"\n" "$latestTag"
printf "const MongoCloneCommit = \"%s\"\n" "$latestSHA"
} > mongoPatchVersion.go
rm -rf "$dir"
echo ""
echo "Done."

16
mongo/.errcheck-excludes Normal file
View File

@ -0,0 +1,16 @@
(go.mongodb.org/mongo-driver/x/mongo/driver.Connection).Close
(*go.mongodb.org/mongo-driver/x/network/connection.connection).Close
(go.mongodb.org/mongo-driver/x/network/connection.Connection).Close
(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.connection).close
(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.Topology).Unsubscribe
(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.Server).Close
(*go.mongodb.org/mongo-driver/x/network/connection.pool).closeConnection
(*go.mongodb.org/mongo-driver/x/mongo/driver/topology.pool).close
(go.mongodb.org/mongo-driver/x/network/wiremessage.ReadWriteCloser).Close
(*go.mongodb.org/mongo-driver/mongo.Cursor).Close
(*go.mongodb.org/mongo-driver/mongo.ChangeStream).Close
(*go.mongodb.org/mongo-driver/mongo.Client).Disconnect
(net.Conn).Close
encoding/pem.Encode
fmt.Fprintf
fmt.Fprint

13
mongo/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
.vscode
debug
.idea
*.iml
*.ipr
*.iws
.idea
*.sublime-project
*.sublime-workspace
driver-test-data.tar.gz
perf
**mongocryptd.pid
*.test

3
mongo/.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "specifications"]
path = specifications
url = git@github.com:mongodb/specifications.git

123
mongo/.golangci.yml Normal file
View File

@ -0,0 +1,123 @@
run:
timeout: 5m
linters:
disable-all: true
# TODO(GODRIVER-2156): Enable all commented-out linters.
enable:
- errcheck
# - errorlint
- gocritic
- goimports
- gosimple
- gosec
- govet
- ineffassign
- makezero
- misspell
- nakedret
- paralleltest
- prealloc
- revive
- staticcheck
- typecheck
- unused
- unconvert
- unparam
linters-settings:
errcheck:
exclude: .errcheck-excludes
gocritic:
enabled-checks:
# Detects suspicious append result assignments. E.g. "b := append(a, 1, 2, 3)"
- appendAssign
govet:
disable:
- cgocall
- composites
paralleltest:
# Ignore missing calls to `t.Parallel()` and only report incorrect uses of `t.Parallel()`.
ignore-missing: true
staticcheck:
checks: [
"all",
"-SA1019", # Disable deprecation warnings for now.
"-SA1012", # Disable "do not pass a nil Context" to allow testing nil contexts in tests.
]
issues:
exclude-use-default: false
exclude:
# Add all default excluded issues except issues related to exported types/functions not having
# comments; we want those warnings. The defaults are copied from the "--exclude-use-default"
# documentation on https://golangci-lint.run/usage/configuration/#command-line-options
## Defaults ##
# EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok
- Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked
# EXC0003 golint: False positive when tests are defined in package 'test'
- func name will be used as test\.Test.* by other packages, and that stutters; consider calling this
# EXC0004 govet: Common false positives
- (possible misuse of unsafe.Pointer|should have signature)
# EXC0005 staticcheck: Developers tend to write in C-style with an explicit 'break' in a 'switch', so it's ok to ignore
- ineffective break statement. Did you mean to break out of the outer loop
# EXC0006 gosec: Too many false-positives on 'unsafe' usage
- Use of unsafe calls should be audited
# EXC0007 gosec: Too many false-positives for parametrized shell calls
- Subprocess launch(ed with variable|ing should be audited)
# EXC0008 gosec: Duplicated errcheck checks
- (G104|G307)
# EXC0009 gosec: Too many issues in popular repos
- (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)
# EXC0010 gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)'
- Potential file inclusion via variable
## End Defaults ##
# Ignore capitalization warning for this weird field name.
- "var-naming: struct field CqCssWxW should be CqCSSWxW"
# Ignore warnings for common "wiremessage.Read..." usage because the safest way to use that API
# is by assigning possibly unused returned byte buffers.
- "SA4006: this value of `wm` is never used"
- "SA4006: this value of `rem` is never used"
- "ineffectual assignment to wm"
- "ineffectual assignment to rem"
skip-dirs-use-default: false
skip-dirs:
- (^|/)vendor($|/)
- (^|/)testdata($|/)
- (^|/)etc($|/)
exclude-rules:
# Ignore some linters for example code that is intentionally simplified.
- path: examples/
linters:
- revive
- errcheck
# Disable unused code linters for the copy/pasted "awsv4" package.
- path: x/mongo/driver/auth/internal/awsv4
linters:
- unused
# Disable "unused" linter for code files that depend on the "mongocrypt.MongoCrypt" type because
# the linter build doesn't work correctly with CGO enabled. As a result, all calls to a
# "mongocrypt.MongoCrypt" API appear to always panic (see mongocrypt_not_enabled.go), leading
# to confusing messages about unused code.
- path: x/mongo/driver/crypt.go|mongo/(crypt_retrievers|mongocryptd).go
linters:
- unused
# Ignore "TLS MinVersion too low", "TLS InsecureSkipVerify set true", and "Use of weak random
# number generator (math/rand instead of crypto/rand)" in tests.
- path: _test\.go
text: G401|G402|G404
linters:
- gosec
# Ignore missing comments for exported variable/function/type for code in the "internal" and
# "benchmark" directories.
- path: (internal\/|benchmark\/)
text: exported (.+) should have comment( \(or a comment on this block\))? or be unexported
# Ignore missing package comments for directories that aren't frequently used by external users.
- path: (internal\/|benchmark\/|x\/|cmd\/|mongo\/integration\/)
text: should have a package comment
# Disable unused linter for "golang.org/x/exp/rand" package in internal/randutil/rand.
- path: internal/randutil/rand
linters:
- unused

201
mongo/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

210
mongo/Makefile Normal file
View File

@ -0,0 +1,210 @@
ATLAS_URIS = "$(ATLAS_FREE)" "$(ATLAS_REPLSET)" "$(ATLAS_SHARD)" "$(ATLAS_TLS11)" "$(ATLAS_TLS12)" "$(ATLAS_FREE_SRV)" "$(ATLAS_REPLSET_SRV)" "$(ATLAS_SHARD_SRV)" "$(ATLAS_TLS11_SRV)" "$(ATLAS_TLS12_SRV)" "$(ATLAS_SERVERLESS)" "$(ATLAS_SERVERLESS_SRV)"
TEST_TIMEOUT = 1800
### Utility targets. ###
.PHONY: default
default: build check-license check-fmt check-modules lint test-short
.PHONY: add-license
add-license:
etc/check_license.sh -a
.PHONY: check-license
check-license:
etc/check_license.sh
.PHONY: build
build: cross-compile build-tests build-compile-check
go build ./...
go build $(BUILD_TAGS) ./...
# Use ^$ to match no tests so that no tests are actually run but all tests are
# compiled. Run with -short to ensure none of the TestMain functions try to
# connect to a server.
.PHONY: build-tests
build-tests:
go test -short $(BUILD_TAGS) -run ^$$ ./...
.PHONY: build-compile-check
build-compile-check:
etc/compile_check.sh
# Cross-compiling on Linux for architectures 386, arm, arm64, amd64, ppc64le, and s390x.
# Omit any build tags because we don't expect our build environment to support compiling the C
# libraries for other architectures.
.PHONY: cross-compile
cross-compile:
GOOS=linux GOARCH=386 go build ./...
GOOS=linux GOARCH=arm go build ./...
GOOS=linux GOARCH=arm64 go build ./...
GOOS=linux GOARCH=amd64 go build ./...
GOOS=linux GOARCH=ppc64le go build ./...
GOOS=linux GOARCH=s390x go build ./...
.PHONY: install-lll
install-lll:
go install github.com/walle/lll/...@latest
.PHONY: check-fmt
check-fmt: install-lll
etc/check_fmt.sh
# check-modules runs "go mod tidy" then "go mod vendor" and exits with a non-zero exit code if there
# are any module or vendored modules changes. The intent is to confirm two properties:
#
# 1. Exactly the required modules are declared as dependencies. We should always be able to run
# "go mod tidy" and expect that no unrelated changes are made to the "go.mod" file.
#
# 2. All required modules are copied into the vendor/ directory and are an exact copy of the
# original module source code (i.e. the vendored modules are not modified from their original code).
.PHONY: check-modules
check-modules:
go mod tidy -v
go mod vendor
git diff --exit-code go.mod go.sum ./vendor
.PHONY: doc
doc:
godoc -http=:6060 -index
.PHONY: fmt
fmt:
go fmt ./...
.PHONY: install-golangci-lint
install-golangci-lint:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0
# Lint with various GOOS and GOARCH targets to catch static analysis failures that may only affect
# specific operating systems or architectures. For example, staticcheck will only check for 64-bit
# alignment of atomically accessed variables on 32-bit architectures (see
# https://staticcheck.io/docs/checks#SA1027)
.PHONY: lint
lint: install-golangci-lint
GOOS=linux GOARCH=386 golangci-lint run --config .golangci.yml ./...
GOOS=linux GOARCH=arm golangci-lint run --config .golangci.yml ./...
GOOS=linux GOARCH=arm64 golangci-lint run --config .golangci.yml ./...
GOOS=linux GOARCH=amd64 golangci-lint run --config .golangci.yml ./...
GOOS=linux GOARCH=ppc64le golangci-lint run --config .golangci.yml ./...
GOOS=linux GOARCH=s390x golangci-lint run --config .golangci.yml ./...
.PHONY: update-notices
update-notices:
etc/generate_notices.pl > THIRD-PARTY-NOTICES
### Local testing targets. ###
.PHONY: test
test:
go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -p 1 ./...
.PHONY: test-cover
test-cover:
go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -cover $(COVER_ARGS) -p 1 ./...
.PHONY: test-race
test-race:
go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -race -p 1 ./...
.PHONY: test-short
test-short:
go test $(BUILD_TAGS) -timeout 60s -short ./...
### Evergreen specific targets. ###
.PHONY: build-aws-ecs-test
build-aws-ecs-test:
go build $(BUILD_TAGS) ./cmd/testaws/main.go
.PHONY: evg-test
evg-test:
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s -p 1 ./... >> test.suite
.PHONY: evg-test-atlas
evg-test-atlas:
go run ./cmd/testatlas/main.go $(ATLAS_URIS)
.PHONY: evg-test-atlas-data-lake
evg-test-atlas-data-lake:
ATLAS_DATA_LAKE_INTEGRATION_TEST=true go test -v ./mongo/integration -run TestUnifiedSpecs/atlas-data-lake-testing >> spec_test.suite
ATLAS_DATA_LAKE_INTEGRATION_TEST=true go test -v ./mongo/integration -run TestAtlasDataLake >> spec_test.suite
.PHONY: evg-test-enterprise-auth
evg-test-enterprise-auth:
go run -tags gssapi ./cmd/testentauth/main.go
.PHONY: evg-test-kmip
evg-test-kmip:
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionSpec/kmipKMS >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionProse/data_key_and_double_encryption >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionProse/corpus >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionProse/custom_endpoint >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionProse/kms_tls_options_test >> test.suite
.PHONY: evg-test-kms
evg-test-kms:
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionProse/kms_tls_tests >> test.suite
.PHONY: evg-test-load-balancers
evg-test-load-balancers:
# Load balancer should be tested with all unified tests as well as tests in the following
# components: retryable reads, retryable writes, change streams, initial DNS seedlist discovery.
go test $(BUILD_TAGS) ./mongo/integration -run TestUnifiedSpecs/retryable-reads -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestRetryableWritesSpec -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestChangeStreamSpec -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestInitialDNSSeedlistDiscoverySpec/load_balanced -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestLoadBalancerSupport -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration/unified -run TestUnifiedSpec -v -timeout $(TEST_TIMEOUT)s >> test.suite
.PHONY: evg-test-ocsp
evg-test-ocsp:
go test -v ./mongo -run TestOCSP $(OCSP_TLS_SHOULD_SUCCEED) >> test.suite
.PHONY: evg-test-serverless
evg-test-serverless:
# Serverless should be tested with all unified tests as well as tests in the following components: CRUD, load balancer,
# retryable reads, retryable writes, sessions, transactions and cursor behavior.
go test $(BUILD_TAGS) ./mongo/integration -run TestCrudSpec -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestWriteErrorsWithLabels -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestWriteErrorsDetails -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestHintErrors -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestWriteConcernError -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestErrorsCodeNamePropagated -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestLoadBalancerSupport -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestUnifiedSpecs/retryable-reads -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestRetryableReadsProse -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestRetryableWritesSpec -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestRetryableWritesProse -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestUnifiedSpecs/sessions -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestSessionsProse -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestUnifiedSpecs/transactions/legacy -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestConvenientTransactions -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration -run TestCursor -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test $(BUILD_TAGS) ./mongo/integration/unified -run TestUnifiedSpec -v -timeout $(TEST_TIMEOUT)s >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionSpec >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration -run TestClientSideEncryptionProse >> test.suite
.PHONY: evg-test-versioned-api
evg-test-versioned-api:
# Versioned API related tests are in the mongo, integration and unified packages.
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration >> test.suite
go test -exec "env PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH)" $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s ./mongo/integration/unified >> test.suite
.PHONY: build-gcpkms-test
build-gcpkms-test:
go build $(BUILD_TAGS) ./cmd/testgcpkms
### Benchmark specific targets and support. ###
.PHONY: benchmark
benchmark:perf
go test $(BUILD_TAGS) -benchmem -bench=. ./benchmark
.PHONY: driver-benchmark
driver-benchmark:perf
@go run cmd/godriver-benchmark/main.go | tee perf.suite
perf:driver-test-data.tar.gz
tar -zxf $< $(if $(eq $(UNAME_S),Darwin),-s , --transform=s)/testdata/perf/
@touch $@
driver-test-data.tar.gz:
curl --retry 5 "https://s3.amazonaws.com/boxes.10gen.com/build/driver-test-data.tar.gz" -o driver-test-data.tar.gz --silent --max-time 120

251
mongo/README.md Normal file
View File

@ -0,0 +1,251 @@
<p align="center"><img src="etc/assets/mongo-gopher.png" width="250"></p>
<p align="center">
<a href="https://goreportcard.com/report/go.mongodb.org/mongo-driver"><img src="https://goreportcard.com/badge/go.mongodb.org/mongo-driver"></a>
<a href="https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo"><img src="etc/assets/godev-mongo-blue.svg" alt="docs"></a>
<a href="https://pkg.go.dev/go.mongodb.org/mongo-driver/bson"><img src="etc/assets/godev-bson-blue.svg" alt="docs"></a>
<a href="https://www.mongodb.com/docs/drivers/go/current/"><img src="etc/assets/docs-mongodb-green.svg"></a>
</p>
# MongoDB Go Driver
The MongoDB supported driver for Go.
-------------------------
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Feedback](#feedback)
- [Testing / Development](#testing--development)
- [Continuous Integration](#continuous-integration)
- [License](#license)
-------------------------
## Requirements
- Go 1.13 or higher. We aim to support the latest versions of Go.
- Go 1.20 or higher is required to run the driver test suite.
- MongoDB 3.6 and higher.
-------------------------
## Installation
The recommended way to get started using the MongoDB Go driver is by using Go modules to install the dependency in
your project. This can be done either by importing packages from `go.mongodb.org/mongo-driver` and having the build
step install the dependency or by explicitly running
```bash
go get go.mongodb.org/mongo-driver/mongo
```
When using a version of Go that does not support modules, the driver can be installed using `dep` by running
```bash
dep ensure -add "go.mongodb.org/mongo-driver/mongo"
```
-------------------------
## Usage
To get started with the driver, import the `mongo` package and create a `mongo.Client` with the `Connect` function:
```go
import (
"context"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
```
Make sure to defer a call to `Disconnect` after instantiating your client:
```go
defer func() {
if err = client.Disconnect(ctx); err != nil {
panic(err)
}
}()
```
For more advanced configuration and authentication, see the [documentation for mongo.Connect](https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Connect).
Calling `Connect` does not block for server discovery. If you wish to know if a MongoDB server has been found and connected to,
use the `Ping` method:
```go
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
err = client.Ping(ctx, readpref.Primary())
```
To insert a document into a collection, first retrieve a `Database` and then `Collection` instance from the `Client`:
```go
collection := client.Database("testing").Collection("numbers")
```
The `Collection` instance can then be used to insert documents:
```go
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
res, err := collection.InsertOne(ctx, bson.D{{"name", "pi"}, {"value", 3.14159}})
id := res.InsertedID
```
To use `bson.D`, you will need to add `"go.mongodb.org/mongo-driver/bson"` to your imports.
Your import statement should now look like this:
```go
import (
"context"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
```
Several query methods return a cursor, which can be used like this:
```go
ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cur, err := collection.Find(ctx, bson.D{})
if err != nil { log.Fatal(err) }
defer cur.Close(ctx)
for cur.Next(ctx) {
var result bson.D
err := cur.Decode(&result)
if err != nil { log.Fatal(err) }
// do something with result....
}
if err := cur.Err(); err != nil {
log.Fatal(err)
}
```
For methods that return a single item, a `SingleResult` instance is returned:
```go
var result struct {
Value float64
}
filter := bson.D{{"name", "pi"}}
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = collection.FindOne(ctx, filter).Decode(&result)
if err == mongo.ErrNoDocuments {
// Do something when no record was found
fmt.Println("record does not exist")
} else if err != nil {
log.Fatal(err)
}
// Do something with result...
```
Additional examples and documentation can be found under the examples directory and [on the MongoDB Documentation website](https://www.mongodb.com/docs/drivers/go/current/).
-------------------------
## Feedback
For help with the driver, please post in the [MongoDB Community Forums](https://developer.mongodb.com/community/forums/tag/golang/).
New features and bugs can be reported on jira: https://jira.mongodb.org/browse/GODRIVER
-------------------------
## Testing / Development
The driver tests can be run against several database configurations. The most simple configuration is a standalone mongod with no auth, no ssl, and no compression. To run these basic driver tests, make sure a standalone MongoDB server instance is running at localhost:27017. To run the tests, you can run `make` (on Windows, run `nmake`). This will run coverage, run go-lint, run go-vet, and build the examples.
### Testing Different Topologies
To test a **replica set** or **sharded cluster**, set `MONGODB_URI="<connection-string>"` for the `make` command.
For example, for a local replica set named `rs1` comprised of three nodes on ports 27017, 27018, and 27019:
```
MONGODB_URI="mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs1" make
```
### Testing Auth and TLS
To test authentication and TLS, first set up a MongoDB cluster with auth and TLS configured. Testing authentication requires a user with the `root` role on the `admin` database. Here is an example command that would run a mongod with TLS correctly configured for tests. Either set or replace PATH_TO_SERVER_KEY_FILE and PATH_TO_CA_FILE with paths to their respective files:
```
mongod \
--auth \
--tlsMode requireTLS \
--tlsCertificateKeyFile $PATH_TO_SERVER_KEY_FILE \
--tlsCAFile $PATH_TO_CA_FILE \
--tlsAllowInvalidCertificates
```
To run the tests with `make`, set:
- `MONGO_GO_DRIVER_CA_FILE` to the location of the CA file used by the database
- `MONGO_GO_DRIVER_KEY_FILE` to the location of the client key file
- `MONGO_GO_DRIVER_PKCS8_ENCRYPTED_KEY_FILE` to the location of the pkcs8 client key file encrypted with the password string: `password`
- `MONGO_GO_DRIVER_PKCS8_UNENCRYPTED_KEY_FILE` to the location of the unencrypted pkcs8 key file
- `MONGODB_URI` to the connection string of the server
- `AUTH=auth`
- `SSL=ssl`
For example:
```
AUTH=auth SSL=ssl \
MONGO_GO_DRIVER_CA_FILE=$PATH_TO_CA_FILE \
MONGO_GO_DRIVER_KEY_FILE=$PATH_TO_CLIENT_KEY_FILE \
MONGO_GO_DRIVER_PKCS8_ENCRYPTED_KEY_FILE=$PATH_TO_ENCRYPTED_KEY_FILE \
MONGO_GO_DRIVER_PKCS8_UNENCRYPTED_KEY_FILE=$PATH_TO_UNENCRYPTED_KEY_FILE \
MONGODB_URI="mongodb://user:password@localhost:27017/?authSource=admin" \
make
```
Notes:
- The `--tlsAllowInvalidCertificates` flag is required on the server for the test suite to work correctly.
- The test suite requires the auth database to be set with `?authSource=admin`, not `/admin`.
### Testing Compression
The MongoDB Go Driver supports wire protocol compression using Snappy, zLib, or zstd. To run tests with wire protocol compression, set `MONGO_GO_DRIVER_COMPRESSOR` to `snappy`, `zlib`, or `zstd`. For example:
```
MONGO_GO_DRIVER_COMPRESSOR=snappy make
```
Ensure the [`--networkMessageCompressors` flag](https://www.mongodb.com/docs/manual/reference/program/mongod/#cmdoption-mongod-networkmessagecompressors) on mongod or mongos includes `zlib` if testing zLib compression.
-------------------------
## Contribution
Check out the [project page](https://jira.mongodb.org/browse/GODRIVER) for tickets that need completing. See our [contribution guidelines](docs/CONTRIBUTING.md) for details.
-------------------------
## Continuous Integration
Commits to master are run automatically on [evergreen](https://evergreen.mongodb.com/waterfall/mongo-go-driver).
-------------------------
## Frequently Encountered Issues
See our [common issues](docs/common-issues.md) documentation for troubleshooting frequently encountered issues.
-------------------------
## Thanks and Acknowledgement
<a href="https://github.com/ashleymcnamara">@ashleymcnamara</a> - Mongo Gopher Artwork
-------------------------
## License
The MongoDB Go Driver is licensed under the [Apache License](LICENSE).

1554
mongo/THIRD-PARTY-NOTICES Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,307 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"testing"
)
type encodetest struct {
Field1String string
Field1Int64 int64
Field1Float64 float64
Field2String string
Field2Int64 int64
Field2Float64 float64
Field3String string
Field3Int64 int64
Field3Float64 float64
Field4String string
Field4Int64 int64
Field4Float64 float64
}
type nestedtest1 struct {
Nested nestedtest2
}
type nestedtest2 struct {
Nested nestedtest3
}
type nestedtest3 struct {
Nested nestedtest4
}
type nestedtest4 struct {
Nested nestedtest5
}
type nestedtest5 struct {
Nested nestedtest6
}
type nestedtest6 struct {
Nested nestedtest7
}
type nestedtest7 struct {
Nested nestedtest8
}
type nestedtest8 struct {
Nested nestedtest9
}
type nestedtest9 struct {
Nested nestedtest10
}
type nestedtest10 struct {
Nested nestedtest11
}
type nestedtest11 struct {
Nested encodetest
}
var encodetestInstance = encodetest{
Field1String: "foo",
Field1Int64: 1,
Field1Float64: 3.0,
Field2String: "bar",
Field2Int64: 2,
Field2Float64: 3.1,
Field3String: "baz",
Field3Int64: 3,
Field3Float64: 3.14,
Field4String: "qux",
Field4Int64: 4,
Field4Float64: 3.141,
}
var nestedInstance = nestedtest1{
nestedtest2{
nestedtest3{
nestedtest4{
nestedtest5{
nestedtest6{
nestedtest7{
nestedtest8{
nestedtest9{
nestedtest10{
nestedtest11{
encodetest{
Field1String: "foo",
Field1Int64: 1,
Field1Float64: 3.0,
Field2String: "bar",
Field2Int64: 2,
Field2Float64: 3.1,
Field3String: "baz",
Field3Int64: 3,
Field3Float64: 3.14,
Field4String: "qux",
Field4Int64: 4,
Field4Float64: 3.141,
},
},
},
},
},
},
},
},
},
},
},
}
const extendedBSONDir = "../testdata/extended_bson"
// readExtJSONFile reads the GZIP-compressed extended JSON document from the given filename in the
// "extended BSON" test data directory (../testdata/extended_bson) and returns it as a
// map[string]interface{}. It panics on any errors.
func readExtJSONFile(filename string) map[string]interface{} {
filePath := path.Join(extendedBSONDir, filename)
file, err := os.Open(filePath)
if err != nil {
panic(fmt.Sprintf("error opening file %q: %s", filePath, err))
}
defer func() {
_ = file.Close()
}()
gz, err := gzip.NewReader(file)
if err != nil {
panic(fmt.Sprintf("error creating GZIP reader: %s", err))
}
defer func() {
_ = gz.Close()
}()
data, err := ioutil.ReadAll(gz)
if err != nil {
panic(fmt.Sprintf("error reading GZIP contents of file: %s", err))
}
var v map[string]interface{}
err = UnmarshalExtJSON(data, false, &v)
if err != nil {
panic(fmt.Sprintf("error unmarshalling extended JSON: %s", err))
}
return v
}
func BenchmarkMarshal(b *testing.B) {
cases := []struct {
desc string
value interface{}
}{
{
desc: "simple struct",
value: encodetestInstance,
},
{
desc: "nested struct",
value: nestedInstance,
},
{
desc: "deep_bson.json.gz",
value: readExtJSONFile("deep_bson.json.gz"),
},
{
desc: "flat_bson.json.gz",
value: readExtJSONFile("flat_bson.json.gz"),
},
{
desc: "full_bson.json.gz",
value: readExtJSONFile("full_bson.json.gz"),
},
}
for _, tc := range cases {
b.Run(tc.desc, func(b *testing.B) {
b.Run("BSON", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := Marshal(tc.value)
if err != nil {
b.Errorf("error marshalling BSON: %s", err)
}
}
})
b.Run("extJSON", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := MarshalExtJSON(tc.value, true, false)
if err != nil {
b.Errorf("error marshalling extended JSON: %s", err)
}
}
})
b.Run("JSON", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := json.Marshal(tc.value)
if err != nil {
b.Errorf("error marshalling JSON: %s", err)
}
}
})
})
}
}
func BenchmarkUnmarshal(b *testing.B) {
cases := []struct {
desc string
value interface{}
}{
{
desc: "simple struct",
value: encodetestInstance,
},
{
desc: "nested struct",
value: nestedInstance,
},
{
desc: "deep_bson.json.gz",
value: readExtJSONFile("deep_bson.json.gz"),
},
{
desc: "flat_bson.json.gz",
value: readExtJSONFile("flat_bson.json.gz"),
},
{
desc: "full_bson.json.gz",
value: readExtJSONFile("full_bson.json.gz"),
},
}
for _, tc := range cases {
b.Run(tc.desc, func(b *testing.B) {
b.Run("BSON", func(b *testing.B) {
data, err := Marshal(tc.value)
if err != nil {
b.Errorf("error marshalling BSON: %s", err)
return
}
b.ResetTimer()
var v2 map[string]interface{}
for i := 0; i < b.N; i++ {
err := Unmarshal(data, &v2)
if err != nil {
b.Errorf("error unmarshalling BSON: %s", err)
}
}
})
b.Run("extJSON", func(b *testing.B) {
data, err := MarshalExtJSON(tc.value, true, false)
if err != nil {
b.Errorf("error marshalling extended JSON: %s", err)
return
}
b.ResetTimer()
var v2 map[string]interface{}
for i := 0; i < b.N; i++ {
err := UnmarshalExtJSON(data, true, &v2)
if err != nil {
b.Errorf("error unmarshalling extended JSON: %s", err)
}
}
})
b.Run("JSON", func(b *testing.B) {
data, err := json.Marshal(tc.value)
if err != nil {
b.Errorf("error marshalling JSON: %s", err)
return
}
b.ResetTimer()
var v2 map[string]interface{}
for i := 0; i < b.N; i++ {
err := json.Unmarshal(data, &v2)
if err != nil {
b.Errorf("error unmarshalling JSON: %s", err)
}
}
})
})
}
}

50
mongo/bson/bson.go Normal file
View File

@ -0,0 +1,50 @@
// 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 gopkg.in/mgo.v2/bson by Gustavo Niemeyer
// See THIRD-PARTY-NOTICES for original license terms.
package bson // import "go.mongodb.org/mongo-driver/bson"
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Zeroer allows custom struct types to implement a report of zero
// state. All struct types that don't implement Zeroer or where IsZero
// returns false are considered to be not zero.
type Zeroer interface {
IsZero() bool
}
// D is an ordered representation of a BSON document. This type should be used when the order of the elements matters,
// such as MongoDB command documents. If the order of the elements does not matter, an M should be used instead.
//
// A D should not be constructed with duplicate key names, as that can cause undefined server behavior.
//
// Example usage:
//
// bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
type D = primitive.D
// E represents a BSON element for a D. It is usually used inside a D.
type E = primitive.E
// M is an unordered representation of a BSON document. This type should be used when the order of the elements does not
// matter. This type is handled as a regular map[string]interface{} when encoding and decoding. Elements will be
// serialized in an undefined, random order. If the order of the elements matters, a D should be used instead.
//
// Example usage:
//
// bson.M{"foo": "bar", "hello": "world", "pi": 3.14159}
type M = primitive.M
// An A is an ordered representation of a BSON array.
//
// Example usage:
//
// bson.A{"bar", "world", 3.14159, bson.D{{"qux", 12345}}}
type A = primitive.A

View File

@ -0,0 +1,530 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"os"
"path"
"strconv"
"strings"
"testing"
"unicode"
"unicode/utf8"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"github.com/tidwall/pretty"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)
type testCase struct {
Description string `json:"description"`
BsonType string `json:"bson_type"`
TestKey *string `json:"test_key"`
Valid []validityTestCase `json:"valid"`
DecodeErrors []decodeErrorTestCase `json:"decodeErrors"`
ParseErrors []parseErrorTestCase `json:"parseErrors"`
Deprecated *bool `json:"deprecated"`
}
type validityTestCase struct {
Description string `json:"description"`
CanonicalBson string `json:"canonical_bson"`
CanonicalExtJSON string `json:"canonical_extjson"`
RelaxedExtJSON *string `json:"relaxed_extjson"`
DegenerateBSON *string `json:"degenerate_bson"`
DegenerateExtJSON *string `json:"degenerate_extjson"`
ConvertedBSON *string `json:"converted_bson"`
ConvertedExtJSON *string `json:"converted_extjson"`
Lossy *bool `json:"lossy"`
}
type decodeErrorTestCase struct {
Description string `json:"description"`
Bson string `json:"bson"`
}
type parseErrorTestCase struct {
Description string `json:"description"`
String string `json:"string"`
}
const dataDir = "../testdata/bson-corpus/"
func findJSONFilesInDir(dir string) ([]string, error) {
files := make([]string, 0)
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsDir() || path.Ext(entry.Name()) != ".json" {
continue
}
files = append(files, entry.Name())
}
return files, nil
}
// seedExtJSON will add the byte representation of the "extJSON" string to the fuzzer's coprus.
func seedExtJSON(f *testing.F, extJSON string, extJSONType string, desc string) {
jbytes, err := jsonToBytes(extJSON, extJSONType, desc)
if err != nil {
f.Fatalf("failed to convert JSON to bytes: %v", err)
}
f.Add(jbytes)
}
// seedTestCase will add the byte representation for each "extJSON" string of each valid test case to the fuzzer's
// corpus.
func seedTestCase(f *testing.F, tcase *testCase) {
for _, vtc := range tcase.Valid {
seedExtJSON(f, vtc.CanonicalExtJSON, "canonical", vtc.Description)
// Seed the relaxed extended JSON.
if vtc.RelaxedExtJSON != nil {
seedExtJSON(f, *vtc.RelaxedExtJSON, "relaxed", vtc.Description)
}
// Seed the degenerate extended JSON.
if vtc.DegenerateExtJSON != nil {
seedExtJSON(f, *vtc.DegenerateExtJSON, "degenerate", vtc.Description)
}
// Seed the converted extended JSON.
if vtc.ConvertedExtJSON != nil {
seedExtJSON(f, *vtc.ConvertedExtJSON, "converted", vtc.Description)
}
}
}
// seedBSONCorpus will unmarshal the data from "testdata/bson-corpus" into a slice of "testCase" structs and then
// marshal the "*_extjson" field of each "validityTestCase" into a slice of bytes to seed the fuzz corpus.
func seedBSONCorpus(f *testing.F) {
fileNames, err := findJSONFilesInDir(dataDir)
if err != nil {
f.Fatalf("failed to find JSON files in directory %q: %v", dataDir, err)
}
for _, fileName := range fileNames {
filePath := path.Join(dataDir, fileName)
file, err := os.Open(filePath)
if err != nil {
f.Fatalf("failed to open file %q: %v", filePath, err)
}
var tcase testCase
if err := json.NewDecoder(file).Decode(&tcase); err != nil {
f.Fatal(err)
}
seedTestCase(f, &tcase)
}
}
func needsEscapedUnicode(bsonType string) bool {
return bsonType == "0x02" || bsonType == "0x0D" || bsonType == "0x0E" || bsonType == "0x0F"
}
func unescapeUnicode(s, bsonType string) string {
if !needsEscapedUnicode(bsonType) {
return s
}
newS := ""
for i := 0; i < len(s); i++ {
c := s[i]
switch c {
case '\\':
switch s[i+1] {
case 'u':
us := s[i : i+6]
u, err := strconv.Unquote(strings.Replace(strconv.Quote(us), `\\u`, `\u`, 1))
if err != nil {
return ""
}
for _, r := range u {
if r < ' ' {
newS += fmt.Sprintf(`\u%04x`, r)
} else {
newS += string(r)
}
}
i += 5
default:
newS += string(c)
}
default:
if c > unicode.MaxASCII {
r, size := utf8.DecodeRune([]byte(s[i:]))
newS += string(r)
i += size - 1
} else {
newS += string(c)
}
}
}
return newS
}
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, 'E') && !strings.ContainsRune(s, '.') {
s += ".0"
}
}
return s
}
func normalizeCanonicalDouble(t *testing.T, key string, cEJ string) string {
// Unmarshal string into map
cEJMap := make(map[string]map[string]string)
err := json.Unmarshal([]byte(cEJ), &cEJMap)
require.NoError(t, err)
// Parse the float contained by the map.
expectedString := cEJMap[key]["$numberDouble"]
expectedFloat, err := strconv.ParseFloat(expectedString, 64)
require.NoError(t, err)
// Normalize the string
return fmt.Sprintf(`{"%s":{"$numberDouble":"%s"}}`, key, formatDouble(expectedFloat))
}
func normalizeRelaxedDouble(t *testing.T, key string, rEJ string) string {
// Unmarshal string into map
rEJMap := make(map[string]float64)
err := json.Unmarshal([]byte(rEJ), &rEJMap)
if err != nil {
return normalizeCanonicalDouble(t, key, rEJ)
}
// Parse the float contained by the map.
expectedFloat := rEJMap[key]
// Normalize the string
return fmt.Sprintf(`{"%s":%s}`, key, formatDouble(expectedFloat))
}
// bsonToNative decodes the BSON bytes (b) into a native Document
func bsonToNative(t *testing.T, b []byte, bType, testDesc string) D {
var doc D
err := Unmarshal(b, &doc)
expectNoError(t, err, fmt.Sprintf("%s: decoding %s BSON", testDesc, bType))
return doc
}
// nativeToBSON encodes the native Document (doc) into canonical BSON and compares it to the expected
// canonical BSON (cB)
func nativeToBSON(t *testing.T, cB []byte, doc D, testDesc, bType, docSrcDesc string) {
actual, err := Marshal(doc)
expectNoError(t, err, fmt.Sprintf("%s: encoding %s BSON", testDesc, bType))
if diff := cmp.Diff(cB, actual); diff != "" {
t.Errorf("%s: 'native_to_bson(%s) = cB' failed (-want, +got):\n-%v\n+%v\n",
testDesc, docSrcDesc, cB, actual)
t.FailNow()
}
}
// jsonToNative decodes the extended JSON string (ej) into a native Document
func jsonToNative(ej, ejType, testDesc string) (D, error) {
var doc D
if err := UnmarshalExtJSON([]byte(ej), ejType != "relaxed", &doc); err != nil {
return nil, fmt.Errorf("%s: decoding %s extended JSON: %w", testDesc, ejType, err)
}
return doc, nil
}
// jsonToBytes decodes the extended JSON string (ej) into canonical BSON and then encodes it into a byte slice.
func jsonToBytes(ej, ejType, testDesc string) ([]byte, error) {
native, err := jsonToNative(ej, ejType, testDesc)
if err != nil {
return nil, err
}
b, err := Marshal(native)
if err != nil {
return nil, fmt.Errorf("%s: encoding %s BSON: %w", testDesc, ejType, err)
}
return b, nil
}
// nativeToJSON encodes the native Document (doc) into an extended JSON string
func nativeToJSON(t *testing.T, ej string, doc D, testDesc, ejType, ejShortName, docSrcDesc string) {
actualEJ, err := MarshalExtJSON(doc, ejType != "relaxed", true)
expectNoError(t, err, fmt.Sprintf("%s: encoding %s extended JSON", testDesc, ejType))
if diff := cmp.Diff(ej, string(actualEJ)); diff != "" {
t.Errorf("%s: 'native_to_%s_extended_json(%s) = %s' failed (-want, +got):\n%s\n",
testDesc, ejType, docSrcDesc, ejShortName, diff)
t.FailNow()
}
}
func runTest(t *testing.T, file string) {
filepath := path.Join(dataDir, file)
content, err := os.ReadFile(filepath)
require.NoError(t, err)
// Remove ".json" from filename.
file = file[:len(file)-5]
testName := "bson_corpus--" + file
t.Run(testName, func(t *testing.T) {
var test testCase
require.NoError(t, json.Unmarshal(content, &test))
t.Run("valid", func(t *testing.T) {
for _, v := range test.Valid {
t.Run(v.Description, func(t *testing.T) {
// get canonical BSON
cB, err := hex.DecodeString(v.CanonicalBson)
expectNoError(t, err, fmt.Sprintf("%s: reading canonical BSON", v.Description))
// get canonical extended JSON
cEJ := unescapeUnicode(string(pretty.Ugly([]byte(v.CanonicalExtJSON))), test.BsonType)
if test.BsonType == "0x01" {
cEJ = normalizeCanonicalDouble(t, *test.TestKey, cEJ)
}
/*** canonical BSON round-trip tests ***/
doc := bsonToNative(t, cB, "canonical", v.Description)
// native_to_bson(bson_to_native(cB)) = cB
nativeToBSON(t, cB, doc, v.Description, "canonical", "bson_to_native(cB)")
// native_to_canonical_extended_json(bson_to_native(cB)) = cEJ
nativeToJSON(t, cEJ, doc, v.Description, "canonical", "cEJ", "bson_to_native(cB)")
// native_to_relaxed_extended_json(bson_to_native(cB)) = rEJ (if rEJ exists)
if v.RelaxedExtJSON != nil {
rEJ := unescapeUnicode(string(pretty.Ugly([]byte(*v.RelaxedExtJSON))), test.BsonType)
if test.BsonType == "0x01" {
rEJ = normalizeRelaxedDouble(t, *test.TestKey, rEJ)
}
nativeToJSON(t, rEJ, doc, v.Description, "relaxed", "rEJ", "bson_to_native(cB)")
/*** relaxed extended JSON round-trip tests (if exists) ***/
doc, err = jsonToNative(rEJ, "relaxed", v.Description)
require.NoError(t, err)
// native_to_relaxed_extended_json(json_to_native(rEJ)) = rEJ
nativeToJSON(t, rEJ, doc, v.Description, "relaxed", "eJR", "json_to_native(rEJ)")
}
/*** canonical extended JSON round-trip tests ***/
doc, err = jsonToNative(cEJ, "canonical", v.Description)
require.NoError(t, err)
// native_to_canonical_extended_json(json_to_native(cEJ)) = cEJ
nativeToJSON(t, cEJ, doc, v.Description, "canonical", "cEJ", "json_to_native(cEJ)")
// native_to_bson(json_to_native(cEJ)) = cb (unless lossy)
if v.Lossy == nil || !*v.Lossy {
nativeToBSON(t, cB, doc, v.Description, "canonical", "json_to_native(cEJ)")
}
/*** degenerate BSON round-trip tests (if exists) ***/
if v.DegenerateBSON != nil {
dB, err := hex.DecodeString(*v.DegenerateBSON)
expectNoError(t, err, fmt.Sprintf("%s: reading degenerate BSON", v.Description))
doc = bsonToNative(t, dB, "degenerate", v.Description)
// native_to_bson(bson_to_native(dB)) = cB
nativeToBSON(t, cB, doc, v.Description, "degenerate", "bson_to_native(dB)")
}
/*** degenerate JSON round-trip tests (if exists) ***/
if v.DegenerateExtJSON != nil {
dEJ := unescapeUnicode(string(pretty.Ugly([]byte(*v.DegenerateExtJSON))), test.BsonType)
if test.BsonType == "0x01" {
dEJ = normalizeCanonicalDouble(t, *test.TestKey, dEJ)
}
doc, err = jsonToNative(dEJ, "degenerate canonical", v.Description)
require.NoError(t, err)
// native_to_canonical_extended_json(json_to_native(dEJ)) = cEJ
nativeToJSON(t, cEJ, doc, v.Description, "degenerate canonical", "cEJ", "json_to_native(dEJ)")
// native_to_bson(json_to_native(dEJ)) = cB (unless lossy)
if v.Lossy == nil || !*v.Lossy {
nativeToBSON(t, cB, doc, v.Description, "canonical", "json_to_native(dEJ)")
}
}
})
}
})
t.Run("decode error", func(t *testing.T) {
for _, d := range test.DecodeErrors {
t.Run(d.Description, func(t *testing.T) {
b, err := hex.DecodeString(d.Bson)
expectNoError(t, err, d.Description)
var doc D
err = Unmarshal(b, &doc)
// The driver unmarshals invalid UTF-8 strings without error. Loop over the unmarshalled elements
// and assert that there was no error if any of the string or DBPointer values contain invalid UTF-8
// characters.
for _, elem := range doc {
str, ok := elem.Value.(string)
invalidString := ok && !utf8.ValidString(str)
dbPtr, ok := elem.Value.(primitive.DBPointer)
invalidDBPtr := ok && !utf8.ValidString(dbPtr.DB)
if invalidString || invalidDBPtr {
expectNoError(t, err, d.Description)
return
}
}
expectError(t, err, fmt.Sprintf("%s: expected decode error", d.Description))
})
}
})
t.Run("parse error", func(t *testing.T) {
for _, p := range test.ParseErrors {
t.Run(p.Description, func(t *testing.T) {
s := unescapeUnicode(p.String, test.BsonType)
if test.BsonType == "0x13" {
s = fmt.Sprintf(`{"decimal128": {"$numberDecimal": "%s"}}`, s)
}
switch test.BsonType {
case "0x00", "0x05", "0x13":
var doc D
err := UnmarshalExtJSON([]byte(s), true, &doc)
// Null bytes are validated when marshaling to BSON
if strings.Contains(p.Description, "Null") {
_, err = Marshal(doc)
}
expectError(t, err, fmt.Sprintf("%s: expected parse error", p.Description))
default:
t.Errorf("Update test to check for parse errors for type %s", test.BsonType)
t.Fail()
}
})
}
})
})
}
func Test_BsonCorpus(t *testing.T) {
jsonFiles, err := findJSONFilesInDir(dataDir)
if err != nil {
t.Fatalf("error finding JSON files in %s: %v", dataDir, err)
}
for _, file := range jsonFiles {
runTest(t, file)
}
}
func expectNoError(t *testing.T, err error, desc string) {
if err != nil {
t.Helper()
t.Errorf("%s: Unepexted error: %v", desc, err)
t.FailNow()
}
}
func expectError(t *testing.T, err error, desc string) {
if err == nil {
t.Helper()
t.Errorf("%s: Expected error", desc)
t.FailNow()
}
}
func TestRelaxedUUIDValidation(t *testing.T) {
testCases := []struct {
description string
canonicalExtJSON string
degenerateExtJSON string
expectedErr string
}{
{
"valid uuid",
"{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}",
"{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}",
"",
},
{
"invalid uuid--no hyphens",
"",
"{\"x\" : { \"$uuid\" : \"73ffd26444b34c6990e8e7d1dfc035d4\"}}",
"$uuid value does not follow RFC 4122 format regarding length and hyphens",
},
{
"invalid uuid--trailing hyphens",
"",
"{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035--\"}}",
"$uuid value does not follow RFC 4122 format regarding length and hyphens",
},
{
"invalid uuid--malformed hex",
"",
"{\"x\" : { \"$uuid\" : \"q3@fd26l-44b3-4c69-90e8-e7d1dfc035d4\"}}",
"$uuid value does not follow RFC 4122 format regarding hex bytes: encoding/hex: invalid byte: U+0071 'q'",
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
// get canonical extended JSON
cEJ := unescapeUnicode(string(pretty.Ugly([]byte(tc.canonicalExtJSON))), "0x05")
// get degenerate extended JSON
dEJ := unescapeUnicode(string(pretty.Ugly([]byte(tc.degenerateExtJSON))), "0x05")
// convert dEJ to native doc
var doc D
err := UnmarshalExtJSON([]byte(dEJ), true, &doc)
if tc.expectedErr != "" {
assert.Equal(t, tc.expectedErr, err.Error(), "expected error %v, got %v", tc.expectedErr, err)
} else {
assert.Nil(t, err, "expected no error, got error: %v", err)
// Marshal doc into extended JSON and compare with cEJ
nativeToJSON(t, cEJ, doc, tc.description, "degenerate canonical", "cEJ", "json_to_native(dEJ)")
}
})
}
}

279
mongo/bson/bson_test.go Normal file
View File

@ -0,0 +1,279 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
func noerr(t *testing.T, err error) {
if err != nil {
t.Helper()
t.Errorf("Unexpected error: (%T)%v", err, err)
t.FailNow()
}
}
func TestTimeRoundTrip(t *testing.T) {
val := struct {
Value time.Time
ID string
}{
ID: "time-rt-test",
}
if !val.Value.IsZero() {
t.Errorf("Did not get zero time as expected.")
}
bsonOut, err := Marshal(val)
noerr(t, err)
rtval := struct {
Value time.Time
ID string
}{}
err = Unmarshal(bsonOut, &rtval)
noerr(t, err)
if !cmp.Equal(val, rtval) {
t.Errorf("Did not round trip properly. got %v; want %v", val, rtval)
}
if !rtval.Value.IsZero() {
t.Errorf("Did not get zero time as expected.")
}
}
func TestNonNullTimeRoundTrip(t *testing.T) {
now := time.Now()
now = time.Unix(now.Unix(), 0)
val := struct {
Value time.Time
ID string
}{
ID: "time-rt-test",
Value: now,
}
bsonOut, err := Marshal(val)
noerr(t, err)
rtval := struct {
Value time.Time
ID string
}{}
err = Unmarshal(bsonOut, &rtval)
noerr(t, err)
if !cmp.Equal(val, rtval) {
t.Errorf("Did not round trip properly. got %v; want %v", val, rtval)
}
}
func TestD(t *testing.T) {
t.Run("can marshal", func(t *testing.T) {
d := D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
idx, want := bsoncore.AppendDocumentStart(nil)
want = bsoncore.AppendStringElement(want, "foo", "bar")
want = bsoncore.AppendStringElement(want, "hello", "world")
want = bsoncore.AppendDoubleElement(want, "pi", 3.14159)
want, err := bsoncore.AppendDocumentEnd(want, idx)
noerr(t, err)
got, err := Marshal(d)
noerr(t, err)
if !bytes.Equal(got, want) {
t.Errorf("Marshaled documents do not match. got %v; want %v", Raw(got), Raw(want))
}
})
t.Run("can unmarshal", func(t *testing.T) {
want := D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
idx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendStringElement(doc, "foo", "bar")
doc = bsoncore.AppendStringElement(doc, "hello", "world")
doc = bsoncore.AppendDoubleElement(doc, "pi", 3.14159)
doc, err := bsoncore.AppendDocumentEnd(doc, idx)
noerr(t, err)
var got D
err = Unmarshal(doc, &got)
noerr(t, err)
if !cmp.Equal(got, want) {
t.Errorf("Unmarshaled documents do not match. got %v; want %v", got, want)
}
})
}
type stringerString string
func (ss stringerString) String() string {
return "bar"
}
type keyBool bool
func (kb keyBool) MarshalKey() (string, error) {
return fmt.Sprintf("%v", kb), nil
}
func (kb *keyBool) UnmarshalKey(key string) error {
switch key {
case "true":
*kb = true
case "false":
*kb = false
default:
return fmt.Errorf("invalid bool value %v", key)
}
return nil
}
type keyStruct struct {
val int64
}
func (k keyStruct) MarshalText() (text []byte, err error) {
str := strconv.FormatInt(k.val, 10)
return []byte(str), nil
}
func (k *keyStruct) UnmarshalText(text []byte) error {
val, err := strconv.ParseInt(string(text), 10, 64)
if err != nil {
return err
}
*k = keyStruct{
val: val,
}
return nil
}
func TestMapCodec(t *testing.T) {
t.Run("EncodeKeysWithStringer", func(t *testing.T) {
strstr := stringerString("foo")
mapObj := map[stringerString]int{strstr: 1}
testCases := []struct {
name string
opts *bsonoptions.MapCodecOptions
key string
}{
{"default", bsonoptions.MapCodec(), "foo"},
{"true", bsonoptions.MapCodec().SetEncodeKeysWithStringer(true), "bar"},
{"false", bsonoptions.MapCodec().SetEncodeKeysWithStringer(false), "foo"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mapCodec := bsoncodec.NewMapCodec(tc.opts)
mapRegistry := NewRegistryBuilder().RegisterDefaultEncoder(reflect.Map, mapCodec).Build()
val, err := MarshalWithRegistry(mapRegistry, mapObj)
assert.Nil(t, err, "Marshal error: %v", err)
assert.True(t, strings.Contains(string(val), tc.key), "expected result to contain %v, got: %v", tc.key, string(val))
})
}
})
t.Run("keys implements keyMarshaler and keyUnmarshaler", func(t *testing.T) {
mapObj := map[keyBool]int{keyBool(true): 1}
doc, err := Marshal(mapObj)
assert.Nil(t, err, "Marshal error: %v", err)
idx, want := bsoncore.AppendDocumentStart(nil)
want = bsoncore.AppendInt32Element(want, "true", 1)
want, _ = bsoncore.AppendDocumentEnd(want, idx)
assert.Equal(t, want, doc, "expected result %v, got %v", string(want), string(doc))
var got map[keyBool]int
err = Unmarshal(doc, &got)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, mapObj, got, "expected result %v, got %v", mapObj, got)
})
t.Run("keys implements encoding.TextMarshaler and encoding.TextUnmarshaler", func(t *testing.T) {
mapObj := map[keyStruct]int{
{val: 10}: 100,
}
doc, err := Marshal(mapObj)
assert.Nil(t, err, "Marshal error: %v", err)
idx, want := bsoncore.AppendDocumentStart(nil)
want = bsoncore.AppendInt32Element(want, "10", 100)
want, _ = bsoncore.AppendDocumentEnd(want, idx)
assert.Equal(t, want, doc, "expected result %v, got %v", string(want), string(doc))
var got map[keyStruct]int
err = Unmarshal(doc, &got)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, mapObj, got, "expected result %v, got %v", mapObj, got)
})
}
func TestExtJSONEscapeKey(t *testing.T) {
doc := D{{Key: "\\usb#", Value: int32(1)}}
b, err := MarshalExtJSON(&doc, false, false)
noerr(t, err)
want := "{\"\\\\usb#\":1}"
if diff := cmp.Diff(want, string(b)); diff != "" {
t.Errorf("Marshaled documents do not match. got %v, want %v", string(b), want)
}
var got D
err = UnmarshalExtJSON(b, false, &got)
noerr(t, err)
if !cmp.Equal(got, doc) {
t.Errorf("Unmarshaled documents do not match. got %v; want %v", got, doc)
}
}
func TestBsoncoreArray(t *testing.T) {
type BSONDocumentArray struct {
Array []D `bson:"array"`
}
type BSONArray struct {
Array bsoncore.Array `bson:"array"`
}
bda := BSONDocumentArray{
Array: []D{
{{"x", 1}},
{{"x", 2}},
{{"x", 3}},
},
}
expectedBSON, err := Marshal(bda)
assert.Nil(t, err, "Marshal bsoncore.Document array error: %v", err)
var ba BSONArray
err = Unmarshal(expectedBSON, &ba)
assert.Nil(t, err, "Unmarshal error: %v", err)
actualBSON, err := Marshal(ba)
assert.Nil(t, err, "Marshal bsoncore.Array error: %v", err)
assert.Equal(t, expectedBSON, actualBSON,
"expected BSON to be %v after Marshalling again; got %v", expectedBSON, actualBSON)
doc := bsoncore.Document(actualBSON)
v := doc.Lookup("array")
assert.Equal(t, bsontype.Array, v.Type, "expected type array, got %v", v.Type)
}

View File

@ -0,0 +1,50 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
// ArrayCodec is the Codec used for bsoncore.Array values.
type ArrayCodec struct{}
var defaultArrayCodec = NewArrayCodec()
// NewArrayCodec returns an ArrayCodec.
func NewArrayCodec() *ArrayCodec {
return &ArrayCodec{}
}
// EncodeValue is the ValueEncoder for bsoncore.Array values.
func (ac *ArrayCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tCoreArray {
return ValueEncoderError{Name: "CoreArrayEncodeValue", Types: []reflect.Type{tCoreArray}, Received: val}
}
arr := val.Interface().(bsoncore.Array)
return bsonrw.Copier{}.CopyArrayFromBytes(vw, arr)
}
// DecodeValue is the ValueDecoder for bsoncore.Array values.
func (ac *ArrayCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != tCoreArray {
return ValueDecoderError{Name: "CoreArrayDecodeValue", Types: []reflect.Type{tCoreArray}, Received: val}
}
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, 0))
}
val.SetLen(0)
arr, err := bsonrw.Copier{}.AppendArrayBytes(val.Interface().(bsoncore.Array), vr)
val.Set(reflect.ValueOf(arr))
return err
}

View File

@ -0,0 +1,238 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec // import "go.mongodb.org/mongo-driver/bson/bsoncodec"
import (
"fmt"
"reflect"
"strings"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var (
emptyValue = reflect.Value{}
)
// Marshaler is an interface implemented by types that can marshal themselves
// into a BSON document represented as bytes. The bytes returned must be a valid
// BSON document if the error is nil.
type Marshaler interface {
MarshalBSON() ([]byte, error)
}
// ValueMarshaler is an interface implemented by types that can marshal
// themselves into a BSON value as bytes. The type must be the valid type for
// the bytes returned. The bytes and byte type together must be valid if the
// error is nil.
type ValueMarshaler interface {
MarshalBSONValue() (bsontype.Type, []byte, error)
}
// Unmarshaler is an interface implemented by types that can unmarshal a BSON
// document representation of themselves. The BSON bytes can be assumed to be
// valid. UnmarshalBSON must copy the BSON bytes if it wishes to retain the data
// after returning.
type Unmarshaler interface {
UnmarshalBSON([]byte) error
}
// ValueUnmarshaler is an interface implemented by types that can unmarshal a
// BSON value representation of themselves. The BSON bytes and type can be
// assumed to be valid. UnmarshalBSONValue must copy the BSON value bytes if it
// wishes to retain the data after returning.
type ValueUnmarshaler interface {
UnmarshalBSONValue(bsontype.Type, []byte) error
}
// ValueEncoderError is an error returned from a ValueEncoder when the provided value can't be
// encoded by the ValueEncoder.
type ValueEncoderError struct {
Name string
Types []reflect.Type
Kinds []reflect.Kind
Received reflect.Value
}
func (vee ValueEncoderError) Error() string {
typeKinds := make([]string, 0, len(vee.Types)+len(vee.Kinds))
for _, t := range vee.Types {
typeKinds = append(typeKinds, t.String())
}
for _, k := range vee.Kinds {
if k == reflect.Map {
typeKinds = append(typeKinds, "map[string]*")
continue
}
typeKinds = append(typeKinds, k.String())
}
received := vee.Received.Kind().String()
if vee.Received.IsValid() {
received = vee.Received.Type().String()
}
return fmt.Sprintf("%s can only encode valid %s, but got %s", vee.Name, strings.Join(typeKinds, ", "), received)
}
// ValueDecoderError is an error returned from a ValueDecoder when the provided value can't be
// decoded by the ValueDecoder.
type ValueDecoderError struct {
Name string
Types []reflect.Type
Kinds []reflect.Kind
Received reflect.Value
}
func (vde ValueDecoderError) Error() string {
typeKinds := make([]string, 0, len(vde.Types)+len(vde.Kinds))
for _, t := range vde.Types {
typeKinds = append(typeKinds, t.String())
}
for _, k := range vde.Kinds {
if k == reflect.Map {
typeKinds = append(typeKinds, "map[string]*")
continue
}
typeKinds = append(typeKinds, k.String())
}
received := vde.Received.Kind().String()
if vde.Received.IsValid() {
received = vde.Received.Type().String()
}
return fmt.Sprintf("%s can only decode valid and settable %s, but got %s", vde.Name, strings.Join(typeKinds, ", "), received)
}
// EncodeContext is the contextual information required for a Codec to encode a
// value.
type EncodeContext struct {
*Registry
MinSize bool
}
// DecodeContext is the contextual information required for a Codec to decode a
// value.
type DecodeContext struct {
*Registry
Truncate bool
// Ancestor is the type of a containing document. This is mainly used to determine what type
// should be used when decoding an embedded document into an empty interface. For example, if
// Ancestor is a bson.M, BSON embedded document values being decoded into an empty interface
// will be decoded into a bson.M.
//
// Deprecated: Use DefaultDocumentM or DefaultDocumentD instead.
Ancestor reflect.Type
// defaultDocumentType specifies the Go type to decode top-level and nested BSON documents into. In particular, the
// usage for this field is restricted to data typed as "interface{}" or "map[string]interface{}". If DocumentType is
// set to a type that a BSON document cannot be unmarshaled into (e.g. "string"), unmarshalling will result in an
// error. DocumentType overrides the Ancestor field.
defaultDocumentType reflect.Type
}
// DefaultDocumentM will decode empty documents using the primitive.M type. This behavior is restricted to data typed as
// "interface{}" or "map[string]interface{}".
func (dc *DecodeContext) DefaultDocumentM() {
dc.defaultDocumentType = reflect.TypeOf(primitive.M{})
}
// DefaultDocumentD will decode empty documents using the primitive.D type. This behavior is restricted to data typed as
// "interface{}" or "map[string]interface{}".
func (dc *DecodeContext) DefaultDocumentD() {
dc.defaultDocumentType = reflect.TypeOf(primitive.D{})
}
// ValueCodec is the interface that groups the methods to encode and decode
// values.
type ValueCodec interface {
ValueEncoder
ValueDecoder
}
// ValueEncoder is the interface implemented by types that can handle the encoding of a value.
type ValueEncoder interface {
EncodeValue(EncodeContext, bsonrw.ValueWriter, reflect.Value) error
}
// ValueEncoderFunc is an adapter function that allows a function with the correct signature to be
// used as a ValueEncoder.
type ValueEncoderFunc func(EncodeContext, bsonrw.ValueWriter, reflect.Value) error
// EncodeValue implements the ValueEncoder interface.
func (fn ValueEncoderFunc) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
return fn(ec, vw, val)
}
// ValueDecoder is the interface implemented by types that can handle the decoding of a value.
type ValueDecoder interface {
DecodeValue(DecodeContext, bsonrw.ValueReader, reflect.Value) error
}
// ValueDecoderFunc is an adapter function that allows a function with the correct signature to be
// used as a ValueDecoder.
type ValueDecoderFunc func(DecodeContext, bsonrw.ValueReader, reflect.Value) error
// DecodeValue implements the ValueDecoder interface.
func (fn ValueDecoderFunc) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
return fn(dc, vr, val)
}
// typeDecoder is the interface implemented by types that can handle the decoding of a value given its type.
type typeDecoder interface {
decodeType(DecodeContext, bsonrw.ValueReader, reflect.Type) (reflect.Value, error)
}
// typeDecoderFunc is an adapter function that allows a function with the correct signature to be used as a typeDecoder.
type typeDecoderFunc func(DecodeContext, bsonrw.ValueReader, reflect.Type) (reflect.Value, error)
func (fn typeDecoderFunc) decodeType(dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type) (reflect.Value, error) {
return fn(dc, vr, t)
}
// decodeAdapter allows two functions with the correct signatures to be used as both a ValueDecoder and typeDecoder.
type decodeAdapter struct {
ValueDecoderFunc
typeDecoderFunc
}
var _ ValueDecoder = decodeAdapter{}
var _ typeDecoder = decodeAdapter{}
// decodeTypeOrValue calls decoder.decodeType is decoder is a typeDecoder. Otherwise, it allocates a new element of type
// t and calls decoder.DecodeValue on it.
func decodeTypeOrValue(decoder ValueDecoder, dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type) (reflect.Value, error) {
td, _ := decoder.(typeDecoder)
return decodeTypeOrValueWithInfo(decoder, td, dc, vr, t, true)
}
func decodeTypeOrValueWithInfo(vd ValueDecoder, td typeDecoder, dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type, convert bool) (reflect.Value, error) {
if td != nil {
val, err := td.decodeType(dc, vr, t)
if err == nil && convert && val.Type() != t {
// This conversion step is necessary for slices and maps. If a user declares variables like:
//
// type myBool bool
// var m map[string]myBool
//
// and tries to decode BSON bytes into the map, the decoding will fail if this conversion is not present
// because we'll try to assign a value of type bool to one of type myBool.
val = val.Convert(t)
}
return val, err
}
val := reflect.New(t).Elem()
err := vd.DecodeValue(dc, vr, val)
return val, err
}
// CodecZeroer is the interface implemented by Codecs that can also determine if
// a value of the type that would be encoded is zero.
type CodecZeroer interface {
IsTypeZero(interface{}) bool
}

View File

@ -0,0 +1,143 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"fmt"
"reflect"
"testing"
"time"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func ExampleValueEncoder() {
var _ ValueEncoderFunc = func(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if val.Kind() != reflect.String {
return ValueEncoderError{Name: "StringEncodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val}
}
return vw.WriteString(val.String())
}
}
func ExampleValueDecoder() {
var _ ValueDecoderFunc = func(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Kind() != reflect.String {
return ValueDecoderError{Name: "StringDecodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val}
}
if vr.Type() != bsontype.String {
return fmt.Errorf("cannot decode %v into a string type", vr.Type())
}
str, err := vr.ReadString()
if err != nil {
return err
}
val.SetString(str)
return nil
}
}
func noerr(t *testing.T, err error) {
if err != nil {
t.Helper()
t.Errorf("Unexpected error: (%T)%v", err, err)
t.FailNow()
}
}
func compareTime(t1, t2 time.Time) bool {
if t1.Location() != t2.Location() {
return false
}
return t1.Equal(t2)
}
func compareErrors(err1, err2 error) bool {
if err1 == nil && err2 == nil {
return true
}
if err1 == nil || err2 == nil {
return false
}
if err1.Error() != err2.Error() {
return false
}
return true
}
func compareDecimal128(d1, d2 primitive.Decimal128) bool {
d1H, d1L := d1.GetBytes()
d2H, d2L := d2.GetBytes()
if d1H != d2H {
return false
}
if d1L != d2L {
return false
}
return true
}
type noPrivateFields struct {
a string
}
func compareNoPrivateFields(npf1, npf2 noPrivateFields) bool {
return npf1.a != npf2.a // We don't want these to be equal
}
type zeroTest struct {
reportZero bool
}
func (z zeroTest) IsZero() bool { return z.reportZero }
func compareZeroTest(_, _ zeroTest) bool { return true }
type nonZeroer struct {
value bool
}
type llCodec struct {
t *testing.T
decodeval interface{}
encodeval interface{}
err error
}
func (llc *llCodec) EncodeValue(_ EncodeContext, _ bsonrw.ValueWriter, i interface{}) error {
if llc.err != nil {
return llc.err
}
llc.encodeval = i
return nil
}
func (llc *llCodec) DecodeValue(_ DecodeContext, _ bsonrw.ValueReader, val reflect.Value) error {
if llc.err != nil {
return llc.err
}
if !reflect.TypeOf(llc.decodeval).AssignableTo(val.Type()) {
llc.t.Errorf("decodeval must be assignable to val provided to DecodeValue, but is not. decodeval %T; val %T", llc.decodeval, val)
return nil
}
val.Set(reflect.ValueOf(llc.decodeval))
return nil
}

View File

@ -0,0 +1,111 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"fmt"
"reflect"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
// ByteSliceCodec is the Codec used for []byte values.
type ByteSliceCodec struct {
EncodeNilAsEmpty bool
}
var (
defaultByteSliceCodec = NewByteSliceCodec()
_ ValueCodec = defaultByteSliceCodec
_ typeDecoder = defaultByteSliceCodec
)
// NewByteSliceCodec returns a StringCodec with options opts.
func NewByteSliceCodec(opts ...*bsonoptions.ByteSliceCodecOptions) *ByteSliceCodec {
byteSliceOpt := bsonoptions.MergeByteSliceCodecOptions(opts...)
codec := ByteSliceCodec{}
if byteSliceOpt.EncodeNilAsEmpty != nil {
codec.EncodeNilAsEmpty = *byteSliceOpt.EncodeNilAsEmpty
}
return &codec
}
// EncodeValue is the ValueEncoder for []byte.
func (bsc *ByteSliceCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tByteSlice {
return ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: val}
}
if val.IsNil() && !bsc.EncodeNilAsEmpty {
return vw.WriteNull()
}
return vw.WriteBinary(val.Interface().([]byte))
}
func (bsc *ByteSliceCodec) decodeType(dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type) (reflect.Value, error) {
if t != tByteSlice {
return emptyValue, ValueDecoderError{
Name: "ByteSliceDecodeValue",
Types: []reflect.Type{tByteSlice},
Received: reflect.Zero(t),
}
}
var data []byte
var err error
switch vrType := vr.Type(); vrType {
case bsontype.String:
str, err := vr.ReadString()
if err != nil {
return emptyValue, err
}
data = []byte(str)
case bsontype.Symbol:
sym, err := vr.ReadSymbol()
if err != nil {
return emptyValue, err
}
data = []byte(sym)
case bsontype.Binary:
var subtype byte
data, subtype, err = vr.ReadBinary()
if err != nil {
return emptyValue, err
}
if subtype != bsontype.BinaryGeneric && subtype != bsontype.BinaryBinaryOld {
return emptyValue, decodeBinaryError{subtype: subtype, typeName: "[]byte"}
}
case bsontype.Null:
err = vr.ReadNull()
case bsontype.Undefined:
err = vr.ReadUndefined()
default:
return emptyValue, fmt.Errorf("cannot decode %v into a []byte", vrType)
}
if err != nil {
return emptyValue, err
}
return reflect.ValueOf(data), nil
}
// DecodeValue is the ValueDecoder for []byte.
func (bsc *ByteSliceCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != tByteSlice {
return ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: val}
}
elem, err := bsc.decodeType(dc, vr, tByteSlice)
if err != nil {
return err
}
val.Set(elem)
return nil
}

View File

@ -0,0 +1,63 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"go.mongodb.org/mongo-driver/bson/bsonrw"
)
// condAddrEncoder is the encoder used when a pointer to the encoding value has an encoder.
type condAddrEncoder struct {
canAddrEnc ValueEncoder
elseEnc ValueEncoder
}
var _ ValueEncoder = (*condAddrEncoder)(nil)
// newCondAddrEncoder returns an condAddrEncoder.
func newCondAddrEncoder(canAddrEnc, elseEnc ValueEncoder) *condAddrEncoder {
encoder := condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc}
return &encoder
}
// EncodeValue is the ValueEncoderFunc for a value that may be addressable.
func (cae *condAddrEncoder) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if val.CanAddr() {
return cae.canAddrEnc.EncodeValue(ec, vw, val)
}
if cae.elseEnc != nil {
return cae.elseEnc.EncodeValue(ec, vw, val)
}
return ErrNoEncoder{Type: val.Type()}
}
// condAddrDecoder is the decoder used when a pointer to the value has a decoder.
type condAddrDecoder struct {
canAddrDec ValueDecoder
elseDec ValueDecoder
}
var _ ValueDecoder = (*condAddrDecoder)(nil)
// newCondAddrDecoder returns an CondAddrDecoder.
func newCondAddrDecoder(canAddrDec, elseDec ValueDecoder) *condAddrDecoder {
decoder := condAddrDecoder{canAddrDec: canAddrDec, elseDec: elseDec}
return &decoder
}
// DecodeValue is the ValueDecoderFunc for a value that may be addressable.
func (cad *condAddrDecoder) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if val.CanAddr() {
return cad.canAddrDec.DecodeValue(dc, vr, val)
}
if cad.elseDec != nil {
return cad.elseDec.DecodeValue(dc, vr, val)
}
return ErrNoDecoder{Type: val.Type()}
}

View File

@ -0,0 +1,97 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"testing"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)
func TestCondAddrCodec(t *testing.T) {
var inner int
canAddrVal := reflect.ValueOf(&inner)
addressable := canAddrVal.Elem()
unaddressable := reflect.ValueOf(inner)
rw := &bsonrwtest.ValueReaderWriter{}
t.Run("addressEncode", func(t *testing.T) {
invoked := 0
encode1 := ValueEncoderFunc(func(EncodeContext, bsonrw.ValueWriter, reflect.Value) error {
invoked = 1
return nil
})
encode2 := ValueEncoderFunc(func(EncodeContext, bsonrw.ValueWriter, reflect.Value) error {
invoked = 2
return nil
})
condEncoder := newCondAddrEncoder(encode1, encode2)
testCases := []struct {
name string
val reflect.Value
invoked int
}{
{"canAddr", addressable, 1},
{"else", unaddressable, 2},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := condEncoder.EncodeValue(EncodeContext{}, rw, tc.val)
assert.Nil(t, err, "CondAddrEncoder error: %v", err)
assert.Equal(t, invoked, tc.invoked, "Expected function %v to be called, called %v", tc.invoked, invoked)
})
}
t.Run("error", func(t *testing.T) {
errEncoder := newCondAddrEncoder(encode1, nil)
err := errEncoder.EncodeValue(EncodeContext{}, rw, unaddressable)
want := ErrNoEncoder{Type: unaddressable.Type()}
assert.Equal(t, err, want, "expected error %v, got %v", want, err)
})
})
t.Run("addressDecode", func(t *testing.T) {
invoked := 0
decode1 := ValueDecoderFunc(func(DecodeContext, bsonrw.ValueReader, reflect.Value) error {
invoked = 1
return nil
})
decode2 := ValueDecoderFunc(func(DecodeContext, bsonrw.ValueReader, reflect.Value) error {
invoked = 2
return nil
})
condDecoder := newCondAddrDecoder(decode1, decode2)
testCases := []struct {
name string
val reflect.Value
invoked int
}{
{"canAddr", addressable, 1},
{"else", unaddressable, 2},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := condDecoder.DecodeValue(DecodeContext{}, rw, tc.val)
assert.Nil(t, err, "CondAddrDecoder error: %v", err)
assert.Equal(t, invoked, tc.invoked, "Expected function %v to be called, called %v", tc.invoked, invoked)
})
}
t.Run("error", func(t *testing.T) {
errDecoder := newCondAddrDecoder(decode1, nil)
err := errDecoder.DecodeValue(DecodeContext{}, rw, unaddressable)
want := ErrNoDecoder{Type: unaddressable.Type()}
assert.Equal(t, err, want, "expected error %v, got %v", want, err)
})
})
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,766 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"encoding/json"
"errors"
"fmt"
"math"
"net/url"
"reflect"
"sync"
"time"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
var defaultValueEncoders DefaultValueEncoders
var bvwPool = bsonrw.NewBSONValueWriterPool()
var errInvalidValue = errors.New("cannot encode invalid element")
var sliceWriterPool = sync.Pool{
New: func() interface{} {
sw := make(bsonrw.SliceWriter, 0)
return &sw
},
}
func encodeElement(ec EncodeContext, dw bsonrw.DocumentWriter, e primitive.E) error {
vw, err := dw.WriteDocumentElement(e.Key)
if err != nil {
return err
}
if e.Value == nil {
return vw.WriteNull()
}
encoder, err := ec.LookupEncoder(reflect.TypeOf(e.Value))
if err != nil {
return err
}
err = encoder.EncodeValue(ec, vw, reflect.ValueOf(e.Value))
if err != nil {
return err
}
return nil
}
// DefaultValueEncoders is a namespace type for the default ValueEncoders used
// when creating a registry.
type DefaultValueEncoders struct{}
// RegisterDefaultEncoders will register the encoder methods attached to DefaultValueEncoders with
// the provided RegistryBuilder.
func (dve DefaultValueEncoders) RegisterDefaultEncoders(rb *RegistryBuilder) {
if rb == nil {
panic(errors.New("argument to RegisterDefaultEncoders must not be nil"))
}
rb.
RegisterTypeEncoder(tByteSlice, defaultByteSliceCodec).
RegisterTypeEncoder(tTime, defaultTimeCodec).
RegisterTypeEncoder(tEmpty, defaultEmptyInterfaceCodec).
RegisterTypeEncoder(tCoreArray, defaultArrayCodec).
RegisterTypeEncoder(tOID, ValueEncoderFunc(dve.ObjectIDEncodeValue)).
RegisterTypeEncoder(tDecimal, ValueEncoderFunc(dve.Decimal128EncodeValue)).
RegisterTypeEncoder(tJSONNumber, ValueEncoderFunc(dve.JSONNumberEncodeValue)).
RegisterTypeEncoder(tURL, ValueEncoderFunc(dve.URLEncodeValue)).
RegisterTypeEncoder(tJavaScript, ValueEncoderFunc(dve.JavaScriptEncodeValue)).
RegisterTypeEncoder(tSymbol, ValueEncoderFunc(dve.SymbolEncodeValue)).
RegisterTypeEncoder(tBinary, ValueEncoderFunc(dve.BinaryEncodeValue)).
RegisterTypeEncoder(tUndefined, ValueEncoderFunc(dve.UndefinedEncodeValue)).
RegisterTypeEncoder(tDateTime, ValueEncoderFunc(dve.DateTimeEncodeValue)).
RegisterTypeEncoder(tNull, ValueEncoderFunc(dve.NullEncodeValue)).
RegisterTypeEncoder(tRegex, ValueEncoderFunc(dve.RegexEncodeValue)).
RegisterTypeEncoder(tDBPointer, ValueEncoderFunc(dve.DBPointerEncodeValue)).
RegisterTypeEncoder(tTimestamp, ValueEncoderFunc(dve.TimestampEncodeValue)).
RegisterTypeEncoder(tMinKey, ValueEncoderFunc(dve.MinKeyEncodeValue)).
RegisterTypeEncoder(tMaxKey, ValueEncoderFunc(dve.MaxKeyEncodeValue)).
RegisterTypeEncoder(tCoreDocument, ValueEncoderFunc(dve.CoreDocumentEncodeValue)).
RegisterTypeEncoder(tCodeWithScope, ValueEncoderFunc(dve.CodeWithScopeEncodeValue)).
RegisterDefaultEncoder(reflect.Bool, ValueEncoderFunc(dve.BooleanEncodeValue)).
RegisterDefaultEncoder(reflect.Int, ValueEncoderFunc(dve.IntEncodeValue)).
RegisterDefaultEncoder(reflect.Int8, ValueEncoderFunc(dve.IntEncodeValue)).
RegisterDefaultEncoder(reflect.Int16, ValueEncoderFunc(dve.IntEncodeValue)).
RegisterDefaultEncoder(reflect.Int32, ValueEncoderFunc(dve.IntEncodeValue)).
RegisterDefaultEncoder(reflect.Int64, ValueEncoderFunc(dve.IntEncodeValue)).
RegisterDefaultEncoder(reflect.Uint, defaultUIntCodec).
RegisterDefaultEncoder(reflect.Uint8, defaultUIntCodec).
RegisterDefaultEncoder(reflect.Uint16, defaultUIntCodec).
RegisterDefaultEncoder(reflect.Uint32, defaultUIntCodec).
RegisterDefaultEncoder(reflect.Uint64, defaultUIntCodec).
RegisterDefaultEncoder(reflect.Float32, ValueEncoderFunc(dve.FloatEncodeValue)).
RegisterDefaultEncoder(reflect.Float64, ValueEncoderFunc(dve.FloatEncodeValue)).
RegisterDefaultEncoder(reflect.Array, ValueEncoderFunc(dve.ArrayEncodeValue)).
RegisterDefaultEncoder(reflect.Map, defaultMapCodec).
RegisterDefaultEncoder(reflect.Slice, defaultSliceCodec).
RegisterDefaultEncoder(reflect.String, defaultStringCodec).
RegisterDefaultEncoder(reflect.Struct, newDefaultStructCodec()).
RegisterDefaultEncoder(reflect.Ptr, NewPointerCodec()).
RegisterHookEncoder(tValueMarshaler, ValueEncoderFunc(dve.ValueMarshalerEncodeValue)).
RegisterHookEncoder(tMarshaler, ValueEncoderFunc(dve.MarshalerEncodeValue)).
RegisterHookEncoder(tProxy, ValueEncoderFunc(dve.ProxyEncodeValue))
}
// BooleanEncodeValue is the ValueEncoderFunc for bool types.
func (dve DefaultValueEncoders) BooleanEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Kind() != reflect.Bool {
return ValueEncoderError{Name: "BooleanEncodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: val}
}
return vw.WriteBoolean(val.Bool())
}
func fitsIn32Bits(i int64) bool {
return math.MinInt32 <= i && i <= math.MaxInt32
}
// IntEncodeValue is the ValueEncoderFunc for int types.
func (dve DefaultValueEncoders) IntEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
switch val.Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32:
return vw.WriteInt32(int32(val.Int()))
case reflect.Int:
i64 := val.Int()
if fitsIn32Bits(i64) {
return vw.WriteInt32(int32(i64))
}
return vw.WriteInt64(i64)
case reflect.Int64:
i64 := val.Int()
if ec.MinSize && fitsIn32Bits(i64) {
return vw.WriteInt32(int32(i64))
}
return vw.WriteInt64(i64)
}
return ValueEncoderError{
Name: "IntEncodeValue",
Kinds: []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
Received: val,
}
}
// UintEncodeValue is the ValueEncoderFunc for uint types.
//
// Deprecated: UintEncodeValue is not registered by default. Use UintCodec.EncodeValue instead.
func (dve DefaultValueEncoders) UintEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
switch val.Kind() {
case reflect.Uint8, reflect.Uint16:
return vw.WriteInt32(int32(val.Uint()))
case reflect.Uint, reflect.Uint32, reflect.Uint64:
u64 := val.Uint()
if ec.MinSize && u64 <= math.MaxInt32 {
return vw.WriteInt32(int32(u64))
}
if u64 > math.MaxInt64 {
return fmt.Errorf("%d overflows int64", u64)
}
return vw.WriteInt64(int64(u64))
}
return ValueEncoderError{
Name: "UintEncodeValue",
Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
Received: val,
}
}
// FloatEncodeValue is the ValueEncoderFunc for float types.
func (dve DefaultValueEncoders) FloatEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
switch val.Kind() {
case reflect.Float32, reflect.Float64:
return vw.WriteDouble(val.Float())
}
return ValueEncoderError{Name: "FloatEncodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: val}
}
// StringEncodeValue is the ValueEncoderFunc for string types.
//
// Deprecated: StringEncodeValue is not registered by default. Use StringCodec.EncodeValue instead.
func (dve DefaultValueEncoders) StringEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if val.Kind() != reflect.String {
return ValueEncoderError{
Name: "StringEncodeValue",
Kinds: []reflect.Kind{reflect.String},
Received: val,
}
}
return vw.WriteString(val.String())
}
// ObjectIDEncodeValue is the ValueEncoderFunc for primitive.ObjectID.
func (dve DefaultValueEncoders) ObjectIDEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tOID {
return ValueEncoderError{Name: "ObjectIDEncodeValue", Types: []reflect.Type{tOID}, Received: val}
}
return vw.WriteObjectID(val.Interface().(primitive.ObjectID))
}
// Decimal128EncodeValue is the ValueEncoderFunc for primitive.Decimal128.
func (dve DefaultValueEncoders) Decimal128EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tDecimal {
return ValueEncoderError{Name: "Decimal128EncodeValue", Types: []reflect.Type{tDecimal}, Received: val}
}
return vw.WriteDecimal128(val.Interface().(primitive.Decimal128))
}
// JSONNumberEncodeValue is the ValueEncoderFunc for json.Number.
func (dve DefaultValueEncoders) JSONNumberEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tJSONNumber {
return ValueEncoderError{Name: "JSONNumberEncodeValue", Types: []reflect.Type{tJSONNumber}, Received: val}
}
jsnum := val.Interface().(json.Number)
// Attempt int first, then float64
if i64, err := jsnum.Int64(); err == nil {
return dve.IntEncodeValue(ec, vw, reflect.ValueOf(i64))
}
f64, err := jsnum.Float64()
if err != nil {
return err
}
return dve.FloatEncodeValue(ec, vw, reflect.ValueOf(f64))
}
// URLEncodeValue is the ValueEncoderFunc for url.URL.
func (dve DefaultValueEncoders) URLEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tURL {
return ValueEncoderError{Name: "URLEncodeValue", Types: []reflect.Type{tURL}, Received: val}
}
u := val.Interface().(url.URL)
return vw.WriteString(u.String())
}
// TimeEncodeValue is the ValueEncoderFunc for time.TIme.
//
// Deprecated: TimeEncodeValue is not registered by default. Use TimeCodec.EncodeValue instead.
func (dve DefaultValueEncoders) TimeEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tTime {
return ValueEncoderError{Name: "TimeEncodeValue", Types: []reflect.Type{tTime}, Received: val}
}
tt := val.Interface().(time.Time)
dt := primitive.NewDateTimeFromTime(tt)
return vw.WriteDateTime(int64(dt))
}
// ByteSliceEncodeValue is the ValueEncoderFunc for []byte.
//
// Deprecated: ByteSliceEncodeValue is not registered by default. Use ByteSliceCodec.EncodeValue instead.
func (dve DefaultValueEncoders) ByteSliceEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tByteSlice {
return ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: val}
}
if val.IsNil() {
return vw.WriteNull()
}
return vw.WriteBinary(val.Interface().([]byte))
}
// MapEncodeValue is the ValueEncoderFunc for map[string]* types.
//
// Deprecated: MapEncodeValue is not registered by default. Use MapCodec.EncodeValue instead.
func (dve DefaultValueEncoders) MapEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Kind() != reflect.Map || val.Type().Key().Kind() != reflect.String {
return ValueEncoderError{Name: "MapEncodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: val}
}
if val.IsNil() {
// If we have a nill map but we can't WriteNull, that means we're probably trying to encode
// to a TopLevel document. We can't currently tell if this is what actually happened, but if
// there's a deeper underlying problem, the error will also be returned from WriteDocument,
// so just continue. The operations on a map reflection value are valid, so we can call
// MapKeys within mapEncodeValue without a problem.
err := vw.WriteNull()
if err == nil {
return nil
}
}
dw, err := vw.WriteDocument()
if err != nil {
return err
}
return dve.mapEncodeValue(ec, dw, val, nil)
}
// mapEncodeValue handles encoding of the values of a map. The collisionFn returns
// true if the provided key exists, this is mainly used for inline maps in the
// struct codec.
func (dve DefaultValueEncoders) mapEncodeValue(ec EncodeContext, dw bsonrw.DocumentWriter, val reflect.Value, collisionFn func(string) bool) error {
elemType := val.Type().Elem()
encoder, err := ec.LookupEncoder(elemType)
if err != nil && elemType.Kind() != reflect.Interface {
return err
}
keys := val.MapKeys()
for _, key := range keys {
if collisionFn != nil && collisionFn(key.String()) {
return fmt.Errorf("Key %s of inlined map conflicts with a struct field name", key)
}
currEncoder, currVal, lookupErr := dve.lookupElementEncoder(ec, encoder, val.MapIndex(key))
if lookupErr != nil && lookupErr != errInvalidValue {
return lookupErr
}
vw, err := dw.WriteDocumentElement(key.String())
if err != nil {
return err
}
if lookupErr == errInvalidValue {
err = vw.WriteNull()
if err != nil {
return err
}
continue
}
err = currEncoder.EncodeValue(ec, vw, currVal)
if err != nil {
return err
}
}
return dw.WriteDocumentEnd()
}
// ArrayEncodeValue is the ValueEncoderFunc for array types.
func (dve DefaultValueEncoders) ArrayEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Kind() != reflect.Array {
return ValueEncoderError{Name: "ArrayEncodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: val}
}
// If we have a []primitive.E we want to treat it as a document instead of as an array.
if val.Type().Elem() == tE {
dw, err := vw.WriteDocument()
if err != nil {
return err
}
for idx := 0; idx < val.Len(); idx++ {
e := val.Index(idx).Interface().(primitive.E)
err = encodeElement(ec, dw, e)
if err != nil {
return err
}
}
return dw.WriteDocumentEnd()
}
// If we have a []byte we want to treat it as a binary instead of as an array.
if val.Type().Elem() == tByte {
var byteSlice []byte
for idx := 0; idx < val.Len(); idx++ {
byteSlice = append(byteSlice, val.Index(idx).Interface().(byte))
}
return vw.WriteBinary(byteSlice)
}
aw, err := vw.WriteArray()
if err != nil {
return err
}
elemType := val.Type().Elem()
encoder, err := ec.LookupEncoder(elemType)
if err != nil && elemType.Kind() != reflect.Interface {
return err
}
for idx := 0; idx < val.Len(); idx++ {
currEncoder, currVal, lookupErr := dve.lookupElementEncoder(ec, encoder, val.Index(idx))
if lookupErr != nil && lookupErr != errInvalidValue {
return lookupErr
}
vw, err := aw.WriteArrayElement()
if err != nil {
return err
}
if lookupErr == errInvalidValue {
err = vw.WriteNull()
if err != nil {
return err
}
continue
}
err = currEncoder.EncodeValue(ec, vw, currVal)
if err != nil {
return err
}
}
return aw.WriteArrayEnd()
}
// SliceEncodeValue is the ValueEncoderFunc for slice types.
//
// Deprecated: SliceEncodeValue is not registered by default. Use SliceCodec.EncodeValue instead.
func (dve DefaultValueEncoders) SliceEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Kind() != reflect.Slice {
return ValueEncoderError{Name: "SliceEncodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: val}
}
if val.IsNil() {
return vw.WriteNull()
}
// If we have a []primitive.E we want to treat it as a document instead of as an array.
if val.Type().ConvertibleTo(tD) {
d := val.Convert(tD).Interface().(primitive.D)
dw, err := vw.WriteDocument()
if err != nil {
return err
}
for _, e := range d {
err = encodeElement(ec, dw, e)
if err != nil {
return err
}
}
return dw.WriteDocumentEnd()
}
aw, err := vw.WriteArray()
if err != nil {
return err
}
elemType := val.Type().Elem()
encoder, err := ec.LookupEncoder(elemType)
if err != nil && elemType.Kind() != reflect.Interface {
return err
}
for idx := 0; idx < val.Len(); idx++ {
currEncoder, currVal, lookupErr := dve.lookupElementEncoder(ec, encoder, val.Index(idx))
if lookupErr != nil && lookupErr != errInvalidValue {
return lookupErr
}
vw, err := aw.WriteArrayElement()
if err != nil {
return err
}
if lookupErr == errInvalidValue {
err = vw.WriteNull()
if err != nil {
return err
}
continue
}
err = currEncoder.EncodeValue(ec, vw, currVal)
if err != nil {
return err
}
}
return aw.WriteArrayEnd()
}
func (dve DefaultValueEncoders) lookupElementEncoder(ec EncodeContext, origEncoder ValueEncoder, currVal reflect.Value) (ValueEncoder, reflect.Value, error) {
if origEncoder != nil || (currVal.Kind() != reflect.Interface) {
return origEncoder, currVal, nil
}
currVal = currVal.Elem()
if !currVal.IsValid() {
return nil, currVal, errInvalidValue
}
currEncoder, err := ec.LookupEncoder(currVal.Type())
return currEncoder, currVal, err
}
// EmptyInterfaceEncodeValue is the ValueEncoderFunc for interface{}.
//
// Deprecated: EmptyInterfaceEncodeValue is not registered by default. Use EmptyInterfaceCodec.EncodeValue instead.
func (dve DefaultValueEncoders) EmptyInterfaceEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tEmpty {
return ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: val}
}
if val.IsNil() {
return vw.WriteNull()
}
encoder, err := ec.LookupEncoder(val.Elem().Type())
if err != nil {
return err
}
return encoder.EncodeValue(ec, vw, val.Elem())
}
// ValueMarshalerEncodeValue is the ValueEncoderFunc for ValueMarshaler implementations.
func (dve DefaultValueEncoders) ValueMarshalerEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
// Either val or a pointer to val must implement ValueMarshaler
switch {
case !val.IsValid():
return ValueEncoderError{Name: "ValueMarshalerEncodeValue", Types: []reflect.Type{tValueMarshaler}, Received: val}
case val.Type().Implements(tValueMarshaler):
// If ValueMarshaler is implemented on a concrete type, make sure that val isn't a nil pointer
if isImplementationNil(val, tValueMarshaler) {
return vw.WriteNull()
}
case reflect.PtrTo(val.Type()).Implements(tValueMarshaler) && val.CanAddr():
val = val.Addr()
default:
return ValueEncoderError{Name: "ValueMarshalerEncodeValue", Types: []reflect.Type{tValueMarshaler}, Received: val}
}
fn := val.Convert(tValueMarshaler).MethodByName("MarshalBSONValue")
returns := fn.Call(nil)
if !returns[2].IsNil() {
return returns[2].Interface().(error)
}
t, data := returns[0].Interface().(bsontype.Type), returns[1].Interface().([]byte)
return bsonrw.Copier{}.CopyValueFromBytes(vw, t, data)
}
// MarshalerEncodeValue is the ValueEncoderFunc for Marshaler implementations.
func (dve DefaultValueEncoders) MarshalerEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
// Either val or a pointer to val must implement Marshaler
switch {
case !val.IsValid():
return ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: val}
case val.Type().Implements(tMarshaler):
// If Marshaler is implemented on a concrete type, make sure that val isn't a nil pointer
if isImplementationNil(val, tMarshaler) {
return vw.WriteNull()
}
case reflect.PtrTo(val.Type()).Implements(tMarshaler) && val.CanAddr():
val = val.Addr()
default:
return ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: val}
}
fn := val.Convert(tMarshaler).MethodByName("MarshalBSON")
returns := fn.Call(nil)
if !returns[1].IsNil() {
return returns[1].Interface().(error)
}
data := returns[0].Interface().([]byte)
return bsonrw.Copier{}.CopyValueFromBytes(vw, bsontype.EmbeddedDocument, data)
}
// ProxyEncodeValue is the ValueEncoderFunc for Proxy implementations.
func (dve DefaultValueEncoders) ProxyEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
// Either val or a pointer to val must implement Proxy
switch {
case !val.IsValid():
return ValueEncoderError{Name: "ProxyEncodeValue", Types: []reflect.Type{tProxy}, Received: val}
case val.Type().Implements(tProxy):
// If Proxy is implemented on a concrete type, make sure that val isn't a nil pointer
if isImplementationNil(val, tProxy) {
return vw.WriteNull()
}
case reflect.PtrTo(val.Type()).Implements(tProxy) && val.CanAddr():
val = val.Addr()
default:
return ValueEncoderError{Name: "ProxyEncodeValue", Types: []reflect.Type{tProxy}, Received: val}
}
fn := val.Convert(tProxy).MethodByName("ProxyBSON")
returns := fn.Call(nil)
if !returns[1].IsNil() {
return returns[1].Interface().(error)
}
data := returns[0]
var encoder ValueEncoder
var err error
if data.Elem().IsValid() {
encoder, err = ec.LookupEncoder(data.Elem().Type())
} else {
encoder, err = ec.LookupEncoder(nil)
}
if err != nil {
return err
}
return encoder.EncodeValue(ec, vw, data.Elem())
}
// JavaScriptEncodeValue is the ValueEncoderFunc for the primitive.JavaScript type.
func (DefaultValueEncoders) JavaScriptEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tJavaScript {
return ValueEncoderError{Name: "JavaScriptEncodeValue", Types: []reflect.Type{tJavaScript}, Received: val}
}
return vw.WriteJavascript(val.String())
}
// SymbolEncodeValue is the ValueEncoderFunc for the primitive.Symbol type.
func (DefaultValueEncoders) SymbolEncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tSymbol {
return ValueEncoderError{Name: "SymbolEncodeValue", Types: []reflect.Type{tSymbol}, Received: val}
}
return vw.WriteSymbol(val.String())
}
// BinaryEncodeValue is the ValueEncoderFunc for Binary.
func (DefaultValueEncoders) BinaryEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tBinary {
return ValueEncoderError{Name: "BinaryEncodeValue", Types: []reflect.Type{tBinary}, Received: val}
}
b := val.Interface().(primitive.Binary)
return vw.WriteBinaryWithSubtype(b.Data, b.Subtype)
}
// UndefinedEncodeValue is the ValueEncoderFunc for Undefined.
func (DefaultValueEncoders) UndefinedEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tUndefined {
return ValueEncoderError{Name: "UndefinedEncodeValue", Types: []reflect.Type{tUndefined}, Received: val}
}
return vw.WriteUndefined()
}
// DateTimeEncodeValue is the ValueEncoderFunc for DateTime.
func (DefaultValueEncoders) DateTimeEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tDateTime {
return ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{tDateTime}, Received: val}
}
return vw.WriteDateTime(val.Int())
}
// NullEncodeValue is the ValueEncoderFunc for Null.
func (DefaultValueEncoders) NullEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tNull {
return ValueEncoderError{Name: "NullEncodeValue", Types: []reflect.Type{tNull}, Received: val}
}
return vw.WriteNull()
}
// RegexEncodeValue is the ValueEncoderFunc for Regex.
func (DefaultValueEncoders) RegexEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tRegex {
return ValueEncoderError{Name: "RegexEncodeValue", Types: []reflect.Type{tRegex}, Received: val}
}
regex := val.Interface().(primitive.Regex)
return vw.WriteRegex(regex.Pattern, regex.Options)
}
// DBPointerEncodeValue is the ValueEncoderFunc for DBPointer.
func (DefaultValueEncoders) DBPointerEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tDBPointer {
return ValueEncoderError{Name: "DBPointerEncodeValue", Types: []reflect.Type{tDBPointer}, Received: val}
}
dbp := val.Interface().(primitive.DBPointer)
return vw.WriteDBPointer(dbp.DB, dbp.Pointer)
}
// TimestampEncodeValue is the ValueEncoderFunc for Timestamp.
func (DefaultValueEncoders) TimestampEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tTimestamp {
return ValueEncoderError{Name: "TimestampEncodeValue", Types: []reflect.Type{tTimestamp}, Received: val}
}
ts := val.Interface().(primitive.Timestamp)
return vw.WriteTimestamp(ts.T, ts.I)
}
// MinKeyEncodeValue is the ValueEncoderFunc for MinKey.
func (DefaultValueEncoders) MinKeyEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tMinKey {
return ValueEncoderError{Name: "MinKeyEncodeValue", Types: []reflect.Type{tMinKey}, Received: val}
}
return vw.WriteMinKey()
}
// MaxKeyEncodeValue is the ValueEncoderFunc for MaxKey.
func (DefaultValueEncoders) MaxKeyEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tMaxKey {
return ValueEncoderError{Name: "MaxKeyEncodeValue", Types: []reflect.Type{tMaxKey}, Received: val}
}
return vw.WriteMaxKey()
}
// CoreDocumentEncodeValue is the ValueEncoderFunc for bsoncore.Document.
func (DefaultValueEncoders) CoreDocumentEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tCoreDocument {
return ValueEncoderError{Name: "CoreDocumentEncodeValue", Types: []reflect.Type{tCoreDocument}, Received: val}
}
cdoc := val.Interface().(bsoncore.Document)
return bsonrw.Copier{}.CopyDocumentFromBytes(vw, cdoc)
}
// CodeWithScopeEncodeValue is the ValueEncoderFunc for CodeWithScope.
func (dve DefaultValueEncoders) CodeWithScopeEncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tCodeWithScope {
return ValueEncoderError{Name: "CodeWithScopeEncodeValue", Types: []reflect.Type{tCodeWithScope}, Received: val}
}
cws := val.Interface().(primitive.CodeWithScope)
dw, err := vw.WriteCodeWithScope(string(cws.Code))
if err != nil {
return err
}
sw := sliceWriterPool.Get().(*bsonrw.SliceWriter)
defer sliceWriterPool.Put(sw)
*sw = (*sw)[:0]
scopeVW := bvwPool.Get(sw)
defer bvwPool.Put(scopeVW)
encoder, err := ec.LookupEncoder(reflect.TypeOf(cws.Scope))
if err != nil {
return err
}
err = encoder.EncodeValue(ec, scopeVW, reflect.ValueOf(cws.Scope))
if err != nil {
return err
}
err = bsonrw.Copier{}.CopyBytesToDocumentWriter(dw, *sw)
if err != nil {
return err
}
return dw.WriteDocumentEnd()
}
// isImplementationNil returns if val is a nil pointer and inter is implemented on a concrete type
func isImplementationNil(val reflect.Value, inter reflect.Type) bool {
vt := val.Type()
for vt.Kind() == reflect.Ptr {
vt = vt.Elem()
}
return vt.Implements(inter) && val.Kind() == reflect.Ptr && val.IsNil()
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
// 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 bsoncodec provides a system for encoding values to BSON representations and decoding
// values from BSON representations. This package considers both binary BSON and ExtendedJSON as
// BSON representations. The types in this package enable a flexible system for handling this
// encoding and decoding.
//
// The codec system is composed of two parts:
//
// 1) ValueEncoders and ValueDecoders that handle encoding and decoding Go values to and from BSON
// representations.
//
// 2) A Registry that holds these ValueEncoders and ValueDecoders and provides methods for
// retrieving them.
//
// # ValueEncoders and ValueDecoders
//
// The ValueEncoder interface is implemented by types that can encode a provided Go type to BSON.
// The value to encode is provided as a reflect.Value and a bsonrw.ValueWriter is used within the
// EncodeValue method to actually create the BSON representation. For convenience, ValueEncoderFunc
// is provided to allow use of a function with the correct signature as a ValueEncoder. An
// EncodeContext instance is provided to allow implementations to lookup further ValueEncoders and
// to provide configuration information.
//
// The ValueDecoder interface is the inverse of the ValueEncoder. Implementations should ensure that
// the value they receive is settable. Similar to ValueEncoderFunc, ValueDecoderFunc is provided to
// allow the use of a function with the correct signature as a ValueDecoder. A DecodeContext
// instance is provided and serves similar functionality to the EncodeContext.
//
// # Registry and RegistryBuilder
//
// A Registry is an immutable store for ValueEncoders, ValueDecoders, and a type map. See the Registry type
// documentation for examples of registering various custom encoders and decoders. A Registry can be constructed using a
// RegistryBuilder, which handles three main types of codecs:
//
// 1. Type encoders/decoders - These can be registered using the RegisterTypeEncoder and RegisterTypeDecoder methods.
// The registered codec will be invoked when encoding/decoding a value whose type matches the registered type exactly.
// If the registered type is an interface, the codec will be invoked when encoding or decoding values whose type is the
// interface, but not for values with concrete types that implement the interface.
//
// 2. Hook encoders/decoders - These can be registered using the RegisterHookEncoder and RegisterHookDecoder methods.
// These methods only accept interface types and the registered codecs will be invoked when encoding or decoding values
// whose types implement the interface. An example of a hook defined by the driver is bson.Marshaler. The driver will
// call the MarshalBSON method for any value whose type implements bson.Marshaler, regardless of the value's concrete
// type.
//
// 3. Type map entries - This can be used to associate a BSON type with a Go type. These type associations are used when
// decoding into a bson.D/bson.M or a struct field of type interface{}. For example, by default, BSON int32 and int64
// values decode as Go int32 and int64 instances, respectively, when decoding into a bson.D. The following code would
// change the behavior so these values decode as Go int instances instead:
//
// intType := reflect.TypeOf(int(0))
// registryBuilder.RegisterTypeMapEntry(bsontype.Int32, intType).RegisterTypeMapEntry(bsontype.Int64, intType)
//
// 4. Kind encoder/decoders - These can be registered using the RegisterDefaultEncoder and RegisterDefaultDecoder
// methods. The registered codec will be invoked when encoding or decoding values whose reflect.Kind matches the
// registered reflect.Kind as long as the value's type doesn't match a registered type or hook encoder/decoder first.
// These methods should be used to change the behavior for all values for a specific kind.
//
// # Registry Lookup Procedure
//
// When looking up an encoder in a Registry, the precedence rules are as follows:
//
// 1. A type encoder registered for the exact type of the value.
//
// 2. A hook encoder registered for an interface that is implemented by the value or by a pointer to the value. If the
// value matches multiple hooks (e.g. the type implements bsoncodec.Marshaler and bsoncodec.ValueMarshaler), the first
// one registered will be selected. Note that registries constructed using bson.NewRegistryBuilder have driver-defined
// hooks registered for the bsoncodec.Marshaler, bsoncodec.ValueMarshaler, and bsoncodec.Proxy interfaces, so those
// will take precedence over any new hooks.
//
// 3. A kind encoder registered for the value's kind.
//
// If all of these lookups fail to find an encoder, an error of type ErrNoEncoder is returned. The same precedence
// rules apply for decoders, with the exception that an error of type ErrNoDecoder will be returned if no decoder is
// found.
//
// # DefaultValueEncoders and DefaultValueDecoders
//
// The DefaultValueEncoders and DefaultValueDecoders types provide a full set of ValueEncoders and
// ValueDecoders for handling a wide range of Go types, including all of the types within the
// primitive package. To make registering these codecs easier, a helper method on each type is
// provided. For the DefaultValueEncoders type the method is called RegisterDefaultEncoders and for
// the DefaultValueDecoders type the method is called RegisterDefaultDecoders, this method also
// handles registering type map entries for each BSON type.
package bsoncodec

View File

@ -0,0 +1,147 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// EmptyInterfaceCodec is the Codec used for interface{} values.
type EmptyInterfaceCodec struct {
DecodeBinaryAsSlice bool
}
var (
defaultEmptyInterfaceCodec = NewEmptyInterfaceCodec()
_ ValueCodec = defaultEmptyInterfaceCodec
_ typeDecoder = defaultEmptyInterfaceCodec
)
// NewEmptyInterfaceCodec returns a EmptyInterfaceCodec with options opts.
func NewEmptyInterfaceCodec(opts ...*bsonoptions.EmptyInterfaceCodecOptions) *EmptyInterfaceCodec {
interfaceOpt := bsonoptions.MergeEmptyInterfaceCodecOptions(opts...)
codec := EmptyInterfaceCodec{}
if interfaceOpt.DecodeBinaryAsSlice != nil {
codec.DecodeBinaryAsSlice = *interfaceOpt.DecodeBinaryAsSlice
}
return &codec
}
// EncodeValue is the ValueEncoderFunc for interface{}.
func (eic EmptyInterfaceCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tEmpty {
return ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: val}
}
if val.IsNil() {
return vw.WriteNull()
}
encoder, err := ec.LookupEncoder(val.Elem().Type())
if err != nil {
return err
}
return encoder.EncodeValue(ec, vw, val.Elem())
}
func (eic EmptyInterfaceCodec) getEmptyInterfaceDecodeType(dc DecodeContext, valueType bsontype.Type) (reflect.Type, error) {
isDocument := valueType == bsontype.Type(0) || valueType == bsontype.EmbeddedDocument
if isDocument {
if dc.defaultDocumentType != nil {
// If the bsontype is an embedded document and the DocumentType is set on the DecodeContext, then return
// that type.
return dc.defaultDocumentType, nil
}
if dc.Ancestor != nil {
// Using ancestor information rather than looking up the type map entry forces consistent decoding.
// If we're decoding into a bson.D, subdocuments should also be decoded as bson.D, even if a type map entry
// has been registered.
return dc.Ancestor, nil
}
}
rtype, err := dc.LookupTypeMapEntry(valueType)
if err == nil {
return rtype, nil
}
if isDocument {
// For documents, fallback to looking up a type map entry for bsontype.Type(0) or bsontype.EmbeddedDocument,
// depending on the original valueType.
var lookupType bsontype.Type
switch valueType {
case bsontype.Type(0):
lookupType = bsontype.EmbeddedDocument
case bsontype.EmbeddedDocument:
lookupType = bsontype.Type(0)
}
rtype, err = dc.LookupTypeMapEntry(lookupType)
if err == nil {
return rtype, nil
}
}
return nil, err
}
func (eic EmptyInterfaceCodec) decodeType(dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type) (reflect.Value, error) {
if t != tEmpty {
return emptyValue, ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.Zero(t)}
}
rtype, err := eic.getEmptyInterfaceDecodeType(dc, vr.Type())
if err != nil {
switch vr.Type() {
case bsontype.Null:
return reflect.Zero(t), vr.ReadNull()
default:
return emptyValue, err
}
}
decoder, err := dc.LookupDecoder(rtype)
if err != nil {
return emptyValue, err
}
elem, err := decodeTypeOrValue(decoder, dc, vr, rtype)
if err != nil {
return emptyValue, err
}
if eic.DecodeBinaryAsSlice && rtype == tBinary {
binElem := elem.Interface().(primitive.Binary)
if binElem.Subtype == bsontype.BinaryGeneric || binElem.Subtype == bsontype.BinaryBinaryOld {
elem = reflect.ValueOf(binElem.Data)
}
}
return elem, nil
}
// DecodeValue is the ValueDecoderFunc for interface{}.
func (eic EmptyInterfaceCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != tEmpty {
return ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: val}
}
elem, err := eic.decodeType(dc, vr, val.Type())
if err != nil {
return err
}
val.Set(elem)
return nil
}

View File

@ -0,0 +1,309 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"encoding"
"fmt"
"reflect"
"strconv"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
var defaultMapCodec = NewMapCodec()
// MapCodec is the Codec used for map values.
type MapCodec struct {
DecodeZerosMap bool
EncodeNilAsEmpty bool
EncodeKeysWithStringer bool
}
var _ ValueCodec = &MapCodec{}
// KeyMarshaler is the interface implemented by an object that can marshal itself into a string key.
// This applies to types used as map keys and is similar to encoding.TextMarshaler.
type KeyMarshaler interface {
MarshalKey() (key string, err error)
}
// KeyUnmarshaler is the interface implemented by an object that can unmarshal a string representation
// of itself. This applies to types used as map keys and is similar to encoding.TextUnmarshaler.
//
// UnmarshalKey must be able to decode the form generated by MarshalKey.
// UnmarshalKey must copy the text if it wishes to retain the text
// after returning.
type KeyUnmarshaler interface {
UnmarshalKey(key string) error
}
// NewMapCodec returns a MapCodec with options opts.
func NewMapCodec(opts ...*bsonoptions.MapCodecOptions) *MapCodec {
mapOpt := bsonoptions.MergeMapCodecOptions(opts...)
codec := MapCodec{}
if mapOpt.DecodeZerosMap != nil {
codec.DecodeZerosMap = *mapOpt.DecodeZerosMap
}
if mapOpt.EncodeNilAsEmpty != nil {
codec.EncodeNilAsEmpty = *mapOpt.EncodeNilAsEmpty
}
if mapOpt.EncodeKeysWithStringer != nil {
codec.EncodeKeysWithStringer = *mapOpt.EncodeKeysWithStringer
}
return &codec
}
// EncodeValue is the ValueEncoder for map[*]* types.
func (mc *MapCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Kind() != reflect.Map {
return ValueEncoderError{Name: "MapEncodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: val}
}
if val.IsNil() && !mc.EncodeNilAsEmpty {
// If we have a nil map but we can't WriteNull, that means we're probably trying to encode
// to a TopLevel document. We can't currently tell if this is what actually happened, but if
// there's a deeper underlying problem, the error will also be returned from WriteDocument,
// so just continue. The operations on a map reflection value are valid, so we can call
// MapKeys within mapEncodeValue without a problem.
err := vw.WriteNull()
if err == nil {
return nil
}
}
dw, err := vw.WriteDocument()
if err != nil {
return err
}
return mc.mapEncodeValue(ec, dw, val, nil)
}
// mapEncodeValue handles encoding of the values of a map. The collisionFn returns
// true if the provided key exists, this is mainly used for inline maps in the
// struct codec.
func (mc *MapCodec) mapEncodeValue(ec EncodeContext, dw bsonrw.DocumentWriter, val reflect.Value, collisionFn func(string) bool) error {
elemType := val.Type().Elem()
encoder, err := ec.LookupEncoder(elemType)
if err != nil && elemType.Kind() != reflect.Interface {
return err
}
keys := val.MapKeys()
for _, key := range keys {
keyStr, err := mc.encodeKey(key)
if err != nil {
return err
}
if collisionFn != nil && collisionFn(keyStr) {
return fmt.Errorf("Key %s of inlined map conflicts with a struct field name", key)
}
currEncoder, currVal, lookupErr := defaultValueEncoders.lookupElementEncoder(ec, encoder, val.MapIndex(key))
if lookupErr != nil && lookupErr != errInvalidValue {
return lookupErr
}
vw, err := dw.WriteDocumentElement(keyStr)
if err != nil {
return err
}
if lookupErr == errInvalidValue {
err = vw.WriteNull()
if err != nil {
return err
}
continue
}
err = currEncoder.EncodeValue(ec, vw, currVal)
if err != nil {
return err
}
}
return dw.WriteDocumentEnd()
}
// DecodeValue is the ValueDecoder for map[string/decimal]* types.
func (mc *MapCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if val.Kind() != reflect.Map || (!val.CanSet() && val.IsNil()) {
return ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: val}
}
switch vrType := vr.Type(); vrType {
case bsontype.Type(0), bsontype.EmbeddedDocument:
case bsontype.Null:
val.Set(reflect.Zero(val.Type()))
return vr.ReadNull()
case bsontype.Undefined:
val.Set(reflect.Zero(val.Type()))
return vr.ReadUndefined()
default:
return fmt.Errorf("cannot decode %v into a %s", vrType, val.Type())
}
dr, err := vr.ReadDocument()
if err != nil {
return err
}
if val.IsNil() {
val.Set(reflect.MakeMap(val.Type()))
}
if val.Len() > 0 && mc.DecodeZerosMap {
clearMap(val)
}
eType := val.Type().Elem()
decoder, err := dc.LookupDecoder(eType)
if err != nil {
return err
}
eTypeDecoder, _ := decoder.(typeDecoder)
if eType == tEmpty {
dc.Ancestor = val.Type()
}
keyType := val.Type().Key()
for {
key, vr, err := dr.ReadElement()
if err == bsonrw.ErrEOD {
break
}
if err != nil {
return err
}
k, err := mc.decodeKey(key, keyType)
if err != nil {
return err
}
elem, err := decodeTypeOrValueWithInfo(decoder, eTypeDecoder, dc, vr, eType, true)
if err != nil {
return newDecodeError(key, err)
}
val.SetMapIndex(k, elem)
}
return nil
}
func clearMap(m reflect.Value) {
var none reflect.Value
for _, k := range m.MapKeys() {
m.SetMapIndex(k, none)
}
}
func (mc *MapCodec) encodeKey(val reflect.Value) (string, error) {
if mc.EncodeKeysWithStringer {
return fmt.Sprint(val), nil
}
// keys of any string type are used directly
if val.Kind() == reflect.String {
return val.String(), nil
}
// KeyMarshalers are marshaled
if km, ok := val.Interface().(KeyMarshaler); ok {
if val.Kind() == reflect.Ptr && val.IsNil() {
return "", nil
}
buf, err := km.MarshalKey()
if err == nil {
return buf, nil
}
return "", err
}
// keys implement encoding.TextMarshaler are marshaled.
if km, ok := val.Interface().(encoding.TextMarshaler); ok {
if val.Kind() == reflect.Ptr && val.IsNil() {
return "", nil
}
buf, err := km.MarshalText()
if err != nil {
return "", err
}
return string(buf), nil
}
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(val.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(val.Uint(), 10), nil
}
return "", fmt.Errorf("unsupported key type: %v", val.Type())
}
var keyUnmarshalerType = reflect.TypeOf((*KeyUnmarshaler)(nil)).Elem()
var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
func (mc *MapCodec) decodeKey(key string, keyType reflect.Type) (reflect.Value, error) {
keyVal := reflect.ValueOf(key)
var err error
switch {
// First, if EncodeKeysWithStringer is not enabled, try to decode withKeyUnmarshaler
case !mc.EncodeKeysWithStringer && reflect.PtrTo(keyType).Implements(keyUnmarshalerType):
keyVal = reflect.New(keyType)
v := keyVal.Interface().(KeyUnmarshaler)
err = v.UnmarshalKey(key)
keyVal = keyVal.Elem()
// Try to decode encoding.TextUnmarshalers.
case reflect.PtrTo(keyType).Implements(textUnmarshalerType):
keyVal = reflect.New(keyType)
v := keyVal.Interface().(encoding.TextUnmarshaler)
err = v.UnmarshalText([]byte(key))
keyVal = keyVal.Elem()
// Otherwise, go to type specific behavior
default:
switch keyType.Kind() {
case reflect.String:
keyVal = reflect.ValueOf(key).Convert(keyType)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n, parseErr := strconv.ParseInt(key, 10, 64)
if parseErr != nil || reflect.Zero(keyType).OverflowInt(n) {
err = fmt.Errorf("failed to unmarshal number key %v", key)
}
keyVal = reflect.ValueOf(n).Convert(keyType)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
n, parseErr := strconv.ParseUint(key, 10, 64)
if parseErr != nil || reflect.Zero(keyType).OverflowUint(n) {
err = fmt.Errorf("failed to unmarshal number key %v", key)
break
}
keyVal = reflect.ValueOf(n).Convert(keyType)
case reflect.Float32, reflect.Float64:
if mc.EncodeKeysWithStringer {
parsed, err := strconv.ParseFloat(key, 64)
if err != nil {
return keyVal, fmt.Errorf("Map key is defined to be a decimal type (%v) but got error %v", keyType.Kind(), err)
}
keyVal = reflect.ValueOf(parsed)
break
}
fallthrough
default:
return keyVal, fmt.Errorf("unsupported key type: %v", keyType)
}
}
return keyVal, err
}

View File

@ -0,0 +1,65 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import "fmt"
type mode int
const (
_ mode = iota
mTopLevel
mDocument
mArray
mValue
mElement
mCodeWithScope
mSpacer
)
func (m mode) String() string {
var str string
switch m {
case mTopLevel:
str = "TopLevel"
case mDocument:
str = "DocumentMode"
case mArray:
str = "ArrayMode"
case mValue:
str = "ValueMode"
case mElement:
str = "ElementMode"
case mCodeWithScope:
str = "CodeWithScopeMode"
case mSpacer:
str = "CodeWithScopeSpacerFrame"
default:
str = "UnknownMode"
}
return str
}
// TransitionError is an error returned when an invalid progressing a
// ValueReader or ValueWriter state machine occurs.
type TransitionError struct {
parent mode
current mode
destination mode
}
func (te TransitionError) Error() string {
if te.destination == mode(0) {
return fmt.Sprintf("invalid state transition: cannot read/write value while in %s", te.current)
}
if te.parent == mode(0) {
return fmt.Sprintf("invalid state transition: %s -> %s", te.current, te.destination)
}
return fmt.Sprintf("invalid state transition: %s -> %s; parent %s", te.current, te.destination, te.parent)
}

View File

@ -0,0 +1,109 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"sync"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
var _ ValueEncoder = &PointerCodec{}
var _ ValueDecoder = &PointerCodec{}
// PointerCodec is the Codec used for pointers.
type PointerCodec struct {
ecache map[reflect.Type]ValueEncoder
dcache map[reflect.Type]ValueDecoder
l sync.RWMutex
}
// NewPointerCodec returns a PointerCodec that has been initialized.
func NewPointerCodec() *PointerCodec {
return &PointerCodec{
ecache: make(map[reflect.Type]ValueEncoder),
dcache: make(map[reflect.Type]ValueDecoder),
}
}
// EncodeValue handles encoding a pointer by either encoding it to BSON Null if the pointer is nil
// or looking up an encoder for the type of value the pointer points to.
func (pc *PointerCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if val.Kind() != reflect.Ptr {
if !val.IsValid() {
return vw.WriteNull()
}
return ValueEncoderError{Name: "PointerCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: val}
}
if val.IsNil() {
return vw.WriteNull()
}
pc.l.RLock()
enc, ok := pc.ecache[val.Type()]
pc.l.RUnlock()
if ok {
if enc == nil {
return ErrNoEncoder{Type: val.Type()}
}
return enc.EncodeValue(ec, vw, val.Elem())
}
enc, err := ec.LookupEncoder(val.Type().Elem())
pc.l.Lock()
pc.ecache[val.Type()] = enc
pc.l.Unlock()
if err != nil {
return err
}
return enc.EncodeValue(ec, vw, val.Elem())
}
// DecodeValue handles decoding a pointer by looking up a decoder for the type it points to and
// using that to decode. If the BSON value is Null, this method will set the pointer to nil.
func (pc *PointerCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Kind() != reflect.Ptr {
return ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: val}
}
if vr.Type() == bsontype.Null {
val.Set(reflect.Zero(val.Type()))
return vr.ReadNull()
}
if vr.Type() == bsontype.Undefined {
val.Set(reflect.Zero(val.Type()))
return vr.ReadUndefined()
}
if val.IsNil() {
val.Set(reflect.New(val.Type().Elem()))
}
pc.l.RLock()
dec, ok := pc.dcache[val.Type()]
pc.l.RUnlock()
if ok {
if dec == nil {
return ErrNoDecoder{Type: val.Type()}
}
return dec.DecodeValue(dc, vr, val.Elem())
}
dec, err := dc.LookupDecoder(val.Type().Elem())
pc.l.Lock()
pc.dcache[val.Type()] = dec
pc.l.Unlock()
if err != nil {
return err
}
return dec.DecodeValue(dc, vr, val.Elem())
}

View File

@ -0,0 +1,14 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
// Proxy is an interface implemented by types that cannot themselves be directly encoded. Types
// that implement this interface with have ProxyBSON called during the encoding process and that
// value will be encoded in place for the implementer.
type Proxy interface {
ProxyBSON() (interface{}, error)
}

View File

@ -0,0 +1,469 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"errors"
"fmt"
"reflect"
"sync"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
// ErrNilType is returned when nil is passed to either LookupEncoder or LookupDecoder.
var ErrNilType = errors.New("cannot perform a decoder lookup on <nil>")
// ErrNotPointer is returned when a non-pointer type is provided to LookupDecoder.
var ErrNotPointer = errors.New("non-pointer provided to LookupDecoder")
// ErrNoEncoder is returned when there wasn't an encoder available for a type.
type ErrNoEncoder struct {
Type reflect.Type
}
func (ene ErrNoEncoder) Error() string {
if ene.Type == nil {
return "no encoder found for <nil>"
}
return "no encoder found for " + ene.Type.String()
}
// ErrNoDecoder is returned when there wasn't a decoder available for a type.
type ErrNoDecoder struct {
Type reflect.Type
}
func (end ErrNoDecoder) Error() string {
return "no decoder found for " + end.Type.String()
}
// ErrNoTypeMapEntry is returned when there wasn't a type available for the provided BSON type.
type ErrNoTypeMapEntry struct {
Type bsontype.Type
}
func (entme ErrNoTypeMapEntry) Error() string {
return "no type map entry found for " + entme.Type.String()
}
// ErrNotInterface is returned when the provided type is not an interface.
var ErrNotInterface = errors.New("The provided type is not an interface")
// A RegistryBuilder is used to build a Registry. This type is not goroutine
// safe.
type RegistryBuilder struct {
typeEncoders map[reflect.Type]ValueEncoder
interfaceEncoders []interfaceValueEncoder
kindEncoders map[reflect.Kind]ValueEncoder
typeDecoders map[reflect.Type]ValueDecoder
interfaceDecoders []interfaceValueDecoder
kindDecoders map[reflect.Kind]ValueDecoder
typeMap map[bsontype.Type]reflect.Type
}
// A Registry is used to store and retrieve codecs for types and interfaces. This type is the main
// typed passed around and Encoders and Decoders are constructed from it.
type Registry struct {
typeEncoders map[reflect.Type]ValueEncoder
typeDecoders map[reflect.Type]ValueDecoder
interfaceEncoders []interfaceValueEncoder
interfaceDecoders []interfaceValueDecoder
kindEncoders map[reflect.Kind]ValueEncoder
kindDecoders map[reflect.Kind]ValueDecoder
typeMap map[bsontype.Type]reflect.Type
mu sync.RWMutex
}
// NewRegistryBuilder creates a new empty RegistryBuilder.
func NewRegistryBuilder() *RegistryBuilder {
return &RegistryBuilder{
typeEncoders: make(map[reflect.Type]ValueEncoder),
typeDecoders: make(map[reflect.Type]ValueDecoder),
interfaceEncoders: make([]interfaceValueEncoder, 0),
interfaceDecoders: make([]interfaceValueDecoder, 0),
kindEncoders: make(map[reflect.Kind]ValueEncoder),
kindDecoders: make(map[reflect.Kind]ValueDecoder),
typeMap: make(map[bsontype.Type]reflect.Type),
}
}
func buildDefaultRegistry() *Registry {
rb := NewRegistryBuilder()
defaultValueEncoders.RegisterDefaultEncoders(rb)
defaultValueDecoders.RegisterDefaultDecoders(rb)
return rb.Build()
}
// RegisterCodec will register the provided ValueCodec for the provided type.
func (rb *RegistryBuilder) RegisterCodec(t reflect.Type, codec ValueCodec) *RegistryBuilder {
rb.RegisterTypeEncoder(t, codec)
rb.RegisterTypeDecoder(t, codec)
return rb
}
// RegisterTypeEncoder will register the provided ValueEncoder for the provided type.
//
// The type will be used directly, so an encoder can be registered for a type and a different encoder can be registered
// for a pointer to that type.
//
// If the given type is an interface, the encoder will be called when marshalling a type that is that interface. It
// will not be called when marshalling a non-interface type that implements the interface.
func (rb *RegistryBuilder) RegisterTypeEncoder(t reflect.Type, enc ValueEncoder) *RegistryBuilder {
rb.typeEncoders[t] = enc
return rb
}
// RegisterHookEncoder will register an encoder for the provided interface type t. This encoder will be called when
// marshalling a type if the type implements t or a pointer to the type implements t. If the provided type is not
// an interface (i.e. t.Kind() != reflect.Interface), this method will panic.
func (rb *RegistryBuilder) RegisterHookEncoder(t reflect.Type, enc ValueEncoder) *RegistryBuilder {
if t.Kind() != reflect.Interface {
panicStr := fmt.Sprintf("RegisterHookEncoder expects a type with kind reflect.Interface, "+
"got type %s with kind %s", t, t.Kind())
panic(panicStr)
}
for idx, encoder := range rb.interfaceEncoders {
if encoder.i == t {
rb.interfaceEncoders[idx].ve = enc
return rb
}
}
rb.interfaceEncoders = append(rb.interfaceEncoders, interfaceValueEncoder{i: t, ve: enc})
return rb
}
// RegisterTypeDecoder will register the provided ValueDecoder for the provided type.
//
// The type will be used directly, so a decoder can be registered for a type and a different decoder can be registered
// for a pointer to that type.
//
// If the given type is an interface, the decoder will be called when unmarshalling into a type that is that interface.
// It will not be called when unmarshalling into a non-interface type that implements the interface.
func (rb *RegistryBuilder) RegisterTypeDecoder(t reflect.Type, dec ValueDecoder) *RegistryBuilder {
rb.typeDecoders[t] = dec
return rb
}
// RegisterHookDecoder will register an decoder for the provided interface type t. This decoder will be called when
// unmarshalling into a type if the type implements t or a pointer to the type implements t. If the provided type is not
// an interface (i.e. t.Kind() != reflect.Interface), this method will panic.
func (rb *RegistryBuilder) RegisterHookDecoder(t reflect.Type, dec ValueDecoder) *RegistryBuilder {
if t.Kind() != reflect.Interface {
panicStr := fmt.Sprintf("RegisterHookDecoder expects a type with kind reflect.Interface, "+
"got type %s with kind %s", t, t.Kind())
panic(panicStr)
}
for idx, decoder := range rb.interfaceDecoders {
if decoder.i == t {
rb.interfaceDecoders[idx].vd = dec
return rb
}
}
rb.interfaceDecoders = append(rb.interfaceDecoders, interfaceValueDecoder{i: t, vd: dec})
return rb
}
// RegisterEncoder registers the provided type and encoder pair.
//
// Deprecated: Use RegisterTypeEncoder or RegisterHookEncoder instead.
func (rb *RegistryBuilder) RegisterEncoder(t reflect.Type, enc ValueEncoder) *RegistryBuilder {
if t == tEmpty {
rb.typeEncoders[t] = enc
return rb
}
switch t.Kind() {
case reflect.Interface:
for idx, ir := range rb.interfaceEncoders {
if ir.i == t {
rb.interfaceEncoders[idx].ve = enc
return rb
}
}
rb.interfaceEncoders = append(rb.interfaceEncoders, interfaceValueEncoder{i: t, ve: enc})
default:
rb.typeEncoders[t] = enc
}
return rb
}
// RegisterDecoder registers the provided type and decoder pair.
//
// Deprecated: Use RegisterTypeDecoder or RegisterHookDecoder instead.
func (rb *RegistryBuilder) RegisterDecoder(t reflect.Type, dec ValueDecoder) *RegistryBuilder {
if t == nil {
rb.typeDecoders[nil] = dec
return rb
}
if t == tEmpty {
rb.typeDecoders[t] = dec
return rb
}
switch t.Kind() {
case reflect.Interface:
for idx, ir := range rb.interfaceDecoders {
if ir.i == t {
rb.interfaceDecoders[idx].vd = dec
return rb
}
}
rb.interfaceDecoders = append(rb.interfaceDecoders, interfaceValueDecoder{i: t, vd: dec})
default:
rb.typeDecoders[t] = dec
}
return rb
}
// RegisterDefaultEncoder will registr the provided ValueEncoder to the provided
// kind.
func (rb *RegistryBuilder) RegisterDefaultEncoder(kind reflect.Kind, enc ValueEncoder) *RegistryBuilder {
rb.kindEncoders[kind] = enc
return rb
}
// RegisterDefaultDecoder will register the provided ValueDecoder to the
// provided kind.
func (rb *RegistryBuilder) RegisterDefaultDecoder(kind reflect.Kind, dec ValueDecoder) *RegistryBuilder {
rb.kindDecoders[kind] = dec
return rb
}
// RegisterTypeMapEntry will register the provided type to the BSON type. The primary usage for this
// mapping is decoding situations where an empty interface is used and a default type needs to be
// created and decoded into.
//
// By default, BSON documents will decode into interface{} values as bson.D. To change the default type for BSON
// documents, a type map entry for bsontype.EmbeddedDocument should be registered. For example, to force BSON documents
// to decode to bson.Raw, use the following code:
//
// rb.RegisterTypeMapEntry(bsontype.EmbeddedDocument, reflect.TypeOf(bson.Raw{}))
func (rb *RegistryBuilder) RegisterTypeMapEntry(bt bsontype.Type, rt reflect.Type) *RegistryBuilder {
rb.typeMap[bt] = rt
return rb
}
// Build creates a Registry from the current state of this RegistryBuilder.
func (rb *RegistryBuilder) Build() *Registry {
registry := new(Registry)
registry.typeEncoders = make(map[reflect.Type]ValueEncoder)
for t, enc := range rb.typeEncoders {
registry.typeEncoders[t] = enc
}
registry.typeDecoders = make(map[reflect.Type]ValueDecoder)
for t, dec := range rb.typeDecoders {
registry.typeDecoders[t] = dec
}
registry.interfaceEncoders = make([]interfaceValueEncoder, len(rb.interfaceEncoders))
copy(registry.interfaceEncoders, rb.interfaceEncoders)
registry.interfaceDecoders = make([]interfaceValueDecoder, len(rb.interfaceDecoders))
copy(registry.interfaceDecoders, rb.interfaceDecoders)
registry.kindEncoders = make(map[reflect.Kind]ValueEncoder)
for kind, enc := range rb.kindEncoders {
registry.kindEncoders[kind] = enc
}
registry.kindDecoders = make(map[reflect.Kind]ValueDecoder)
for kind, dec := range rb.kindDecoders {
registry.kindDecoders[kind] = dec
}
registry.typeMap = make(map[bsontype.Type]reflect.Type)
for bt, rt := range rb.typeMap {
registry.typeMap[bt] = rt
}
return registry
}
// LookupEncoder inspects the registry for an encoder for the given type. The lookup precedence works as follows:
//
// 1. An encoder registered for the exact type. If the given type represents an interface, an encoder registered using
// RegisterTypeEncoder for the interface will be selected.
//
// 2. An encoder registered using RegisterHookEncoder for an interface implemented by the type or by a pointer to the
// type.
//
// 3. An encoder registered for the reflect.Kind of the value.
//
// If no encoder is found, an error of type ErrNoEncoder is returned.
func (r *Registry) LookupEncoder(t reflect.Type) (ValueEncoder, error) {
encodererr := ErrNoEncoder{Type: t}
r.mu.RLock()
enc, found := r.lookupTypeEncoder(t)
r.mu.RUnlock()
if found {
if enc == nil {
return nil, ErrNoEncoder{Type: t}
}
return enc, nil
}
enc, found = r.lookupInterfaceEncoder(t, true)
if found {
r.mu.Lock()
r.typeEncoders[t] = enc
r.mu.Unlock()
return enc, nil
}
if t == nil {
r.mu.Lock()
r.typeEncoders[t] = nil
r.mu.Unlock()
return nil, encodererr
}
enc, found = r.kindEncoders[t.Kind()]
if !found {
r.mu.Lock()
r.typeEncoders[t] = nil
r.mu.Unlock()
return nil, encodererr
}
r.mu.Lock()
r.typeEncoders[t] = enc
r.mu.Unlock()
return enc, nil
}
func (r *Registry) lookupTypeEncoder(t reflect.Type) (ValueEncoder, bool) {
enc, found := r.typeEncoders[t]
return enc, found
}
func (r *Registry) lookupInterfaceEncoder(t reflect.Type, allowAddr bool) (ValueEncoder, bool) {
if t == nil {
return nil, false
}
for _, ienc := range r.interfaceEncoders {
if t.Implements(ienc.i) {
return ienc.ve, true
}
if allowAddr && t.Kind() != reflect.Ptr && reflect.PtrTo(t).Implements(ienc.i) {
// if *t implements an interface, this will catch if t implements an interface further ahead
// in interfaceEncoders
defaultEnc, found := r.lookupInterfaceEncoder(t, false)
if !found {
defaultEnc = r.kindEncoders[t.Kind()]
}
return newCondAddrEncoder(ienc.ve, defaultEnc), true
}
}
return nil, false
}
// LookupDecoder inspects the registry for an decoder for the given type. The lookup precedence works as follows:
//
// 1. A decoder registered for the exact type. If the given type represents an interface, a decoder registered using
// RegisterTypeDecoder for the interface will be selected.
//
// 2. A decoder registered using RegisterHookDecoder for an interface implemented by the type or by a pointer to the
// type.
//
// 3. A decoder registered for the reflect.Kind of the value.
//
// If no decoder is found, an error of type ErrNoDecoder is returned.
func (r *Registry) LookupDecoder(t reflect.Type) (ValueDecoder, error) {
if t == nil {
return nil, ErrNilType
}
decodererr := ErrNoDecoder{Type: t}
r.mu.RLock()
dec, found := r.lookupTypeDecoder(t)
r.mu.RUnlock()
if found {
if dec == nil {
return nil, ErrNoDecoder{Type: t}
}
return dec, nil
}
dec, found = r.lookupInterfaceDecoder(t, true)
if found {
r.mu.Lock()
r.typeDecoders[t] = dec
r.mu.Unlock()
return dec, nil
}
dec, found = r.kindDecoders[t.Kind()]
if !found {
r.mu.Lock()
r.typeDecoders[t] = nil
r.mu.Unlock()
return nil, decodererr
}
r.mu.Lock()
r.typeDecoders[t] = dec
r.mu.Unlock()
return dec, nil
}
func (r *Registry) lookupTypeDecoder(t reflect.Type) (ValueDecoder, bool) {
dec, found := r.typeDecoders[t]
return dec, found
}
func (r *Registry) lookupInterfaceDecoder(t reflect.Type, allowAddr bool) (ValueDecoder, bool) {
for _, idec := range r.interfaceDecoders {
if t.Implements(idec.i) {
return idec.vd, true
}
if allowAddr && t.Kind() != reflect.Ptr && reflect.PtrTo(t).Implements(idec.i) {
// if *t implements an interface, this will catch if t implements an interface further ahead
// in interfaceDecoders
defaultDec, found := r.lookupInterfaceDecoder(t, false)
if !found {
defaultDec = r.kindDecoders[t.Kind()]
}
return newCondAddrDecoder(idec.vd, defaultDec), true
}
}
return nil, false
}
// LookupTypeMapEntry inspects the registry's type map for a Go type for the corresponding BSON
// type. If no type is found, ErrNoTypeMapEntry is returned.
func (r *Registry) LookupTypeMapEntry(bt bsontype.Type) (reflect.Type, error) {
t, ok := r.typeMap[bt]
if !ok || t == nil {
return nil, ErrNoTypeMapEntry{Type: bt}
}
return t, nil
}
type interfaceValueEncoder struct {
i reflect.Type
ve ValueEncoder
}
type interfaceValueDecoder struct {
i reflect.Type
vd ValueDecoder
}

View File

@ -0,0 +1,125 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec_test
import (
"fmt"
"math"
"reflect"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
func ExampleRegistry_customEncoder() {
// Write a custom encoder for an integer type that is multiplied by -1 when
// encoding.
// To register the default encoders and decoders in addition to this custom
// one, use bson.NewRegistryBuilder instead.
rb := bsoncodec.NewRegistryBuilder()
type negatedInt int
niType := reflect.TypeOf(negatedInt(0))
encoder := func(
ec bsoncodec.EncodeContext,
vw bsonrw.ValueWriter,
val reflect.Value,
) error {
// All encoder implementations should check that val is valid and is of
// the correct type before proceeding.
if !val.IsValid() || val.Type() != niType {
return bsoncodec.ValueEncoderError{
Name: "negatedIntEncodeValue",
Types: []reflect.Type{niType},
Received: val,
}
}
// Negate val and encode as a BSON int32 if it can fit in 32 bits and a
// BSON int64 otherwise.
negatedVal := val.Int() * -1
if math.MinInt32 <= negatedVal && negatedVal <= math.MaxInt32 {
return vw.WriteInt32(int32(negatedVal))
}
return vw.WriteInt64(negatedVal)
}
rb.RegisterTypeEncoder(niType, bsoncodec.ValueEncoderFunc(encoder))
}
func ExampleRegistry_customDecoder() {
// Write a custom decoder for a boolean type that can be stored in the
// database as a BSON boolean, int32, int64, double, or null. BSON int32,
// int64, and double values are considered "true" in this decoder if they
// are non-zero. BSON null values are always considered false.
// To register the default encoders and decoders in addition to this custom
// one, use bson.NewRegistryBuilder instead.
rb := bsoncodec.NewRegistryBuilder()
type lenientBool bool
decoder := func(
dc bsoncodec.DecodeContext,
vr bsonrw.ValueReader,
val reflect.Value,
) error {
// All decoder implementations should check that val is valid, settable,
// and is of the correct kind before proceeding.
if !val.IsValid() || !val.CanSet() || val.Kind() != reflect.Bool {
return bsoncodec.ValueDecoderError{
Name: "lenientBoolDecodeValue",
Kinds: []reflect.Kind{reflect.Bool},
Received: val,
}
}
var result bool
switch vr.Type() {
case bsontype.Boolean:
b, err := vr.ReadBoolean()
if err != nil {
return err
}
result = b
case bsontype.Int32:
i32, err := vr.ReadInt32()
if err != nil {
return err
}
result = i32 != 0
case bsontype.Int64:
i64, err := vr.ReadInt64()
if err != nil {
return err
}
result = i64 != 0
case bsontype.Double:
f64, err := vr.ReadDouble()
if err != nil {
return err
}
result = f64 != 0
case bsontype.Null:
if err := vr.ReadNull(); err != nil {
return err
}
result = false
default:
return fmt.Errorf(
"received invalid BSON type to decode into lenientBool: %s",
vr.Type())
}
val.SetBool(result)
return nil
}
lbType := reflect.TypeOf(lenientBool(true))
rb.RegisterTypeDecoder(lbType, bsoncodec.ValueDecoderFunc(decoder))
}

View File

@ -0,0 +1,452 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)
func TestRegistry(t *testing.T) {
t.Run("Register", func(t *testing.T) {
fc1, fc2, fc3, fc4 := new(fakeCodec), new(fakeCodec), new(fakeCodec), new(fakeCodec)
t.Run("interface", func(t *testing.T) {
var t1f *testInterface1
var t2f *testInterface2
var t4f *testInterface4
ips := []interfaceValueEncoder{
{i: reflect.TypeOf(t1f).Elem(), ve: fc1},
{i: reflect.TypeOf(t2f).Elem(), ve: fc2},
{i: reflect.TypeOf(t1f).Elem(), ve: fc3},
{i: reflect.TypeOf(t4f).Elem(), ve: fc4},
}
want := []interfaceValueEncoder{
{i: reflect.TypeOf(t1f).Elem(), ve: fc3},
{i: reflect.TypeOf(t2f).Elem(), ve: fc2},
{i: reflect.TypeOf(t4f).Elem(), ve: fc4},
}
rb := NewRegistryBuilder()
for _, ip := range ips {
rb.RegisterHookEncoder(ip.i, ip.ve)
}
got := rb.interfaceEncoders
if !cmp.Equal(got, want, cmp.AllowUnexported(interfaceValueEncoder{}, fakeCodec{}), cmp.Comparer(typeComparer)) {
t.Errorf("The registered interfaces are not correct. got %v; want %v", got, want)
}
})
t.Run("type", func(t *testing.T) {
ft1, ft2, ft4 := fakeType1{}, fakeType2{}, fakeType4{}
rb := NewRegistryBuilder().
RegisterTypeEncoder(reflect.TypeOf(ft1), fc1).
RegisterTypeEncoder(reflect.TypeOf(ft2), fc2).
RegisterTypeEncoder(reflect.TypeOf(ft1), fc3).
RegisterTypeEncoder(reflect.TypeOf(ft4), fc4)
want := []struct {
t reflect.Type
c ValueEncoder
}{
{reflect.TypeOf(ft1), fc3},
{reflect.TypeOf(ft2), fc2},
{reflect.TypeOf(ft4), fc4},
}
got := rb.typeEncoders
for _, s := range want {
wantT, wantC := s.t, s.c
gotC, exists := got[wantT]
if !exists {
t.Errorf("Did not find type in the type registry: %v", wantT)
}
if !cmp.Equal(gotC, wantC, cmp.AllowUnexported(fakeCodec{})) {
t.Errorf("Codecs did not match. got %#v; want %#v", gotC, wantC)
}
}
})
t.Run("kind", func(t *testing.T) {
k1, k2, k4 := reflect.Struct, reflect.Slice, reflect.Map
rb := NewRegistryBuilder().
RegisterDefaultEncoder(k1, fc1).
RegisterDefaultEncoder(k2, fc2).
RegisterDefaultEncoder(k1, fc3).
RegisterDefaultEncoder(k4, fc4)
want := []struct {
k reflect.Kind
c ValueEncoder
}{
{k1, fc3},
{k2, fc2},
{k4, fc4},
}
got := rb.kindEncoders
for _, s := range want {
wantK, wantC := s.k, s.c
gotC, exists := got[wantK]
if !exists {
t.Errorf("Did not find kind in the kind registry: %v", wantK)
}
if !cmp.Equal(gotC, wantC, cmp.AllowUnexported(fakeCodec{})) {
t.Errorf("Codecs did not match. got %#v; want %#v", gotC, wantC)
}
}
})
t.Run("RegisterDefault", func(t *testing.T) {
t.Run("MapCodec", func(t *testing.T) {
codec := fakeCodec{num: 1}
codec2 := fakeCodec{num: 2}
rb := NewRegistryBuilder()
rb.RegisterDefaultEncoder(reflect.Map, codec)
if rb.kindEncoders[reflect.Map] != codec {
t.Errorf("Did not properly set the map codec. got %v; want %v", rb.kindEncoders[reflect.Map], codec)
}
rb.RegisterDefaultEncoder(reflect.Map, codec2)
if rb.kindEncoders[reflect.Map] != codec2 {
t.Errorf("Did not properly set the map codec. got %v; want %v", rb.kindEncoders[reflect.Map], codec2)
}
})
t.Run("StructCodec", func(t *testing.T) {
codec := fakeCodec{num: 1}
codec2 := fakeCodec{num: 2}
rb := NewRegistryBuilder()
rb.RegisterDefaultEncoder(reflect.Struct, codec)
if rb.kindEncoders[reflect.Struct] != codec {
t.Errorf("Did not properly set the struct codec. got %v; want %v", rb.kindEncoders[reflect.Struct], codec)
}
rb.RegisterDefaultEncoder(reflect.Struct, codec2)
if rb.kindEncoders[reflect.Struct] != codec2 {
t.Errorf("Did not properly set the struct codec. got %v; want %v", rb.kindEncoders[reflect.Struct], codec2)
}
})
t.Run("SliceCodec", func(t *testing.T) {
codec := fakeCodec{num: 1}
codec2 := fakeCodec{num: 2}
rb := NewRegistryBuilder()
rb.RegisterDefaultEncoder(reflect.Slice, codec)
if rb.kindEncoders[reflect.Slice] != codec {
t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Slice], codec)
}
rb.RegisterDefaultEncoder(reflect.Slice, codec2)
if rb.kindEncoders[reflect.Slice] != codec2 {
t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Slice], codec2)
}
})
t.Run("ArrayCodec", func(t *testing.T) {
codec := fakeCodec{num: 1}
codec2 := fakeCodec{num: 2}
rb := NewRegistryBuilder()
rb.RegisterDefaultEncoder(reflect.Array, codec)
if rb.kindEncoders[reflect.Array] != codec {
t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Array], codec)
}
rb.RegisterDefaultEncoder(reflect.Array, codec2)
if rb.kindEncoders[reflect.Array] != codec2 {
t.Errorf("Did not properly set the slice codec. got %v; want %v", rb.kindEncoders[reflect.Array], codec2)
}
})
})
t.Run("Lookup", func(t *testing.T) {
type Codec interface {
ValueEncoder
ValueDecoder
}
var arrinstance [12]int
arr := reflect.TypeOf(arrinstance)
slc := reflect.TypeOf(make([]int, 12))
m := reflect.TypeOf(make(map[string]int))
strct := reflect.TypeOf(struct{ Foo string }{})
ft1 := reflect.PtrTo(reflect.TypeOf(fakeType1{}))
ft2 := reflect.TypeOf(fakeType2{})
ft3 := reflect.TypeOf(fakeType5(func(string, string) string { return "fakeType5" }))
ti1 := reflect.TypeOf((*testInterface1)(nil)).Elem()
ti2 := reflect.TypeOf((*testInterface2)(nil)).Elem()
ti1Impl := reflect.TypeOf(testInterface1Impl{})
ti2Impl := reflect.TypeOf(testInterface2Impl{})
ti3 := reflect.TypeOf((*testInterface3)(nil)).Elem()
ti3Impl := reflect.TypeOf(testInterface3Impl{})
ti3ImplPtr := reflect.TypeOf((*testInterface3Impl)(nil))
fc1, fc2 := fakeCodec{num: 1}, fakeCodec{num: 2}
fsc, fslcc, fmc := new(fakeStructCodec), new(fakeSliceCodec), new(fakeMapCodec)
pc := NewPointerCodec()
reg := NewRegistryBuilder().
RegisterTypeEncoder(ft1, fc1).
RegisterTypeEncoder(ft2, fc2).
RegisterTypeEncoder(ti1, fc1).
RegisterDefaultEncoder(reflect.Struct, fsc).
RegisterDefaultEncoder(reflect.Slice, fslcc).
RegisterDefaultEncoder(reflect.Array, fslcc).
RegisterDefaultEncoder(reflect.Map, fmc).
RegisterDefaultEncoder(reflect.Ptr, pc).
RegisterTypeDecoder(ft1, fc1).
RegisterTypeDecoder(ft2, fc2).
RegisterTypeDecoder(ti1, fc1). // values whose exact type is testInterface1 will use fc1 encoder
RegisterDefaultDecoder(reflect.Struct, fsc).
RegisterDefaultDecoder(reflect.Slice, fslcc).
RegisterDefaultDecoder(reflect.Array, fslcc).
RegisterDefaultDecoder(reflect.Map, fmc).
RegisterDefaultDecoder(reflect.Ptr, pc).
RegisterHookEncoder(ti2, fc2).
RegisterHookDecoder(ti2, fc2).
RegisterHookEncoder(ti3, fc3).
RegisterHookDecoder(ti3, fc3).
Build()
testCases := []struct {
name string
t reflect.Type
wantcodec Codec
wanterr error
testcache bool
}{
{
"type registry (pointer)",
ft1,
fc1,
nil,
false,
},
{
"type registry (non-pointer)",
ft2,
fc2,
nil,
false,
},
{
// lookup an interface type and expect that the registered encoder is returned
"interface with type encoder",
ti1,
fc1,
nil,
true,
},
{
// lookup a type that implements an interface and expect that the default struct codec is returned
"interface implementation with type encoder",
ti1Impl,
fsc,
nil,
false,
},
{
// lookup an interface type and expect that the registered hook is returned
"interface with hook",
ti2,
fc2,
nil,
false,
},
{
// lookup a type that implements an interface and expect that the registered hook is returned
"interface implementation with hook",
ti2Impl,
fc2,
nil,
false,
},
{
// lookup a pointer to a type where the pointer implements an interface and expect that the
// registered hook is returned
"interface pointer to implementation with hook (pointer)",
ti3ImplPtr,
fc3,
nil,
false,
},
{
"default struct codec (pointer)",
reflect.PtrTo(strct),
pc,
nil,
false,
},
{
"default struct codec (non-pointer)",
strct,
fsc,
nil,
false,
},
{
"default array codec",
arr,
fslcc,
nil,
false,
},
{
"default slice codec",
slc,
fslcc,
nil,
false,
},
{
"default map",
m,
fmc,
nil,
false,
},
{
"map non-string key",
reflect.TypeOf(map[int]int{}),
fmc,
nil,
false,
},
{
"No Codec Registered",
ft3,
nil,
ErrNoEncoder{Type: ft3},
false,
},
}
allowunexported := cmp.AllowUnexported(fakeCodec{}, fakeStructCodec{}, fakeSliceCodec{}, fakeMapCodec{})
comparepc := func(pc1, pc2 *PointerCodec) bool { return true }
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run("Encoder", func(t *testing.T) {
gotcodec, goterr := reg.LookupEncoder(tc.t)
if !cmp.Equal(goterr, tc.wanterr, cmp.Comparer(compareErrors)) {
t.Errorf("Errors did not match. got %v; want %v", goterr, tc.wanterr)
}
if !cmp.Equal(gotcodec, tc.wantcodec, allowunexported, cmp.Comparer(comparepc)) {
t.Errorf("Codecs did not match. got %v; want %v", gotcodec, tc.wantcodec)
}
})
t.Run("Decoder", func(t *testing.T) {
var wanterr error
if ene, ok := tc.wanterr.(ErrNoEncoder); ok {
wanterr = ErrNoDecoder(ene)
} else {
wanterr = tc.wanterr
}
gotcodec, goterr := reg.LookupDecoder(tc.t)
if !cmp.Equal(goterr, wanterr, cmp.Comparer(compareErrors)) {
t.Errorf("Errors did not match. got %v; want %v", goterr, wanterr)
}
if !cmp.Equal(gotcodec, tc.wantcodec, allowunexported, cmp.Comparer(comparepc)) {
t.Errorf("Codecs did not match. got %v; want %v", gotcodec, tc.wantcodec)
t.Errorf("Codecs did not match. got %T; want %T", gotcodec, tc.wantcodec)
}
})
})
}
// lookup a type whose pointer implements an interface and expect that the registered hook is
// returned
t.Run("interface implementation with hook (pointer)", func(t *testing.T) {
t.Run("Encoder", func(t *testing.T) {
gotEnc, err := reg.LookupEncoder(ti3Impl)
assert.Nil(t, err, "LookupEncoder error: %v", err)
cae, ok := gotEnc.(*condAddrEncoder)
assert.True(t, ok, "Expected CondAddrEncoder, got %T", gotEnc)
if !cmp.Equal(cae.canAddrEnc, fc3, allowunexported, cmp.Comparer(comparepc)) {
t.Errorf("expected canAddrEnc %v, got %v", cae.canAddrEnc, fc3)
}
if !cmp.Equal(cae.elseEnc, fsc, allowunexported, cmp.Comparer(comparepc)) {
t.Errorf("expected elseEnc %v, got %v", cae.elseEnc, fsc)
}
})
t.Run("Decoder", func(t *testing.T) {
gotDec, err := reg.LookupDecoder(ti3Impl)
assert.Nil(t, err, "LookupDecoder error: %v", err)
cad, ok := gotDec.(*condAddrDecoder)
assert.True(t, ok, "Expected CondAddrDecoder, got %T", gotDec)
if !cmp.Equal(cad.canAddrDec, fc3, allowunexported, cmp.Comparer(comparepc)) {
t.Errorf("expected canAddrDec %v, got %v", cad.canAddrDec, fc3)
}
if !cmp.Equal(cad.elseDec, fsc, allowunexported, cmp.Comparer(comparepc)) {
t.Errorf("expected elseDec %v, got %v", cad.elseDec, fsc)
}
})
})
})
})
t.Run("Type Map", func(t *testing.T) {
reg := NewRegistryBuilder().
RegisterTypeMapEntry(bsontype.String, reflect.TypeOf("")).
RegisterTypeMapEntry(bsontype.Int32, reflect.TypeOf(int(0))).
Build()
var got, want reflect.Type
want = reflect.TypeOf("")
got, err := reg.LookupTypeMapEntry(bsontype.String)
noerr(t, err)
if got != want {
t.Errorf("Did not get expected type. got %v; want %v", got, want)
}
want = reflect.TypeOf(int(0))
got, err = reg.LookupTypeMapEntry(bsontype.Int32)
noerr(t, err)
if got != want {
t.Errorf("Did not get expected type. got %v; want %v", got, want)
}
want = nil
wanterr := ErrNoTypeMapEntry{Type: bsontype.ObjectID}
got, err = reg.LookupTypeMapEntry(bsontype.ObjectID)
if err != wanterr {
t.Errorf("Did not get expected error. got %v; want %v", err, wanterr)
}
if got != want {
t.Errorf("Did not get expected type. got %v; want %v", got, want)
}
})
}
type fakeType1 struct{}
type fakeType2 struct{}
type fakeType4 struct{}
type fakeType5 func(string, string) string
type fakeStructCodec struct{ fakeCodec }
type fakeSliceCodec struct{ fakeCodec }
type fakeMapCodec struct{ fakeCodec }
type fakeCodec struct{ num int }
func (fc fakeCodec) EncodeValue(EncodeContext, bsonrw.ValueWriter, reflect.Value) error {
return nil
}
func (fc fakeCodec) DecodeValue(DecodeContext, bsonrw.ValueReader, reflect.Value) error {
return nil
}
type testInterface1 interface{ test1() }
type testInterface2 interface{ test2() }
type testInterface3 interface{ test3() }
type testInterface4 interface{ test4() }
type testInterface1Impl struct{}
var _ testInterface1 = testInterface1Impl{}
func (testInterface1Impl) test1() {}
type testInterface2Impl struct{}
var _ testInterface2 = testInterface2Impl{}
func (testInterface2Impl) test2() {}
type testInterface3Impl struct{}
var _ testInterface3 = (*testInterface3Impl)(nil)
func (*testInterface3Impl) test3() {}
func typeComparer(i1, i2 reflect.Type) bool { return i1 == i2 }

View File

@ -0,0 +1,199 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"fmt"
"reflect"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var defaultSliceCodec = NewSliceCodec()
// SliceCodec is the Codec used for slice values.
type SliceCodec struct {
EncodeNilAsEmpty bool
}
var _ ValueCodec = &MapCodec{}
// NewSliceCodec returns a MapCodec with options opts.
func NewSliceCodec(opts ...*bsonoptions.SliceCodecOptions) *SliceCodec {
sliceOpt := bsonoptions.MergeSliceCodecOptions(opts...)
codec := SliceCodec{}
if sliceOpt.EncodeNilAsEmpty != nil {
codec.EncodeNilAsEmpty = *sliceOpt.EncodeNilAsEmpty
}
return &codec
}
// EncodeValue is the ValueEncoder for slice types.
func (sc SliceCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Kind() != reflect.Slice {
return ValueEncoderError{Name: "SliceEncodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: val}
}
if val.IsNil() && !sc.EncodeNilAsEmpty {
return vw.WriteNull()
}
// If we have a []byte we want to treat it as a binary instead of as an array.
if val.Type().Elem() == tByte {
var byteSlice []byte
for idx := 0; idx < val.Len(); idx++ {
byteSlice = append(byteSlice, val.Index(idx).Interface().(byte))
}
return vw.WriteBinary(byteSlice)
}
// If we have a []primitive.E we want to treat it as a document instead of as an array.
if val.Type().ConvertibleTo(tD) {
d := val.Convert(tD).Interface().(primitive.D)
dw, err := vw.WriteDocument()
if err != nil {
return err
}
for _, e := range d {
err = encodeElement(ec, dw, e)
if err != nil {
return err
}
}
return dw.WriteDocumentEnd()
}
aw, err := vw.WriteArray()
if err != nil {
return err
}
elemType := val.Type().Elem()
encoder, err := ec.LookupEncoder(elemType)
if err != nil && elemType.Kind() != reflect.Interface {
return err
}
for idx := 0; idx < val.Len(); idx++ {
currEncoder, currVal, lookupErr := defaultValueEncoders.lookupElementEncoder(ec, encoder, val.Index(idx))
if lookupErr != nil && lookupErr != errInvalidValue {
return lookupErr
}
vw, err := aw.WriteArrayElement()
if err != nil {
return err
}
if lookupErr == errInvalidValue {
err = vw.WriteNull()
if err != nil {
return err
}
continue
}
err = currEncoder.EncodeValue(ec, vw, currVal)
if err != nil {
return err
}
}
return aw.WriteArrayEnd()
}
// DecodeValue is the ValueDecoder for slice types.
func (sc *SliceCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Kind() != reflect.Slice {
return ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: val}
}
switch vrType := vr.Type(); vrType {
case bsontype.Array:
case bsontype.Null:
val.Set(reflect.Zero(val.Type()))
return vr.ReadNull()
case bsontype.Undefined:
val.Set(reflect.Zero(val.Type()))
return vr.ReadUndefined()
case bsontype.Type(0), bsontype.EmbeddedDocument:
if val.Type().Elem() != tE {
return fmt.Errorf("cannot decode document into %s", val.Type())
}
case bsontype.Binary:
if val.Type().Elem() != tByte {
return fmt.Errorf("SliceDecodeValue can only decode a binary into a byte array, got %v", vrType)
}
data, subtype, err := vr.ReadBinary()
if err != nil {
return err
}
if subtype != bsontype.BinaryGeneric && subtype != bsontype.BinaryBinaryOld {
return fmt.Errorf("SliceDecodeValue can only be used to decode subtype 0x00 or 0x02 for %s, got %v", bsontype.Binary, subtype)
}
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, len(data)))
}
val.SetLen(0)
for _, elem := range data {
val.Set(reflect.Append(val, reflect.ValueOf(elem)))
}
return nil
case bsontype.String:
if sliceType := val.Type().Elem(); sliceType != tByte {
return fmt.Errorf("SliceDecodeValue can only decode a string into a byte array, got %v", sliceType)
}
str, err := vr.ReadString()
if err != nil {
return err
}
byteStr := []byte(str)
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, len(byteStr)))
}
val.SetLen(0)
for _, elem := range byteStr {
val.Set(reflect.Append(val, reflect.ValueOf(elem)))
}
return nil
default:
return fmt.Errorf("cannot decode %v into a slice", vrType)
}
var elemsFunc func(DecodeContext, bsonrw.ValueReader, reflect.Value) ([]reflect.Value, error)
switch val.Type().Elem() {
case tE:
dc.Ancestor = val.Type()
elemsFunc = defaultValueDecoders.decodeD
default:
elemsFunc = defaultValueDecoders.decodeDefault
}
elems, err := elemsFunc(dc, vr, val)
if err != nil {
return err
}
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, len(elems)))
}
val.SetLen(0)
val.Set(reflect.Append(val, elems...))
return nil
}

View File

@ -0,0 +1,119 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"fmt"
"reflect"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
// StringCodec is the Codec used for struct values.
type StringCodec struct {
DecodeObjectIDAsHex bool
}
var (
defaultStringCodec = NewStringCodec()
_ ValueCodec = defaultStringCodec
_ typeDecoder = defaultStringCodec
)
// NewStringCodec returns a StringCodec with options opts.
func NewStringCodec(opts ...*bsonoptions.StringCodecOptions) *StringCodec {
stringOpt := bsonoptions.MergeStringCodecOptions(opts...)
return &StringCodec{*stringOpt.DecodeObjectIDAsHex}
}
// EncodeValue is the ValueEncoder for string types.
func (sc *StringCodec) EncodeValue(ectx EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if val.Kind() != reflect.String {
return ValueEncoderError{
Name: "StringEncodeValue",
Kinds: []reflect.Kind{reflect.String},
Received: val,
}
}
return vw.WriteString(val.String())
}
func (sc *StringCodec) decodeType(dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type) (reflect.Value, error) {
if t.Kind() != reflect.String {
return emptyValue, ValueDecoderError{
Name: "StringDecodeValue",
Kinds: []reflect.Kind{reflect.String},
Received: reflect.Zero(t),
}
}
var str string
var err error
switch vr.Type() {
case bsontype.String:
str, err = vr.ReadString()
if err != nil {
return emptyValue, err
}
case bsontype.ObjectID:
oid, err := vr.ReadObjectID()
if err != nil {
return emptyValue, err
}
if sc.DecodeObjectIDAsHex {
str = oid.Hex()
} else {
byteArray := [12]byte(oid)
str = string(byteArray[:])
}
case bsontype.Symbol:
str, err = vr.ReadSymbol()
if err != nil {
return emptyValue, err
}
case bsontype.Binary:
data, subtype, err := vr.ReadBinary()
if err != nil {
return emptyValue, err
}
if subtype != bsontype.BinaryGeneric && subtype != bsontype.BinaryBinaryOld {
return emptyValue, decodeBinaryError{subtype: subtype, typeName: "string"}
}
str = string(data)
case bsontype.Null:
if err = vr.ReadNull(); err != nil {
return emptyValue, err
}
case bsontype.Undefined:
if err = vr.ReadUndefined(); err != nil {
return emptyValue, err
}
default:
return emptyValue, fmt.Errorf("cannot decode %v into a string type", vr.Type())
}
return reflect.ValueOf(str), nil
}
// DecodeValue is the ValueDecoder for string types.
func (sc *StringCodec) DecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Kind() != reflect.String {
return ValueDecoderError{Name: "StringDecodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val}
}
elem, err := sc.decodeType(dctx, vr, val.Type())
if err != nil {
return err
}
val.SetString(elem.String())
return nil
}

View File

@ -0,0 +1,48 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"testing"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)
func TestStringCodec(t *testing.T) {
t.Run("ObjectIDAsHex", func(t *testing.T) {
oid := primitive.NewObjectID()
byteArray := [12]byte(oid)
reader := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID, Return: oid}
testCases := []struct {
name string
opts *bsonoptions.StringCodecOptions
hex bool
result string
}{
{"default", bsonoptions.StringCodec(), true, oid.Hex()},
{"true", bsonoptions.StringCodec().SetDecodeObjectIDAsHex(true), true, oid.Hex()},
{"false", bsonoptions.StringCodec().SetDecodeObjectIDAsHex(false), false, string(byteArray[:])},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
stringCodec := NewStringCodec(tc.opts)
actual := reflect.New(reflect.TypeOf("")).Elem()
err := stringCodec.DecodeValue(DecodeContext{}, reader, actual)
assert.Nil(t, err, "StringCodec.DecodeValue error: %v", err)
actualString := actual.Interface().(string)
assert.Equal(t, tc.result, actualString, "Expected string %v, got %v", tc.result, actualString)
})
}
})
}

View File

@ -0,0 +1,669 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"errors"
"fmt"
"reflect"
"sort"
"strings"
"sync"
"time"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
// DecodeError represents an error that occurs when unmarshalling BSON bytes into a native Go type.
type DecodeError struct {
keys []string
wrapped error
}
// Unwrap returns the underlying error
func (de *DecodeError) Unwrap() error {
return de.wrapped
}
// Error implements the error interface.
func (de *DecodeError) Error() string {
// The keys are stored in reverse order because the de.keys slice is builtup while propagating the error up the
// stack of BSON keys, so we call de.Keys(), which reverses them.
keyPath := strings.Join(de.Keys(), ".")
return fmt.Sprintf("error decoding key %s: %v", keyPath, de.wrapped)
}
// Keys returns the BSON key path that caused an error as a slice of strings. The keys in the slice are in top-down
// order. For example, if the document being unmarshalled was {a: {b: {c: 1}}} and the value for c was supposed to be
// a string, the keys slice will be ["a", "b", "c"].
func (de *DecodeError) Keys() []string {
reversedKeys := make([]string, 0, len(de.keys))
for idx := len(de.keys) - 1; idx >= 0; idx-- {
reversedKeys = append(reversedKeys, de.keys[idx])
}
return reversedKeys
}
// Zeroer allows custom struct types to implement a report of zero
// state. All struct types that don't implement Zeroer or where IsZero
// returns false are considered to be not zero.
type Zeroer interface {
IsZero() bool
}
// StructCodec is the Codec used for struct values.
type StructCodec struct {
cache map[reflect.Type]*structDescription
l sync.RWMutex
parser StructTagParser
DecodeZeroStruct bool
DecodeDeepZeroInline bool
EncodeOmitDefaultStruct bool
AllowUnexportedFields bool
OverwriteDuplicatedInlinedFields bool
}
var _ ValueEncoder = &StructCodec{}
var _ ValueDecoder = &StructCodec{}
// NewStructCodec returns a StructCodec that uses p for struct tag parsing.
func NewStructCodec(p StructTagParser, opts ...*bsonoptions.StructCodecOptions) (*StructCodec, error) {
if p == nil {
return nil, errors.New("a StructTagParser must be provided to NewStructCodec")
}
structOpt := bsonoptions.MergeStructCodecOptions(opts...)
codec := &StructCodec{
cache: make(map[reflect.Type]*structDescription),
parser: p,
}
if structOpt.DecodeZeroStruct != nil {
codec.DecodeZeroStruct = *structOpt.DecodeZeroStruct
}
if structOpt.DecodeDeepZeroInline != nil {
codec.DecodeDeepZeroInline = *structOpt.DecodeDeepZeroInline
}
if structOpt.EncodeOmitDefaultStruct != nil {
codec.EncodeOmitDefaultStruct = *structOpt.EncodeOmitDefaultStruct
}
if structOpt.OverwriteDuplicatedInlinedFields != nil {
codec.OverwriteDuplicatedInlinedFields = *structOpt.OverwriteDuplicatedInlinedFields
}
if structOpt.AllowUnexportedFields != nil {
codec.AllowUnexportedFields = *structOpt.AllowUnexportedFields
}
return codec, nil
}
// EncodeValue handles encoding generic struct types.
func (sc *StructCodec) EncodeValue(r EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Kind() != reflect.Struct {
return ValueEncoderError{Name: "StructCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Struct}, Received: val}
}
sd, err := sc.describeStruct(r.Registry, val.Type())
if err != nil {
return err
}
dw, err := vw.WriteDocument()
if err != nil {
return err
}
var rv reflect.Value
for _, desc := range sd.fl {
if desc.inline == nil {
rv = val.Field(desc.idx)
} else {
rv, err = fieldByIndexErr(val, desc.inline)
if err != nil {
continue
}
}
desc.encoder, rv, err = defaultValueEncoders.lookupElementEncoder(r, desc.encoder, rv)
if err != nil && err != errInvalidValue {
return err
}
if err == errInvalidValue {
if desc.omitEmpty {
continue
}
vw2, err := dw.WriteDocumentElement(desc.name)
if err != nil {
return err
}
err = vw2.WriteNull()
if err != nil {
return err
}
continue
}
if desc.encoder == nil {
return ErrNoEncoder{Type: rv.Type()}
}
encoder := desc.encoder
var isZero bool
rvInterface := rv.Interface()
if cz, ok := encoder.(CodecZeroer); ok {
isZero = cz.IsTypeZero(rvInterface)
} else if rv.Kind() == reflect.Interface {
// sc.isZero will not treat an interface rv as an interface, so we need to check for the zero interface separately.
isZero = rv.IsNil()
} else {
isZero = sc.isZero(rvInterface)
}
if desc.omitEmpty && isZero {
continue
}
vw2, err := dw.WriteDocumentElement(desc.name)
if err != nil {
return err
}
ectx := EncodeContext{Registry: r.Registry, MinSize: desc.minSize}
err = encoder.EncodeValue(ectx, vw2, rv)
if err != nil {
return err
}
}
if sd.inlineMap >= 0 {
rv := val.Field(sd.inlineMap)
collisionFn := func(key string) bool {
_, exists := sd.fm[key]
return exists
}
return defaultMapCodec.mapEncodeValue(r, dw, rv, collisionFn)
}
return dw.WriteDocumentEnd()
}
func newDecodeError(key string, original error) error {
de, ok := original.(*DecodeError)
if !ok {
return &DecodeError{
keys: []string{key},
wrapped: original,
}
}
de.keys = append(de.keys, key)
return de
}
// DecodeValue implements the Codec interface.
// By default, map types in val will not be cleared. If a map has existing key/value pairs, it will be extended with the new ones from vr.
// For slices, the decoder will set the length of the slice to zero and append all elements. The underlying array will not be cleared.
func (sc *StructCodec) DecodeValue(r DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Kind() != reflect.Struct {
return ValueDecoderError{Name: "StructCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Struct}, Received: val}
}
switch vrType := vr.Type(); vrType {
case bsontype.Type(0), bsontype.EmbeddedDocument:
case bsontype.Null:
if err := vr.ReadNull(); err != nil {
return err
}
val.Set(reflect.Zero(val.Type()))
return nil
case bsontype.Undefined:
if err := vr.ReadUndefined(); err != nil {
return err
}
val.Set(reflect.Zero(val.Type()))
return nil
default:
return fmt.Errorf("cannot decode %v into a %s", vrType, val.Type())
}
sd, err := sc.describeStruct(r.Registry, val.Type())
if err != nil {
return err
}
if sc.DecodeZeroStruct {
val.Set(reflect.Zero(val.Type()))
}
if sc.DecodeDeepZeroInline && sd.inline {
val.Set(deepZero(val.Type()))
}
var decoder ValueDecoder
var inlineMap reflect.Value
if sd.inlineMap >= 0 {
inlineMap = val.Field(sd.inlineMap)
decoder, err = r.LookupDecoder(inlineMap.Type().Elem())
if err != nil {
return err
}
}
dr, err := vr.ReadDocument()
if err != nil {
return err
}
for {
name, vr, err := dr.ReadElement()
if err == bsonrw.ErrEOD {
break
}
if err != nil {
return err
}
fd, exists := sd.fm[name]
if !exists {
// if the original name isn't found in the struct description, try again with the name in lowercase
// this could match if a BSON tag isn't specified because by default, describeStruct lowercases all field
// names
fd, exists = sd.fm[strings.ToLower(name)]
}
if !exists {
if sd.inlineMap < 0 {
// The encoding/json package requires a flag to return on error for non-existent fields.
// This functionality seems appropriate for the struct codec.
err = vr.Skip()
if err != nil {
return err
}
continue
}
if inlineMap.IsNil() {
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
}
elem := reflect.New(inlineMap.Type().Elem()).Elem()
r.Ancestor = inlineMap.Type()
err = decoder.DecodeValue(r, vr, elem)
if err != nil {
return err
}
inlineMap.SetMapIndex(reflect.ValueOf(name), elem)
continue
}
var field reflect.Value
if fd.inline == nil {
field = val.Field(fd.idx)
} else {
field, err = getInlineField(val, fd.inline)
if err != nil {
return err
}
}
if !field.CanSet() { // Being settable is a super set of being addressable.
innerErr := fmt.Errorf("field %v is not settable", field)
return newDecodeError(fd.name, innerErr)
}
if field.Kind() == reflect.Ptr && field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
field = field.Addr()
dctx := DecodeContext{
Registry: r.Registry,
Truncate: fd.truncate || r.Truncate,
defaultDocumentType: r.defaultDocumentType,
}
if fd.decoder == nil {
return newDecodeError(fd.name, ErrNoDecoder{Type: field.Elem().Type()})
}
err = fd.decoder.DecodeValue(dctx, vr, field.Elem())
if err != nil {
return newDecodeError(fd.name, err)
}
}
return nil
}
func (sc *StructCodec) isZero(i interface{}) bool {
v := reflect.ValueOf(i)
// check the value validity
if !v.IsValid() {
return true
}
if z, ok := v.Interface().(Zeroer); ok && (v.Kind() != reflect.Ptr || !v.IsNil()) {
return z.IsZero()
}
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
case reflect.Struct:
if sc.EncodeOmitDefaultStruct {
vt := v.Type()
if vt == tTime {
return v.Interface().(time.Time).IsZero()
}
for i := 0; i < v.NumField(); i++ {
if vt.Field(i).PkgPath != "" && !vt.Field(i).Anonymous {
continue // Private field
}
fld := v.Field(i)
if !sc.isZero(fld.Interface()) {
return false
}
}
return true
}
}
return false
}
type structDescription struct {
fm map[string]fieldDescription
fl []fieldDescription
inlineMap int
inline bool
}
type fieldDescription struct {
name string // BSON key name
fieldName string // struct field name
idx int
omitEmpty bool
minSize bool
truncate bool
inline []int
encoder ValueEncoder
decoder ValueDecoder
}
type byIndex []fieldDescription
func (bi byIndex) Len() int { return len(bi) }
func (bi byIndex) Swap(i, j int) { bi[i], bi[j] = bi[j], bi[i] }
func (bi byIndex) Less(i, j int) bool {
// If a field is inlined, its index in the top level struct is stored at inline[0]
iIdx, jIdx := bi[i].idx, bi[j].idx
if len(bi[i].inline) > 0 {
iIdx = bi[i].inline[0]
}
if len(bi[j].inline) > 0 {
jIdx = bi[j].inline[0]
}
if iIdx != jIdx {
return iIdx < jIdx
}
for k, biik := range bi[i].inline {
if k >= len(bi[j].inline) {
return false
}
if biik != bi[j].inline[k] {
return biik < bi[j].inline[k]
}
}
return len(bi[i].inline) < len(bi[j].inline)
}
func (sc *StructCodec) describeStruct(r *Registry, t reflect.Type) (*structDescription, error) {
// We need to analyze the struct, including getting the tags, collecting
// information about inlining, and create a map of the field name to the field.
sc.l.RLock()
ds, exists := sc.cache[t]
sc.l.RUnlock()
if exists {
return ds, nil
}
numFields := t.NumField()
sd := &structDescription{
fm: make(map[string]fieldDescription, numFields),
fl: make([]fieldDescription, 0, numFields),
inlineMap: -1,
}
var fields []fieldDescription
for i := 0; i < numFields; i++ {
sf := t.Field(i)
if sf.PkgPath != "" && (!sc.AllowUnexportedFields || !sf.Anonymous) {
// field is private or unexported fields aren't allowed, ignore
continue
}
sfType := sf.Type
encoder, err := r.LookupEncoder(sfType)
if err != nil {
encoder = nil
}
decoder, err := r.LookupDecoder(sfType)
if err != nil {
decoder = nil
}
description := fieldDescription{
fieldName: sf.Name,
idx: i,
encoder: encoder,
decoder: decoder,
}
stags, err := sc.parser.ParseStructTags(sf)
if err != nil {
return nil, err
}
if stags.Skip {
continue
}
description.name = stags.Name
description.omitEmpty = stags.OmitEmpty
description.minSize = stags.MinSize
description.truncate = stags.Truncate
if stags.Inline {
sd.inline = true
switch sfType.Kind() {
case reflect.Map:
if sd.inlineMap >= 0 {
return nil, errors.New("(struct " + t.String() + ") multiple inline maps")
}
if sfType.Key() != tString {
return nil, errors.New("(struct " + t.String() + ") inline map must have a string keys")
}
sd.inlineMap = description.idx
case reflect.Ptr:
sfType = sfType.Elem()
if sfType.Kind() != reflect.Struct {
return nil, fmt.Errorf("(struct %s) inline fields must be a struct, a struct pointer, or a map", t.String())
}
fallthrough
case reflect.Struct:
inlinesf, err := sc.describeStruct(r, sfType)
if err != nil {
return nil, err
}
for _, fd := range inlinesf.fl {
if fd.inline == nil {
fd.inline = []int{i, fd.idx}
} else {
fd.inline = append([]int{i}, fd.inline...)
}
fields = append(fields, fd)
}
default:
return nil, fmt.Errorf("(struct %s) inline fields must be a struct, a struct pointer, or a map", t.String())
}
continue
}
fields = append(fields, description)
}
// Sort fieldDescriptions by name and use dominance rules to determine which should be added for each name
sort.Slice(fields, func(i, j int) bool {
x := fields
// sort field by name, breaking ties with depth, then
// breaking ties with index sequence.
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].inline) != len(x[j].inline) {
return len(x[i].inline) < len(x[j].inline)
}
return byIndex(x).Less(i, j)
})
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
sd.fl = append(sd.fl, fi)
sd.fm[name] = fi
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if !ok || !sc.OverwriteDuplicatedInlinedFields {
return nil, fmt.Errorf("struct %s has duplicated key %s", t.String(), name)
}
sd.fl = append(sd.fl, dominant)
sd.fm[name] = dominant
}
sort.Sort(byIndex(sd.fl))
sc.l.Lock()
sc.cache[t] = sd
sc.l.Unlock()
return sd, nil
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's inlining rules. If there are multiple top-level
// fields, the boolean will be false: This condition is an error in Go
// and we skip all the fields.
func dominantField(fields []fieldDescription) (fieldDescription, bool) {
// The fields are sorted in increasing index-length order, then by presence of tag.
// That means that the first field is the dominant one. We need only check
// for error cases: two fields at top level.
if len(fields) > 1 &&
len(fields[0].inline) == len(fields[1].inline) {
return fieldDescription{}, false
}
return fields[0], true
}
func fieldByIndexErr(v reflect.Value, index []int) (result reflect.Value, err error) {
defer func() {
if recovered := recover(); recovered != nil {
switch r := recovered.(type) {
case string:
err = fmt.Errorf("%s", r)
case error:
err = r
}
}
}()
result = v.FieldByIndex(index)
return
}
func getInlineField(val reflect.Value, index []int) (reflect.Value, error) {
field, err := fieldByIndexErr(val, index)
if err == nil {
return field, nil
}
// if parent of this element doesn't exist, fix its parent
inlineParent := index[:len(index)-1]
var fParent reflect.Value
if fParent, err = fieldByIndexErr(val, inlineParent); err != nil {
fParent, err = getInlineField(val, inlineParent)
if err != nil {
return fParent, err
}
}
fParent.Set(reflect.New(fParent.Type().Elem()))
return fieldByIndexErr(val, index)
}
// DeepZero returns recursive zero object
func deepZero(st reflect.Type) (result reflect.Value) {
result = reflect.Indirect(reflect.New(st))
if result.Kind() == reflect.Struct {
for i := 0; i < result.NumField(); i++ {
if f := result.Field(i); f.Kind() == reflect.Ptr {
if f.CanInterface() {
if ft := reflect.TypeOf(f.Interface()); ft.Elem().Kind() == reflect.Struct {
result.Field(i).Set(recursivePointerTo(deepZero(ft.Elem())))
}
}
}
}
}
return
}
// recursivePointerTo calls reflect.New(v.Type) but recursively for its fields inside
func recursivePointerTo(v reflect.Value) reflect.Value {
v = reflect.Indirect(v)
result := reflect.New(v.Type())
if v.Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
if f := v.Field(i); f.Kind() == reflect.Ptr {
if f.Elem().Kind() == reflect.Struct {
result.Elem().Field(i).Set(recursivePointerTo(f))
}
}
}
}
return result
}

View File

@ -0,0 +1,47 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestZeoerInterfaceUsedByDecoder(t *testing.T) {
enc := &StructCodec{}
// cases that are zero, because they are known types or pointers
var st *nonZeroer
assert.True(t, enc.isZero(st))
assert.True(t, enc.isZero(0))
assert.True(t, enc.isZero(false))
// cases that shouldn't be zero
st = &nonZeroer{value: false}
assert.False(t, enc.isZero(struct{ val bool }{val: true}))
assert.False(t, enc.isZero(struct{ val bool }{val: false}))
assert.False(t, enc.isZero(st))
st.value = true
assert.False(t, enc.isZero(st))
// a test to see if the interface impacts the outcome
z := zeroTest{}
assert.False(t, enc.isZero(z))
z.reportZero = true
assert.True(t, enc.isZero(z))
// *time.Time with nil should be zero
var tp *time.Time
assert.True(t, enc.isZero(tp))
// actually all zeroer if nil should also be zero
var zp *zeroTest
assert.True(t, enc.isZero(zp))
}

View File

@ -0,0 +1,139 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"strings"
)
// StructTagParser returns the struct tags for a given struct field.
type StructTagParser interface {
ParseStructTags(reflect.StructField) (StructTags, error)
}
// StructTagParserFunc is an adapter that allows a generic function to be used
// as a StructTagParser.
type StructTagParserFunc func(reflect.StructField) (StructTags, error)
// ParseStructTags implements the StructTagParser interface.
func (stpf StructTagParserFunc) ParseStructTags(sf reflect.StructField) (StructTags, error) {
return stpf(sf)
}
// StructTags represents the struct tag fields that the StructCodec uses during
// the encoding and decoding process.
//
// In the case of a struct, the lowercased field name is used as the key for each exported
// field but this behavior may be changed using a struct tag. The tag may also contain flags to
// adjust the marshalling behavior for the field.
//
// The properties are defined below:
//
// OmitEmpty Only include the field if it's not set to the zero value for the type or to
// empty slices or maps.
//
// MinSize Marshal an integer of a type larger than 32 bits value as an int32, if that's
// feasible while preserving the numeric value.
//
// Truncate When unmarshaling a BSON double, it is permitted to lose precision to fit within
// a float32.
//
// Inline Inline the field, which must be a struct or a map, causing all of its fields
// or keys to be processed as if they were part of the outer struct. For maps,
// keys must not conflict with the bson keys of other struct fields.
//
// Skip This struct field should be skipped. This is usually denoted by parsing a "-"
// for the name.
//
// TODO(skriptble): Add tags for undefined as nil and for null as nil.
type StructTags struct {
Name string
OmitEmpty bool
MinSize bool
Truncate bool
Inline bool
Skip bool
}
// DefaultStructTagParser is the StructTagParser used by the StructCodec by default.
// It will handle the bson struct tag. See the documentation for StructTags to see
// what each of the returned fields means.
//
// If there is no name in the struct tag fields, the struct field name is lowercased.
// The tag formats accepted are:
//
// "[<key>][,<flag1>[,<flag2>]]"
//
// `(...) bson:"[<key>][,<flag1>[,<flag2>]]" (...)`
//
// An example:
//
// type T struct {
// A bool
// B int "myb"
// C string "myc,omitempty"
// D string `bson:",omitempty" json:"jsonkey"`
// E int64 ",minsize"
// F int64 "myf,omitempty,minsize"
// }
//
// A struct tag either consisting entirely of '-' or with a bson key with a
// value consisting entirely of '-' will return a StructTags with Skip true and
// the remaining fields will be their default values.
var DefaultStructTagParser StructTagParserFunc = func(sf reflect.StructField) (StructTags, error) {
key := strings.ToLower(sf.Name)
tag, ok := sf.Tag.Lookup("bson")
if !ok && !strings.Contains(string(sf.Tag), ":") && len(sf.Tag) > 0 {
tag = string(sf.Tag)
}
return parseTags(key, tag)
}
func parseTags(key string, tag string) (StructTags, error) {
var st StructTags
if tag == "-" {
st.Skip = true
return st, nil
}
for idx, str := range strings.Split(tag, ",") {
if idx == 0 && str != "" {
key = str
}
switch str {
case "omitempty":
st.OmitEmpty = true
case "minsize":
st.MinSize = true
case "truncate":
st.Truncate = true
case "inline":
st.Inline = true
}
}
st.Name = key
return st, nil
}
// JSONFallbackStructTagParser has the same behavior as DefaultStructTagParser
// but will also fallback to parsing the json tag instead on a field where the
// bson tag isn't available.
var JSONFallbackStructTagParser StructTagParserFunc = func(sf reflect.StructField) (StructTags, error) {
key := strings.ToLower(sf.Name)
tag, ok := sf.Tag.Lookup("bson")
if !ok {
tag, ok = sf.Tag.Lookup("json")
}
if !ok && !strings.Contains(string(sf.Tag), ":") && len(sf.Tag) > 0 {
tag = string(sf.Tag)
}
return parseTags(key, tag)
}

View File

@ -0,0 +1,160 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestStructTagParsers(t *testing.T) {
testCases := []struct {
name string
sf reflect.StructField
want StructTags
parser StructTagParserFunc
}{
{
"default no bson tag",
reflect.StructField{Name: "foo", Tag: reflect.StructTag("bar")},
StructTags{Name: "bar"},
DefaultStructTagParser,
},
{
"default empty",
reflect.StructField{Name: "foo", Tag: reflect.StructTag("")},
StructTags{Name: "foo"},
DefaultStructTagParser,
},
{
"default tag only dash",
reflect.StructField{Name: "foo", Tag: reflect.StructTag("-")},
StructTags{Skip: true},
DefaultStructTagParser,
},
{
"default bson tag only dash",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"-"`)},
StructTags{Skip: true},
DefaultStructTagParser,
},
{
"default all options",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bar,omitempty,minsize,truncate,inline`)},
StructTags{Name: "bar", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
DefaultStructTagParser,
},
{
"default all options default name",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`,omitempty,minsize,truncate,inline`)},
StructTags{Name: "foo", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
DefaultStructTagParser,
},
{
"default bson tag all options",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"bar,omitempty,minsize,truncate,inline"`)},
StructTags{Name: "bar", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
DefaultStructTagParser,
},
{
"default bson tag all options default name",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:",omitempty,minsize,truncate,inline"`)},
StructTags{Name: "foo", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
DefaultStructTagParser,
},
{
"default ignore xml",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`xml:"bar"`)},
StructTags{Name: "foo"},
DefaultStructTagParser,
},
{
"JSONFallback no bson tag",
reflect.StructField{Name: "foo", Tag: reflect.StructTag("bar")},
StructTags{Name: "bar"},
JSONFallbackStructTagParser,
},
{
"JSONFallback empty",
reflect.StructField{Name: "foo", Tag: reflect.StructTag("")},
StructTags{Name: "foo"},
JSONFallbackStructTagParser,
},
{
"JSONFallback tag only dash",
reflect.StructField{Name: "foo", Tag: reflect.StructTag("-")},
StructTags{Skip: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback bson tag only dash",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"-"`)},
StructTags{Skip: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback all options",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bar,omitempty,minsize,truncate,inline`)},
StructTags{Name: "bar", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback all options default name",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`,omitempty,minsize,truncate,inline`)},
StructTags{Name: "foo", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback bson tag all options",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"bar,omitempty,minsize,truncate,inline"`)},
StructTags{Name: "bar", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback bson tag all options default name",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:",omitempty,minsize,truncate,inline"`)},
StructTags{Name: "foo", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback json tag all options",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`json:"bar,omitempty,minsize,truncate,inline"`)},
StructTags{Name: "bar", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback json tag all options default name",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`json:",omitempty,minsize,truncate,inline"`)},
StructTags{Name: "foo", OmitEmpty: true, MinSize: true, Truncate: true, Inline: true},
JSONFallbackStructTagParser,
},
{
"JSONFallback bson tag overrides other tags",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"bar" json:"qux,truncate"`)},
StructTags{Name: "bar"},
JSONFallbackStructTagParser,
},
{
"JSONFallback ignore xml",
reflect.StructField{Name: "foo", Tag: reflect.StructTag(`xml:"bar"`)},
StructTags{Name: "foo"},
JSONFallbackStructTagParser,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.parser(tc.sf)
noerr(t, err)
if !cmp.Equal(got, tc.want) {
t.Errorf("Returned struct tags do not match. got %#v; want %#v", got, tc.want)
}
})
}
}

View File

@ -0,0 +1,127 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"fmt"
"reflect"
"time"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
const (
timeFormatString = "2006-01-02T15:04:05.999Z07:00"
)
// TimeCodec is the Codec used for time.Time values.
type TimeCodec struct {
UseLocalTimeZone bool
}
var (
defaultTimeCodec = NewTimeCodec()
_ ValueCodec = defaultTimeCodec
_ typeDecoder = defaultTimeCodec
)
// NewTimeCodec returns a TimeCodec with options opts.
func NewTimeCodec(opts ...*bsonoptions.TimeCodecOptions) *TimeCodec {
timeOpt := bsonoptions.MergeTimeCodecOptions(opts...)
codec := TimeCodec{}
if timeOpt.UseLocalTimeZone != nil {
codec.UseLocalTimeZone = *timeOpt.UseLocalTimeZone
}
return &codec
}
func (tc *TimeCodec) decodeType(dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type) (reflect.Value, error) {
if t != tTime {
return emptyValue, ValueDecoderError{
Name: "TimeDecodeValue",
Types: []reflect.Type{tTime},
Received: reflect.Zero(t),
}
}
var timeVal time.Time
switch vrType := vr.Type(); vrType {
case bsontype.DateTime:
dt, err := vr.ReadDateTime()
if err != nil {
return emptyValue, err
}
timeVal = time.Unix(dt/1000, dt%1000*1000000)
case bsontype.String:
// assume strings are in the isoTimeFormat
timeStr, err := vr.ReadString()
if err != nil {
return emptyValue, err
}
timeVal, err = time.Parse(timeFormatString, timeStr)
if err != nil {
return emptyValue, err
}
case bsontype.Int64:
i64, err := vr.ReadInt64()
if err != nil {
return emptyValue, err
}
timeVal = time.Unix(i64/1000, i64%1000*1000000)
case bsontype.Timestamp:
t, _, err := vr.ReadTimestamp()
if err != nil {
return emptyValue, err
}
timeVal = time.Unix(int64(t), 0)
case bsontype.Null:
if err := vr.ReadNull(); err != nil {
return emptyValue, err
}
case bsontype.Undefined:
if err := vr.ReadUndefined(); err != nil {
return emptyValue, err
}
default:
return emptyValue, fmt.Errorf("cannot decode %v into a time.Time", vrType)
}
if !tc.UseLocalTimeZone {
timeVal = timeVal.UTC()
}
return reflect.ValueOf(timeVal), nil
}
// DecodeValue is the ValueDecoderFunc for time.Time.
func (tc *TimeCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != tTime {
return ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}, Received: val}
}
elem, err := tc.decodeType(dc, vr, tTime)
if err != nil {
return err
}
val.Set(elem)
return nil
}
// EncodeValue is the ValueEncoderFunc for time.TIme.
func (tc *TimeCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tTime {
return ValueEncoderError{Name: "TimeEncodeValue", Types: []reflect.Type{tTime}, Received: val}
}
tt := val.Interface().(time.Time)
dt := primitive.NewDateTimeFromTime(tt)
return vw.WriteDateTime(int64(dt))
}

View File

@ -0,0 +1,79 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"reflect"
"testing"
"time"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
func TestTimeCodec(t *testing.T) {
now := time.Now().Truncate(time.Millisecond)
t.Run("UseLocalTimeZone", func(t *testing.T) {
reader := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: now.UnixNano() / int64(time.Millisecond)}
testCases := []struct {
name string
opts *bsonoptions.TimeCodecOptions
utc bool
}{
{"default", bsonoptions.TimeCodec(), true},
{"false", bsonoptions.TimeCodec().SetUseLocalTimeZone(false), true},
{"true", bsonoptions.TimeCodec().SetUseLocalTimeZone(true), false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
timeCodec := NewTimeCodec(tc.opts)
actual := reflect.New(reflect.TypeOf(now)).Elem()
err := timeCodec.DecodeValue(DecodeContext{}, reader, actual)
assert.Nil(t, err, "TimeCodec.DecodeValue error: %v", err)
actualTime := actual.Interface().(time.Time)
assert.Equal(t, actualTime.Location().String() == "UTC", tc.utc,
"Expected UTC: %v, got %v", tc.utc, actualTime.Location())
assert.Equal(t, now, actualTime, "expected time %v, got %v", now, actualTime)
})
}
})
t.Run("DecodeFromBsontype", func(t *testing.T) {
testCases := []struct {
name string
reader *bsonrwtest.ValueReaderWriter
}{
{"string", &bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: now.Format(timeFormatString)}},
{"int64", &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: now.Unix()*1000 + int64(now.Nanosecond()/1e6)}},
{"timestamp", &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Timestamp,
Return: bsoncore.Value{
Type: bsontype.Timestamp,
Data: bsoncore.AppendTimestamp(nil, uint32(now.Unix()), 0),
}},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := reflect.New(reflect.TypeOf(now)).Elem()
err := defaultTimeCodec.DecodeValue(DecodeContext{}, tc.reader, actual)
assert.Nil(t, err, "DecodeValue error: %v", err)
actualTime := actual.Interface().(time.Time)
if tc.name == "timestamp" {
now = time.Unix(now.Unix(), 0)
}
assert.Equal(t, now, actualTime, "expected time %v, got %v", now, actualTime)
})
}
})
}

View File

@ -0,0 +1,57 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"encoding/json"
"net/url"
"reflect"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
var tBool = reflect.TypeOf(false)
var tFloat64 = reflect.TypeOf(float64(0))
var tInt32 = reflect.TypeOf(int32(0))
var tInt64 = reflect.TypeOf(int64(0))
var tString = reflect.TypeOf("")
var tTime = reflect.TypeOf(time.Time{})
var tEmpty = reflect.TypeOf((*interface{})(nil)).Elem()
var tByteSlice = reflect.TypeOf([]byte(nil))
var tByte = reflect.TypeOf(byte(0x00))
var tURL = reflect.TypeOf(url.URL{})
var tJSONNumber = reflect.TypeOf(json.Number(""))
var tValueMarshaler = reflect.TypeOf((*ValueMarshaler)(nil)).Elem()
var tValueUnmarshaler = reflect.TypeOf((*ValueUnmarshaler)(nil)).Elem()
var tMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
var tUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
var tProxy = reflect.TypeOf((*Proxy)(nil)).Elem()
var tBinary = reflect.TypeOf(primitive.Binary{})
var tUndefined = reflect.TypeOf(primitive.Undefined{})
var tOID = reflect.TypeOf(primitive.ObjectID{})
var tDateTime = reflect.TypeOf(primitive.DateTime(0))
var tNull = reflect.TypeOf(primitive.Null{})
var tRegex = reflect.TypeOf(primitive.Regex{})
var tCodeWithScope = reflect.TypeOf(primitive.CodeWithScope{})
var tDBPointer = reflect.TypeOf(primitive.DBPointer{})
var tJavaScript = reflect.TypeOf(primitive.JavaScript(""))
var tSymbol = reflect.TypeOf(primitive.Symbol(""))
var tTimestamp = reflect.TypeOf(primitive.Timestamp{})
var tDecimal = reflect.TypeOf(primitive.Decimal128{})
var tMinKey = reflect.TypeOf(primitive.MinKey{})
var tMaxKey = reflect.TypeOf(primitive.MaxKey{})
var tD = reflect.TypeOf(primitive.D{})
var tA = reflect.TypeOf(primitive.A{})
var tE = reflect.TypeOf(primitive.E{})
var tCoreDocument = reflect.TypeOf(bsoncore.Document{})
var tCoreArray = reflect.TypeOf(bsoncore.Array{})

View File

@ -0,0 +1,173 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bsoncodec
import (
"fmt"
"math"
"reflect"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
// UIntCodec is the Codec used for uint values.
type UIntCodec struct {
EncodeToMinSize bool
}
var (
defaultUIntCodec = NewUIntCodec()
_ ValueCodec = defaultUIntCodec
_ typeDecoder = defaultUIntCodec
)
// NewUIntCodec returns a UIntCodec with options opts.
func NewUIntCodec(opts ...*bsonoptions.UIntCodecOptions) *UIntCodec {
uintOpt := bsonoptions.MergeUIntCodecOptions(opts...)
codec := UIntCodec{}
if uintOpt.EncodeToMinSize != nil {
codec.EncodeToMinSize = *uintOpt.EncodeToMinSize
}
return &codec
}
// EncodeValue is the ValueEncoder for uint types.
func (uic *UIntCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
switch val.Kind() {
case reflect.Uint8, reflect.Uint16:
return vw.WriteInt32(int32(val.Uint()))
case reflect.Uint, reflect.Uint32, reflect.Uint64:
u64 := val.Uint()
// If ec.MinSize or if encodeToMinSize is true for a non-uint64 value we should write val as an int32
useMinSize := ec.MinSize || (uic.EncodeToMinSize && val.Kind() != reflect.Uint64)
if u64 <= math.MaxInt32 && useMinSize {
return vw.WriteInt32(int32(u64))
}
if u64 > math.MaxInt64 {
return fmt.Errorf("%d overflows int64", u64)
}
return vw.WriteInt64(int64(u64))
}
return ValueEncoderError{
Name: "UintEncodeValue",
Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
Received: val,
}
}
func (uic *UIntCodec) decodeType(dc DecodeContext, vr bsonrw.ValueReader, t reflect.Type) (reflect.Value, error) {
var i64 int64
var err error
switch vrType := vr.Type(); vrType {
case bsontype.Int32:
i32, err := vr.ReadInt32()
if err != nil {
return emptyValue, err
}
i64 = int64(i32)
case bsontype.Int64:
i64, err = vr.ReadInt64()
if err != nil {
return emptyValue, err
}
case bsontype.Double:
f64, err := vr.ReadDouble()
if err != nil {
return emptyValue, err
}
if !dc.Truncate && math.Floor(f64) != f64 {
return emptyValue, errCannotTruncate
}
if f64 > float64(math.MaxInt64) {
return emptyValue, fmt.Errorf("%g overflows int64", f64)
}
i64 = int64(f64)
case bsontype.Boolean:
b, err := vr.ReadBoolean()
if err != nil {
return emptyValue, err
}
if b {
i64 = 1
}
case bsontype.Null:
if err = vr.ReadNull(); err != nil {
return emptyValue, err
}
case bsontype.Undefined:
if err = vr.ReadUndefined(); err != nil {
return emptyValue, err
}
default:
return emptyValue, fmt.Errorf("cannot decode %v into an integer type", vrType)
}
switch t.Kind() {
case reflect.Uint8:
if i64 < 0 || i64 > math.MaxUint8 {
return emptyValue, fmt.Errorf("%d overflows uint8", i64)
}
return reflect.ValueOf(uint8(i64)), nil
case reflect.Uint16:
if i64 < 0 || i64 > math.MaxUint16 {
return emptyValue, fmt.Errorf("%d overflows uint16", i64)
}
return reflect.ValueOf(uint16(i64)), nil
case reflect.Uint32:
if i64 < 0 || i64 > math.MaxUint32 {
return emptyValue, fmt.Errorf("%d overflows uint32", i64)
}
return reflect.ValueOf(uint32(i64)), nil
case reflect.Uint64:
if i64 < 0 {
return emptyValue, fmt.Errorf("%d overflows uint64", i64)
}
return reflect.ValueOf(uint64(i64)), nil
case reflect.Uint:
if i64 < 0 || int64(uint(i64)) != i64 { // Can we fit this inside of an uint
return emptyValue, fmt.Errorf("%d overflows uint", i64)
}
return reflect.ValueOf(uint(i64)), nil
default:
return emptyValue, ValueDecoderError{
Name: "UintDecodeValue",
Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
Received: reflect.Zero(t),
}
}
}
// DecodeValue is the ValueDecoder for uint types.
func (uic *UIntCodec) DecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() {
return ValueDecoderError{
Name: "UintDecodeValue",
Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
Received: val,
}
}
elem, err := uic.decodeType(dc, vr, val.Type())
if err != nil {
return err
}
val.SetUint(elem.Uint())
return nil
}

View File

@ -0,0 +1,38 @@
// 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 bsonoptions
// ByteSliceCodecOptions represents all possible options for byte slice encoding and decoding.
type ByteSliceCodecOptions struct {
EncodeNilAsEmpty *bool // Specifies if a nil byte slice should encode as an empty binary instead of null. Defaults to false.
}
// ByteSliceCodec creates a new *ByteSliceCodecOptions
func ByteSliceCodec() *ByteSliceCodecOptions {
return &ByteSliceCodecOptions{}
}
// SetEncodeNilAsEmpty specifies if a nil byte slice should encode as an empty binary instead of null. Defaults to false.
func (bs *ByteSliceCodecOptions) SetEncodeNilAsEmpty(b bool) *ByteSliceCodecOptions {
bs.EncodeNilAsEmpty = &b
return bs
}
// MergeByteSliceCodecOptions combines the given *ByteSliceCodecOptions into a single *ByteSliceCodecOptions in a last one wins fashion.
func MergeByteSliceCodecOptions(opts ...*ByteSliceCodecOptions) *ByteSliceCodecOptions {
bs := ByteSliceCodec()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.EncodeNilAsEmpty != nil {
bs.EncodeNilAsEmpty = opt.EncodeNilAsEmpty
}
}
return bs
}

View File

@ -0,0 +1,8 @@
// 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 bsonoptions defines the optional configurations for the BSON codecs.
package bsonoptions

View File

@ -0,0 +1,38 @@
// 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 bsonoptions
// EmptyInterfaceCodecOptions represents all possible options for interface{} encoding and decoding.
type EmptyInterfaceCodecOptions struct {
DecodeBinaryAsSlice *bool // Specifies if Old and Generic type binarys should default to []slice instead of primitive.Binary. Defaults to false.
}
// EmptyInterfaceCodec creates a new *EmptyInterfaceCodecOptions
func EmptyInterfaceCodec() *EmptyInterfaceCodecOptions {
return &EmptyInterfaceCodecOptions{}
}
// SetDecodeBinaryAsSlice specifies if Old and Generic type binarys should default to []slice instead of primitive.Binary. Defaults to false.
func (e *EmptyInterfaceCodecOptions) SetDecodeBinaryAsSlice(b bool) *EmptyInterfaceCodecOptions {
e.DecodeBinaryAsSlice = &b
return e
}
// MergeEmptyInterfaceCodecOptions combines the given *EmptyInterfaceCodecOptions into a single *EmptyInterfaceCodecOptions in a last one wins fashion.
func MergeEmptyInterfaceCodecOptions(opts ...*EmptyInterfaceCodecOptions) *EmptyInterfaceCodecOptions {
e := EmptyInterfaceCodec()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.DecodeBinaryAsSlice != nil {
e.DecodeBinaryAsSlice = opt.DecodeBinaryAsSlice
}
}
return e
}

View File

@ -0,0 +1,67 @@
// 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 bsonoptions
// MapCodecOptions represents all possible options for map encoding and decoding.
type MapCodecOptions struct {
DecodeZerosMap *bool // Specifies if the map should be zeroed before decoding into it. Defaults to false.
EncodeNilAsEmpty *bool // Specifies if a nil map should encode as an empty document instead of null. Defaults to false.
// Specifies how keys should be handled. If false, the behavior matches encoding/json, where the encoding key type must
// either be a string, an integer type, or implement bsoncodec.KeyMarshaler and the decoding key type must either be a
// string, an integer type, or implement bsoncodec.KeyUnmarshaler. If true, keys are encoded with fmt.Sprint() and the
// encoding key type must be a string, an integer type, or a float. If true, the use of Stringer will override
// TextMarshaler/TextUnmarshaler. Defaults to false.
EncodeKeysWithStringer *bool
}
// MapCodec creates a new *MapCodecOptions
func MapCodec() *MapCodecOptions {
return &MapCodecOptions{}
}
// SetDecodeZerosMap specifies if the map should be zeroed before decoding into it. Defaults to false.
func (t *MapCodecOptions) SetDecodeZerosMap(b bool) *MapCodecOptions {
t.DecodeZerosMap = &b
return t
}
// SetEncodeNilAsEmpty specifies if a nil map should encode as an empty document instead of null. Defaults to false.
func (t *MapCodecOptions) SetEncodeNilAsEmpty(b bool) *MapCodecOptions {
t.EncodeNilAsEmpty = &b
return t
}
// SetEncodeKeysWithStringer specifies how keys should be handled. If false, the behavior matches encoding/json, where the
// encoding key type must either be a string, an integer type, or implement bsoncodec.KeyMarshaler and the decoding key
// type must either be a string, an integer type, or implement bsoncodec.KeyUnmarshaler. If true, keys are encoded with
// fmt.Sprint() and the encoding key type must be a string, an integer type, or a float. If true, the use of Stringer
// will override TextMarshaler/TextUnmarshaler. Defaults to false.
func (t *MapCodecOptions) SetEncodeKeysWithStringer(b bool) *MapCodecOptions {
t.EncodeKeysWithStringer = &b
return t
}
// MergeMapCodecOptions combines the given *MapCodecOptions into a single *MapCodecOptions in a last one wins fashion.
func MergeMapCodecOptions(opts ...*MapCodecOptions) *MapCodecOptions {
s := MapCodec()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.DecodeZerosMap != nil {
s.DecodeZerosMap = opt.DecodeZerosMap
}
if opt.EncodeNilAsEmpty != nil {
s.EncodeNilAsEmpty = opt.EncodeNilAsEmpty
}
if opt.EncodeKeysWithStringer != nil {
s.EncodeKeysWithStringer = opt.EncodeKeysWithStringer
}
}
return s
}

View File

@ -0,0 +1,38 @@
// 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 bsonoptions
// SliceCodecOptions represents all possible options for slice encoding and decoding.
type SliceCodecOptions struct {
EncodeNilAsEmpty *bool // Specifies if a nil slice should encode as an empty array instead of null. Defaults to false.
}
// SliceCodec creates a new *SliceCodecOptions
func SliceCodec() *SliceCodecOptions {
return &SliceCodecOptions{}
}
// SetEncodeNilAsEmpty specifies if a nil slice should encode as an empty array instead of null. Defaults to false.
func (s *SliceCodecOptions) SetEncodeNilAsEmpty(b bool) *SliceCodecOptions {
s.EncodeNilAsEmpty = &b
return s
}
// MergeSliceCodecOptions combines the given *SliceCodecOptions into a single *SliceCodecOptions in a last one wins fashion.
func MergeSliceCodecOptions(opts ...*SliceCodecOptions) *SliceCodecOptions {
s := SliceCodec()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.EncodeNilAsEmpty != nil {
s.EncodeNilAsEmpty = opt.EncodeNilAsEmpty
}
}
return s
}

View File

@ -0,0 +1,41 @@
// 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 bsonoptions
var defaultDecodeOIDAsHex = true
// StringCodecOptions represents all possible options for string encoding and decoding.
type StringCodecOptions struct {
DecodeObjectIDAsHex *bool // Specifies if we should decode ObjectID as the hex value. Defaults to true.
}
// StringCodec creates a new *StringCodecOptions
func StringCodec() *StringCodecOptions {
return &StringCodecOptions{}
}
// SetDecodeObjectIDAsHex specifies if object IDs should be decoded as their hex representation. If false, a string made
// from the raw object ID bytes will be used. Defaults to true.
func (t *StringCodecOptions) SetDecodeObjectIDAsHex(b bool) *StringCodecOptions {
t.DecodeObjectIDAsHex = &b
return t
}
// MergeStringCodecOptions combines the given *StringCodecOptions into a single *StringCodecOptions in a last one wins fashion.
func MergeStringCodecOptions(opts ...*StringCodecOptions) *StringCodecOptions {
s := &StringCodecOptions{&defaultDecodeOIDAsHex}
for _, opt := range opts {
if opt == nil {
continue
}
if opt.DecodeObjectIDAsHex != nil {
s.DecodeObjectIDAsHex = opt.DecodeObjectIDAsHex
}
}
return s
}

View File

@ -0,0 +1,87 @@
// 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 bsonoptions
var defaultOverwriteDuplicatedInlinedFields = true
// StructCodecOptions represents all possible options for struct encoding and decoding.
type StructCodecOptions struct {
DecodeZeroStruct *bool // Specifies if structs should be zeroed before decoding into them. Defaults to false.
DecodeDeepZeroInline *bool // Specifies if structs should be recursively zeroed when a inline value is decoded. Defaults to false.
EncodeOmitDefaultStruct *bool // Specifies if default structs should be considered empty by omitempty. Defaults to false.
AllowUnexportedFields *bool // Specifies if unexported fields should be marshaled/unmarshaled. Defaults to false.
OverwriteDuplicatedInlinedFields *bool // Specifies if fields in inlined structs can be overwritten by higher level struct fields with the same key. Defaults to true.
}
// StructCodec creates a new *StructCodecOptions
func StructCodec() *StructCodecOptions {
return &StructCodecOptions{}
}
// SetDecodeZeroStruct specifies if structs should be zeroed before decoding into them. Defaults to false.
func (t *StructCodecOptions) SetDecodeZeroStruct(b bool) *StructCodecOptions {
t.DecodeZeroStruct = &b
return t
}
// SetDecodeDeepZeroInline specifies if structs should be zeroed before decoding into them. Defaults to false.
func (t *StructCodecOptions) SetDecodeDeepZeroInline(b bool) *StructCodecOptions {
t.DecodeDeepZeroInline = &b
return t
}
// SetEncodeOmitDefaultStruct specifies if default structs should be considered empty by omitempty. A default struct has all
// its values set to their default value. Defaults to false.
func (t *StructCodecOptions) SetEncodeOmitDefaultStruct(b bool) *StructCodecOptions {
t.EncodeOmitDefaultStruct = &b
return t
}
// SetOverwriteDuplicatedInlinedFields specifies if inlined struct fields can be overwritten by higher level struct fields with the
// same bson key. When true and decoding, values will be written to the outermost struct with a matching key, and when
// encoding, keys will have the value of the top-most matching field. When false, decoding and encoding will error if
// there are duplicate keys after the struct is inlined. Defaults to true.
func (t *StructCodecOptions) SetOverwriteDuplicatedInlinedFields(b bool) *StructCodecOptions {
t.OverwriteDuplicatedInlinedFields = &b
return t
}
// SetAllowUnexportedFields specifies if unexported fields should be marshaled/unmarshaled. Defaults to false.
func (t *StructCodecOptions) SetAllowUnexportedFields(b bool) *StructCodecOptions {
t.AllowUnexportedFields = &b
return t
}
// MergeStructCodecOptions combines the given *StructCodecOptions into a single *StructCodecOptions in a last one wins fashion.
func MergeStructCodecOptions(opts ...*StructCodecOptions) *StructCodecOptions {
s := &StructCodecOptions{
OverwriteDuplicatedInlinedFields: &defaultOverwriteDuplicatedInlinedFields,
}
for _, opt := range opts {
if opt == nil {
continue
}
if opt.DecodeZeroStruct != nil {
s.DecodeZeroStruct = opt.DecodeZeroStruct
}
if opt.DecodeDeepZeroInline != nil {
s.DecodeDeepZeroInline = opt.DecodeDeepZeroInline
}
if opt.EncodeOmitDefaultStruct != nil {
s.EncodeOmitDefaultStruct = opt.EncodeOmitDefaultStruct
}
if opt.OverwriteDuplicatedInlinedFields != nil {
s.OverwriteDuplicatedInlinedFields = opt.OverwriteDuplicatedInlinedFields
}
if opt.AllowUnexportedFields != nil {
s.AllowUnexportedFields = opt.AllowUnexportedFields
}
}
return s
}

View File

@ -0,0 +1,38 @@
// 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 bsonoptions
// TimeCodecOptions represents all possible options for time.Time encoding and decoding.
type TimeCodecOptions struct {
UseLocalTimeZone *bool // Specifies if we should decode into the local time zone. Defaults to false.
}
// TimeCodec creates a new *TimeCodecOptions
func TimeCodec() *TimeCodecOptions {
return &TimeCodecOptions{}
}
// SetUseLocalTimeZone specifies if we should decode into the local time zone. Defaults to false.
func (t *TimeCodecOptions) SetUseLocalTimeZone(b bool) *TimeCodecOptions {
t.UseLocalTimeZone = &b
return t
}
// MergeTimeCodecOptions combines the given *TimeCodecOptions into a single *TimeCodecOptions in a last one wins fashion.
func MergeTimeCodecOptions(opts ...*TimeCodecOptions) *TimeCodecOptions {
t := TimeCodec()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.UseLocalTimeZone != nil {
t.UseLocalTimeZone = opt.UseLocalTimeZone
}
}
return t
}

View File

@ -0,0 +1,38 @@
// 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 bsonoptions
// UIntCodecOptions represents all possible options for uint encoding and decoding.
type UIntCodecOptions struct {
EncodeToMinSize *bool // Specifies if all uints except uint64 should be decoded to minimum size bsontype. Defaults to false.
}
// UIntCodec creates a new *UIntCodecOptions
func UIntCodec() *UIntCodecOptions {
return &UIntCodecOptions{}
}
// SetEncodeToMinSize specifies if all uints except uint64 should be decoded to minimum size bsontype. Defaults to false.
func (u *UIntCodecOptions) SetEncodeToMinSize(b bool) *UIntCodecOptions {
u.EncodeToMinSize = &b
return u
}
// MergeUIntCodecOptions combines the given *UIntCodecOptions into a single *UIntCodecOptions in a last one wins fashion.
func MergeUIntCodecOptions(opts ...*UIntCodecOptions) *UIntCodecOptions {
u := UIntCodec()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.EncodeToMinSize != nil {
u.EncodeToMinSize = opt.EncodeToMinSize
}
}
return u
}

View File

@ -0,0 +1,33 @@
// 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 bsonrw
import "testing"
func compareErrors(err1, err2 error) bool {
if err1 == nil && err2 == nil {
return true
}
if err1 == nil || err2 == nil {
return false
}
if err1.Error() != err2.Error() {
return false
}
return true
}
func noerr(t *testing.T, err error) {
if err != nil {
t.Helper()
t.Errorf("Unexpected error: (%T)%v", err, err)
t.FailNow()
}
}

View File

@ -0,0 +1,848 @@
// 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 bsonrwtest provides utilities for testing the "bson/bsonrw" package.
package bsonrwtest // import "go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
import (
"testing"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
var _ bsonrw.ValueReader = (*ValueReaderWriter)(nil)
var _ bsonrw.ValueWriter = (*ValueReaderWriter)(nil)
// Invoked is a type used to indicate what method was called last.
type Invoked byte
// These are the different methods that can be invoked.
const (
Nothing Invoked = iota
ReadArray
ReadBinary
ReadBoolean
ReadDocument
ReadCodeWithScope
ReadDBPointer
ReadDateTime
ReadDecimal128
ReadDouble
ReadInt32
ReadInt64
ReadJavascript
ReadMaxKey
ReadMinKey
ReadNull
ReadObjectID
ReadRegex
ReadString
ReadSymbol
ReadTimestamp
ReadUndefined
ReadElement
ReadValue
WriteArray
WriteBinary
WriteBinaryWithSubtype
WriteBoolean
WriteCodeWithScope
WriteDBPointer
WriteDateTime
WriteDecimal128
WriteDouble
WriteInt32
WriteInt64
WriteJavascript
WriteMaxKey
WriteMinKey
WriteNull
WriteObjectID
WriteRegex
WriteString
WriteDocument
WriteSymbol
WriteTimestamp
WriteUndefined
WriteDocumentElement
WriteDocumentEnd
WriteArrayElement
WriteArrayEnd
Skip
)
func (i Invoked) String() string {
switch i {
case Nothing:
return "Nothing"
case ReadArray:
return "ReadArray"
case ReadBinary:
return "ReadBinary"
case ReadBoolean:
return "ReadBoolean"
case ReadDocument:
return "ReadDocument"
case ReadCodeWithScope:
return "ReadCodeWithScope"
case ReadDBPointer:
return "ReadDBPointer"
case ReadDateTime:
return "ReadDateTime"
case ReadDecimal128:
return "ReadDecimal128"
case ReadDouble:
return "ReadDouble"
case ReadInt32:
return "ReadInt32"
case ReadInt64:
return "ReadInt64"
case ReadJavascript:
return "ReadJavascript"
case ReadMaxKey:
return "ReadMaxKey"
case ReadMinKey:
return "ReadMinKey"
case ReadNull:
return "ReadNull"
case ReadObjectID:
return "ReadObjectID"
case ReadRegex:
return "ReadRegex"
case ReadString:
return "ReadString"
case ReadSymbol:
return "ReadSymbol"
case ReadTimestamp:
return "ReadTimestamp"
case ReadUndefined:
return "ReadUndefined"
case ReadElement:
return "ReadElement"
case ReadValue:
return "ReadValue"
case WriteArray:
return "WriteArray"
case WriteBinary:
return "WriteBinary"
case WriteBinaryWithSubtype:
return "WriteBinaryWithSubtype"
case WriteBoolean:
return "WriteBoolean"
case WriteCodeWithScope:
return "WriteCodeWithScope"
case WriteDBPointer:
return "WriteDBPointer"
case WriteDateTime:
return "WriteDateTime"
case WriteDecimal128:
return "WriteDecimal128"
case WriteDouble:
return "WriteDouble"
case WriteInt32:
return "WriteInt32"
case WriteInt64:
return "WriteInt64"
case WriteJavascript:
return "WriteJavascript"
case WriteMaxKey:
return "WriteMaxKey"
case WriteMinKey:
return "WriteMinKey"
case WriteNull:
return "WriteNull"
case WriteObjectID:
return "WriteObjectID"
case WriteRegex:
return "WriteRegex"
case WriteString:
return "WriteString"
case WriteDocument:
return "WriteDocument"
case WriteSymbol:
return "WriteSymbol"
case WriteTimestamp:
return "WriteTimestamp"
case WriteUndefined:
return "WriteUndefined"
case WriteDocumentElement:
return "WriteDocumentElement"
case WriteDocumentEnd:
return "WriteDocumentEnd"
case WriteArrayElement:
return "WriteArrayElement"
case WriteArrayEnd:
return "WriteArrayEnd"
default:
return "<unknown>"
}
}
// ValueReaderWriter is a test implementation of a bsonrw.ValueReader and bsonrw.ValueWriter
type ValueReaderWriter struct {
T *testing.T
Invoked Invoked
Return interface{} // Can be a primitive or a bsoncore.Value
BSONType bsontype.Type
Err error
ErrAfter Invoked // error after this method is called
depth uint64
}
// prevent infinite recursion.
func (llvrw *ValueReaderWriter) checkdepth() {
llvrw.depth++
if llvrw.depth > 1000 {
panic("max depth exceeded")
}
}
// Type implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) Type() bsontype.Type {
llvrw.checkdepth()
return llvrw.BSONType
}
// Skip implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) Skip() error {
llvrw.checkdepth()
llvrw.Invoked = Skip
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// ReadArray implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadArray() (bsonrw.ArrayReader, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadArray
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// ReadBinary implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadBinary() (b []byte, btype byte, err error) {
llvrw.checkdepth()
llvrw.Invoked = ReadBinary
if llvrw.ErrAfter == llvrw.Invoked {
return nil, 0x00, llvrw.Err
}
switch tt := llvrw.Return.(type) {
case bsoncore.Value:
subtype, data, _, ok := bsoncore.ReadBinary(tt.Data)
if !ok {
llvrw.T.Error("Invalid Value provided for return value of ReadBinary.")
return nil, 0x00, nil
}
return data, subtype, nil
default:
llvrw.T.Errorf("Incorrect type provided for return value of ReadBinary: %T", llvrw.Return)
return nil, 0x00, nil
}
}
// ReadBoolean implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadBoolean() (bool, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadBoolean
if llvrw.ErrAfter == llvrw.Invoked {
return false, llvrw.Err
}
switch tt := llvrw.Return.(type) {
case bool:
return tt, nil
case bsoncore.Value:
b, _, ok := bsoncore.ReadBoolean(tt.Data)
if !ok {
llvrw.T.Error("Invalid Value provided for return value of ReadBoolean.")
return false, nil
}
return b, nil
default:
llvrw.T.Errorf("Incorrect type provided for return value of ReadBoolean: %T", llvrw.Return)
return false, nil
}
}
// ReadDocument implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadDocument() (bsonrw.DocumentReader, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadDocument
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// ReadCodeWithScope implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadCodeWithScope() (code string, dr bsonrw.DocumentReader, err error) {
llvrw.checkdepth()
llvrw.Invoked = ReadCodeWithScope
if llvrw.ErrAfter == llvrw.Invoked {
return "", nil, llvrw.Err
}
return "", llvrw, nil
}
// ReadDBPointer implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadDBPointer() (ns string, oid primitive.ObjectID, err error) {
llvrw.checkdepth()
llvrw.Invoked = ReadDBPointer
if llvrw.ErrAfter == llvrw.Invoked {
return "", primitive.ObjectID{}, llvrw.Err
}
switch tt := llvrw.Return.(type) {
case bsoncore.Value:
ns, oid, _, ok := bsoncore.ReadDBPointer(tt.Data)
if !ok {
llvrw.T.Error("Invalid Value instance provided for return value of ReadDBPointer")
return "", primitive.ObjectID{}, nil
}
return ns, oid, nil
default:
llvrw.T.Errorf("Incorrect type provided for return value of ReadDBPointer: %T", llvrw.Return)
return "", primitive.ObjectID{}, nil
}
}
// ReadDateTime implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadDateTime() (int64, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadDateTime
if llvrw.ErrAfter == llvrw.Invoked {
return 0, llvrw.Err
}
dt, ok := llvrw.Return.(int64)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadDateTime: %T", llvrw.Return)
return 0, nil
}
return dt, nil
}
// ReadDecimal128 implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadDecimal128() (primitive.Decimal128, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadDecimal128
if llvrw.ErrAfter == llvrw.Invoked {
return primitive.Decimal128{}, llvrw.Err
}
d128, ok := llvrw.Return.(primitive.Decimal128)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadDecimal128: %T", llvrw.Return)
return primitive.Decimal128{}, nil
}
return d128, nil
}
// ReadDouble implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadDouble() (float64, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadDouble
if llvrw.ErrAfter == llvrw.Invoked {
return 0, llvrw.Err
}
f64, ok := llvrw.Return.(float64)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadDouble: %T", llvrw.Return)
return 0, nil
}
return f64, nil
}
// ReadInt32 implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadInt32() (int32, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadInt32
if llvrw.ErrAfter == llvrw.Invoked {
return 0, llvrw.Err
}
i32, ok := llvrw.Return.(int32)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadInt32: %T", llvrw.Return)
return 0, nil
}
return i32, nil
}
// ReadInt64 implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadInt64() (int64, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadInt64
if llvrw.ErrAfter == llvrw.Invoked {
return 0, llvrw.Err
}
i64, ok := llvrw.Return.(int64)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadInt64: %T", llvrw.Return)
return 0, nil
}
return i64, nil
}
// ReadJavascript implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadJavascript() (code string, err error) {
llvrw.checkdepth()
llvrw.Invoked = ReadJavascript
if llvrw.ErrAfter == llvrw.Invoked {
return "", llvrw.Err
}
js, ok := llvrw.Return.(string)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadJavascript: %T", llvrw.Return)
return "", nil
}
return js, nil
}
// ReadMaxKey implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadMaxKey() error {
llvrw.checkdepth()
llvrw.Invoked = ReadMaxKey
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// ReadMinKey implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadMinKey() error {
llvrw.checkdepth()
llvrw.Invoked = ReadMinKey
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// ReadNull implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadNull() error {
llvrw.checkdepth()
llvrw.Invoked = ReadNull
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// ReadObjectID implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadObjectID() (primitive.ObjectID, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadObjectID
if llvrw.ErrAfter == llvrw.Invoked {
return primitive.ObjectID{}, llvrw.Err
}
oid, ok := llvrw.Return.(primitive.ObjectID)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadObjectID: %T", llvrw.Return)
return primitive.ObjectID{}, nil
}
return oid, nil
}
// ReadRegex implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadRegex() (pattern string, options string, err error) {
llvrw.checkdepth()
llvrw.Invoked = ReadRegex
if llvrw.ErrAfter == llvrw.Invoked {
return "", "", llvrw.Err
}
switch tt := llvrw.Return.(type) {
case bsoncore.Value:
pattern, options, _, ok := bsoncore.ReadRegex(tt.Data)
if !ok {
llvrw.T.Error("Invalid Value instance provided for ReadRegex")
return "", "", nil
}
return pattern, options, nil
default:
llvrw.T.Errorf("Incorrect type provided for return value of ReadRegex: %T", llvrw.Return)
return "", "", nil
}
}
// ReadString implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadString() (string, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadString
if llvrw.ErrAfter == llvrw.Invoked {
return "", llvrw.Err
}
str, ok := llvrw.Return.(string)
if !ok {
llvrw.T.Errorf("Incorrect type provided for return value of ReadString: %T", llvrw.Return)
return "", nil
}
return str, nil
}
// ReadSymbol implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadSymbol() (symbol string, err error) {
llvrw.checkdepth()
llvrw.Invoked = ReadSymbol
if llvrw.ErrAfter == llvrw.Invoked {
return "", llvrw.Err
}
switch tt := llvrw.Return.(type) {
case string:
return tt, nil
case bsoncore.Value:
symbol, _, ok := bsoncore.ReadSymbol(tt.Data)
if !ok {
llvrw.T.Error("Invalid Value instance provided for ReadSymbol")
return "", nil
}
return symbol, nil
default:
llvrw.T.Errorf("Incorrect type provided for return value of ReadSymbol: %T", llvrw.Return)
return "", nil
}
}
// ReadTimestamp implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadTimestamp() (t uint32, i uint32, err error) {
llvrw.checkdepth()
llvrw.Invoked = ReadTimestamp
if llvrw.ErrAfter == llvrw.Invoked {
return 0, 0, llvrw.Err
}
switch tt := llvrw.Return.(type) {
case bsoncore.Value:
t, i, _, ok := bsoncore.ReadTimestamp(tt.Data)
if !ok {
llvrw.T.Errorf("Invalid Value instance provided for return value of ReadTimestamp")
return 0, 0, nil
}
return t, i, nil
default:
llvrw.T.Errorf("Incorrect type provided for return value of ReadTimestamp: %T", llvrw.Return)
return 0, 0, nil
}
}
// ReadUndefined implements the bsonrw.ValueReader interface.
func (llvrw *ValueReaderWriter) ReadUndefined() error {
llvrw.checkdepth()
llvrw.Invoked = ReadUndefined
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteArray implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteArray() (bsonrw.ArrayWriter, error) {
llvrw.checkdepth()
llvrw.Invoked = WriteArray
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// WriteBinary implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteBinary(b []byte) error {
llvrw.checkdepth()
llvrw.Invoked = WriteBinary
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteBinaryWithSubtype implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteBinaryWithSubtype(b []byte, btype byte) error {
llvrw.checkdepth()
llvrw.Invoked = WriteBinaryWithSubtype
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteBoolean implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteBoolean(bool) error {
llvrw.checkdepth()
llvrw.Invoked = WriteBoolean
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteCodeWithScope implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteCodeWithScope(code string) (bsonrw.DocumentWriter, error) {
llvrw.checkdepth()
llvrw.Invoked = WriteCodeWithScope
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// WriteDBPointer implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteDBPointer(ns string, oid primitive.ObjectID) error {
llvrw.checkdepth()
llvrw.Invoked = WriteDBPointer
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteDateTime implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteDateTime(dt int64) error {
llvrw.checkdepth()
llvrw.Invoked = WriteDateTime
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteDecimal128 implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteDecimal128(primitive.Decimal128) error {
llvrw.checkdepth()
llvrw.Invoked = WriteDecimal128
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteDouble implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteDouble(float64) error {
llvrw.checkdepth()
llvrw.Invoked = WriteDouble
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteInt32 implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteInt32(int32) error {
llvrw.checkdepth()
llvrw.Invoked = WriteInt32
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteInt64 implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteInt64(int64) error {
llvrw.checkdepth()
llvrw.Invoked = WriteInt64
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteJavascript implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteJavascript(code string) error {
llvrw.checkdepth()
llvrw.Invoked = WriteJavascript
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteMaxKey implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteMaxKey() error {
llvrw.checkdepth()
llvrw.Invoked = WriteMaxKey
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteMinKey implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteMinKey() error {
llvrw.checkdepth()
llvrw.Invoked = WriteMinKey
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteNull implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteNull() error {
llvrw.checkdepth()
llvrw.Invoked = WriteNull
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteObjectID implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteObjectID(primitive.ObjectID) error {
llvrw.checkdepth()
llvrw.Invoked = WriteObjectID
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteRegex implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteRegex(pattern string, options string) error {
llvrw.checkdepth()
llvrw.Invoked = WriteRegex
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteString implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteString(string) error {
llvrw.checkdepth()
llvrw.Invoked = WriteString
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteDocument implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteDocument() (bsonrw.DocumentWriter, error) {
llvrw.checkdepth()
llvrw.Invoked = WriteDocument
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// WriteSymbol implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteSymbol(symbol string) error {
llvrw.checkdepth()
llvrw.Invoked = WriteSymbol
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteTimestamp implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteTimestamp(t uint32, i uint32) error {
llvrw.checkdepth()
llvrw.Invoked = WriteTimestamp
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// WriteUndefined implements the bsonrw.ValueWriter interface.
func (llvrw *ValueReaderWriter) WriteUndefined() error {
llvrw.checkdepth()
llvrw.Invoked = WriteUndefined
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// ReadElement implements the bsonrw.DocumentReader interface.
func (llvrw *ValueReaderWriter) ReadElement() (string, bsonrw.ValueReader, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadElement
if llvrw.ErrAfter == llvrw.Invoked {
return "", nil, llvrw.Err
}
return "", llvrw, nil
}
// WriteDocumentElement implements the bsonrw.DocumentWriter interface.
func (llvrw *ValueReaderWriter) WriteDocumentElement(string) (bsonrw.ValueWriter, error) {
llvrw.checkdepth()
llvrw.Invoked = WriteDocumentElement
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// WriteDocumentEnd implements the bsonrw.DocumentWriter interface.
func (llvrw *ValueReaderWriter) WriteDocumentEnd() error {
llvrw.checkdepth()
llvrw.Invoked = WriteDocumentEnd
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}
// ReadValue implements the bsonrw.ArrayReader interface.
func (llvrw *ValueReaderWriter) ReadValue() (bsonrw.ValueReader, error) {
llvrw.checkdepth()
llvrw.Invoked = ReadValue
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// WriteArrayElement implements the bsonrw.ArrayWriter interface.
func (llvrw *ValueReaderWriter) WriteArrayElement() (bsonrw.ValueWriter, error) {
llvrw.checkdepth()
llvrw.Invoked = WriteArrayElement
if llvrw.ErrAfter == llvrw.Invoked {
return nil, llvrw.Err
}
return llvrw, nil
}
// WriteArrayEnd implements the bsonrw.ArrayWriter interface.
func (llvrw *ValueReaderWriter) WriteArrayEnd() error {
llvrw.checkdepth()
llvrw.Invoked = WriteArrayEnd
if llvrw.ErrAfter == llvrw.Invoked {
return llvrw.Err
}
return nil
}

445
mongo/bson/bsonrw/copier.go Normal file
View File

@ -0,0 +1,445 @@
// 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 bsonrw
import (
"fmt"
"io"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
// Copier is a type that allows copying between ValueReaders, ValueWriters, and
// []byte values.
type Copier struct{}
// NewCopier creates a new copier with the given registry. If a nil registry is provided
// a default registry is used.
func NewCopier() Copier {
return Copier{}
}
// CopyDocument handles copying a document from src to dst.
func CopyDocument(dst ValueWriter, src ValueReader) error {
return Copier{}.CopyDocument(dst, src)
}
// CopyDocument handles copying one document from the src to the dst.
func (c Copier) CopyDocument(dst ValueWriter, src ValueReader) error {
dr, err := src.ReadDocument()
if err != nil {
return err
}
dw, err := dst.WriteDocument()
if err != nil {
return err
}
return c.copyDocumentCore(dw, dr)
}
// CopyArrayFromBytes copies the values from a BSON array represented as a
// []byte to a ValueWriter.
func (c Copier) CopyArrayFromBytes(dst ValueWriter, src []byte) error {
aw, err := dst.WriteArray()
if err != nil {
return err
}
err = c.CopyBytesToArrayWriter(aw, src)
if err != nil {
return err
}
return aw.WriteArrayEnd()
}
// CopyDocumentFromBytes copies the values from a BSON document represented as a
// []byte to a ValueWriter.
func (c Copier) CopyDocumentFromBytes(dst ValueWriter, src []byte) error {
dw, err := dst.WriteDocument()
if err != nil {
return err
}
err = c.CopyBytesToDocumentWriter(dw, src)
if err != nil {
return err
}
return dw.WriteDocumentEnd()
}
type writeElementFn func(key string) (ValueWriter, error)
// CopyBytesToArrayWriter copies the values from a BSON Array represented as a []byte to an
// ArrayWriter.
func (c Copier) CopyBytesToArrayWriter(dst ArrayWriter, src []byte) error {
wef := func(_ string) (ValueWriter, error) {
return dst.WriteArrayElement()
}
return c.copyBytesToValueWriter(src, wef)
}
// CopyBytesToDocumentWriter copies the values from a BSON document represented as a []byte to a
// DocumentWriter.
func (c Copier) CopyBytesToDocumentWriter(dst DocumentWriter, src []byte) error {
wef := func(key string) (ValueWriter, error) {
return dst.WriteDocumentElement(key)
}
return c.copyBytesToValueWriter(src, wef)
}
func (c Copier) copyBytesToValueWriter(src []byte, wef writeElementFn) error {
// TODO(skriptble): Create errors types here. Anything thats a tag should be a property.
length, rem, ok := bsoncore.ReadLength(src)
if !ok {
return fmt.Errorf("couldn't read length from src, not enough bytes. length=%d", len(src))
}
if len(src) < int(length) {
return fmt.Errorf("length read exceeds number of bytes available. length=%d bytes=%d", len(src), length)
}
rem = rem[:length-4]
var t bsontype.Type
var key string
var val bsoncore.Value
for {
t, rem, ok = bsoncore.ReadType(rem)
if !ok {
return io.EOF
}
if t == bsontype.Type(0) {
if len(rem) != 0 {
return fmt.Errorf("document end byte found before end of document. remaining bytes=%v", rem)
}
break
}
key, rem, ok = bsoncore.ReadKey(rem)
if !ok {
return fmt.Errorf("invalid key found. remaining bytes=%v", rem)
}
// write as either array element or document element using writeElementFn
vw, err := wef(key)
if err != nil {
return err
}
val, rem, ok = bsoncore.ReadValue(rem, t)
if !ok {
return fmt.Errorf("not enough bytes available to read type. bytes=%d type=%s", len(rem), t)
}
err = c.CopyValueFromBytes(vw, t, val.Data)
if err != nil {
return err
}
}
return nil
}
// CopyDocumentToBytes copies an entire document from the ValueReader and
// returns it as bytes.
func (c Copier) CopyDocumentToBytes(src ValueReader) ([]byte, error) {
return c.AppendDocumentBytes(nil, src)
}
// AppendDocumentBytes functions the same as CopyDocumentToBytes, but will
// append the result to dst.
func (c Copier) AppendDocumentBytes(dst []byte, src ValueReader) ([]byte, error) {
if br, ok := src.(BytesReader); ok {
_, dst, err := br.ReadValueBytes(dst)
return dst, err
}
vw := vwPool.Get().(*valueWriter)
defer vwPool.Put(vw)
vw.reset(dst)
err := c.CopyDocument(vw, src)
dst = vw.buf
return dst, err
}
// AppendArrayBytes copies an array from the ValueReader to dst.
func (c Copier) AppendArrayBytes(dst []byte, src ValueReader) ([]byte, error) {
if br, ok := src.(BytesReader); ok {
_, dst, err := br.ReadValueBytes(dst)
return dst, err
}
vw := vwPool.Get().(*valueWriter)
defer vwPool.Put(vw)
vw.reset(dst)
err := c.copyArray(vw, src)
dst = vw.buf
return dst, err
}
// CopyValueFromBytes will write the value represtend by t and src to dst.
func (c Copier) CopyValueFromBytes(dst ValueWriter, t bsontype.Type, src []byte) error {
if wvb, ok := dst.(BytesWriter); ok {
return wvb.WriteValueBytes(t, src)
}
vr := vrPool.Get().(*valueReader)
defer vrPool.Put(vr)
vr.reset(src)
vr.pushElement(t)
return c.CopyValue(dst, vr)
}
// CopyValueToBytes copies a value from src and returns it as a bsontype.Type and a
// []byte.
func (c Copier) CopyValueToBytes(src ValueReader) (bsontype.Type, []byte, error) {
return c.AppendValueBytes(nil, src)
}
// AppendValueBytes functions the same as CopyValueToBytes, but will append the
// result to dst.
func (c Copier) AppendValueBytes(dst []byte, src ValueReader) (bsontype.Type, []byte, error) {
if br, ok := src.(BytesReader); ok {
return br.ReadValueBytes(dst)
}
vw := vwPool.Get().(*valueWriter)
defer vwPool.Put(vw)
start := len(dst)
vw.reset(dst)
vw.push(mElement)
err := c.CopyValue(vw, src)
if err != nil {
return 0, dst, err
}
return bsontype.Type(vw.buf[start]), vw.buf[start+2:], nil
}
// CopyValue will copy a single value from src to dst.
func (c Copier) CopyValue(dst ValueWriter, src ValueReader) error {
var err error
switch src.Type() {
case bsontype.Double:
var f64 float64
f64, err = src.ReadDouble()
if err != nil {
break
}
err = dst.WriteDouble(f64)
case bsontype.String:
var str string
str, err = src.ReadString()
if err != nil {
return err
}
err = dst.WriteString(str)
case bsontype.EmbeddedDocument:
err = c.CopyDocument(dst, src)
case bsontype.Array:
err = c.copyArray(dst, src)
case bsontype.Binary:
var data []byte
var subtype byte
data, subtype, err = src.ReadBinary()
if err != nil {
break
}
err = dst.WriteBinaryWithSubtype(data, subtype)
case bsontype.Undefined:
err = src.ReadUndefined()
if err != nil {
break
}
err = dst.WriteUndefined()
case bsontype.ObjectID:
var oid primitive.ObjectID
oid, err = src.ReadObjectID()
if err != nil {
break
}
err = dst.WriteObjectID(oid)
case bsontype.Boolean:
var b bool
b, err = src.ReadBoolean()
if err != nil {
break
}
err = dst.WriteBoolean(b)
case bsontype.DateTime:
var dt int64
dt, err = src.ReadDateTime()
if err != nil {
break
}
err = dst.WriteDateTime(dt)
case bsontype.Null:
err = src.ReadNull()
if err != nil {
break
}
err = dst.WriteNull()
case bsontype.Regex:
var pattern, options string
pattern, options, err = src.ReadRegex()
if err != nil {
break
}
err = dst.WriteRegex(pattern, options)
case bsontype.DBPointer:
var ns string
var pointer primitive.ObjectID
ns, pointer, err = src.ReadDBPointer()
if err != nil {
break
}
err = dst.WriteDBPointer(ns, pointer)
case bsontype.JavaScript:
var js string
js, err = src.ReadJavascript()
if err != nil {
break
}
err = dst.WriteJavascript(js)
case bsontype.Symbol:
var symbol string
symbol, err = src.ReadSymbol()
if err != nil {
break
}
err = dst.WriteSymbol(symbol)
case bsontype.CodeWithScope:
var code string
var srcScope DocumentReader
code, srcScope, err = src.ReadCodeWithScope()
if err != nil {
break
}
var dstScope DocumentWriter
dstScope, err = dst.WriteCodeWithScope(code)
if err != nil {
break
}
err = c.copyDocumentCore(dstScope, srcScope)
case bsontype.Int32:
var i32 int32
i32, err = src.ReadInt32()
if err != nil {
break
}
err = dst.WriteInt32(i32)
case bsontype.Timestamp:
var t, i uint32
t, i, err = src.ReadTimestamp()
if err != nil {
break
}
err = dst.WriteTimestamp(t, i)
case bsontype.Int64:
var i64 int64
i64, err = src.ReadInt64()
if err != nil {
break
}
err = dst.WriteInt64(i64)
case bsontype.Decimal128:
var d128 primitive.Decimal128
d128, err = src.ReadDecimal128()
if err != nil {
break
}
err = dst.WriteDecimal128(d128)
case bsontype.MinKey:
err = src.ReadMinKey()
if err != nil {
break
}
err = dst.WriteMinKey()
case bsontype.MaxKey:
err = src.ReadMaxKey()
if err != nil {
break
}
err = dst.WriteMaxKey()
default:
err = fmt.Errorf("Cannot copy unknown BSON type %s", src.Type())
}
return err
}
func (c Copier) copyArray(dst ValueWriter, src ValueReader) error {
ar, err := src.ReadArray()
if err != nil {
return err
}
aw, err := dst.WriteArray()
if err != nil {
return err
}
for {
vr, err := ar.ReadValue()
if err == ErrEOA {
break
}
if err != nil {
return err
}
vw, err := aw.WriteArrayElement()
if err != nil {
return err
}
err = c.CopyValue(vw, vr)
if err != nil {
return err
}
}
return aw.WriteArrayEnd()
}
func (c Copier) copyDocumentCore(dw DocumentWriter, dr DocumentReader) error {
for {
key, vr, err := dr.ReadElement()
if err == ErrEOD {
break
}
if err != nil {
return err
}
vw, err := dw.WriteDocumentElement(key)
if err != nil {
return err
}
err = c.CopyValue(vw, vr)
if err != nil {
return err
}
}
return dw.WriteDocumentEnd()
}

View File

@ -0,0 +1,529 @@
// 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 bsonrw
import (
"bytes"
"errors"
"fmt"
"testing"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
func TestCopier(t *testing.T) {
t.Run("CopyDocument", func(t *testing.T) {
t.Run("ReadDocument Error", func(t *testing.T) {
want := errors.New("ReadDocumentError")
src := &TestValueReaderWriter{t: t, err: want, errAfter: llvrwReadDocument}
got := Copier{}.CopyDocument(nil, src)
if !compareErrors(got, want) {
t.Errorf("Did not receive correct error. got %v; want %v", got, want)
}
})
t.Run("WriteDocument Error", func(t *testing.T) {
want := errors.New("WriteDocumentError")
src := &TestValueReaderWriter{}
dst := &TestValueReaderWriter{t: t, err: want, errAfter: llvrwWriteDocument}
got := Copier{}.CopyDocument(dst, src)
if !compareErrors(got, want) {
t.Errorf("Did not receive correct error. got %v; want %v", got, want)
}
})
t.Run("success", func(t *testing.T) {
idx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendStringElement(doc, "Hello", "world")
doc, err := bsoncore.AppendDocumentEnd(doc, idx)
noerr(t, err)
src := newValueReader(doc)
dst := newValueWriterFromSlice(make([]byte, 0))
want := doc
err = Copier{}.CopyDocument(dst, src)
noerr(t, err)
got := dst.buf
if !bytes.Equal(got, want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, want)
}
})
})
t.Run("copyArray", func(t *testing.T) {
t.Run("ReadArray Error", func(t *testing.T) {
want := errors.New("ReadArrayError")
src := &TestValueReaderWriter{t: t, err: want, errAfter: llvrwReadArray}
got := Copier{}.copyArray(nil, src)
if !compareErrors(got, want) {
t.Errorf("Did not receive correct error. got %v; want %v", got, want)
}
})
t.Run("WriteArray Error", func(t *testing.T) {
want := errors.New("WriteArrayError")
src := &TestValueReaderWriter{}
dst := &TestValueReaderWriter{t: t, err: want, errAfter: llvrwWriteArray}
got := Copier{}.copyArray(dst, src)
if !compareErrors(got, want) {
t.Errorf("Did not receive correct error. got %v; want %v", got, want)
}
})
t.Run("success", func(t *testing.T) {
idx, doc := bsoncore.AppendDocumentStart(nil)
aidx, doc := bsoncore.AppendArrayElementStart(doc, "foo")
doc = bsoncore.AppendStringElement(doc, "0", "Hello, world!")
doc, err := bsoncore.AppendArrayEnd(doc, aidx)
noerr(t, err)
doc, err = bsoncore.AppendDocumentEnd(doc, idx)
noerr(t, err)
src := newValueReader(doc)
_, err = src.ReadDocument()
noerr(t, err)
_, _, err = src.ReadElement()
noerr(t, err)
dst := newValueWriterFromSlice(make([]byte, 0))
_, err = dst.WriteDocument()
noerr(t, err)
_, err = dst.WriteDocumentElement("foo")
noerr(t, err)
want := doc
err = Copier{}.copyArray(dst, src)
noerr(t, err)
err = dst.WriteDocumentEnd()
noerr(t, err)
got := dst.buf
if !bytes.Equal(got, want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, want)
}
})
})
t.Run("CopyValue", func(t *testing.T) {
testCases := []struct {
name string
dst *TestValueReaderWriter
src *TestValueReaderWriter
err error
}{
{
"Double/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Double, err: errors.New("1"), errAfter: llvrwReadDouble},
errors.New("1"),
},
{
"Double/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Double, err: errors.New("2"), errAfter: llvrwWriteDouble},
&TestValueReaderWriter{bsontype: bsontype.Double, readval: float64(3.14159)},
errors.New("2"),
},
{
"String/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.String, err: errors.New("1"), errAfter: llvrwReadString},
errors.New("1"),
},
{
"String/dst/error",
&TestValueReaderWriter{bsontype: bsontype.String, err: errors.New("2"), errAfter: llvrwWriteString},
&TestValueReaderWriter{bsontype: bsontype.String, readval: "hello, world"},
errors.New("2"),
},
{
"Document/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.EmbeddedDocument, err: errors.New("1"), errAfter: llvrwReadDocument},
errors.New("1"),
},
{
"Array/dst/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Array, err: errors.New("2"), errAfter: llvrwReadArray},
errors.New("2"),
},
{
"Binary/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Binary, err: errors.New("1"), errAfter: llvrwReadBinary},
errors.New("1"),
},
{
"Binary/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Binary, err: errors.New("2"), errAfter: llvrwWriteBinaryWithSubtype},
&TestValueReaderWriter{
bsontype: bsontype.Binary,
readval: bsoncore.Value{
Type: bsontype.Binary,
Data: []byte{0x03, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x02, 0x03},
},
},
errors.New("2"),
},
{
"Undefined/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Undefined, err: errors.New("1"), errAfter: llvrwReadUndefined},
errors.New("1"),
},
{
"Undefined/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Undefined, err: errors.New("2"), errAfter: llvrwWriteUndefined},
&TestValueReaderWriter{bsontype: bsontype.Undefined},
errors.New("2"),
},
{
"ObjectID/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.ObjectID, err: errors.New("1"), errAfter: llvrwReadObjectID},
errors.New("1"),
},
{
"ObjectID/dst/error",
&TestValueReaderWriter{bsontype: bsontype.ObjectID, err: errors.New("2"), errAfter: llvrwWriteObjectID},
&TestValueReaderWriter{bsontype: bsontype.ObjectID, readval: primitive.ObjectID{0x01, 0x02, 0x03}},
errors.New("2"),
},
{
"Boolean/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Boolean, err: errors.New("1"), errAfter: llvrwReadBoolean},
errors.New("1"),
},
{
"Boolean/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Boolean, err: errors.New("2"), errAfter: llvrwWriteBoolean},
&TestValueReaderWriter{bsontype: bsontype.Boolean, readval: bool(true)},
errors.New("2"),
},
{
"DateTime/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.DateTime, err: errors.New("1"), errAfter: llvrwReadDateTime},
errors.New("1"),
},
{
"DateTime/dst/error",
&TestValueReaderWriter{bsontype: bsontype.DateTime, err: errors.New("2"), errAfter: llvrwWriteDateTime},
&TestValueReaderWriter{bsontype: bsontype.DateTime, readval: int64(1234567890)},
errors.New("2"),
},
{
"Null/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Null, err: errors.New("1"), errAfter: llvrwReadNull},
errors.New("1"),
},
{
"Null/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Null, err: errors.New("2"), errAfter: llvrwWriteNull},
&TestValueReaderWriter{bsontype: bsontype.Null},
errors.New("2"),
},
{
"Regex/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Regex, err: errors.New("1"), errAfter: llvrwReadRegex},
errors.New("1"),
},
{
"Regex/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Regex, err: errors.New("2"), errAfter: llvrwWriteRegex},
&TestValueReaderWriter{
bsontype: bsontype.Regex,
readval: bsoncore.Value{
Type: bsontype.Regex,
Data: bsoncore.AppendRegex(nil, "hello", "world"),
},
},
errors.New("2"),
},
{
"DBPointer/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.DBPointer, err: errors.New("1"), errAfter: llvrwReadDBPointer},
errors.New("1"),
},
{
"DBPointer/dst/error",
&TestValueReaderWriter{bsontype: bsontype.DBPointer, err: errors.New("2"), errAfter: llvrwWriteDBPointer},
&TestValueReaderWriter{
bsontype: bsontype.DBPointer,
readval: bsoncore.Value{
Type: bsontype.DBPointer,
Data: bsoncore.AppendDBPointer(nil, "foo", primitive.ObjectID{0x01, 0x02, 0x03}),
},
},
errors.New("2"),
},
{
"Javascript/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.JavaScript, err: errors.New("1"), errAfter: llvrwReadJavascript},
errors.New("1"),
},
{
"Javascript/dst/error",
&TestValueReaderWriter{bsontype: bsontype.JavaScript, err: errors.New("2"), errAfter: llvrwWriteJavascript},
&TestValueReaderWriter{bsontype: bsontype.JavaScript, readval: "hello, world"},
errors.New("2"),
},
{
"Symbol/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Symbol, err: errors.New("1"), errAfter: llvrwReadSymbol},
errors.New("1"),
},
{
"Symbol/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Symbol, err: errors.New("2"), errAfter: llvrwWriteSymbol},
&TestValueReaderWriter{
bsontype: bsontype.Symbol,
readval: bsoncore.Value{
Type: bsontype.Symbol,
Data: bsoncore.AppendSymbol(nil, "hello, world"),
},
},
errors.New("2"),
},
{
"CodeWithScope/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.CodeWithScope, err: errors.New("1"), errAfter: llvrwReadCodeWithScope},
errors.New("1"),
},
{
"CodeWithScope/dst/error",
&TestValueReaderWriter{bsontype: bsontype.CodeWithScope, err: errors.New("2"), errAfter: llvrwWriteCodeWithScope},
&TestValueReaderWriter{bsontype: bsontype.CodeWithScope},
errors.New("2"),
},
{
"CodeWithScope/dst/copyDocumentCore error",
&TestValueReaderWriter{err: errors.New("3"), errAfter: llvrwWriteDocumentElement},
&TestValueReaderWriter{bsontype: bsontype.CodeWithScope},
errors.New("3"),
},
{
"Int32/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Int32, err: errors.New("1"), errAfter: llvrwReadInt32},
errors.New("1"),
},
{
"Int32/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Int32, err: errors.New("2"), errAfter: llvrwWriteInt32},
&TestValueReaderWriter{bsontype: bsontype.Int32, readval: int32(12345)},
errors.New("2"),
},
{
"Timestamp/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Timestamp, err: errors.New("1"), errAfter: llvrwReadTimestamp},
errors.New("1"),
},
{
"Timestamp/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Timestamp, err: errors.New("2"), errAfter: llvrwWriteTimestamp},
&TestValueReaderWriter{
bsontype: bsontype.Timestamp,
readval: bsoncore.Value{
Type: bsontype.Timestamp,
Data: bsoncore.AppendTimestamp(nil, 12345, 67890),
},
},
errors.New("2"),
},
{
"Int64/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Int64, err: errors.New("1"), errAfter: llvrwReadInt64},
errors.New("1"),
},
{
"Int64/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Int64, err: errors.New("2"), errAfter: llvrwWriteInt64},
&TestValueReaderWriter{bsontype: bsontype.Int64, readval: int64(1234567890)},
errors.New("2"),
},
{
"Decimal128/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.Decimal128, err: errors.New("1"), errAfter: llvrwReadDecimal128},
errors.New("1"),
},
{
"Decimal128/dst/error",
&TestValueReaderWriter{bsontype: bsontype.Decimal128, err: errors.New("2"), errAfter: llvrwWriteDecimal128},
&TestValueReaderWriter{bsontype: bsontype.Decimal128, readval: primitive.NewDecimal128(12345, 67890)},
errors.New("2"),
},
{
"MinKey/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.MinKey, err: errors.New("1"), errAfter: llvrwReadMinKey},
errors.New("1"),
},
{
"MinKey/dst/error",
&TestValueReaderWriter{bsontype: bsontype.MinKey, err: errors.New("2"), errAfter: llvrwWriteMinKey},
&TestValueReaderWriter{bsontype: bsontype.MinKey},
errors.New("2"),
},
{
"MaxKey/src/error",
&TestValueReaderWriter{},
&TestValueReaderWriter{bsontype: bsontype.MaxKey, err: errors.New("1"), errAfter: llvrwReadMaxKey},
errors.New("1"),
},
{
"MaxKey/dst/error",
&TestValueReaderWriter{bsontype: bsontype.MaxKey, err: errors.New("2"), errAfter: llvrwWriteMaxKey},
&TestValueReaderWriter{bsontype: bsontype.MaxKey},
errors.New("2"),
},
{
"Unknown BSON type error",
&TestValueReaderWriter{},
&TestValueReaderWriter{},
fmt.Errorf("Cannot copy unknown BSON type %s", bsontype.Type(0)),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.dst.t, tc.src.t = t, t
err := Copier{}.CopyValue(tc.dst, tc.src)
if !compareErrors(err, tc.err) {
t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err)
}
})
}
})
t.Run("CopyValueFromBytes", func(t *testing.T) {
t.Run("BytesWriter", func(t *testing.T) {
vw := newValueWriterFromSlice(make([]byte, 0))
_, err := vw.WriteDocument()
noerr(t, err)
_, err = vw.WriteDocumentElement("foo")
noerr(t, err)
err = Copier{}.CopyValueFromBytes(vw, bsontype.String, bsoncore.AppendString(nil, "bar"))
noerr(t, err)
err = vw.WriteDocumentEnd()
noerr(t, err)
var idx int32
want, err := bsoncore.AppendDocumentEnd(
bsoncore.AppendStringElement(
bsoncore.AppendDocumentStartInline(nil, &idx),
"foo", "bar",
),
idx,
)
noerr(t, err)
got := vw.buf
if !bytes.Equal(got, want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, want)
}
})
t.Run("Non BytesWriter", func(t *testing.T) {
llvrw := &TestValueReaderWriter{t: t}
err := Copier{}.CopyValueFromBytes(llvrw, bsontype.String, bsoncore.AppendString(nil, "bar"))
noerr(t, err)
got, want := llvrw.invoked, llvrwWriteString
if got != want {
t.Errorf("Incorrect method invoked on llvrw. got %v; want %v", got, want)
}
})
})
t.Run("CopyValueToBytes", func(t *testing.T) {
t.Run("BytesReader", func(t *testing.T) {
var idx int32
b, err := bsoncore.AppendDocumentEnd(
bsoncore.AppendStringElement(
bsoncore.AppendDocumentStartInline(nil, &idx),
"hello", "world",
),
idx,
)
noerr(t, err)
vr := newValueReader(b)
_, err = vr.ReadDocument()
noerr(t, err)
_, _, err = vr.ReadElement()
noerr(t, err)
btype, got, err := Copier{}.CopyValueToBytes(vr)
noerr(t, err)
want := bsoncore.AppendString(nil, "world")
if btype != bsontype.String {
t.Errorf("Incorrect type returned. got %v; want %v", btype, bsontype.String)
}
if !bytes.Equal(got, want) {
t.Errorf("Bytes do not match. got %v; want %v", got, want)
}
})
t.Run("Non BytesReader", func(t *testing.T) {
llvrw := &TestValueReaderWriter{t: t, bsontype: bsontype.String, readval: "Hello, world!"}
btype, got, err := Copier{}.CopyValueToBytes(llvrw)
noerr(t, err)
want := bsoncore.AppendString(nil, "Hello, world!")
if btype != bsontype.String {
t.Errorf("Incorrect type returned. got %v; want %v", btype, bsontype.String)
}
if !bytes.Equal(got, want) {
t.Errorf("Bytes do not match. got %v; want %v", got, want)
}
})
})
t.Run("AppendValueBytes", func(t *testing.T) {
t.Run("BytesReader", func(t *testing.T) {
var idx int32
b, err := bsoncore.AppendDocumentEnd(
bsoncore.AppendStringElement(
bsoncore.AppendDocumentStartInline(nil, &idx),
"hello", "world",
),
idx,
)
noerr(t, err)
vr := newValueReader(b)
_, err = vr.ReadDocument()
noerr(t, err)
_, _, err = vr.ReadElement()
noerr(t, err)
btype, got, err := Copier{}.AppendValueBytes(nil, vr)
noerr(t, err)
want := bsoncore.AppendString(nil, "world")
if btype != bsontype.String {
t.Errorf("Incorrect type returned. got %v; want %v", btype, bsontype.String)
}
if !bytes.Equal(got, want) {
t.Errorf("Bytes do not match. got %v; want %v", got, want)
}
})
t.Run("Non BytesReader", func(t *testing.T) {
llvrw := &TestValueReaderWriter{t: t, bsontype: bsontype.String, readval: "Hello, world!"}
btype, got, err := Copier{}.AppendValueBytes(nil, llvrw)
noerr(t, err)
want := bsoncore.AppendString(nil, "Hello, world!")
if btype != bsontype.String {
t.Errorf("Incorrect type returned. got %v; want %v", btype, bsontype.String)
}
if !bytes.Equal(got, want) {
t.Errorf("Bytes do not match. got %v; want %v", got, want)
}
})
t.Run("CopyValue error", func(t *testing.T) {
want := errors.New("CopyValue error")
llvrw := &TestValueReaderWriter{t: t, bsontype: bsontype.String, err: want, errAfter: llvrwReadString}
_, _, got := Copier{}.AppendValueBytes(make([]byte, 0), llvrw)
if !compareErrors(got, want) {
t.Errorf("Errors do not match. got %v; want %v", got, want)
}
})
})
}

9
mongo/bson/bsonrw/doc.go Normal file
View File

@ -0,0 +1,9 @@
// 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 bsonrw contains abstractions for reading and writing
// BSON and BSON like types from sources.
package bsonrw // import "go.mongodb.org/mongo-driver/bson/bsonrw"

View File

@ -0,0 +1,806 @@
// 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 bsonrw
import (
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
const maxNestingDepth = 200
// ErrInvalidJSON indicates the JSON input is invalid
var ErrInvalidJSON = errors.New("invalid JSON input")
type jsonParseState byte
const (
jpsStartState jsonParseState = iota
jpsSawBeginObject
jpsSawEndObject
jpsSawBeginArray
jpsSawEndArray
jpsSawColon
jpsSawComma
jpsSawKey
jpsSawValue
jpsDoneState
jpsInvalidState
)
type jsonParseMode byte
const (
jpmInvalidMode jsonParseMode = iota
jpmObjectMode
jpmArrayMode
)
type extJSONValue struct {
t bsontype.Type
v interface{}
}
type extJSONObject struct {
keys []string
values []*extJSONValue
}
type extJSONParser struct {
js *jsonScanner
s jsonParseState
m []jsonParseMode
k string
v *extJSONValue
err error
canonical bool
depth int
maxDepth int
emptyObject bool
relaxedUUID bool
}
// newExtJSONParser returns a new extended JSON parser, ready to to begin
// parsing from the first character of the argued json input. It will not
// perform any read-ahead and will therefore not report any errors about
// malformed JSON at this point.
func newExtJSONParser(r io.Reader, canonical bool) *extJSONParser {
return &extJSONParser{
js: &jsonScanner{r: r},
s: jpsStartState,
m: []jsonParseMode{},
canonical: canonical,
maxDepth: maxNestingDepth,
}
}
// peekType examines the next value and returns its BSON Type
func (ejp *extJSONParser) peekType() (bsontype.Type, error) {
var t bsontype.Type
var err error
initialState := ejp.s
ejp.advanceState()
switch ejp.s {
case jpsSawValue:
t = ejp.v.t
case jpsSawBeginArray:
t = bsontype.Array
case jpsInvalidState:
err = ejp.err
case jpsSawComma:
// in array mode, seeing a comma means we need to progress again to actually observe a type
if ejp.peekMode() == jpmArrayMode {
return ejp.peekType()
}
case jpsSawEndArray:
// this would only be a valid state if we were in array mode, so return end-of-array error
err = ErrEOA
case jpsSawBeginObject:
// peek key to determine type
ejp.advanceState()
switch ejp.s {
case jpsSawEndObject: // empty embedded document
t = bsontype.EmbeddedDocument
ejp.emptyObject = true
case jpsInvalidState:
err = ejp.err
case jpsSawKey:
if initialState == jpsStartState {
return bsontype.EmbeddedDocument, nil
}
t = wrapperKeyBSONType(ejp.k)
// if $uuid is encountered, parse as binary subtype 4
if ejp.k == "$uuid" {
ejp.relaxedUUID = true
t = bsontype.Binary
}
switch t {
case bsontype.JavaScript:
// just saw $code, need to check for $scope at same level
_, err = ejp.readValue(bsontype.JavaScript)
if err != nil {
break
}
switch ejp.s {
case jpsSawEndObject: // type is TypeJavaScript
case jpsSawComma:
ejp.advanceState()
if ejp.s == jpsSawKey && ejp.k == "$scope" {
t = bsontype.CodeWithScope
} else {
err = fmt.Errorf("invalid extended JSON: unexpected key %s in CodeWithScope object", ejp.k)
}
case jpsInvalidState:
err = ejp.err
default:
err = ErrInvalidJSON
}
case bsontype.CodeWithScope:
err = errors.New("invalid extended JSON: code with $scope must contain $code before $scope")
}
}
}
return t, err
}
// readKey parses the next key and its type and returns them
func (ejp *extJSONParser) readKey() (string, bsontype.Type, error) {
if ejp.emptyObject {
ejp.emptyObject = false
return "", 0, ErrEOD
}
// advance to key (or return with error)
switch ejp.s {
case jpsStartState:
ejp.advanceState()
if ejp.s == jpsSawBeginObject {
ejp.advanceState()
}
case jpsSawBeginObject:
ejp.advanceState()
case jpsSawValue, jpsSawEndObject, jpsSawEndArray:
ejp.advanceState()
switch ejp.s {
case jpsSawBeginObject, jpsSawComma:
ejp.advanceState()
case jpsSawEndObject:
return "", 0, ErrEOD
case jpsDoneState:
return "", 0, io.EOF
case jpsInvalidState:
return "", 0, ejp.err
default:
return "", 0, ErrInvalidJSON
}
case jpsSawKey: // do nothing (key was peeked before)
default:
return "", 0, invalidRequestError("key")
}
// read key
var key string
switch ejp.s {
case jpsSawKey:
key = ejp.k
case jpsSawEndObject:
return "", 0, ErrEOD
case jpsInvalidState:
return "", 0, ejp.err
default:
return "", 0, invalidRequestError("key")
}
// check for colon
ejp.advanceState()
if err := ensureColon(ejp.s, key); err != nil {
return "", 0, err
}
// peek at the value to determine type
t, err := ejp.peekType()
if err != nil {
return "", 0, err
}
return key, t, nil
}
// readValue returns the value corresponding to the Type returned by peekType
func (ejp *extJSONParser) readValue(t bsontype.Type) (*extJSONValue, error) {
if ejp.s == jpsInvalidState {
return nil, ejp.err
}
var v *extJSONValue
switch t {
case bsontype.Null, bsontype.Boolean, bsontype.String:
if ejp.s != jpsSawValue {
return nil, invalidRequestError(t.String())
}
v = ejp.v
case bsontype.Int32, bsontype.Int64, bsontype.Double:
// relaxed version allows these to be literal number values
if ejp.s == jpsSawValue {
v = ejp.v
break
}
fallthrough
case bsontype.Decimal128, bsontype.Symbol, bsontype.ObjectID, bsontype.MinKey, bsontype.MaxKey, bsontype.Undefined:
switch ejp.s {
case jpsSawKey:
// read colon
ejp.advanceState()
if err := ensureColon(ejp.s, ejp.k); err != nil {
return nil, err
}
// read value
ejp.advanceState()
if ejp.s != jpsSawValue || !ejp.ensureExtValueType(t) {
return nil, invalidJSONErrorForType("value", t)
}
v = ejp.v
// read end object
ejp.advanceState()
if ejp.s != jpsSawEndObject {
return nil, invalidJSONErrorForType("} after value", t)
}
default:
return nil, invalidRequestError(t.String())
}
case bsontype.Binary, bsontype.Regex, bsontype.Timestamp, bsontype.DBPointer:
if ejp.s != jpsSawKey {
return nil, invalidRequestError(t.String())
}
// read colon
ejp.advanceState()
if err := ensureColon(ejp.s, ejp.k); err != nil {
return nil, err
}
ejp.advanceState()
if t == bsontype.Binary && ejp.s == jpsSawValue {
// convert relaxed $uuid format
if ejp.relaxedUUID {
defer func() { ejp.relaxedUUID = false }()
uuid, err := ejp.v.parseSymbol()
if err != nil {
return nil, err
}
// RFC 4122 defines the length of a UUID as 36 and the hyphens in a UUID as appearing
// in the 8th, 13th, 18th, and 23rd characters.
//
// See https://tools.ietf.org/html/rfc4122#section-3
valid := len(uuid) == 36 &&
string(uuid[8]) == "-" &&
string(uuid[13]) == "-" &&
string(uuid[18]) == "-" &&
string(uuid[23]) == "-"
if !valid {
return nil, fmt.Errorf("$uuid value does not follow RFC 4122 format regarding length and hyphens")
}
// remove hyphens
uuidNoHyphens := strings.Replace(uuid, "-", "", -1)
if len(uuidNoHyphens) != 32 {
return nil, fmt.Errorf("$uuid value does not follow RFC 4122 format regarding length and hyphens")
}
// convert hex to bytes
bytes, err := hex.DecodeString(uuidNoHyphens)
if err != nil {
return nil, fmt.Errorf("$uuid value does not follow RFC 4122 format regarding hex bytes: %v", err)
}
ejp.advanceState()
if ejp.s != jpsSawEndObject {
return nil, invalidJSONErrorForType("$uuid and value and then }", bsontype.Binary)
}
base64 := &extJSONValue{
t: bsontype.String,
v: base64.StdEncoding.EncodeToString(bytes),
}
subType := &extJSONValue{
t: bsontype.String,
v: "04",
}
v = &extJSONValue{
t: bsontype.EmbeddedDocument,
v: &extJSONObject{
keys: []string{"base64", "subType"},
values: []*extJSONValue{base64, subType},
},
}
break
}
// convert legacy $binary format
base64 := ejp.v
ejp.advanceState()
if ejp.s != jpsSawComma {
return nil, invalidJSONErrorForType(",", bsontype.Binary)
}
ejp.advanceState()
key, t, err := ejp.readKey()
if err != nil {
return nil, err
}
if key != "$type" {
return nil, invalidJSONErrorForType("$type", bsontype.Binary)
}
subType, err := ejp.readValue(t)
if err != nil {
return nil, err
}
ejp.advanceState()
if ejp.s != jpsSawEndObject {
return nil, invalidJSONErrorForType("2 key-value pairs and then }", bsontype.Binary)
}
v = &extJSONValue{
t: bsontype.EmbeddedDocument,
v: &extJSONObject{
keys: []string{"base64", "subType"},
values: []*extJSONValue{base64, subType},
},
}
break
}
// read KV pairs
if ejp.s != jpsSawBeginObject {
return nil, invalidJSONErrorForType("{", t)
}
keys, vals, err := ejp.readObject(2, true)
if err != nil {
return nil, err
}
ejp.advanceState()
if ejp.s != jpsSawEndObject {
return nil, invalidJSONErrorForType("2 key-value pairs and then }", t)
}
v = &extJSONValue{t: bsontype.EmbeddedDocument, v: &extJSONObject{keys: keys, values: vals}}
case bsontype.DateTime:
switch ejp.s {
case jpsSawValue:
v = ejp.v
case jpsSawKey:
// read colon
ejp.advanceState()
if err := ensureColon(ejp.s, ejp.k); err != nil {
return nil, err
}
ejp.advanceState()
switch ejp.s {
case jpsSawBeginObject:
keys, vals, err := ejp.readObject(1, true)
if err != nil {
return nil, err
}
v = &extJSONValue{t: bsontype.EmbeddedDocument, v: &extJSONObject{keys: keys, values: vals}}
case jpsSawValue:
if ejp.canonical {
return nil, invalidJSONError("{")
}
v = ejp.v
default:
if ejp.canonical {
return nil, invalidJSONErrorForType("object", t)
}
return nil, invalidJSONErrorForType("ISO-8601 Internet Date/Time Format as described in RFC-3339", t)
}
ejp.advanceState()
if ejp.s != jpsSawEndObject {
return nil, invalidJSONErrorForType("value and then }", t)
}
default:
return nil, invalidRequestError(t.String())
}
case bsontype.JavaScript:
switch ejp.s {
case jpsSawKey:
// read colon
ejp.advanceState()
if err := ensureColon(ejp.s, ejp.k); err != nil {
return nil, err
}
// read value
ejp.advanceState()
if ejp.s != jpsSawValue {
return nil, invalidJSONErrorForType("value", t)
}
v = ejp.v
// read end object or comma and just return
ejp.advanceState()
case jpsSawEndObject:
v = ejp.v
default:
return nil, invalidRequestError(t.String())
}
case bsontype.CodeWithScope:
if ejp.s == jpsSawKey && ejp.k == "$scope" {
v = ejp.v // this is the $code string from earlier
// read colon
ejp.advanceState()
if err := ensureColon(ejp.s, ejp.k); err != nil {
return nil, err
}
// read {
ejp.advanceState()
if ejp.s != jpsSawBeginObject {
return nil, invalidJSONError("$scope to be embedded document")
}
} else {
return nil, invalidRequestError(t.String())
}
case bsontype.EmbeddedDocument, bsontype.Array:
return nil, invalidRequestError(t.String())
}
return v, nil
}
// readObject is a utility method for reading full objects of known (or expected) size
// it is useful for extended JSON types such as binary, datetime, regex, and timestamp
func (ejp *extJSONParser) readObject(numKeys int, started bool) ([]string, []*extJSONValue, error) {
keys := make([]string, numKeys)
vals := make([]*extJSONValue, numKeys)
if !started {
ejp.advanceState()
if ejp.s != jpsSawBeginObject {
return nil, nil, invalidJSONError("{")
}
}
for i := 0; i < numKeys; i++ {
key, t, err := ejp.readKey()
if err != nil {
return nil, nil, err
}
switch ejp.s {
case jpsSawKey:
v, err := ejp.readValue(t)
if err != nil {
return nil, nil, err
}
keys[i] = key
vals[i] = v
case jpsSawValue:
keys[i] = key
vals[i] = ejp.v
default:
return nil, nil, invalidJSONError("value")
}
}
ejp.advanceState()
if ejp.s != jpsSawEndObject {
return nil, nil, invalidJSONError("}")
}
return keys, vals, nil
}
// advanceState reads the next JSON token from the scanner and transitions
// from the current state based on that token's type
func (ejp *extJSONParser) advanceState() {
if ejp.s == jpsDoneState || ejp.s == jpsInvalidState {
return
}
jt, err := ejp.js.nextToken()
if err != nil {
ejp.err = err
ejp.s = jpsInvalidState
return
}
valid := ejp.validateToken(jt.t)
if !valid {
ejp.err = unexpectedTokenError(jt)
ejp.s = jpsInvalidState
return
}
switch jt.t {
case jttBeginObject:
ejp.s = jpsSawBeginObject
ejp.pushMode(jpmObjectMode)
ejp.depth++
if ejp.depth > ejp.maxDepth {
ejp.err = nestingDepthError(jt.p, ejp.depth)
ejp.s = jpsInvalidState
}
case jttEndObject:
ejp.s = jpsSawEndObject
ejp.depth--
if ejp.popMode() != jpmObjectMode {
ejp.err = unexpectedTokenError(jt)
ejp.s = jpsInvalidState
}
case jttBeginArray:
ejp.s = jpsSawBeginArray
ejp.pushMode(jpmArrayMode)
case jttEndArray:
ejp.s = jpsSawEndArray
if ejp.popMode() != jpmArrayMode {
ejp.err = unexpectedTokenError(jt)
ejp.s = jpsInvalidState
}
case jttColon:
ejp.s = jpsSawColon
case jttComma:
ejp.s = jpsSawComma
case jttEOF:
ejp.s = jpsDoneState
if len(ejp.m) != 0 {
ejp.err = unexpectedTokenError(jt)
ejp.s = jpsInvalidState
}
case jttString:
switch ejp.s {
case jpsSawComma:
if ejp.peekMode() == jpmArrayMode {
ejp.s = jpsSawValue
ejp.v = extendJSONToken(jt)
return
}
fallthrough
case jpsSawBeginObject:
ejp.s = jpsSawKey
ejp.k = jt.v.(string)
return
}
fallthrough
default:
ejp.s = jpsSawValue
ejp.v = extendJSONToken(jt)
}
}
var jpsValidTransitionTokens = map[jsonParseState]map[jsonTokenType]bool{
jpsStartState: {
jttBeginObject: true,
jttBeginArray: true,
jttInt32: true,
jttInt64: true,
jttDouble: true,
jttString: true,
jttBool: true,
jttNull: true,
jttEOF: true,
},
jpsSawBeginObject: {
jttEndObject: true,
jttString: true,
},
jpsSawEndObject: {
jttEndObject: true,
jttEndArray: true,
jttComma: true,
jttEOF: true,
},
jpsSawBeginArray: {
jttBeginObject: true,
jttBeginArray: true,
jttEndArray: true,
jttInt32: true,
jttInt64: true,
jttDouble: true,
jttString: true,
jttBool: true,
jttNull: true,
},
jpsSawEndArray: {
jttEndObject: true,
jttEndArray: true,
jttComma: true,
jttEOF: true,
},
jpsSawColon: {
jttBeginObject: true,
jttBeginArray: true,
jttInt32: true,
jttInt64: true,
jttDouble: true,
jttString: true,
jttBool: true,
jttNull: true,
},
jpsSawComma: {
jttBeginObject: true,
jttBeginArray: true,
jttInt32: true,
jttInt64: true,
jttDouble: true,
jttString: true,
jttBool: true,
jttNull: true,
},
jpsSawKey: {
jttColon: true,
},
jpsSawValue: {
jttEndObject: true,
jttEndArray: true,
jttComma: true,
jttEOF: true,
},
jpsDoneState: {},
jpsInvalidState: {},
}
func (ejp *extJSONParser) validateToken(jtt jsonTokenType) bool {
switch ejp.s {
case jpsSawEndObject:
// if we are at depth zero and the next token is a '{',
// we can consider it valid only if we are not in array mode.
if jtt == jttBeginObject && ejp.depth == 0 {
return ejp.peekMode() != jpmArrayMode
}
case jpsSawComma:
switch ejp.peekMode() {
// the only valid next token after a comma inside a document is a string (a key)
case jpmObjectMode:
return jtt == jttString
case jpmInvalidMode:
return false
}
}
_, ok := jpsValidTransitionTokens[ejp.s][jtt]
return ok
}
// ensureExtValueType returns true if the current value has the expected
// value type for single-key extended JSON types. For example,
// {"$numberInt": v} v must be TypeString
func (ejp *extJSONParser) ensureExtValueType(t bsontype.Type) bool {
switch t {
case bsontype.MinKey, bsontype.MaxKey:
return ejp.v.t == bsontype.Int32
case bsontype.Undefined:
return ejp.v.t == bsontype.Boolean
case bsontype.Int32, bsontype.Int64, bsontype.Double, bsontype.Decimal128, bsontype.Symbol, bsontype.ObjectID:
return ejp.v.t == bsontype.String
default:
return false
}
}
func (ejp *extJSONParser) pushMode(m jsonParseMode) {
ejp.m = append(ejp.m, m)
}
func (ejp *extJSONParser) popMode() jsonParseMode {
l := len(ejp.m)
if l == 0 {
return jpmInvalidMode
}
m := ejp.m[l-1]
ejp.m = ejp.m[:l-1]
return m
}
func (ejp *extJSONParser) peekMode() jsonParseMode {
l := len(ejp.m)
if l == 0 {
return jpmInvalidMode
}
return ejp.m[l-1]
}
func extendJSONToken(jt *jsonToken) *extJSONValue {
var t bsontype.Type
switch jt.t {
case jttInt32:
t = bsontype.Int32
case jttInt64:
t = bsontype.Int64
case jttDouble:
t = bsontype.Double
case jttString:
t = bsontype.String
case jttBool:
t = bsontype.Boolean
case jttNull:
t = bsontype.Null
default:
return nil
}
return &extJSONValue{t: t, v: jt.v}
}
func ensureColon(s jsonParseState, key string) error {
if s != jpsSawColon {
return fmt.Errorf("invalid JSON input: missing colon after key \"%s\"", key)
}
return nil
}
func invalidRequestError(s string) error {
return fmt.Errorf("invalid request to read %s", s)
}
func invalidJSONError(expected string) error {
return fmt.Errorf("invalid JSON input; expected %s", expected)
}
func invalidJSONErrorForType(expected string, t bsontype.Type) error {
return fmt.Errorf("invalid JSON input; expected %s for %s", expected, t)
}
func unexpectedTokenError(jt *jsonToken) error {
switch jt.t {
case jttInt32, jttInt64, jttDouble:
return fmt.Errorf("invalid JSON input; unexpected number (%v) at position %d", jt.v, jt.p)
case jttString:
return fmt.Errorf("invalid JSON input; unexpected string (\"%v\") at position %d", jt.v, jt.p)
case jttBool:
return fmt.Errorf("invalid JSON input; unexpected boolean literal (%v) at position %d", jt.v, jt.p)
case jttNull:
return fmt.Errorf("invalid JSON input; unexpected null literal at position %d", jt.p)
case jttEOF:
return fmt.Errorf("invalid JSON input; unexpected end of input at position %d", jt.p)
default:
return fmt.Errorf("invalid JSON input; unexpected %c at position %d", jt.v.(byte), jt.p)
}
}
func nestingDepthError(p, depth int) error {
return fmt.Errorf("invalid JSON input; nesting too deep (%d levels) at position %d", depth, p)
}

View File

@ -0,0 +1,788 @@
// 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 bsonrw
import (
"io"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
var (
keyDiff = specificDiff("key")
typDiff = specificDiff("type")
valDiff = specificDiff("value")
expectErrEOF = expectSpecificError(io.EOF)
expectErrEOD = expectSpecificError(ErrEOD)
expectErrEOA = expectSpecificError(ErrEOA)
)
type expectedErrorFunc func(t *testing.T, err error, desc string)
type peekTypeTestCase struct {
desc string
input string
typs []bsontype.Type
errFs []expectedErrorFunc
}
type readKeyValueTestCase struct {
desc string
input string
keys []string
typs []bsontype.Type
vals []*extJSONValue
keyEFs []expectedErrorFunc
valEFs []expectedErrorFunc
}
func expectSpecificError(expected error) expectedErrorFunc {
return func(t *testing.T, err error, desc string) {
if err != expected {
t.Helper()
t.Errorf("%s: Expected %v but got: %v", desc, expected, err)
t.FailNow()
}
}
}
func specificDiff(name string) func(t *testing.T, expected, actual interface{}, desc string) {
return func(t *testing.T, expected, actual interface{}, desc string) {
if diff := cmp.Diff(expected, actual); diff != "" {
t.Helper()
t.Errorf("%s: Incorrect JSON %s (-want, +got): %s\n", desc, name, diff)
t.FailNow()
}
}
}
func expectErrorNOOP(_ *testing.T, _ error, _ string) {
}
func readKeyDiff(t *testing.T, eKey, aKey string, eTyp, aTyp bsontype.Type, err error, errF expectedErrorFunc, desc string) {
keyDiff(t, eKey, aKey, desc)
typDiff(t, eTyp, aTyp, desc)
errF(t, err, desc)
}
func readValueDiff(t *testing.T, eVal, aVal *extJSONValue, err error, errF expectedErrorFunc, desc string) {
if aVal != nil {
typDiff(t, eVal.t, aVal.t, desc)
valDiff(t, eVal.v, aVal.v, desc)
} else {
valDiff(t, eVal, aVal, desc)
}
errF(t, err, desc)
}
func TestExtJSONParserPeekType(t *testing.T) {
makeValidPeekTypeTestCase := func(input string, typ bsontype.Type, desc string) peekTypeTestCase {
return peekTypeTestCase{
desc: desc, input: input,
typs: []bsontype.Type{typ},
errFs: []expectedErrorFunc{expectNoError},
}
}
makeInvalidTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase {
return peekTypeTestCase{
desc: desc, input: input,
typs: []bsontype.Type{bsontype.Type(0)},
errFs: []expectedErrorFunc{lastEF},
}
}
makeInvalidPeekTypeTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase {
return peekTypeTestCase{
desc: desc, input: input,
typs: []bsontype.Type{bsontype.Array, bsontype.String, bsontype.Type(0)},
errFs: []expectedErrorFunc{expectNoError, expectNoError, lastEF},
}
}
cases := []peekTypeTestCase{
makeValidPeekTypeTestCase(`null`, bsontype.Null, "Null"),
makeValidPeekTypeTestCase(`"string"`, bsontype.String, "String"),
makeValidPeekTypeTestCase(`true`, bsontype.Boolean, "Boolean--true"),
makeValidPeekTypeTestCase(`false`, bsontype.Boolean, "Boolean--false"),
makeValidPeekTypeTestCase(`{"$minKey": 1}`, bsontype.MinKey, "MinKey"),
makeValidPeekTypeTestCase(`{"$maxKey": 1}`, bsontype.MaxKey, "MaxKey"),
makeValidPeekTypeTestCase(`{"$numberInt": "42"}`, bsontype.Int32, "Int32"),
makeValidPeekTypeTestCase(`{"$numberLong": "42"}`, bsontype.Int64, "Int64"),
makeValidPeekTypeTestCase(`{"$symbol": "symbol"}`, bsontype.Symbol, "Symbol"),
makeValidPeekTypeTestCase(`{"$numberDouble": "42.42"}`, bsontype.Double, "Double"),
makeValidPeekTypeTestCase(`{"$undefined": true}`, bsontype.Undefined, "Undefined"),
makeValidPeekTypeTestCase(`{"$numberDouble": "NaN"}`, bsontype.Double, "Double--NaN"),
makeValidPeekTypeTestCase(`{"$numberDecimal": "1234"}`, bsontype.Decimal128, "Decimal"),
makeValidPeekTypeTestCase(`{"foo": "bar"}`, bsontype.EmbeddedDocument, "Toplevel document"),
makeValidPeekTypeTestCase(`{"$date": {"$numberLong": "0"}}`, bsontype.DateTime, "Datetime"),
makeValidPeekTypeTestCase(`{"$code": "function() {}"}`, bsontype.JavaScript, "Code no scope"),
makeValidPeekTypeTestCase(`[{"$numberInt": "1"},{"$numberInt": "2"}]`, bsontype.Array, "Array"),
makeValidPeekTypeTestCase(`{"$timestamp": {"t": 42, "i": 1}}`, bsontype.Timestamp, "Timestamp"),
makeValidPeekTypeTestCase(`{"$oid": "57e193d7a9cc81b4027498b5"}`, bsontype.ObjectID, "Object ID"),
makeValidPeekTypeTestCase(`{"$binary": {"base64": "AQIDBAU=", "subType": "80"}}`, bsontype.Binary, "Binary"),
makeValidPeekTypeTestCase(`{"$code": "function() {}", "$scope": {}}`, bsontype.CodeWithScope, "Code With Scope"),
makeValidPeekTypeTestCase(`{"$binary": {"base64": "o0w498Or7cijeBSpkquNtg==", "subType": "03"}}`, bsontype.Binary, "Binary"),
makeValidPeekTypeTestCase(`{"$binary": "o0w498Or7cijeBSpkquNtg==", "$type": "03"}`, bsontype.Binary, "Binary"),
makeValidPeekTypeTestCase(`{"$regularExpression": {"pattern": "foo*", "options": "ix"}}`, bsontype.Regex, "Regular expression"),
makeValidPeekTypeTestCase(`{"$dbPointer": {"$ref": "db.collection", "$id": {"$oid": "57e193d7a9cc81b4027498b1"}}}`, bsontype.DBPointer, "DBPointer"),
makeValidPeekTypeTestCase(`{"$ref": "collection", "$id": {"$oid": "57fd71e96e32ab4225b723fb"}, "$db": "database"}`, bsontype.EmbeddedDocument, "DBRef"),
makeInvalidPeekTypeTestCase("invalid array--missing ]", `["a"`, expectError),
makeInvalidPeekTypeTestCase("invalid array--colon in array", `["a":`, expectError),
makeInvalidPeekTypeTestCase("invalid array--extra comma", `["a",,`, expectError),
makeInvalidPeekTypeTestCase("invalid array--trailing comma", `["a",]`, expectError),
makeInvalidPeekTypeTestCase("peekType after end of array", `["a"]`, expectErrEOA),
{
desc: "invalid array--leading comma",
input: `[,`,
typs: []bsontype.Type{bsontype.Array, bsontype.Type(0)},
errFs: []expectedErrorFunc{expectNoError, expectError},
},
makeInvalidTestCase("lone $scope", `{"$scope": {}}`, expectError),
makeInvalidTestCase("empty code with unknown extra key", `{"$code":"", "0":""}`, expectError),
makeInvalidTestCase("non-empty code with unknown extra key", `{"$code":"foobar", "0":""}`, expectError),
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
ejp := newExtJSONParser(strings.NewReader(tc.input), true)
// Manually set the parser's starting state to jpsSawColon so peekType will read ahead to find the extjson
// type of the value. If not set, the parser will be in jpsStartState and advance to jpsSawKey, which will
// cause it to return without peeking the extjson type.
ejp.s = jpsSawColon
for i, eTyp := range tc.typs {
errF := tc.errFs[i]
typ, err := ejp.peekType()
errF(t, err, tc.desc)
if err != nil {
// Don't inspect the type if there was an error
return
}
typDiff(t, eTyp, typ, tc.desc)
}
})
}
}
func TestExtJSONParserReadKeyReadValue(t *testing.T) {
// several test cases will use the same keys, types, and values, and only differ on input structure
keys := []string{"_id", "Symbol", "String", "Int32", "Int64", "Int", "MinKey"}
types := []bsontype.Type{bsontype.ObjectID, bsontype.Symbol, bsontype.String, bsontype.Int32, bsontype.Int64, bsontype.Int32, bsontype.MinKey}
values := []*extJSONValue{
{t: bsontype.String, v: "57e193d7a9cc81b4027498b5"},
{t: bsontype.String, v: "symbol"},
{t: bsontype.String, v: "string"},
{t: bsontype.String, v: "42"},
{t: bsontype.String, v: "42"},
{t: bsontype.Int32, v: int32(42)},
{t: bsontype.Int32, v: int32(1)},
}
errFuncs := make([]expectedErrorFunc, 7)
for i := 0; i < 7; i++ {
errFuncs[i] = expectNoError
}
firstKeyError := func(desc, input string) readKeyValueTestCase {
return readKeyValueTestCase{
desc: desc,
input: input,
keys: []string{""},
typs: []bsontype.Type{bsontype.Type(0)},
vals: []*extJSONValue{nil},
keyEFs: []expectedErrorFunc{expectError},
valEFs: []expectedErrorFunc{expectErrorNOOP},
}
}
secondKeyError := func(desc, input, firstKey string, firstType bsontype.Type, firstValue *extJSONValue) readKeyValueTestCase {
return readKeyValueTestCase{
desc: desc,
input: input,
keys: []string{firstKey, ""},
typs: []bsontype.Type{firstType, bsontype.Type(0)},
vals: []*extJSONValue{firstValue, nil},
keyEFs: []expectedErrorFunc{expectNoError, expectError},
valEFs: []expectedErrorFunc{expectNoError, expectErrorNOOP},
}
}
cases := []readKeyValueTestCase{
{
desc: "normal spacing",
input: `{
"_id": { "$oid": "57e193d7a9cc81b4027498b5" },
"Symbol": { "$symbol": "symbol" },
"String": "string",
"Int32": { "$numberInt": "42" },
"Int64": { "$numberLong": "42" },
"Int": 42,
"MinKey": { "$minKey": 1 }
}`,
keys: keys, typs: types, vals: values,
keyEFs: errFuncs, valEFs: errFuncs,
},
{
desc: "new line before comma",
input: `{ "_id": { "$oid": "57e193d7a9cc81b4027498b5" }
, "Symbol": { "$symbol": "symbol" }
, "String": "string"
, "Int32": { "$numberInt": "42" }
, "Int64": { "$numberLong": "42" }
, "Int": 42
, "MinKey": { "$minKey": 1 }
}`,
keys: keys, typs: types, vals: values,
keyEFs: errFuncs, valEFs: errFuncs,
},
{
desc: "tabs around colons",
input: `{
"_id": { "$oid" : "57e193d7a9cc81b4027498b5" },
"Symbol": { "$symbol" : "symbol" },
"String": "string",
"Int32": { "$numberInt" : "42" },
"Int64": { "$numberLong": "42" },
"Int": 42,
"MinKey": { "$minKey": 1 }
}`,
keys: keys, typs: types, vals: values,
keyEFs: errFuncs, valEFs: errFuncs,
},
{
desc: "no whitespace",
input: `{"_id":{"$oid":"57e193d7a9cc81b4027498b5"},"Symbol":{"$symbol":"symbol"},"String":"string","Int32":{"$numberInt":"42"},"Int64":{"$numberLong":"42"},"Int":42,"MinKey":{"$minKey":1}}`,
keys: keys, typs: types, vals: values,
keyEFs: errFuncs, valEFs: errFuncs,
},
{
desc: "mixed whitespace",
input: ` {
"_id" : { "$oid": "57e193d7a9cc81b4027498b5" },
"Symbol" : { "$symbol": "symbol" } ,
"String" : "string",
"Int32" : { "$numberInt": "42" } ,
"Int64" : {"$numberLong" : "42"},
"Int" : 42,
"MinKey" : { "$minKey": 1 } } `,
keys: keys, typs: types, vals: values,
keyEFs: errFuncs, valEFs: errFuncs,
},
{
desc: "nested object",
input: `{"k1": 1, "k2": { "k3": { "k4": 4 } }, "k5": 5}`,
keys: []string{"k1", "k2", "k3", "k4", "", "", "k5", ""},
typs: []bsontype.Type{bsontype.Int32, bsontype.EmbeddedDocument, bsontype.EmbeddedDocument, bsontype.Int32, bsontype.Type(0), bsontype.Type(0), bsontype.Int32, bsontype.Type(0)},
vals: []*extJSONValue{
{t: bsontype.Int32, v: int32(1)}, nil, nil, {t: bsontype.Int32, v: int32(4)}, nil, nil, {t: bsontype.Int32, v: int32(5)}, nil,
},
keyEFs: []expectedErrorFunc{
expectNoError, expectNoError, expectNoError, expectNoError, expectErrEOD,
expectErrEOD, expectNoError, expectErrEOD,
},
valEFs: []expectedErrorFunc{
expectNoError, expectError, expectError, expectNoError, expectErrorNOOP,
expectErrorNOOP, expectNoError, expectErrorNOOP,
},
},
{
desc: "invalid input: invalid values for extended type",
input: `{"a": {"$numberInt": "1", "x"`,
keys: []string{"a"},
typs: []bsontype.Type{bsontype.Int32},
vals: []*extJSONValue{nil},
keyEFs: []expectedErrorFunc{expectNoError},
valEFs: []expectedErrorFunc{expectError},
},
firstKeyError("invalid input: missing key--EOF", "{"),
firstKeyError("invalid input: missing key--colon first", "{:"),
firstKeyError("invalid input: missing value", `{"a":`),
firstKeyError("invalid input: missing colon", `{"a" 1`),
firstKeyError("invalid input: extra colon", `{"a"::`),
secondKeyError("invalid input: missing }", `{"a": 1`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
secondKeyError("invalid input: missing comma", `{"a": 1 "b"`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
secondKeyError("invalid input: extra comma", `{"a": 1,, "b"`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
secondKeyError("invalid input: trailing comma in object", `{"a": 1,}`, "a", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}),
{
desc: "invalid input: lone scope after a complete value",
input: `{"a": "", "b": {"$scope: ""}}`,
keys: []string{"a"},
typs: []bsontype.Type{bsontype.String},
vals: []*extJSONValue{{bsontype.String, ""}},
keyEFs: []expectedErrorFunc{expectNoError, expectNoError},
valEFs: []expectedErrorFunc{expectNoError, expectError},
},
{
desc: "invalid input: lone scope nested",
input: `{"a":{"b":{"$scope":{`,
keys: []string{},
typs: []bsontype.Type{},
vals: []*extJSONValue{nil},
keyEFs: []expectedErrorFunc{expectNoError},
valEFs: []expectedErrorFunc{expectError},
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
ejp := newExtJSONParser(strings.NewReader(tc.input), true)
for i, eKey := range tc.keys {
eTyp := tc.typs[i]
eVal := tc.vals[i]
keyErrF := tc.keyEFs[i]
valErrF := tc.valEFs[i]
k, typ, err := ejp.readKey()
readKeyDiff(t, eKey, k, eTyp, typ, err, keyErrF, tc.desc)
v, err := ejp.readValue(typ)
readValueDiff(t, eVal, v, err, valErrF, tc.desc)
}
})
}
}
type ejpExpectationTest func(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{})
type ejpTestCase struct {
f ejpExpectationTest
p *extJSONParser
k string
t bsontype.Type
v interface{}
}
// expectSingleValue is used for simple JSON types (strings, numbers, literals) and for extended JSON types that
// have single key-value pairs (i.e. { "$minKey": 1 }, { "$numberLong": "42.42" })
func expectSingleValue(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) {
eVal := expectedValue.(*extJSONValue)
k, typ, err := p.readKey()
readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey)
v, err := p.readValue(typ)
readValueDiff(t, eVal, v, err, expectNoError, expectedKey)
}
// expectMultipleValues is used for values that are subdocuments of known size and with known keys (such as extended
// JSON types { "$timestamp": {"t": 1, "i": 1} } and { "$regularExpression": {"pattern": "", options: ""} })
func expectMultipleValues(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) {
k, typ, err := p.readKey()
readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey)
v, err := p.readValue(typ)
expectNoError(t, err, "")
typDiff(t, bsontype.EmbeddedDocument, v.t, expectedKey)
actObj := v.v.(*extJSONObject)
expObj := expectedValue.(*extJSONObject)
for i, actKey := range actObj.keys {
expKey := expObj.keys[i]
actVal := actObj.values[i]
expVal := expObj.values[i]
keyDiff(t, expKey, actKey, expectedKey)
typDiff(t, expVal.t, actVal.t, expectedKey)
valDiff(t, expVal.v, actVal.v, expectedKey)
}
}
type ejpKeyTypValTriple struct {
key string
typ bsontype.Type
val *extJSONValue
}
type ejpSubDocumentTestValue struct {
code string // code is only used for TypeCodeWithScope (and is ignored for TypeEmbeddedDocument
ktvs []ejpKeyTypValTriple // list of (key, type, value) triples; this is "scope" for TypeCodeWithScope
}
// expectSubDocument is used for embedded documents and code with scope types; it reads all the keys and values
// in the embedded document (or scope for codeWithScope) and compares them to the expectedValue's list of (key, type,
// value) triples
func expectSubDocument(t *testing.T, p *extJSONParser, expectedKey string, expectedType bsontype.Type, expectedValue interface{}) {
subdoc := expectedValue.(ejpSubDocumentTestValue)
k, typ, err := p.readKey()
readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey)
if expectedType == bsontype.CodeWithScope {
v, err := p.readValue(typ)
readValueDiff(t, &extJSONValue{t: bsontype.String, v: subdoc.code}, v, err, expectNoError, expectedKey)
}
for _, ktv := range subdoc.ktvs {
eKey := ktv.key
eTyp := ktv.typ
eVal := ktv.val
k, typ, err = p.readKey()
readKeyDiff(t, eKey, k, eTyp, typ, err, expectNoError, expectedKey)
v, err := p.readValue(typ)
readValueDiff(t, eVal, v, err, expectNoError, expectedKey)
}
if expectedType == bsontype.CodeWithScope {
// expect scope doc to close
k, typ, err = p.readKey()
readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, expectedKey)
}
// expect subdoc to close
k, typ, err = p.readKey()
readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, expectedKey)
}
// expectArray takes the expectedKey, ignores the expectedType, and uses the expectedValue
// as a slice of (type Type, value *extJSONValue) pairs
func expectArray(t *testing.T, p *extJSONParser, expectedKey string, _ bsontype.Type, expectedValue interface{}) {
ktvs := expectedValue.([]ejpKeyTypValTriple)
k, typ, err := p.readKey()
readKeyDiff(t, expectedKey, k, bsontype.Array, typ, err, expectNoError, expectedKey)
for _, ktv := range ktvs {
eTyp := ktv.typ
eVal := ktv.val
typ, err = p.peekType()
typDiff(t, eTyp, typ, expectedKey)
expectNoError(t, err, expectedKey)
v, err := p.readValue(typ)
readValueDiff(t, eVal, v, err, expectNoError, expectedKey)
}
// expect array to end
typ, err = p.peekType()
typDiff(t, bsontype.Type(0), typ, expectedKey)
expectErrEOA(t, err, expectedKey)
}
func TestExtJSONParserAllTypes(t *testing.T) {
in := ` { "_id" : { "$oid": "57e193d7a9cc81b4027498b5"}
, "Symbol" : { "$symbol": "symbol"}
, "String" : "string"
, "Int32" : { "$numberInt": "42"}
, "Int64" : { "$numberLong": "42"}
, "Double" : { "$numberDouble": "42.42"}
, "SpecialFloat" : { "$numberDouble": "NaN" }
, "Decimal" : { "$numberDecimal": "1234" }
, "Binary" : { "$binary": { "base64": "o0w498Or7cijeBSpkquNtg==", "subType": "03" } }
, "BinaryLegacy" : { "$binary": "o0w498Or7cijeBSpkquNtg==", "$type": "03" }
, "BinaryUserDefined" : { "$binary": { "base64": "AQIDBAU=", "subType": "80" } }
, "Code" : { "$code": "function() {}" }
, "CodeWithEmptyScope" : { "$code": "function() {}", "$scope": {} }
, "CodeWithScope" : { "$code": "function() {}", "$scope": { "x": 1 } }
, "EmptySubdocument" : {}
, "Subdocument" : { "foo": "bar", "baz": { "$numberInt": "42" } }
, "Array" : [{"$numberInt": "1"}, {"$numberLong": "2"}, {"$numberDouble": "3"}, 4, "string", 5.0]
, "Timestamp" : { "$timestamp": { "t": 42, "i": 1 } }
, "RegularExpression" : { "$regularExpression": { "pattern": "foo*", "options": "ix" } }
, "DatetimeEpoch" : { "$date": { "$numberLong": "0" } }
, "DatetimePositive" : { "$date": { "$numberLong": "9223372036854775807" } }
, "DatetimeNegative" : { "$date": { "$numberLong": "-9223372036854775808" } }
, "True" : true
, "False" : false
, "DBPointer" : { "$dbPointer": { "$ref": "db.collection", "$id": { "$oid": "57e193d7a9cc81b4027498b1" } } }
, "DBRef" : { "$ref": "collection", "$id": { "$oid": "57fd71e96e32ab4225b723fb" }, "$db": "database" }
, "DBRefNoDB" : { "$ref": "collection", "$id": { "$oid": "57fd71e96e32ab4225b723fb" } }
, "MinKey" : { "$minKey": 1 }
, "MaxKey" : { "$maxKey": 1 }
, "Null" : null
, "Undefined" : { "$undefined": true }
}`
ejp := newExtJSONParser(strings.NewReader(in), true)
cases := []ejpTestCase{
{
f: expectSingleValue, p: ejp,
k: "_id", t: bsontype.ObjectID, v: &extJSONValue{t: bsontype.String, v: "57e193d7a9cc81b4027498b5"},
},
{
f: expectSingleValue, p: ejp,
k: "Symbol", t: bsontype.Symbol, v: &extJSONValue{t: bsontype.String, v: "symbol"},
},
{
f: expectSingleValue, p: ejp,
k: "String", t: bsontype.String, v: &extJSONValue{t: bsontype.String, v: "string"},
},
{
f: expectSingleValue, p: ejp,
k: "Int32", t: bsontype.Int32, v: &extJSONValue{t: bsontype.String, v: "42"},
},
{
f: expectSingleValue, p: ejp,
k: "Int64", t: bsontype.Int64, v: &extJSONValue{t: bsontype.String, v: "42"},
},
{
f: expectSingleValue, p: ejp,
k: "Double", t: bsontype.Double, v: &extJSONValue{t: bsontype.String, v: "42.42"},
},
{
f: expectSingleValue, p: ejp,
k: "SpecialFloat", t: bsontype.Double, v: &extJSONValue{t: bsontype.String, v: "NaN"},
},
{
f: expectSingleValue, p: ejp,
k: "Decimal", t: bsontype.Decimal128, v: &extJSONValue{t: bsontype.String, v: "1234"},
},
{
f: expectMultipleValues, p: ejp,
k: "Binary", t: bsontype.Binary,
v: &extJSONObject{
keys: []string{"base64", "subType"},
values: []*extJSONValue{
{t: bsontype.String, v: "o0w498Or7cijeBSpkquNtg=="},
{t: bsontype.String, v: "03"},
},
},
},
{
f: expectMultipleValues, p: ejp,
k: "BinaryLegacy", t: bsontype.Binary,
v: &extJSONObject{
keys: []string{"base64", "subType"},
values: []*extJSONValue{
{t: bsontype.String, v: "o0w498Or7cijeBSpkquNtg=="},
{t: bsontype.String, v: "03"},
},
},
},
{
f: expectMultipleValues, p: ejp,
k: "BinaryUserDefined", t: bsontype.Binary,
v: &extJSONObject{
keys: []string{"base64", "subType"},
values: []*extJSONValue{
{t: bsontype.String, v: "AQIDBAU="},
{t: bsontype.String, v: "80"},
},
},
},
{
f: expectSingleValue, p: ejp,
k: "Code", t: bsontype.JavaScript, v: &extJSONValue{t: bsontype.String, v: "function() {}"},
},
{
f: expectSubDocument, p: ejp,
k: "CodeWithEmptyScope", t: bsontype.CodeWithScope,
v: ejpSubDocumentTestValue{
code: "function() {}",
ktvs: []ejpKeyTypValTriple{},
},
},
{
f: expectSubDocument, p: ejp,
k: "CodeWithScope", t: bsontype.CodeWithScope,
v: ejpSubDocumentTestValue{
code: "function() {}",
ktvs: []ejpKeyTypValTriple{
{"x", bsontype.Int32, &extJSONValue{t: bsontype.Int32, v: int32(1)}},
},
},
},
{
f: expectSubDocument, p: ejp,
k: "EmptySubdocument", t: bsontype.EmbeddedDocument,
v: ejpSubDocumentTestValue{
ktvs: []ejpKeyTypValTriple{},
},
},
{
f: expectSubDocument, p: ejp,
k: "Subdocument", t: bsontype.EmbeddedDocument,
v: ejpSubDocumentTestValue{
ktvs: []ejpKeyTypValTriple{
{"foo", bsontype.String, &extJSONValue{t: bsontype.String, v: "bar"}},
{"baz", bsontype.Int32, &extJSONValue{t: bsontype.String, v: "42"}},
},
},
},
{
f: expectArray, p: ejp,
k: "Array", t: bsontype.Array,
v: []ejpKeyTypValTriple{
{typ: bsontype.Int32, val: &extJSONValue{t: bsontype.String, v: "1"}},
{typ: bsontype.Int64, val: &extJSONValue{t: bsontype.String, v: "2"}},
{typ: bsontype.Double, val: &extJSONValue{t: bsontype.String, v: "3"}},
{typ: bsontype.Int32, val: &extJSONValue{t: bsontype.Int32, v: int32(4)}},
{typ: bsontype.String, val: &extJSONValue{t: bsontype.String, v: "string"}},
{typ: bsontype.Double, val: &extJSONValue{t: bsontype.Double, v: 5.0}},
},
},
{
f: expectMultipleValues, p: ejp,
k: "Timestamp", t: bsontype.Timestamp,
v: &extJSONObject{
keys: []string{"t", "i"},
values: []*extJSONValue{
{t: bsontype.Int32, v: int32(42)},
{t: bsontype.Int32, v: int32(1)},
},
},
},
{
f: expectMultipleValues, p: ejp,
k: "RegularExpression", t: bsontype.Regex,
v: &extJSONObject{
keys: []string{"pattern", "options"},
values: []*extJSONValue{
{t: bsontype.String, v: "foo*"},
{t: bsontype.String, v: "ix"},
},
},
},
{
f: expectMultipleValues, p: ejp,
k: "DatetimeEpoch", t: bsontype.DateTime,
v: &extJSONObject{
keys: []string{"$numberLong"},
values: []*extJSONValue{
{t: bsontype.String, v: "0"},
},
},
},
{
f: expectMultipleValues, p: ejp,
k: "DatetimePositive", t: bsontype.DateTime,
v: &extJSONObject{
keys: []string{"$numberLong"},
values: []*extJSONValue{
{t: bsontype.String, v: "9223372036854775807"},
},
},
},
{
f: expectMultipleValues, p: ejp,
k: "DatetimeNegative", t: bsontype.DateTime,
v: &extJSONObject{
keys: []string{"$numberLong"},
values: []*extJSONValue{
{t: bsontype.String, v: "-9223372036854775808"},
},
},
},
{
f: expectSingleValue, p: ejp,
k: "True", t: bsontype.Boolean, v: &extJSONValue{t: bsontype.Boolean, v: true},
},
{
f: expectSingleValue, p: ejp,
k: "False", t: bsontype.Boolean, v: &extJSONValue{t: bsontype.Boolean, v: false},
},
{
f: expectMultipleValues, p: ejp,
k: "DBPointer", t: bsontype.DBPointer,
v: &extJSONObject{
keys: []string{"$ref", "$id"},
values: []*extJSONValue{
{t: bsontype.String, v: "db.collection"},
{t: bsontype.String, v: "57e193d7a9cc81b4027498b1"},
},
},
},
{
f: expectSubDocument, p: ejp,
k: "DBRef", t: bsontype.EmbeddedDocument,
v: ejpSubDocumentTestValue{
ktvs: []ejpKeyTypValTriple{
{"$ref", bsontype.String, &extJSONValue{t: bsontype.String, v: "collection"}},
{"$id", bsontype.ObjectID, &extJSONValue{t: bsontype.String, v: "57fd71e96e32ab4225b723fb"}},
{"$db", bsontype.String, &extJSONValue{t: bsontype.String, v: "database"}},
},
},
},
{
f: expectSubDocument, p: ejp,
k: "DBRefNoDB", t: bsontype.EmbeddedDocument,
v: ejpSubDocumentTestValue{
ktvs: []ejpKeyTypValTriple{
{"$ref", bsontype.String, &extJSONValue{t: bsontype.String, v: "collection"}},
{"$id", bsontype.ObjectID, &extJSONValue{t: bsontype.String, v: "57fd71e96e32ab4225b723fb"}},
},
},
},
{
f: expectSingleValue, p: ejp,
k: "MinKey", t: bsontype.MinKey, v: &extJSONValue{t: bsontype.Int32, v: int32(1)},
},
{
f: expectSingleValue, p: ejp,
k: "MaxKey", t: bsontype.MaxKey, v: &extJSONValue{t: bsontype.Int32, v: int32(1)},
},
{
f: expectSingleValue, p: ejp,
k: "Null", t: bsontype.Null, v: &extJSONValue{t: bsontype.Null, v: nil},
},
{
f: expectSingleValue, p: ejp,
k: "Undefined", t: bsontype.Undefined, v: &extJSONValue{t: bsontype.Boolean, v: true},
},
}
// run the test cases
for _, tc := range cases {
tc.f(t, tc.p, tc.k, tc.t, tc.v)
}
// expect end of whole document: read final }
k, typ, err := ejp.readKey()
readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOD, "")
// expect end of whole document: read EOF
k, typ, err = ejp.readKey()
readKeyDiff(t, "", k, bsontype.Type(0), typ, err, expectErrEOF, "")
if diff := cmp.Diff(jpsDoneState, ejp.s); diff != "" {
t.Errorf("expected parser to be in done state but instead is in %v\n", ejp.s)
t.FailNow()
}
}
func TestExtJSONValue(t *testing.T) {
t.Run("Large Date", func(t *testing.T) {
val := &extJSONValue{
t: bsontype.String,
v: "3001-01-01T00:00:00Z",
}
intVal, err := val.parseDateTime()
if err != nil {
t.Fatalf("error parsing date time: %v", err)
}
if intVal <= 0 {
t.Fatalf("expected value above 0, got %v", intVal)
}
})
t.Run("fallback time format", func(t *testing.T) {
val := &extJSONValue{
t: bsontype.String,
v: "2019-06-04T14:54:31.416+0000",
}
_, err := val.parseDateTime()
if err != nil {
t.Fatalf("error parsing date time: %v", err)
}
})
}

View File

@ -0,0 +1,644 @@
// 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 bsonrw
import (
"fmt"
"io"
"sync"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ExtJSONValueReaderPool is a pool for ValueReaders that read ExtJSON.
type ExtJSONValueReaderPool struct {
pool sync.Pool
}
// NewExtJSONValueReaderPool instantiates a new ExtJSONValueReaderPool.
func NewExtJSONValueReaderPool() *ExtJSONValueReaderPool {
return &ExtJSONValueReaderPool{
pool: sync.Pool{
New: func() interface{} {
return new(extJSONValueReader)
},
},
}
}
// Get retrieves a ValueReader from the pool and uses src as the underlying ExtJSON.
func (bvrp *ExtJSONValueReaderPool) Get(r io.Reader, canonical bool) (ValueReader, error) {
vr := bvrp.pool.Get().(*extJSONValueReader)
return vr.reset(r, canonical)
}
// Put inserts a ValueReader into the pool. If the ValueReader is not a ExtJSON ValueReader nothing
// is inserted into the pool and ok will be false.
func (bvrp *ExtJSONValueReaderPool) Put(vr ValueReader) (ok bool) {
bvr, ok := vr.(*extJSONValueReader)
if !ok {
return false
}
bvr, _ = bvr.reset(nil, false)
bvrp.pool.Put(bvr)
return true
}
type ejvrState struct {
mode mode
vType bsontype.Type
depth int
}
// extJSONValueReader is for reading extended JSON.
type extJSONValueReader struct {
p *extJSONParser
stack []ejvrState
frame int
}
// NewExtJSONValueReader creates a new ValueReader from a given io.Reader
// It will interpret the JSON of r as canonical or relaxed according to the
// given canonical flag
func NewExtJSONValueReader(r io.Reader, canonical bool) (ValueReader, error) {
return newExtJSONValueReader(r, canonical)
}
func newExtJSONValueReader(r io.Reader, canonical bool) (*extJSONValueReader, error) {
ejvr := new(extJSONValueReader)
return ejvr.reset(r, canonical)
}
func (ejvr *extJSONValueReader) reset(r io.Reader, canonical bool) (*extJSONValueReader, error) {
p := newExtJSONParser(r, canonical)
typ, err := p.peekType()
if err != nil {
return nil, ErrInvalidJSON
}
var m mode
switch typ {
case bsontype.EmbeddedDocument:
m = mTopLevel
case bsontype.Array:
m = mArray
default:
m = mValue
}
stack := make([]ejvrState, 1, 5)
stack[0] = ejvrState{
mode: m,
vType: typ,
}
return &extJSONValueReader{
p: p,
stack: stack,
}, nil
}
func (ejvr *extJSONValueReader) advanceFrame() {
if ejvr.frame+1 >= len(ejvr.stack) { // We need to grow the stack
length := len(ejvr.stack)
if length+1 >= cap(ejvr.stack) {
// double it
buf := make([]ejvrState, 2*cap(ejvr.stack)+1)
copy(buf, ejvr.stack)
ejvr.stack = buf
}
ejvr.stack = ejvr.stack[:length+1]
}
ejvr.frame++
// Clean the stack
ejvr.stack[ejvr.frame].mode = 0
ejvr.stack[ejvr.frame].vType = 0
ejvr.stack[ejvr.frame].depth = 0
}
func (ejvr *extJSONValueReader) pushDocument() {
ejvr.advanceFrame()
ejvr.stack[ejvr.frame].mode = mDocument
ejvr.stack[ejvr.frame].depth = ejvr.p.depth
}
func (ejvr *extJSONValueReader) pushCodeWithScope() {
ejvr.advanceFrame()
ejvr.stack[ejvr.frame].mode = mCodeWithScope
}
func (ejvr *extJSONValueReader) pushArray() {
ejvr.advanceFrame()
ejvr.stack[ejvr.frame].mode = mArray
}
func (ejvr *extJSONValueReader) push(m mode, t bsontype.Type) {
ejvr.advanceFrame()
ejvr.stack[ejvr.frame].mode = m
ejvr.stack[ejvr.frame].vType = t
}
func (ejvr *extJSONValueReader) pop() {
switch ejvr.stack[ejvr.frame].mode {
case mElement, mValue:
ejvr.frame--
case mDocument, mArray, mCodeWithScope:
ejvr.frame -= 2 // we pop twice to jump over the vrElement: vrDocument -> vrElement -> vrDocument/TopLevel/etc...
}
}
func (ejvr *extJSONValueReader) skipObject() {
// read entire object until depth returns to 0 (last ending } or ] seen)
depth := 1
for depth > 0 {
ejvr.p.advanceState()
// If object is empty, raise depth and continue. When emptyObject is true, the
// parser has already read both the opening and closing brackets of an empty
// object ("{}"), so the next valid token will be part of the parent document,
// not part of the nested document.
//
// If there is a comma, there are remaining fields, emptyObject must be set back
// to false, and comma must be skipped with advanceState().
if ejvr.p.emptyObject {
if ejvr.p.s == jpsSawComma {
ejvr.p.emptyObject = false
ejvr.p.advanceState()
}
depth--
continue
}
switch ejvr.p.s {
case jpsSawBeginObject, jpsSawBeginArray:
depth++
case jpsSawEndObject, jpsSawEndArray:
depth--
}
}
}
func (ejvr *extJSONValueReader) invalidTransitionErr(destination mode, name string, modes []mode) error {
te := TransitionError{
name: name,
current: ejvr.stack[ejvr.frame].mode,
destination: destination,
modes: modes,
action: "read",
}
if ejvr.frame != 0 {
te.parent = ejvr.stack[ejvr.frame-1].mode
}
return te
}
func (ejvr *extJSONValueReader) typeError(t bsontype.Type) error {
return fmt.Errorf("positioned on %s, but attempted to read %s", ejvr.stack[ejvr.frame].vType, t)
}
func (ejvr *extJSONValueReader) ensureElementValue(t bsontype.Type, destination mode, callerName string, addModes ...mode) error {
switch ejvr.stack[ejvr.frame].mode {
case mElement, mValue:
if ejvr.stack[ejvr.frame].vType != t {
return ejvr.typeError(t)
}
default:
modes := []mode{mElement, mValue}
if addModes != nil {
modes = append(modes, addModes...)
}
return ejvr.invalidTransitionErr(destination, callerName, modes)
}
return nil
}
func (ejvr *extJSONValueReader) Type() bsontype.Type {
return ejvr.stack[ejvr.frame].vType
}
func (ejvr *extJSONValueReader) Skip() error {
switch ejvr.stack[ejvr.frame].mode {
case mElement, mValue:
default:
return ejvr.invalidTransitionErr(0, "Skip", []mode{mElement, mValue})
}
defer ejvr.pop()
t := ejvr.stack[ejvr.frame].vType
switch t {
case bsontype.Array, bsontype.EmbeddedDocument, bsontype.CodeWithScope:
// read entire array, doc or CodeWithScope
ejvr.skipObject()
default:
_, err := ejvr.p.readValue(t)
if err != nil {
return err
}
}
return nil
}
func (ejvr *extJSONValueReader) ReadArray() (ArrayReader, error) {
switch ejvr.stack[ejvr.frame].mode {
case mTopLevel: // allow reading array from top level
case mArray:
return ejvr, nil
default:
if err := ejvr.ensureElementValue(bsontype.Array, mArray, "ReadArray", mTopLevel, mArray); err != nil {
return nil, err
}
}
ejvr.pushArray()
return ejvr, nil
}
func (ejvr *extJSONValueReader) ReadBinary() (b []byte, btype byte, err error) {
if err := ejvr.ensureElementValue(bsontype.Binary, 0, "ReadBinary"); err != nil {
return nil, 0, err
}
v, err := ejvr.p.readValue(bsontype.Binary)
if err != nil {
return nil, 0, err
}
b, btype, err = v.parseBinary()
ejvr.pop()
return b, btype, err
}
func (ejvr *extJSONValueReader) ReadBoolean() (bool, error) {
if err := ejvr.ensureElementValue(bsontype.Boolean, 0, "ReadBoolean"); err != nil {
return false, err
}
v, err := ejvr.p.readValue(bsontype.Boolean)
if err != nil {
return false, err
}
if v.t != bsontype.Boolean {
return false, fmt.Errorf("expected type bool, but got type %s", v.t)
}
ejvr.pop()
return v.v.(bool), nil
}
func (ejvr *extJSONValueReader) ReadDocument() (DocumentReader, error) {
switch ejvr.stack[ejvr.frame].mode {
case mTopLevel:
return ejvr, nil
case mElement, mValue:
if ejvr.stack[ejvr.frame].vType != bsontype.EmbeddedDocument {
return nil, ejvr.typeError(bsontype.EmbeddedDocument)
}
ejvr.pushDocument()
return ejvr, nil
default:
return nil, ejvr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue})
}
}
func (ejvr *extJSONValueReader) ReadCodeWithScope() (code string, dr DocumentReader, err error) {
if err = ejvr.ensureElementValue(bsontype.CodeWithScope, 0, "ReadCodeWithScope"); err != nil {
return "", nil, err
}
v, err := ejvr.p.readValue(bsontype.CodeWithScope)
if err != nil {
return "", nil, err
}
code, err = v.parseJavascript()
ejvr.pushCodeWithScope()
return code, ejvr, err
}
func (ejvr *extJSONValueReader) ReadDBPointer() (ns string, oid primitive.ObjectID, err error) {
if err = ejvr.ensureElementValue(bsontype.DBPointer, 0, "ReadDBPointer"); err != nil {
return "", primitive.NilObjectID, err
}
v, err := ejvr.p.readValue(bsontype.DBPointer)
if err != nil {
return "", primitive.NilObjectID, err
}
ns, oid, err = v.parseDBPointer()
ejvr.pop()
return ns, oid, err
}
func (ejvr *extJSONValueReader) ReadDateTime() (int64, error) {
if err := ejvr.ensureElementValue(bsontype.DateTime, 0, "ReadDateTime"); err != nil {
return 0, err
}
v, err := ejvr.p.readValue(bsontype.DateTime)
if err != nil {
return 0, err
}
d, err := v.parseDateTime()
ejvr.pop()
return d, err
}
func (ejvr *extJSONValueReader) ReadDecimal128() (primitive.Decimal128, error) {
if err := ejvr.ensureElementValue(bsontype.Decimal128, 0, "ReadDecimal128"); err != nil {
return primitive.Decimal128{}, err
}
v, err := ejvr.p.readValue(bsontype.Decimal128)
if err != nil {
return primitive.Decimal128{}, err
}
d, err := v.parseDecimal128()
ejvr.pop()
return d, err
}
func (ejvr *extJSONValueReader) ReadDouble() (float64, error) {
if err := ejvr.ensureElementValue(bsontype.Double, 0, "ReadDouble"); err != nil {
return 0, err
}
v, err := ejvr.p.readValue(bsontype.Double)
if err != nil {
return 0, err
}
d, err := v.parseDouble()
ejvr.pop()
return d, err
}
func (ejvr *extJSONValueReader) ReadInt32() (int32, error) {
if err := ejvr.ensureElementValue(bsontype.Int32, 0, "ReadInt32"); err != nil {
return 0, err
}
v, err := ejvr.p.readValue(bsontype.Int32)
if err != nil {
return 0, err
}
i, err := v.parseInt32()
ejvr.pop()
return i, err
}
func (ejvr *extJSONValueReader) ReadInt64() (int64, error) {
if err := ejvr.ensureElementValue(bsontype.Int64, 0, "ReadInt64"); err != nil {
return 0, err
}
v, err := ejvr.p.readValue(bsontype.Int64)
if err != nil {
return 0, err
}
i, err := v.parseInt64()
ejvr.pop()
return i, err
}
func (ejvr *extJSONValueReader) ReadJavascript() (code string, err error) {
if err = ejvr.ensureElementValue(bsontype.JavaScript, 0, "ReadJavascript"); err != nil {
return "", err
}
v, err := ejvr.p.readValue(bsontype.JavaScript)
if err != nil {
return "", err
}
code, err = v.parseJavascript()
ejvr.pop()
return code, err
}
func (ejvr *extJSONValueReader) ReadMaxKey() error {
if err := ejvr.ensureElementValue(bsontype.MaxKey, 0, "ReadMaxKey"); err != nil {
return err
}
v, err := ejvr.p.readValue(bsontype.MaxKey)
if err != nil {
return err
}
err = v.parseMinMaxKey("max")
ejvr.pop()
return err
}
func (ejvr *extJSONValueReader) ReadMinKey() error {
if err := ejvr.ensureElementValue(bsontype.MinKey, 0, "ReadMinKey"); err != nil {
return err
}
v, err := ejvr.p.readValue(bsontype.MinKey)
if err != nil {
return err
}
err = v.parseMinMaxKey("min")
ejvr.pop()
return err
}
func (ejvr *extJSONValueReader) ReadNull() error {
if err := ejvr.ensureElementValue(bsontype.Null, 0, "ReadNull"); err != nil {
return err
}
v, err := ejvr.p.readValue(bsontype.Null)
if err != nil {
return err
}
if v.t != bsontype.Null {
return fmt.Errorf("expected type null but got type %s", v.t)
}
ejvr.pop()
return nil
}
func (ejvr *extJSONValueReader) ReadObjectID() (primitive.ObjectID, error) {
if err := ejvr.ensureElementValue(bsontype.ObjectID, 0, "ReadObjectID"); err != nil {
return primitive.ObjectID{}, err
}
v, err := ejvr.p.readValue(bsontype.ObjectID)
if err != nil {
return primitive.ObjectID{}, err
}
oid, err := v.parseObjectID()
ejvr.pop()
return oid, err
}
func (ejvr *extJSONValueReader) ReadRegex() (pattern string, options string, err error) {
if err = ejvr.ensureElementValue(bsontype.Regex, 0, "ReadRegex"); err != nil {
return "", "", err
}
v, err := ejvr.p.readValue(bsontype.Regex)
if err != nil {
return "", "", err
}
pattern, options, err = v.parseRegex()
ejvr.pop()
return pattern, options, err
}
func (ejvr *extJSONValueReader) ReadString() (string, error) {
if err := ejvr.ensureElementValue(bsontype.String, 0, "ReadString"); err != nil {
return "", err
}
v, err := ejvr.p.readValue(bsontype.String)
if err != nil {
return "", err
}
if v.t != bsontype.String {
return "", fmt.Errorf("expected type string but got type %s", v.t)
}
ejvr.pop()
return v.v.(string), nil
}
func (ejvr *extJSONValueReader) ReadSymbol() (symbol string, err error) {
if err = ejvr.ensureElementValue(bsontype.Symbol, 0, "ReadSymbol"); err != nil {
return "", err
}
v, err := ejvr.p.readValue(bsontype.Symbol)
if err != nil {
return "", err
}
symbol, err = v.parseSymbol()
ejvr.pop()
return symbol, err
}
func (ejvr *extJSONValueReader) ReadTimestamp() (t uint32, i uint32, err error) {
if err = ejvr.ensureElementValue(bsontype.Timestamp, 0, "ReadTimestamp"); err != nil {
return 0, 0, err
}
v, err := ejvr.p.readValue(bsontype.Timestamp)
if err != nil {
return 0, 0, err
}
t, i, err = v.parseTimestamp()
ejvr.pop()
return t, i, err
}
func (ejvr *extJSONValueReader) ReadUndefined() error {
if err := ejvr.ensureElementValue(bsontype.Undefined, 0, "ReadUndefined"); err != nil {
return err
}
v, err := ejvr.p.readValue(bsontype.Undefined)
if err != nil {
return err
}
err = v.parseUndefined()
ejvr.pop()
return err
}
func (ejvr *extJSONValueReader) ReadElement() (string, ValueReader, error) {
switch ejvr.stack[ejvr.frame].mode {
case mTopLevel, mDocument, mCodeWithScope:
default:
return "", nil, ejvr.invalidTransitionErr(mElement, "ReadElement", []mode{mTopLevel, mDocument, mCodeWithScope})
}
name, t, err := ejvr.p.readKey()
if err != nil {
if err == ErrEOD {
if ejvr.stack[ejvr.frame].mode == mCodeWithScope {
_, err := ejvr.p.peekType()
if err != nil {
return "", nil, err
}
}
ejvr.pop()
}
return "", nil, err
}
ejvr.push(mElement, t)
return name, ejvr, nil
}
func (ejvr *extJSONValueReader) ReadValue() (ValueReader, error) {
switch ejvr.stack[ejvr.frame].mode {
case mArray:
default:
return nil, ejvr.invalidTransitionErr(mValue, "ReadValue", []mode{mArray})
}
t, err := ejvr.p.peekType()
if err != nil {
if err == ErrEOA {
ejvr.pop()
}
return nil, err
}
ejvr.push(mValue, t)
return ejvr, nil
}

View File

@ -0,0 +1,168 @@
// 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 bsonrw
import (
"fmt"
"io"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
func TestExtJSONReader(t *testing.T) {
t.Run("ReadDocument", func(t *testing.T) {
t.Run("EmbeddedDocument", func(t *testing.T) {
ejvr := &extJSONValueReader{
stack: []ejvrState{
{mode: mTopLevel},
{mode: mElement, vType: bsontype.Boolean},
},
frame: 1,
}
ejvr.stack[1].mode = mArray
wanterr := ejvr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue})
_, err := ejvr.ReadDocument()
if err == nil || err.Error() != wanterr.Error() {
t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr)
}
})
})
t.Run("invalid transition", func(t *testing.T) {
t.Run("Skip", func(t *testing.T) {
ejvr := &extJSONValueReader{stack: []ejvrState{{mode: mTopLevel}}}
wanterr := (&extJSONValueReader{stack: []ejvrState{{mode: mTopLevel}}}).invalidTransitionErr(0, "Skip", []mode{mElement, mValue})
goterr := ejvr.Skip()
if !cmp.Equal(goterr, wanterr, cmp.Comparer(compareErrors)) {
t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)
}
})
})
}
func TestReadMultipleTopLevelDocuments(t *testing.T) {
testCases := []struct {
name string
input string
expected [][]byte
}{
{
"single top-level document",
"{\"foo\":1}",
[][]byte{
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x01, 0x00, 0x00, 0x00, 0x00},
},
},
{
"single top-level document with leading and trailing whitespace",
"\n\n {\"foo\":1} \n",
[][]byte{
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x01, 0x00, 0x00, 0x00, 0x00},
},
},
{
"two top-level documents",
"{\"foo\":1}{\"foo\":2}",
[][]byte{
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x01, 0x00, 0x00, 0x00, 0x00},
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x02, 0x00, 0x00, 0x00, 0x00},
},
},
{
"two top-level documents with leading and trailing whitespace and whitespace separation ",
"\n\n {\"foo\":1}\n{\"foo\":2}\n ",
[][]byte{
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x01, 0x00, 0x00, 0x00, 0x00},
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x02, 0x00, 0x00, 0x00, 0x00},
},
},
{
"top-level array with single document",
"[{\"foo\":1}]",
[][]byte{
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x01, 0x00, 0x00, 0x00, 0x00},
},
},
{
"top-level array with 2 documents",
"[{\"foo\":1},{\"foo\":2}]",
[][]byte{
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x01, 0x00, 0x00, 0x00, 0x00},
{0x0E, 0x00, 0x00, 0x00, 0x10, 'f', 'o', 'o', 0x00, 0x02, 0x00, 0x00, 0x00, 0x00},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := strings.NewReader(tc.input)
vr, err := NewExtJSONValueReader(r, false)
if err != nil {
t.Fatalf("expected no error, but got %v", err)
}
actual, err := readAllDocuments(vr)
if err != nil {
t.Fatalf("expected no error, but got %v", err)
}
if diff := cmp.Diff(tc.expected, actual); diff != "" {
t.Fatalf("expected does not match actual: %v", diff)
}
})
}
}
func readAllDocuments(vr ValueReader) ([][]byte, error) {
c := NewCopier()
var actual [][]byte
switch vr.Type() {
case bsontype.EmbeddedDocument:
for {
result, err := c.CopyDocumentToBytes(vr)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
actual = append(actual, result)
}
case bsontype.Array:
ar, err := vr.ReadArray()
if err != nil {
return nil, err
}
for {
evr, err := ar.ReadValue()
if err != nil {
if err == ErrEOA {
break
}
return nil, err
}
result, err := c.CopyDocumentToBytes(evr)
if err != nil {
return nil, err
}
actual = append(actual, result)
}
default:
return nil, fmt.Errorf("expected an array or a document, but got %s", vr.Type())
}
return actual, nil
}

View File

@ -0,0 +1,223 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Based on github.com/golang/go by The Go Authors
// See THIRD-PARTY-NOTICES for original license terms.
package bsonrw
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,
}

View File

@ -0,0 +1,492 @@
// 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 bsonrw
import (
"encoding/base64"
"errors"
"fmt"
"math"
"strconv"
"time"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func wrapperKeyBSONType(key string) bsontype.Type {
switch key {
case "$numberInt":
return bsontype.Int32
case "$numberLong":
return bsontype.Int64
case "$oid":
return bsontype.ObjectID
case "$symbol":
return bsontype.Symbol
case "$numberDouble":
return bsontype.Double
case "$numberDecimal":
return bsontype.Decimal128
case "$binary":
return bsontype.Binary
case "$code":
return bsontype.JavaScript
case "$scope":
return bsontype.CodeWithScope
case "$timestamp":
return bsontype.Timestamp
case "$regularExpression":
return bsontype.Regex
case "$dbPointer":
return bsontype.DBPointer
case "$date":
return bsontype.DateTime
case "$minKey":
return bsontype.MinKey
case "$maxKey":
return bsontype.MaxKey
case "$undefined":
return bsontype.Undefined
}
return bsontype.EmbeddedDocument
}
func (ejv *extJSONValue) parseBinary() (b []byte, subType byte, err error) {
if ejv.t != bsontype.EmbeddedDocument {
return nil, 0, fmt.Errorf("$binary value should be object, but instead is %s", ejv.t)
}
binObj := ejv.v.(*extJSONObject)
bFound := false
stFound := false
for i, key := range binObj.keys {
val := binObj.values[i]
switch key {
case "base64":
if bFound {
return nil, 0, errors.New("duplicate base64 key in $binary")
}
if val.t != bsontype.String {
return nil, 0, fmt.Errorf("$binary base64 value should be string, but instead is %s", val.t)
}
base64Bytes, err := base64.StdEncoding.DecodeString(val.v.(string))
if err != nil {
return nil, 0, fmt.Errorf("invalid $binary base64 string: %s", val.v.(string))
}
b = base64Bytes
bFound = true
case "subType":
if stFound {
return nil, 0, errors.New("duplicate subType key in $binary")
}
if val.t != bsontype.String {
return nil, 0, fmt.Errorf("$binary subType value should be string, but instead is %s", val.t)
}
i, err := strconv.ParseInt(val.v.(string), 16, 64)
if err != nil {
return nil, 0, fmt.Errorf("invalid $binary subType string: %s", val.v.(string))
}
subType = byte(i)
stFound = true
default:
return nil, 0, fmt.Errorf("invalid key in $binary object: %s", key)
}
}
if !bFound {
return nil, 0, errors.New("missing base64 field in $binary object")
}
if !stFound {
return nil, 0, errors.New("missing subType field in $binary object")
}
return b, subType, nil
}
func (ejv *extJSONValue) parseDBPointer() (ns string, oid primitive.ObjectID, err error) {
if ejv.t != bsontype.EmbeddedDocument {
return "", primitive.NilObjectID, fmt.Errorf("$dbPointer value should be object, but instead is %s", ejv.t)
}
dbpObj := ejv.v.(*extJSONObject)
oidFound := false
nsFound := false
for i, key := range dbpObj.keys {
val := dbpObj.values[i]
switch key {
case "$ref":
if nsFound {
return "", primitive.NilObjectID, errors.New("duplicate $ref key in $dbPointer")
}
if val.t != bsontype.String {
return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $ref value should be string, but instead is %s", val.t)
}
ns = val.v.(string)
nsFound = true
case "$id":
if oidFound {
return "", primitive.NilObjectID, errors.New("duplicate $id key in $dbPointer")
}
if val.t != bsontype.String {
return "", primitive.NilObjectID, fmt.Errorf("$dbPointer $id value should be string, but instead is %s", val.t)
}
oid, err = primitive.ObjectIDFromHex(val.v.(string))
if err != nil {
return "", primitive.NilObjectID, err
}
oidFound = true
default:
return "", primitive.NilObjectID, fmt.Errorf("invalid key in $dbPointer object: %s", key)
}
}
if !nsFound {
return "", oid, errors.New("missing $ref field in $dbPointer object")
}
if !oidFound {
return "", oid, errors.New("missing $id field in $dbPointer object")
}
return ns, oid, nil
}
const (
rfc3339Milli = "2006-01-02T15:04:05.999Z07:00"
)
var (
timeFormats = []string{rfc3339Milli, "2006-01-02T15:04:05.999Z0700"}
)
func (ejv *extJSONValue) parseDateTime() (int64, error) {
switch ejv.t {
case bsontype.Int32:
return int64(ejv.v.(int32)), nil
case bsontype.Int64:
return ejv.v.(int64), nil
case bsontype.String:
return parseDatetimeString(ejv.v.(string))
case bsontype.EmbeddedDocument:
return parseDatetimeObject(ejv.v.(*extJSONObject))
default:
return 0, fmt.Errorf("$date value should be string or object, but instead is %s", ejv.t)
}
}
func parseDatetimeString(data string) (int64, error) {
var t time.Time
var err error
// try acceptable time formats until one matches
for _, format := range timeFormats {
t, err = time.Parse(format, data)
if err == nil {
break
}
}
if err != nil {
return 0, fmt.Errorf("invalid $date value string: %s", data)
}
return int64(primitive.NewDateTimeFromTime(t)), nil
}
func parseDatetimeObject(data *extJSONObject) (d int64, err error) {
dFound := false
for i, key := range data.keys {
val := data.values[i]
switch key {
case "$numberLong":
if dFound {
return 0, errors.New("duplicate $numberLong key in $date")
}
if val.t != bsontype.String {
return 0, fmt.Errorf("$date $numberLong field should be string, but instead is %s", val.t)
}
d, err = val.parseInt64()
if err != nil {
return 0, err
}
dFound = true
default:
return 0, fmt.Errorf("invalid key in $date object: %s", key)
}
}
if !dFound {
return 0, errors.New("missing $numberLong field in $date object")
}
return d, nil
}
func (ejv *extJSONValue) parseDecimal128() (primitive.Decimal128, error) {
if ejv.t != bsontype.String {
return primitive.Decimal128{}, fmt.Errorf("$numberDecimal value should be string, but instead is %s", ejv.t)
}
d, err := primitive.ParseDecimal128(ejv.v.(string))
if err != nil {
return primitive.Decimal128{}, fmt.Errorf("$invalid $numberDecimal string: %s", ejv.v.(string))
}
return d, nil
}
func (ejv *extJSONValue) parseDouble() (float64, error) {
if ejv.t == bsontype.Double {
return ejv.v.(float64), nil
}
if ejv.t != bsontype.String {
return 0, fmt.Errorf("$numberDouble value should be string, but instead is %s", ejv.t)
}
switch ejv.v.(string) {
case "Infinity":
return math.Inf(1), nil
case "-Infinity":
return math.Inf(-1), nil
case "NaN":
return math.NaN(), nil
}
f, err := strconv.ParseFloat(ejv.v.(string), 64)
if err != nil {
return 0, err
}
return f, nil
}
func (ejv *extJSONValue) parseInt32() (int32, error) {
if ejv.t == bsontype.Int32 {
return ejv.v.(int32), nil
}
if ejv.t != bsontype.String {
return 0, fmt.Errorf("$numberInt value should be string, but instead is %s", ejv.t)
}
i, err := strconv.ParseInt(ejv.v.(string), 10, 64)
if err != nil {
return 0, err
}
if i < math.MinInt32 || i > math.MaxInt32 {
return 0, fmt.Errorf("$numberInt value should be int32 but instead is int64: %d", i)
}
return int32(i), nil
}
func (ejv *extJSONValue) parseInt64() (int64, error) {
if ejv.t == bsontype.Int64 {
return ejv.v.(int64), nil
}
if ejv.t != bsontype.String {
return 0, fmt.Errorf("$numberLong value should be string, but instead is %s", ejv.t)
}
i, err := strconv.ParseInt(ejv.v.(string), 10, 64)
if err != nil {
return 0, err
}
return i, nil
}
func (ejv *extJSONValue) parseJavascript() (code string, err error) {
if ejv.t != bsontype.String {
return "", fmt.Errorf("$code value should be string, but instead is %s", ejv.t)
}
return ejv.v.(string), nil
}
func (ejv *extJSONValue) parseMinMaxKey(minmax string) error {
if ejv.t != bsontype.Int32 {
return fmt.Errorf("$%sKey value should be int32, but instead is %s", minmax, ejv.t)
}
if ejv.v.(int32) != 1 {
return fmt.Errorf("$%sKey value must be 1, but instead is %d", minmax, ejv.v.(int32))
}
return nil
}
func (ejv *extJSONValue) parseObjectID() (primitive.ObjectID, error) {
if ejv.t != bsontype.String {
return primitive.NilObjectID, fmt.Errorf("$oid value should be string, but instead is %s", ejv.t)
}
return primitive.ObjectIDFromHex(ejv.v.(string))
}
func (ejv *extJSONValue) parseRegex() (pattern, options string, err error) {
if ejv.t != bsontype.EmbeddedDocument {
return "", "", fmt.Errorf("$regularExpression value should be object, but instead is %s", ejv.t)
}
regexObj := ejv.v.(*extJSONObject)
patFound := false
optFound := false
for i, key := range regexObj.keys {
val := regexObj.values[i]
switch key {
case "pattern":
if patFound {
return "", "", errors.New("duplicate pattern key in $regularExpression")
}
if val.t != bsontype.String {
return "", "", fmt.Errorf("$regularExpression pattern value should be string, but instead is %s", val.t)
}
pattern = val.v.(string)
patFound = true
case "options":
if optFound {
return "", "", errors.New("duplicate options key in $regularExpression")
}
if val.t != bsontype.String {
return "", "", fmt.Errorf("$regularExpression options value should be string, but instead is %s", val.t)
}
options = val.v.(string)
optFound = true
default:
return "", "", fmt.Errorf("invalid key in $regularExpression object: %s", key)
}
}
if !patFound {
return "", "", errors.New("missing pattern field in $regularExpression object")
}
if !optFound {
return "", "", errors.New("missing options field in $regularExpression object")
}
return pattern, options, nil
}
func (ejv *extJSONValue) parseSymbol() (string, error) {
if ejv.t != bsontype.String {
return "", fmt.Errorf("$symbol value should be string, but instead is %s", ejv.t)
}
return ejv.v.(string), nil
}
func (ejv *extJSONValue) parseTimestamp() (t, i uint32, err error) {
if ejv.t != bsontype.EmbeddedDocument {
return 0, 0, fmt.Errorf("$timestamp value should be object, but instead is %s", ejv.t)
}
handleKey := func(key string, val *extJSONValue, flag bool) (uint32, error) {
if flag {
return 0, fmt.Errorf("duplicate %s key in $timestamp", key)
}
switch val.t {
case bsontype.Int32:
value := val.v.(int32)
if value < 0 {
return 0, fmt.Errorf("$timestamp %s number should be uint32: %d", key, value)
}
return uint32(value), nil
case bsontype.Int64:
value := val.v.(int64)
if value < 0 || value > int64(math.MaxUint32) {
return 0, fmt.Errorf("$timestamp %s number should be uint32: %d", key, value)
}
return uint32(value), nil
default:
return 0, fmt.Errorf("$timestamp %s value should be uint32, but instead is %s", key, val.t)
}
}
tsObj := ejv.v.(*extJSONObject)
tFound := false
iFound := false
for j, key := range tsObj.keys {
val := tsObj.values[j]
switch key {
case "t":
if t, err = handleKey(key, val, tFound); err != nil {
return 0, 0, err
}
tFound = true
case "i":
if i, err = handleKey(key, val, iFound); err != nil {
return 0, 0, err
}
iFound = true
default:
return 0, 0, fmt.Errorf("invalid key in $timestamp object: %s", key)
}
}
if !tFound {
return 0, 0, errors.New("missing t field in $timestamp object")
}
if !iFound {
return 0, 0, errors.New("missing i field in $timestamp object")
}
return t, i, nil
}
func (ejv *extJSONValue) parseUndefined() error {
if ejv.t != bsontype.Boolean {
return fmt.Errorf("undefined value should be boolean, but instead is %s", ejv.t)
}
if !ejv.v.(bool) {
return fmt.Errorf("$undefined balue boolean should be true, but instead is %v", ejv.v.(bool))
}
return nil
}

View File

@ -0,0 +1,732 @@
// 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 bsonrw
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"math"
"sort"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ExtJSONValueWriterPool is a pool for ExtJSON ValueWriters.
type ExtJSONValueWriterPool struct {
pool sync.Pool
}
// NewExtJSONValueWriterPool creates a new pool for ValueWriter instances that write to ExtJSON.
func NewExtJSONValueWriterPool() *ExtJSONValueWriterPool {
return &ExtJSONValueWriterPool{
pool: sync.Pool{
New: func() interface{} {
return new(extJSONValueWriter)
},
},
}
}
// Get retrieves a ExtJSON ValueWriter from the pool and resets it to use w as the destination.
func (bvwp *ExtJSONValueWriterPool) Get(w io.Writer, canonical, escapeHTML bool) ValueWriter {
vw := bvwp.pool.Get().(*extJSONValueWriter)
if writer, ok := w.(*SliceWriter); ok {
vw.reset(*writer, canonical, escapeHTML)
vw.w = writer
return vw
}
vw.buf = vw.buf[:0]
vw.w = w
return vw
}
// Put inserts a ValueWriter into the pool. If the ValueWriter is not a ExtJSON ValueWriter, nothing
// happens and ok will be false.
func (bvwp *ExtJSONValueWriterPool) Put(vw ValueWriter) (ok bool) {
bvw, ok := vw.(*extJSONValueWriter)
if !ok {
return false
}
if _, ok := bvw.w.(*SliceWriter); ok {
bvw.buf = nil
}
bvw.w = nil
bvwp.pool.Put(bvw)
return true
}
type ejvwState struct {
mode mode
}
type extJSONValueWriter struct {
w io.Writer
buf []byte
stack []ejvwState
frame int64
canonical bool
escapeHTML bool
}
// NewExtJSONValueWriter creates a ValueWriter that writes Extended JSON to w.
func NewExtJSONValueWriter(w io.Writer, canonical, escapeHTML bool) (ValueWriter, error) {
if w == nil {
return nil, errNilWriter
}
return newExtJSONWriter(w, canonical, escapeHTML), nil
}
func newExtJSONWriter(w io.Writer, canonical, escapeHTML bool) *extJSONValueWriter {
stack := make([]ejvwState, 1, 5)
stack[0] = ejvwState{mode: mTopLevel}
return &extJSONValueWriter{
w: w,
buf: []byte{},
stack: stack,
canonical: canonical,
escapeHTML: escapeHTML,
}
}
func newExtJSONWriterFromSlice(buf []byte, canonical, escapeHTML bool) *extJSONValueWriter {
stack := make([]ejvwState, 1, 5)
stack[0] = ejvwState{mode: mTopLevel}
return &extJSONValueWriter{
buf: buf,
stack: stack,
canonical: canonical,
escapeHTML: escapeHTML,
}
}
func (ejvw *extJSONValueWriter) reset(buf []byte, canonical, escapeHTML bool) {
if ejvw.stack == nil {
ejvw.stack = make([]ejvwState, 1, 5)
}
ejvw.stack = ejvw.stack[:1]
ejvw.stack[0] = ejvwState{mode: mTopLevel}
ejvw.canonical = canonical
ejvw.escapeHTML = escapeHTML
ejvw.frame = 0
ejvw.buf = buf
ejvw.w = nil
}
func (ejvw *extJSONValueWriter) advanceFrame() {
if ejvw.frame+1 >= int64(len(ejvw.stack)) { // We need to grow the stack
length := len(ejvw.stack)
if length+1 >= cap(ejvw.stack) {
// double it
buf := make([]ejvwState, 2*cap(ejvw.stack)+1)
copy(buf, ejvw.stack)
ejvw.stack = buf
}
ejvw.stack = ejvw.stack[:length+1]
}
ejvw.frame++
}
func (ejvw *extJSONValueWriter) push(m mode) {
ejvw.advanceFrame()
ejvw.stack[ejvw.frame].mode = m
}
func (ejvw *extJSONValueWriter) pop() {
switch ejvw.stack[ejvw.frame].mode {
case mElement, mValue:
ejvw.frame--
case mDocument, mArray, mCodeWithScope:
ejvw.frame -= 2 // we pop twice to jump over the mElement: mDocument -> mElement -> mDocument/mTopLevel/etc...
}
}
func (ejvw *extJSONValueWriter) invalidTransitionErr(destination mode, name string, modes []mode) error {
te := TransitionError{
name: name,
current: ejvw.stack[ejvw.frame].mode,
destination: destination,
modes: modes,
action: "write",
}
if ejvw.frame != 0 {
te.parent = ejvw.stack[ejvw.frame-1].mode
}
return te
}
func (ejvw *extJSONValueWriter) ensureElementValue(destination mode, callerName string, addmodes ...mode) error {
switch ejvw.stack[ejvw.frame].mode {
case mElement, mValue:
default:
modes := []mode{mElement, mValue}
if addmodes != nil {
modes = append(modes, addmodes...)
}
return ejvw.invalidTransitionErr(destination, callerName, modes)
}
return nil
}
func (ejvw *extJSONValueWriter) writeExtendedSingleValue(key string, value string, quotes bool) {
var s string
if quotes {
s = fmt.Sprintf(`{"$%s":"%s"}`, key, value)
} else {
s = fmt.Sprintf(`{"$%s":%s}`, key, value)
}
ejvw.buf = append(ejvw.buf, []byte(s)...)
}
func (ejvw *extJSONValueWriter) WriteArray() (ArrayWriter, error) {
if err := ejvw.ensureElementValue(mArray, "WriteArray"); err != nil {
return nil, err
}
ejvw.buf = append(ejvw.buf, '[')
ejvw.push(mArray)
return ejvw, nil
}
func (ejvw *extJSONValueWriter) WriteBinary(b []byte) error {
return ejvw.WriteBinaryWithSubtype(b, 0x00)
}
func (ejvw *extJSONValueWriter) WriteBinaryWithSubtype(b []byte, btype byte) error {
if err := ejvw.ensureElementValue(mode(0), "WriteBinaryWithSubtype"); err != nil {
return err
}
var buf bytes.Buffer
buf.WriteString(`{"$binary":{"base64":"`)
buf.WriteString(base64.StdEncoding.EncodeToString(b))
buf.WriteString(fmt.Sprintf(`","subType":"%02x"}},`, btype))
ejvw.buf = append(ejvw.buf, buf.Bytes()...)
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteBoolean(b bool) error {
if err := ejvw.ensureElementValue(mode(0), "WriteBoolean"); err != nil {
return err
}
ejvw.buf = append(ejvw.buf, []byte(strconv.FormatBool(b))...)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteCodeWithScope(code string) (DocumentWriter, error) {
if err := ejvw.ensureElementValue(mCodeWithScope, "WriteCodeWithScope"); err != nil {
return nil, err
}
var buf bytes.Buffer
buf.WriteString(`{"$code":`)
writeStringWithEscapes(code, &buf, ejvw.escapeHTML)
buf.WriteString(`,"$scope":{`)
ejvw.buf = append(ejvw.buf, buf.Bytes()...)
ejvw.push(mCodeWithScope)
return ejvw, nil
}
func (ejvw *extJSONValueWriter) WriteDBPointer(ns string, oid primitive.ObjectID) error {
if err := ejvw.ensureElementValue(mode(0), "WriteDBPointer"); err != nil {
return err
}
var buf bytes.Buffer
buf.WriteString(`{"$dbPointer":{"$ref":"`)
buf.WriteString(ns)
buf.WriteString(`","$id":{"$oid":"`)
buf.WriteString(oid.Hex())
buf.WriteString(`"}}},`)
ejvw.buf = append(ejvw.buf, buf.Bytes()...)
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteDateTime(dt int64) error {
if err := ejvw.ensureElementValue(mode(0), "WriteDateTime"); err != nil {
return err
}
t := time.Unix(dt/1e3, dt%1e3*1e6).UTC()
if ejvw.canonical || t.Year() < 1970 || t.Year() > 9999 {
s := fmt.Sprintf(`{"$numberLong":"%d"}`, dt)
ejvw.writeExtendedSingleValue("date", s, false)
} else {
ejvw.writeExtendedSingleValue("date", t.Format(rfc3339Milli), true)
}
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteDecimal128(d primitive.Decimal128) error {
if err := ejvw.ensureElementValue(mode(0), "WriteDecimal128"); err != nil {
return err
}
ejvw.writeExtendedSingleValue("numberDecimal", d.String(), true)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteDocument() (DocumentWriter, error) {
if ejvw.stack[ejvw.frame].mode == mTopLevel {
ejvw.buf = append(ejvw.buf, '{')
return ejvw, nil
}
if err := ejvw.ensureElementValue(mDocument, "WriteDocument", mTopLevel); err != nil {
return nil, err
}
ejvw.buf = append(ejvw.buf, '{')
ejvw.push(mDocument)
return ejvw, nil
}
func (ejvw *extJSONValueWriter) WriteDouble(f float64) error {
if err := ejvw.ensureElementValue(mode(0), "WriteDouble"); err != nil {
return err
}
s := formatDouble(f)
if ejvw.canonical {
ejvw.writeExtendedSingleValue("numberDouble", s, true)
} else {
switch s {
case "Infinity":
fallthrough
case "-Infinity":
fallthrough
case "NaN":
s = fmt.Sprintf(`{"$numberDouble":"%s"}`, s)
}
ejvw.buf = append(ejvw.buf, []byte(s)...)
}
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteInt32(i int32) error {
if err := ejvw.ensureElementValue(mode(0), "WriteInt32"); err != nil {
return err
}
s := strconv.FormatInt(int64(i), 10)
if ejvw.canonical {
ejvw.writeExtendedSingleValue("numberInt", s, true)
} else {
ejvw.buf = append(ejvw.buf, []byte(s)...)
}
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteInt64(i int64) error {
if err := ejvw.ensureElementValue(mode(0), "WriteInt64"); err != nil {
return err
}
s := strconv.FormatInt(i, 10)
if ejvw.canonical {
ejvw.writeExtendedSingleValue("numberLong", s, true)
} else {
ejvw.buf = append(ejvw.buf, []byte(s)...)
}
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteJavascript(code string) error {
if err := ejvw.ensureElementValue(mode(0), "WriteJavascript"); err != nil {
return err
}
var buf bytes.Buffer
writeStringWithEscapes(code, &buf, ejvw.escapeHTML)
ejvw.writeExtendedSingleValue("code", buf.String(), false)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteMaxKey() error {
if err := ejvw.ensureElementValue(mode(0), "WriteMaxKey"); err != nil {
return err
}
ejvw.writeExtendedSingleValue("maxKey", "1", false)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteMinKey() error {
if err := ejvw.ensureElementValue(mode(0), "WriteMinKey"); err != nil {
return err
}
ejvw.writeExtendedSingleValue("minKey", "1", false)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteNull() error {
if err := ejvw.ensureElementValue(mode(0), "WriteNull"); err != nil {
return err
}
ejvw.buf = append(ejvw.buf, []byte("null")...)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteObjectID(oid primitive.ObjectID) error {
if err := ejvw.ensureElementValue(mode(0), "WriteObjectID"); err != nil {
return err
}
ejvw.writeExtendedSingleValue("oid", oid.Hex(), true)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteRegex(pattern string, options string) error {
if err := ejvw.ensureElementValue(mode(0), "WriteRegex"); err != nil {
return err
}
var buf bytes.Buffer
buf.WriteString(`{"$regularExpression":{"pattern":`)
writeStringWithEscapes(pattern, &buf, ejvw.escapeHTML)
buf.WriteString(`,"options":"`)
buf.WriteString(sortStringAlphebeticAscending(options))
buf.WriteString(`"}},`)
ejvw.buf = append(ejvw.buf, buf.Bytes()...)
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteString(s string) error {
if err := ejvw.ensureElementValue(mode(0), "WriteString"); err != nil {
return err
}
var buf bytes.Buffer
writeStringWithEscapes(s, &buf, ejvw.escapeHTML)
ejvw.buf = append(ejvw.buf, buf.Bytes()...)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteSymbol(symbol string) error {
if err := ejvw.ensureElementValue(mode(0), "WriteSymbol"); err != nil {
return err
}
var buf bytes.Buffer
writeStringWithEscapes(symbol, &buf, ejvw.escapeHTML)
ejvw.writeExtendedSingleValue("symbol", buf.String(), false)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteTimestamp(t uint32, i uint32) error {
if err := ejvw.ensureElementValue(mode(0), "WriteTimestamp"); err != nil {
return err
}
var buf bytes.Buffer
buf.WriteString(`{"$timestamp":{"t":`)
buf.WriteString(strconv.FormatUint(uint64(t), 10))
buf.WriteString(`,"i":`)
buf.WriteString(strconv.FormatUint(uint64(i), 10))
buf.WriteString(`}},`)
ejvw.buf = append(ejvw.buf, buf.Bytes()...)
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteUndefined() error {
if err := ejvw.ensureElementValue(mode(0), "WriteUndefined"); err != nil {
return err
}
ejvw.writeExtendedSingleValue("undefined", "true", false)
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteDocumentElement(key string) (ValueWriter, error) {
switch ejvw.stack[ejvw.frame].mode {
case mDocument, mTopLevel, mCodeWithScope:
var buf bytes.Buffer
writeStringWithEscapes(key, &buf, ejvw.escapeHTML)
ejvw.buf = append(ejvw.buf, []byte(fmt.Sprintf(`%s:`, buf.String()))...)
ejvw.push(mElement)
default:
return nil, ejvw.invalidTransitionErr(mElement, "WriteDocumentElement", []mode{mDocument, mTopLevel, mCodeWithScope})
}
return ejvw, nil
}
func (ejvw *extJSONValueWriter) WriteDocumentEnd() error {
switch ejvw.stack[ejvw.frame].mode {
case mDocument, mTopLevel, mCodeWithScope:
default:
return fmt.Errorf("incorrect mode to end document: %s", ejvw.stack[ejvw.frame].mode)
}
// close the document
if ejvw.buf[len(ejvw.buf)-1] == ',' {
ejvw.buf[len(ejvw.buf)-1] = '}'
} else {
ejvw.buf = append(ejvw.buf, '}')
}
switch ejvw.stack[ejvw.frame].mode {
case mCodeWithScope:
ejvw.buf = append(ejvw.buf, '}')
fallthrough
case mDocument:
ejvw.buf = append(ejvw.buf, ',')
case mTopLevel:
if ejvw.w != nil {
if _, err := ejvw.w.Write(ejvw.buf); err != nil {
return err
}
ejvw.buf = ejvw.buf[:0]
}
}
ejvw.pop()
return nil
}
func (ejvw *extJSONValueWriter) WriteArrayElement() (ValueWriter, error) {
switch ejvw.stack[ejvw.frame].mode {
case mArray:
ejvw.push(mValue)
default:
return nil, ejvw.invalidTransitionErr(mValue, "WriteArrayElement", []mode{mArray})
}
return ejvw, nil
}
func (ejvw *extJSONValueWriter) WriteArrayEnd() error {
switch ejvw.stack[ejvw.frame].mode {
case mArray:
// close the array
if ejvw.buf[len(ejvw.buf)-1] == ',' {
ejvw.buf[len(ejvw.buf)-1] = ']'
} else {
ejvw.buf = append(ejvw.buf, ']')
}
ejvw.buf = append(ejvw.buf, ',')
ejvw.pop()
default:
return fmt.Errorf("incorrect mode to end array: %s", ejvw.stack[ejvw.frame].mode)
}
return nil
}
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, 'E') && !strings.ContainsRune(s, '.') {
s += ".0"
}
}
return s
}
var hexChars = "0123456789abcdef"
func writeStringWithEscapes(s string, buf *bytes.Buffer, escapeHTML bool) {
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('"')
}
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))
}

View File

@ -0,0 +1,260 @@
// 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 bsonrw
import (
"fmt"
"io/ioutil"
"reflect"
"strings"
"testing"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func TestExtJSONValueWriter(t *testing.T) {
oid := primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}
testCases := []struct {
name string
fn interface{}
params []interface{}
}{
{
"WriteBinary",
(*extJSONValueWriter).WriteBinary,
[]interface{}{[]byte{0x01, 0x02, 0x03}},
},
{
"WriteBinaryWithSubtype (not 0x02)",
(*extJSONValueWriter).WriteBinaryWithSubtype,
[]interface{}{[]byte{0x01, 0x02, 0x03}, byte(0xFF)},
},
{
"WriteBinaryWithSubtype (0x02)",
(*extJSONValueWriter).WriteBinaryWithSubtype,
[]interface{}{[]byte{0x01, 0x02, 0x03}, byte(0x02)},
},
{
"WriteBoolean",
(*extJSONValueWriter).WriteBoolean,
[]interface{}{true},
},
{
"WriteDBPointer",
(*extJSONValueWriter).WriteDBPointer,
[]interface{}{"bar", oid},
},
{
"WriteDateTime",
(*extJSONValueWriter).WriteDateTime,
[]interface{}{int64(12345678)},
},
{
"WriteDecimal128",
(*extJSONValueWriter).WriteDecimal128,
[]interface{}{primitive.NewDecimal128(10, 20)},
},
{
"WriteDouble",
(*extJSONValueWriter).WriteDouble,
[]interface{}{float64(3.14159)},
},
{
"WriteInt32",
(*extJSONValueWriter).WriteInt32,
[]interface{}{int32(123456)},
},
{
"WriteInt64",
(*extJSONValueWriter).WriteInt64,
[]interface{}{int64(1234567890)},
},
{
"WriteJavascript",
(*extJSONValueWriter).WriteJavascript,
[]interface{}{"var foo = 'bar';"},
},
{
"WriteMaxKey",
(*extJSONValueWriter).WriteMaxKey,
[]interface{}{},
},
{
"WriteMinKey",
(*extJSONValueWriter).WriteMinKey,
[]interface{}{},
},
{
"WriteNull",
(*extJSONValueWriter).WriteNull,
[]interface{}{},
},
{
"WriteObjectID",
(*extJSONValueWriter).WriteObjectID,
[]interface{}{oid},
},
{
"WriteRegex",
(*extJSONValueWriter).WriteRegex,
[]interface{}{"bar", "baz"},
},
{
"WriteString",
(*extJSONValueWriter).WriteString,
[]interface{}{"hello, world!"},
},
{
"WriteSymbol",
(*extJSONValueWriter).WriteSymbol,
[]interface{}{"symbollolz"},
},
{
"WriteTimestamp",
(*extJSONValueWriter).WriteTimestamp,
[]interface{}{uint32(10), uint32(20)},
},
{
"WriteUndefined",
(*extJSONValueWriter).WriteUndefined,
[]interface{}{},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fn := reflect.ValueOf(tc.fn)
if fn.Kind() != reflect.Func {
t.Fatalf("fn must be of kind Func but it is a %v", fn.Kind())
}
if fn.Type().NumIn() != len(tc.params)+1 || fn.Type().In(0) != reflect.TypeOf((*extJSONValueWriter)(nil)) {
t.Fatalf("fn must have at least one parameter and the first parameter must be a *valueWriter")
}
if fn.Type().NumOut() != 1 || fn.Type().Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
t.Fatalf("fn must have one return value and it must be an error.")
}
params := make([]reflect.Value, 1, len(tc.params)+1)
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
params[0] = reflect.ValueOf(ejvw)
for _, param := range tc.params {
params = append(params, reflect.ValueOf(param))
}
t.Run("incorrect transition", func(t *testing.T) {
results := fn.Call(params)
got := results[0].Interface().(error)
fnName := tc.name
if strings.Contains(fnName, "WriteBinary") {
fnName = "WriteBinaryWithSubtype"
}
want := TransitionError{current: mTopLevel, name: fnName, modes: []mode{mElement, mValue},
action: "write"}
if !compareErrors(got, want) {
t.Errorf("Errors do not match. got %v; want %v", got, want)
}
})
})
}
t.Run("WriteArray", func(t *testing.T) {
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
ejvw.push(mArray)
want := TransitionError{current: mArray, destination: mArray, parent: mTopLevel,
name: "WriteArray", modes: []mode{mElement, mValue}, action: "write"}
_, got := ejvw.WriteArray()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteCodeWithScope", func(t *testing.T) {
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
ejvw.push(mArray)
want := TransitionError{current: mArray, destination: mCodeWithScope, parent: mTopLevel,
name: "WriteCodeWithScope", modes: []mode{mElement, mValue}, action: "write"}
_, got := ejvw.WriteCodeWithScope("")
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteDocument", func(t *testing.T) {
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
ejvw.push(mArray)
want := TransitionError{current: mArray, destination: mDocument, parent: mTopLevel,
name: "WriteDocument", modes: []mode{mElement, mValue, mTopLevel}, action: "write"}
_, got := ejvw.WriteDocument()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteDocumentElement", func(t *testing.T) {
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
ejvw.push(mElement)
want := TransitionError{current: mElement,
destination: mElement,
parent: mTopLevel,
name: "WriteDocumentElement",
modes: []mode{mDocument, mTopLevel, mCodeWithScope},
action: "write"}
_, got := ejvw.WriteDocumentElement("")
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteDocumentEnd", func(t *testing.T) {
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
ejvw.push(mElement)
want := fmt.Errorf("incorrect mode to end document: %s", mElement)
got := ejvw.WriteDocumentEnd()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteArrayElement", func(t *testing.T) {
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
ejvw.push(mElement)
want := TransitionError{current: mElement,
destination: mValue,
parent: mTopLevel,
name: "WriteArrayElement",
modes: []mode{mArray},
action: "write"}
_, got := ejvw.WriteArrayElement()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteArrayEnd", func(t *testing.T) {
ejvw := newExtJSONWriter(ioutil.Discard, true, true)
ejvw.push(mElement)
want := fmt.Errorf("incorrect mode to end array: %s", mElement)
got := ejvw.WriteArrayEnd()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteBytes", func(t *testing.T) {
t.Run("writeElementHeader error", func(t *testing.T) {
ejvw := newExtJSONWriterFromSlice(nil, true, true)
want := TransitionError{current: mTopLevel, destination: mode(0),
name: "WriteBinaryWithSubtype", modes: []mode{mElement, mValue}, action: "write"}
got := ejvw.WriteBinaryWithSubtype(nil, (byte)(bsontype.EmbeddedDocument))
if !compareErrors(got, want) {
t.Errorf("Did not received expected error. got %v; want %v", got, want)
}
})
})
t.Run("FormatDoubleWithExponent", func(t *testing.T) {
want := "3E-12"
got := formatDouble(float64(0.000000000003))
if got != want {
t.Errorf("Did not receive expected string. got %s: want %s", got, want)
}
})
}

View File

@ -0,0 +1,528 @@
// 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 bsonrw
import (
"bytes"
"errors"
"fmt"
"io"
"math"
"strconv"
"unicode"
"unicode/utf16"
)
type jsonTokenType byte
const (
jttBeginObject jsonTokenType = iota
jttEndObject
jttBeginArray
jttEndArray
jttColon
jttComma
jttInt32
jttInt64
jttDouble
jttString
jttBool
jttNull
jttEOF
)
type jsonToken struct {
t jsonTokenType
v interface{}
p int
}
type jsonScanner struct {
r io.Reader
buf []byte
pos int
lastReadErr error
}
// nextToken returns the next JSON token if one exists. A token is a character
// of the JSON grammar, a number, a string, or a literal.
func (js *jsonScanner) nextToken() (*jsonToken, error) {
c, err := js.readNextByte()
// keep reading until a non-space is encountered (break on read error or EOF)
for isWhiteSpace(c) && err == nil {
c, err = js.readNextByte()
}
if err == io.EOF {
return &jsonToken{t: jttEOF}, nil
} else if err != nil {
return nil, err
}
// switch on the character
switch c {
case '{':
return &jsonToken{t: jttBeginObject, v: byte('{'), p: js.pos - 1}, nil
case '}':
return &jsonToken{t: jttEndObject, v: byte('}'), p: js.pos - 1}, nil
case '[':
return &jsonToken{t: jttBeginArray, v: byte('['), p: js.pos - 1}, nil
case ']':
return &jsonToken{t: jttEndArray, v: byte(']'), p: js.pos - 1}, nil
case ':':
return &jsonToken{t: jttColon, v: byte(':'), p: js.pos - 1}, nil
case ',':
return &jsonToken{t: jttComma, v: byte(','), p: js.pos - 1}, nil
case '"': // RFC-8259 only allows for double quotes (") not single (')
return js.scanString()
default:
// check if it's a number
if c == '-' || isDigit(c) {
return js.scanNumber(c)
} else if c == 't' || c == 'f' || c == 'n' {
// maybe a literal
return js.scanLiteral(c)
} else {
return nil, fmt.Errorf("invalid JSON input. Position: %d. Character: %c", js.pos-1, c)
}
}
}
// readNextByte attempts to read the next byte from the buffer. If the buffer
// has been exhausted, this function calls readIntoBuf, thus refilling the
// buffer and resetting the read position to 0
func (js *jsonScanner) readNextByte() (byte, error) {
if js.pos >= len(js.buf) {
err := js.readIntoBuf()
if err != nil {
return 0, err
}
}
b := js.buf[js.pos]
js.pos++
return b, nil
}
// readNNextBytes reads n bytes into dst, starting at offset
func (js *jsonScanner) readNNextBytes(dst []byte, n, offset int) error {
var err error
for i := 0; i < n; i++ {
dst[i+offset], err = js.readNextByte()
if err != nil {
return err
}
}
return nil
}
// readIntoBuf reads up to 512 bytes from the scanner's io.Reader into the buffer
func (js *jsonScanner) readIntoBuf() error {
if js.lastReadErr != nil {
js.buf = js.buf[:0]
js.pos = 0
return js.lastReadErr
}
if cap(js.buf) == 0 {
js.buf = make([]byte, 0, 512)
}
n, err := js.r.Read(js.buf[:cap(js.buf)])
if err != nil {
js.lastReadErr = err
if n > 0 {
err = nil
}
}
js.buf = js.buf[:n]
js.pos = 0
return err
}
func isWhiteSpace(c byte) bool {
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
}
func isDigit(c byte) bool {
return unicode.IsDigit(rune(c))
}
func isValueTerminator(c byte) bool {
return c == ',' || c == '}' || c == ']' || isWhiteSpace(c)
}
// getu4 decodes the 4-byte hex sequence from the beginning of s, returning the hex value as a rune,
// or it returns -1. Note that the "\u" from the unicode escape sequence should not be present.
// It is copied and lightly modified from the Go JSON decode function at
// https://github.com/golang/go/blob/1b0a0316802b8048d69da49dc23c5a5ab08e8ae8/src/encoding/json/decode.go#L1169-L1188
func getu4(s []byte) rune {
if len(s) < 4 {
return -1
}
var r rune
for _, c := range s[:4] {
switch {
case '0' <= c && c <= '9':
c = c - '0'
case 'a' <= c && c <= 'f':
c = c - 'a' + 10
case 'A' <= c && c <= 'F':
c = c - 'A' + 10
default:
return -1
}
r = r*16 + rune(c)
}
return r
}
// scanString reads from an opening '"' to a closing '"' and handles escaped characters
func (js *jsonScanner) scanString() (*jsonToken, error) {
var b bytes.Buffer
var c byte
var err error
p := js.pos - 1
for {
c, err = js.readNextByte()
if err != nil {
if err == io.EOF {
return nil, errors.New("end of input in JSON string")
}
return nil, err
}
evalNextChar:
switch c {
case '\\':
c, err = js.readNextByte()
if err != nil {
if err == io.EOF {
return nil, errors.New("end of input in JSON string")
}
return nil, err
}
evalNextEscapeChar:
switch c {
case '"', '\\', '/':
b.WriteByte(c)
case 'b':
b.WriteByte('\b')
case 'f':
b.WriteByte('\f')
case 'n':
b.WriteByte('\n')
case 'r':
b.WriteByte('\r')
case 't':
b.WriteByte('\t')
case 'u':
us := make([]byte, 4)
err = js.readNNextBytes(us, 4, 0)
if err != nil {
return nil, fmt.Errorf("invalid unicode sequence in JSON string: %s", us)
}
rn := getu4(us)
// If the rune we just decoded is the high or low value of a possible surrogate pair,
// try to decode the next sequence as the low value of a surrogate pair. We're
// expecting the next sequence to be another Unicode escape sequence (e.g. "\uDD1E"),
// but need to handle cases where the input is not a valid surrogate pair.
// For more context on unicode surrogate pairs, see:
// https://www.christianfscott.com/rust-chars-vs-go-runes/
// https://www.unicode.org/glossary/#high_surrogate_code_point
if utf16.IsSurrogate(rn) {
c, err = js.readNextByte()
if err != nil {
if err == io.EOF {
return nil, errors.New("end of input in JSON string")
}
return nil, err
}
// If the next value isn't the beginning of a backslash escape sequence, write
// the Unicode replacement character for the surrogate value and goto the
// beginning of the next char eval block.
if c != '\\' {
b.WriteRune(unicode.ReplacementChar)
goto evalNextChar
}
c, err = js.readNextByte()
if err != nil {
if err == io.EOF {
return nil, errors.New("end of input in JSON string")
}
return nil, err
}
// If the next value isn't the beginning of a unicode escape sequence, write the
// Unicode replacement character for the surrogate value and goto the beginning
// of the next escape char eval block.
if c != 'u' {
b.WriteRune(unicode.ReplacementChar)
goto evalNextEscapeChar
}
err = js.readNNextBytes(us, 4, 0)
if err != nil {
return nil, fmt.Errorf("invalid unicode sequence in JSON string: %s", us)
}
rn2 := getu4(us)
// Try to decode the pair of runes as a utf16 surrogate pair. If that fails, write
// the Unicode replacement character for the surrogate value and the 2nd decoded rune.
if rnPair := utf16.DecodeRune(rn, rn2); rnPair != unicode.ReplacementChar {
b.WriteRune(rnPair)
} else {
b.WriteRune(unicode.ReplacementChar)
b.WriteRune(rn2)
}
break
}
b.WriteRune(rn)
default:
return nil, fmt.Errorf("invalid escape sequence in JSON string '\\%c'", c)
}
case '"':
return &jsonToken{t: jttString, v: b.String(), p: p}, nil
default:
b.WriteByte(c)
}
}
}
// scanLiteral reads an unquoted sequence of characters and determines if it is one of
// three valid JSON literals (true, false, null); if so, it returns the appropriate
// jsonToken; otherwise, it returns an error
func (js *jsonScanner) scanLiteral(first byte) (*jsonToken, error) {
p := js.pos - 1
lit := make([]byte, 4)
lit[0] = first
err := js.readNNextBytes(lit, 3, 1)
if err != nil {
return nil, err
}
c5, err := js.readNextByte()
if bytes.Equal([]byte("true"), lit) && (isValueTerminator(c5) || err == io.EOF) {
js.pos = int(math.Max(0, float64(js.pos-1)))
return &jsonToken{t: jttBool, v: true, p: p}, nil
} else if bytes.Equal([]byte("null"), lit) && (isValueTerminator(c5) || err == io.EOF) {
js.pos = int(math.Max(0, float64(js.pos-1)))
return &jsonToken{t: jttNull, v: nil, p: p}, nil
} else if bytes.Equal([]byte("fals"), lit) {
if c5 == 'e' {
c5, err = js.readNextByte()
if isValueTerminator(c5) || err == io.EOF {
js.pos = int(math.Max(0, float64(js.pos-1)))
return &jsonToken{t: jttBool, v: false, p: p}, nil
}
}
}
return nil, fmt.Errorf("invalid JSON literal. Position: %d, literal: %s", p, lit)
}
type numberScanState byte
const (
nssSawLeadingMinus numberScanState = iota
nssSawLeadingZero
nssSawIntegerDigits
nssSawDecimalPoint
nssSawFractionDigits
nssSawExponentLetter
nssSawExponentSign
nssSawExponentDigits
nssDone
nssInvalid
)
// scanNumber reads a JSON number (according to RFC-8259)
func (js *jsonScanner) scanNumber(first byte) (*jsonToken, error) {
var b bytes.Buffer
var s numberScanState
var c byte
var err error
t := jttInt64 // assume it's an int64 until the type can be determined
start := js.pos - 1
b.WriteByte(first)
switch first {
case '-':
s = nssSawLeadingMinus
case '0':
s = nssSawLeadingZero
default:
s = nssSawIntegerDigits
}
for {
c, err = js.readNextByte()
if err != nil && err != io.EOF {
return nil, err
}
switch s {
case nssSawLeadingMinus:
switch c {
case '0':
s = nssSawLeadingZero
b.WriteByte(c)
default:
if isDigit(c) {
s = nssSawIntegerDigits
b.WriteByte(c)
} else {
s = nssInvalid
}
}
case nssSawLeadingZero:
switch c {
case '.':
s = nssSawDecimalPoint
b.WriteByte(c)
case 'e', 'E':
s = nssSawExponentLetter
b.WriteByte(c)
case '}', ']', ',':
s = nssDone
default:
if isWhiteSpace(c) || err == io.EOF {
s = nssDone
} else {
s = nssInvalid
}
}
case nssSawIntegerDigits:
switch c {
case '.':
s = nssSawDecimalPoint
b.WriteByte(c)
case 'e', 'E':
s = nssSawExponentLetter
b.WriteByte(c)
case '}', ']', ',':
s = nssDone
default:
if isWhiteSpace(c) || err == io.EOF {
s = nssDone
} else if isDigit(c) {
s = nssSawIntegerDigits
b.WriteByte(c)
} else {
s = nssInvalid
}
}
case nssSawDecimalPoint:
t = jttDouble
if isDigit(c) {
s = nssSawFractionDigits
b.WriteByte(c)
} else {
s = nssInvalid
}
case nssSawFractionDigits:
switch c {
case 'e', 'E':
s = nssSawExponentLetter
b.WriteByte(c)
case '}', ']', ',':
s = nssDone
default:
if isWhiteSpace(c) || err == io.EOF {
s = nssDone
} else if isDigit(c) {
s = nssSawFractionDigits
b.WriteByte(c)
} else {
s = nssInvalid
}
}
case nssSawExponentLetter:
t = jttDouble
switch c {
case '+', '-':
s = nssSawExponentSign
b.WriteByte(c)
default:
if isDigit(c) {
s = nssSawExponentDigits
b.WriteByte(c)
} else {
s = nssInvalid
}
}
case nssSawExponentSign:
if isDigit(c) {
s = nssSawExponentDigits
b.WriteByte(c)
} else {
s = nssInvalid
}
case nssSawExponentDigits:
switch c {
case '}', ']', ',':
s = nssDone
default:
if isWhiteSpace(c) || err == io.EOF {
s = nssDone
} else if isDigit(c) {
s = nssSawExponentDigits
b.WriteByte(c)
} else {
s = nssInvalid
}
}
}
switch s {
case nssInvalid:
return nil, fmt.Errorf("invalid JSON number. Position: %d", start)
case nssDone:
js.pos = int(math.Max(0, float64(js.pos-1)))
if t != jttDouble {
v, err := strconv.ParseInt(b.String(), 10, 64)
if err == nil {
if v < math.MinInt32 || v > math.MaxInt32 {
return &jsonToken{t: jttInt64, v: v, p: start}, nil
}
return &jsonToken{t: jttInt32, v: int32(v), p: start}, nil
}
}
v, err := strconv.ParseFloat(b.String(), 64)
if err != nil {
return nil, err
}
return &jsonToken{t: jttDouble, v: v, p: start}, nil
}
}
}

View File

@ -0,0 +1,376 @@
// 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 bsonrw
import (
"strings"
"testing"
"testing/iotest"
"github.com/google/go-cmp/cmp"
)
func jttDiff(t *testing.T, expected, actual jsonTokenType, desc string) {
if diff := cmp.Diff(expected, actual); diff != "" {
t.Helper()
t.Errorf("%s: Incorrect JSON Token Type (-want, +got): %s\n", desc, diff)
t.FailNow()
}
}
func jtvDiff(t *testing.T, expected, actual interface{}, desc string) {
if diff := cmp.Diff(expected, actual); diff != "" {
t.Helper()
t.Errorf("%s: Incorrect JSON Token Value (-want, +got): %s\n", desc, diff)
t.FailNow()
}
}
func expectNilToken(t *testing.T, v *jsonToken, desc string) {
if v != nil {
t.Helper()
t.Errorf("%s: Expected nil JSON token", desc)
t.FailNow()
}
}
func expectError(t *testing.T, err error, desc string) {
if err == nil {
t.Helper()
t.Errorf("%s: Expected error", desc)
t.FailNow()
}
}
func expectNoError(t *testing.T, err error, desc string) {
if err != nil {
t.Helper()
t.Errorf("%s: Unepexted error: %v", desc, err)
t.FailNow()
}
}
type jsonScannerTestCase struct {
desc string
input string
tokens []jsonToken
}
// length = 512
const longKey = "abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr"
func TestJsonScannerValidInputs(t *testing.T) {
cases := []jsonScannerTestCase{
{
desc: "empty input", input: "",
tokens: []jsonToken{},
},
{
desc: "empty object", input: "{}",
tokens: []jsonToken{{t: jttBeginObject, v: byte('{')}, {t: jttEndObject, v: byte('}')}},
},
{
desc: "empty array", input: "[]",
tokens: []jsonToken{{t: jttBeginArray, v: byte('[')}, {t: jttEndArray, v: byte(']')}},
},
{
desc: "valid empty string", input: `""`,
tokens: []jsonToken{{t: jttString, v: ""}},
},
{
desc: "valid string--no escaped characters",
input: `"string"`,
tokens: []jsonToken{{t: jttString, v: "string"}},
},
{
desc: "valid string--escaped characters",
input: `"\"\\\/\b\f\n\r\t"`,
tokens: []jsonToken{{t: jttString, v: "\"\\/\b\f\n\r\t"}},
},
{
desc: "valid string--surrogate pair",
input: `"abc \uD834\uDd1e 123"`,
tokens: []jsonToken{{t: jttString, v: "abc 𝄞 123"}},
},
{
desc: "valid string--high surrogate at end of string",
input: `"abc \uD834"`,
tokens: []jsonToken{{t: jttString, v: "abc <20>"}},
},
{
desc: "valid string--low surrogate at end of string",
input: `"abc \uDD1E"`,
tokens: []jsonToken{{t: jttString, v: "abc <20>"}},
},
{
desc: "valid string--high surrogate with non-surrogate Unicode value",
input: `"abc \uDD1E\u00BF"`,
tokens: []jsonToken{{t: jttString, v: "abc <20>¿"}},
},
{
desc: "valid string--high surrogate with non-Unicode escape sequence",
input: `"abc \uDD1E\t"`,
tokens: []jsonToken{{t: jttString, v: "abc <20>\t"}},
},
{
desc: "valid literal--true", input: "true",
tokens: []jsonToken{{t: jttBool, v: true}},
},
{
desc: "valid literal--false", input: "false",
tokens: []jsonToken{{t: jttBool, v: false}},
},
{
desc: "valid literal--null", input: "null",
tokens: []jsonToken{{t: jttNull}},
},
{
desc: "valid int32: 0", input: "0",
tokens: []jsonToken{{t: jttInt32, v: int32(0)}},
},
{
desc: "valid int32: -0", input: "-0",
tokens: []jsonToken{{t: jttInt32, v: int32(0)}},
},
{
desc: "valid int32: 1", input: "1",
tokens: []jsonToken{{t: jttInt32, v: int32(1)}},
},
{
desc: "valid int32: -1", input: "-1",
tokens: []jsonToken{{t: jttInt32, v: int32(-1)}},
},
{
desc: "valid int32: 10", input: "10",
tokens: []jsonToken{{t: jttInt32, v: int32(10)}},
},
{
desc: "valid int32: 1234", input: "1234",
tokens: []jsonToken{{t: jttInt32, v: int32(1234)}},
},
{
desc: "valid int32: -10", input: "-10",
tokens: []jsonToken{{t: jttInt32, v: int32(-10)}},
},
{
desc: "valid int32: -1234", input: "-1234",
tokens: []jsonToken{{t: jttInt32, v: int32(-1234)}},
},
{
desc: "valid int64: 2147483648", input: "2147483648",
tokens: []jsonToken{{t: jttInt64, v: int64(2147483648)}},
},
{
desc: "valid int64: -2147483649", input: "-2147483649",
tokens: []jsonToken{{t: jttInt64, v: int64(-2147483649)}},
},
{
desc: "valid double: 0.0", input: "0.0",
tokens: []jsonToken{{t: jttDouble, v: 0.0}},
},
{
desc: "valid double: -0.0", input: "-0.0",
tokens: []jsonToken{{t: jttDouble, v: 0.0}},
},
{
desc: "valid double: 0.1", input: "0.1",
tokens: []jsonToken{{t: jttDouble, v: 0.1}},
},
{
desc: "valid double: 0.1234", input: "0.1234",
tokens: []jsonToken{{t: jttDouble, v: 0.1234}},
},
{
desc: "valid double: 1.0", input: "1.0",
tokens: []jsonToken{{t: jttDouble, v: 1.0}},
},
{
desc: "valid double: -1.0", input: "-1.0",
tokens: []jsonToken{{t: jttDouble, v: -1.0}},
},
{
desc: "valid double: 1.234", input: "1.234",
tokens: []jsonToken{{t: jttDouble, v: 1.234}},
},
{
desc: "valid double: -1.234", input: "-1.234",
tokens: []jsonToken{{t: jttDouble, v: -1.234}},
},
{
desc: "valid double: 1e10", input: "1e10",
tokens: []jsonToken{{t: jttDouble, v: 1e+10}},
},
{
desc: "valid double: 1E10", input: "1E10",
tokens: []jsonToken{{t: jttDouble, v: 1e+10}},
},
{
desc: "valid double: 1.2e10", input: "1.2e10",
tokens: []jsonToken{{t: jttDouble, v: 1.2e+10}},
},
{
desc: "valid double: 1.2E10", input: "1.2E10",
tokens: []jsonToken{{t: jttDouble, v: 1.2e+10}},
},
{
desc: "valid double: -1.2e10", input: "-1.2e10",
tokens: []jsonToken{{t: jttDouble, v: -1.2e+10}},
},
{
desc: "valid double: -1.2E10", input: "-1.2E10",
tokens: []jsonToken{{t: jttDouble, v: -1.2e+10}},
},
{
desc: "valid double: -1.2e+10", input: "-1.2e+10",
tokens: []jsonToken{{t: jttDouble, v: -1.2e+10}},
},
{
desc: "valid double: -1.2E+10", input: "-1.2E+10",
tokens: []jsonToken{{t: jttDouble, v: -1.2e+10}},
},
{
desc: "valid double: 1.2e-10", input: "1.2e-10",
tokens: []jsonToken{{t: jttDouble, v: 1.2e-10}},
},
{
desc: "valid double: 1.2E-10", input: "1.2e-10",
tokens: []jsonToken{{t: jttDouble, v: 1.2e-10}},
},
{
desc: "valid double: -1.2e-10", input: "-1.2e-10",
tokens: []jsonToken{{t: jttDouble, v: -1.2e-10}},
},
{
desc: "valid double: -1.2E-10", input: "-1.2E-10",
tokens: []jsonToken{{t: jttDouble, v: -1.2e-10}},
},
{
desc: "valid double: 8005332285744496613785600", input: "8005332285744496613785600",
tokens: []jsonToken{{t: jttDouble, v: float64(8005332285744496613785600)}},
},
{
desc: "valid object, only spaces",
input: `{"key": "string", "key2": 2, "key3": {}, "key4": [], "key5": false }`,
tokens: []jsonToken{
{t: jttBeginObject, v: byte('{')}, {t: jttString, v: "key"}, {t: jttColon, v: byte(':')}, {t: jttString, v: "string"},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key2"}, {t: jttColon, v: byte(':')}, {t: jttInt32, v: int32(2)},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key3"}, {t: jttColon, v: byte(':')}, {t: jttBeginObject, v: byte('{')}, {t: jttEndObject, v: byte('}')},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key4"}, {t: jttColon, v: byte(':')}, {t: jttBeginArray, v: byte('[')}, {t: jttEndArray, v: byte(']')},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key5"}, {t: jttColon, v: byte(':')}, {t: jttBool, v: false}, {t: jttEndObject, v: byte('}')},
},
},
{
desc: "valid object, mixed whitespace",
input: `
{ "key" : "string"
, "key2": 2
, "key3": {}
, "key4": []
, "key5": false
}`,
tokens: []jsonToken{
{t: jttBeginObject, v: byte('{')}, {t: jttString, v: "key"}, {t: jttColon, v: byte(':')}, {t: jttString, v: "string"},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key2"}, {t: jttColon, v: byte(':')}, {t: jttInt32, v: int32(2)},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key3"}, {t: jttColon, v: byte(':')}, {t: jttBeginObject, v: byte('{')}, {t: jttEndObject, v: byte('}')},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key4"}, {t: jttColon, v: byte(':')}, {t: jttBeginArray, v: byte('[')}, {t: jttEndArray, v: byte(']')},
{t: jttComma, v: byte(',')}, {t: jttString, v: "key5"}, {t: jttColon, v: byte(':')}, {t: jttBool, v: false}, {t: jttEndObject, v: byte('}')},
},
},
{
desc: "input greater than buffer size",
input: `{"` + longKey + `": 1}`,
tokens: []jsonToken{
{t: jttBeginObject, v: byte('{')}, {t: jttString, v: longKey}, {t: jttColon, v: byte(':')},
{t: jttInt32, v: int32(1)}, {t: jttEndObject, v: byte('}')},
},
},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
js := &jsonScanner{r: strings.NewReader(tc.input)}
for _, token := range tc.tokens {
c, err := js.nextToken()
expectNoError(t, err, tc.desc)
jttDiff(t, token.t, c.t, tc.desc)
jtvDiff(t, token.v, c.v, tc.desc)
}
c, err := js.nextToken()
noerr(t, err)
jttDiff(t, jttEOF, c.t, tc.desc)
// testing early EOF reading
js = &jsonScanner{r: iotest.DataErrReader(strings.NewReader(tc.input))}
for _, token := range tc.tokens {
c, err := js.nextToken()
expectNoError(t, err, tc.desc)
jttDiff(t, token.t, c.t, tc.desc)
jtvDiff(t, token.v, c.v, tc.desc)
}
c, err = js.nextToken()
noerr(t, err)
jttDiff(t, jttEOF, c.t, tc.desc)
})
}
}
func TestJsonScannerInvalidInputs(t *testing.T) {
cases := []jsonScannerTestCase{
{desc: "missing quotation", input: `"missing`},
{desc: "invalid escape character--first character", input: `"\invalid"`},
{desc: "invalid escape character--middle", input: `"i\nv\alid"`},
{desc: "invalid escape character--single quote", input: `"f\'oo"`},
{desc: "invalid literal--trueee", input: "trueee"},
{desc: "invalid literal--tire", input: "tire"},
{desc: "invalid literal--nulll", input: "nulll"},
{desc: "invalid literal--fals", input: "fals"},
{desc: "invalid literal--falsee", input: "falsee"},
{desc: "invalid literal--fake", input: "fake"},
{desc: "invalid literal--bad", input: "bad"},
{desc: "invalid number: -", input: "-"},
{desc: "invalid number: --0", input: "--0"},
{desc: "invalid number: -a", input: "-a"},
{desc: "invalid number: 00", input: "00"},
{desc: "invalid number: 01", input: "01"},
{desc: "invalid number: 0-", input: "0-"},
{desc: "invalid number: 1-", input: "1-"},
{desc: "invalid number: 0..", input: "0.."},
{desc: "invalid number: 0.-", input: "0.-"},
{desc: "invalid number: 0..0", input: "0..0"},
{desc: "invalid number: 0.1.0", input: "0.1.0"},
{desc: "invalid number: 0e", input: "0e"},
{desc: "invalid number: 0e.", input: "0e."},
{desc: "invalid number: 0e1.", input: "0e1."},
{desc: "invalid number: 0e1e", input: "0e1e"},
{desc: "invalid number: 0e+.1", input: "0e+.1"},
{desc: "invalid number: 0e+1.", input: "0e+1."},
{desc: "invalid number: 0e+1e", input: "0e+1e"},
}
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
js := &jsonScanner{r: strings.NewReader(tc.input)}
c, err := js.nextToken()
expectNilToken(t, c, tc.desc)
expectError(t, err, tc.desc)
})
}
}

108
mongo/bson/bsonrw/mode.go Normal file
View File

@ -0,0 +1,108 @@
// 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 bsonrw
import (
"fmt"
)
type mode int
const (
_ mode = iota
mTopLevel
mDocument
mArray
mValue
mElement
mCodeWithScope
mSpacer
)
func (m mode) String() string {
var str string
switch m {
case mTopLevel:
str = "TopLevel"
case mDocument:
str = "DocumentMode"
case mArray:
str = "ArrayMode"
case mValue:
str = "ValueMode"
case mElement:
str = "ElementMode"
case mCodeWithScope:
str = "CodeWithScopeMode"
case mSpacer:
str = "CodeWithScopeSpacerFrame"
default:
str = "UnknownMode"
}
return str
}
func (m mode) TypeString() string {
var str string
switch m {
case mTopLevel:
str = "TopLevel"
case mDocument:
str = "Document"
case mArray:
str = "Array"
case mValue:
str = "Value"
case mElement:
str = "Element"
case mCodeWithScope:
str = "CodeWithScope"
case mSpacer:
str = "CodeWithScopeSpacer"
default:
str = "Unknown"
}
return str
}
// TransitionError is an error returned when an invalid progressing a
// ValueReader or ValueWriter state machine occurs.
// If read is false, the error is for writing
type TransitionError struct {
name string
parent mode
current mode
destination mode
modes []mode
action string
}
func (te TransitionError) Error() string {
errString := fmt.Sprintf("%s can only %s", te.name, te.action)
if te.destination != mode(0) {
errString = fmt.Sprintf("%s a %s", errString, te.destination.TypeString())
}
errString = fmt.Sprintf("%s while positioned on a", errString)
for ind, m := range te.modes {
if ind != 0 && len(te.modes) > 2 {
errString = fmt.Sprintf("%s,", errString)
}
if ind == len(te.modes)-1 && len(te.modes) > 1 {
errString = fmt.Sprintf("%s or", errString)
}
errString = fmt.Sprintf("%s %s", errString, m.TypeString())
}
errString = fmt.Sprintf("%s but is positioned on a %s", errString, te.current.TypeString())
if te.parent != mode(0) {
errString = fmt.Sprintf("%s with parent %s", errString, te.parent.TypeString())
}
return errString
}

View File

@ -0,0 +1,63 @@
// 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 bsonrw
import (
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ArrayReader is implemented by types that allow reading values from a BSON
// array.
type ArrayReader interface {
ReadValue() (ValueReader, error)
}
// DocumentReader is implemented by types that allow reading elements from a
// BSON document.
type DocumentReader interface {
ReadElement() (string, ValueReader, error)
}
// ValueReader is a generic interface used to read values from BSON. This type
// is implemented by several types with different underlying representations of
// BSON, such as a bson.Document, raw BSON bytes, or extended JSON.
type ValueReader interface {
Type() bsontype.Type
Skip() error
ReadArray() (ArrayReader, error)
ReadBinary() (b []byte, btype byte, err error)
ReadBoolean() (bool, error)
ReadDocument() (DocumentReader, error)
ReadCodeWithScope() (code string, dr DocumentReader, err error)
ReadDBPointer() (ns string, oid primitive.ObjectID, err error)
ReadDateTime() (int64, error)
ReadDecimal128() (primitive.Decimal128, error)
ReadDouble() (float64, error)
ReadInt32() (int32, error)
ReadInt64() (int64, error)
ReadJavascript() (code string, err error)
ReadMaxKey() error
ReadMinKey() error
ReadNull() error
ReadObjectID() (primitive.ObjectID, error)
ReadRegex() (pattern, options string, err error)
ReadString() (string, error)
ReadSymbol() (symbol string, err error)
ReadTimestamp() (t, i uint32, err error)
ReadUndefined() error
}
// BytesReader is a generic interface used to read BSON bytes from a
// ValueReader. This imterface is meant to be a superset of ValueReader, so that
// types that implement ValueReader may also implement this interface.
//
// The bytes of the value will be appended to dst.
type BytesReader interface {
ReadValueBytes(dst []byte) (bsontype.Type, []byte, error)
}

View File

@ -0,0 +1,874 @@
// 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 bsonrw
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"sync"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var _ ValueReader = (*valueReader)(nil)
var vrPool = sync.Pool{
New: func() interface{} {
return new(valueReader)
},
}
// BSONValueReaderPool is a pool for ValueReaders that read BSON.
type BSONValueReaderPool struct {
pool sync.Pool
}
// NewBSONValueReaderPool instantiates a new BSONValueReaderPool.
func NewBSONValueReaderPool() *BSONValueReaderPool {
return &BSONValueReaderPool{
pool: sync.Pool{
New: func() interface{} {
return new(valueReader)
},
},
}
}
// Get retrieves a ValueReader from the pool and uses src as the underlying BSON.
func (bvrp *BSONValueReaderPool) Get(src []byte) ValueReader {
vr := bvrp.pool.Get().(*valueReader)
vr.reset(src)
return vr
}
// Put inserts a ValueReader into the pool. If the ValueReader is not a BSON ValueReader nothing
// is inserted into the pool and ok will be false.
func (bvrp *BSONValueReaderPool) Put(vr ValueReader) (ok bool) {
bvr, ok := vr.(*valueReader)
if !ok {
return false
}
bvr.reset(nil)
bvrp.pool.Put(bvr)
return true
}
// ErrEOA is the error returned when the end of a BSON array has been reached.
var ErrEOA = errors.New("end of array")
// ErrEOD is the error returned when the end of a BSON document has been reached.
var ErrEOD = errors.New("end of document")
type vrState struct {
mode mode
vType bsontype.Type
end int64
}
// valueReader is for reading BSON values.
type valueReader struct {
offset int64
d []byte
stack []vrState
frame int64
}
// NewBSONDocumentReader returns a ValueReader using b for the underlying BSON
// representation. Parameter b must be a BSON Document.
func NewBSONDocumentReader(b []byte) ValueReader {
// TODO(skriptble): There's a lack of symmetry between the reader and writer, since the reader takes a []byte while the
// TODO writer takes an io.Writer. We should have two versions of each, one that takes a []byte and one that takes an
// TODO io.Reader or io.Writer. The []byte version will need to return a thing that can return the finished []byte since
// TODO it might be reallocated when appended to.
return newValueReader(b)
}
// NewBSONValueReader returns a ValueReader that starts in the Value mode instead of in top
// level document mode. This enables the creation of a ValueReader for a single BSON value.
func NewBSONValueReader(t bsontype.Type, val []byte) ValueReader {
stack := make([]vrState, 1, 5)
stack[0] = vrState{
mode: mValue,
vType: t,
}
return &valueReader{
d: val,
stack: stack,
}
}
func newValueReader(b []byte) *valueReader {
stack := make([]vrState, 1, 5)
stack[0] = vrState{
mode: mTopLevel,
}
return &valueReader{
d: b,
stack: stack,
}
}
func (vr *valueReader) reset(b []byte) {
if vr.stack == nil {
vr.stack = make([]vrState, 1, 5)
}
vr.stack = vr.stack[:1]
vr.stack[0] = vrState{mode: mTopLevel}
vr.d = b
vr.offset = 0
vr.frame = 0
}
func (vr *valueReader) advanceFrame() {
if vr.frame+1 >= int64(len(vr.stack)) { // We need to grow the stack
length := len(vr.stack)
if length+1 >= cap(vr.stack) {
// double it
buf := make([]vrState, 2*cap(vr.stack)+1)
copy(buf, vr.stack)
vr.stack = buf
}
vr.stack = vr.stack[:length+1]
}
vr.frame++
// Clean the stack
vr.stack[vr.frame].mode = 0
vr.stack[vr.frame].vType = 0
vr.stack[vr.frame].end = 0
}
func (vr *valueReader) pushDocument() error {
vr.advanceFrame()
vr.stack[vr.frame].mode = mDocument
size, err := vr.readLength()
if err != nil {
return err
}
vr.stack[vr.frame].end = int64(size) + vr.offset - 4
return nil
}
func (vr *valueReader) pushArray() error {
vr.advanceFrame()
vr.stack[vr.frame].mode = mArray
size, err := vr.readLength()
if err != nil {
return err
}
vr.stack[vr.frame].end = int64(size) + vr.offset - 4
return nil
}
func (vr *valueReader) pushElement(t bsontype.Type) {
vr.advanceFrame()
vr.stack[vr.frame].mode = mElement
vr.stack[vr.frame].vType = t
}
func (vr *valueReader) pushValue(t bsontype.Type) {
vr.advanceFrame()
vr.stack[vr.frame].mode = mValue
vr.stack[vr.frame].vType = t
}
func (vr *valueReader) pushCodeWithScope() (int64, error) {
vr.advanceFrame()
vr.stack[vr.frame].mode = mCodeWithScope
size, err := vr.readLength()
if err != nil {
return 0, err
}
vr.stack[vr.frame].end = int64(size) + vr.offset - 4
return int64(size), nil
}
func (vr *valueReader) pop() {
switch vr.stack[vr.frame].mode {
case mElement, mValue:
vr.frame--
case mDocument, mArray, mCodeWithScope:
vr.frame -= 2 // we pop twice to jump over the vrElement: vrDocument -> vrElement -> vrDocument/TopLevel/etc...
}
}
func (vr *valueReader) invalidTransitionErr(destination mode, name string, modes []mode) error {
te := TransitionError{
name: name,
current: vr.stack[vr.frame].mode,
destination: destination,
modes: modes,
action: "read",
}
if vr.frame != 0 {
te.parent = vr.stack[vr.frame-1].mode
}
return te
}
func (vr *valueReader) typeError(t bsontype.Type) error {
return fmt.Errorf("positioned on %s, but attempted to read %s", vr.stack[vr.frame].vType, t)
}
func (vr *valueReader) invalidDocumentLengthError() error {
return fmt.Errorf("document is invalid, end byte is at %d, but null byte found at %d", vr.stack[vr.frame].end, vr.offset)
}
func (vr *valueReader) ensureElementValue(t bsontype.Type, destination mode, callerName string) error {
switch vr.stack[vr.frame].mode {
case mElement, mValue:
if vr.stack[vr.frame].vType != t {
return vr.typeError(t)
}
default:
return vr.invalidTransitionErr(destination, callerName, []mode{mElement, mValue})
}
return nil
}
func (vr *valueReader) Type() bsontype.Type {
return vr.stack[vr.frame].vType
}
func (vr *valueReader) nextElementLength() (int32, error) {
var length int32
var err error
switch vr.stack[vr.frame].vType {
case bsontype.Array, bsontype.EmbeddedDocument, bsontype.CodeWithScope:
length, err = vr.peekLength()
case bsontype.Binary:
length, err = vr.peekLength()
length += 4 + 1 // binary length + subtype byte
case bsontype.Boolean:
length = 1
case bsontype.DBPointer:
length, err = vr.peekLength()
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, err = vr.peekLength()
length += 4
case bsontype.MaxKey, bsontype.MinKey, bsontype.Null, bsontype.Undefined:
length = 0
case bsontype.ObjectID:
length = 12
case bsontype.Regex:
regex := bytes.IndexByte(vr.d[vr.offset:], 0x00)
if regex < 0 {
err = io.EOF
break
}
pattern := bytes.IndexByte(vr.d[vr.offset+int64(regex)+1:], 0x00)
if pattern < 0 {
err = io.EOF
break
}
length = int32(int64(regex) + 1 + int64(pattern) + 1)
default:
return 0, fmt.Errorf("attempted to read bytes of unknown BSON type %v", vr.stack[vr.frame].vType)
}
return length, err
}
func (vr *valueReader) ReadValueBytes(dst []byte) (bsontype.Type, []byte, error) {
switch vr.stack[vr.frame].mode {
case mTopLevel:
length, err := vr.peekLength()
if err != nil {
return bsontype.Type(0), nil, err
}
dst, err = vr.appendBytes(dst, length)
if err != nil {
return bsontype.Type(0), nil, err
}
return bsontype.Type(0), dst, nil
case mElement, mValue:
length, err := vr.nextElementLength()
if err != nil {
return bsontype.Type(0), dst, err
}
dst, err = vr.appendBytes(dst, length)
t := vr.stack[vr.frame].vType
vr.pop()
return t, dst, err
default:
return bsontype.Type(0), nil, vr.invalidTransitionErr(0, "ReadValueBytes", []mode{mElement, mValue})
}
}
func (vr *valueReader) Skip() error {
switch vr.stack[vr.frame].mode {
case mElement, mValue:
default:
return vr.invalidTransitionErr(0, "Skip", []mode{mElement, mValue})
}
length, err := vr.nextElementLength()
if err != nil {
return err
}
err = vr.skipBytes(length)
vr.pop()
return err
}
func (vr *valueReader) ReadArray() (ArrayReader, error) {
if err := vr.ensureElementValue(bsontype.Array, mArray, "ReadArray"); err != nil {
return nil, err
}
err := vr.pushArray()
if err != nil {
return nil, err
}
return vr, nil
}
func (vr *valueReader) ReadBinary() (b []byte, btype byte, err error) {
if err := vr.ensureElementValue(bsontype.Binary, 0, "ReadBinary"); err != nil {
return nil, 0, err
}
length, err := vr.readLength()
if err != nil {
return nil, 0, err
}
btype, err = vr.readByte()
if err != nil {
return nil, 0, err
}
// Check length in case it is an old binary without a length.
if btype == 0x02 && length > 4 {
length, err = vr.readLength()
if err != nil {
return nil, 0, err
}
}
b, err = vr.readBytes(length)
if err != nil {
return nil, 0, err
}
// Make a copy of the returned byte slice because it's just a subslice from the valueReader's
// buffer and is not safe to return in the unmarshaled value.
cp := make([]byte, len(b))
copy(cp, b)
vr.pop()
return cp, btype, nil
}
func (vr *valueReader) ReadBoolean() (bool, error) {
if err := vr.ensureElementValue(bsontype.Boolean, 0, "ReadBoolean"); err != nil {
return false, err
}
b, err := vr.readByte()
if err != nil {
return false, err
}
if b > 1 {
return false, fmt.Errorf("invalid byte for boolean, %b", b)
}
vr.pop()
return b == 1, nil
}
func (vr *valueReader) ReadDocument() (DocumentReader, error) {
switch vr.stack[vr.frame].mode {
case mTopLevel:
// read size
size, err := vr.readLength()
if err != nil {
return nil, err
}
if int(size) != len(vr.d) {
return nil, fmt.Errorf("invalid document length")
}
vr.stack[vr.frame].end = int64(size) + vr.offset - 4
return vr, nil
case mElement, mValue:
if vr.stack[vr.frame].vType != bsontype.EmbeddedDocument {
return nil, vr.typeError(bsontype.EmbeddedDocument)
}
default:
return nil, vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue})
}
err := vr.pushDocument()
if err != nil {
return nil, err
}
return vr, nil
}
func (vr *valueReader) ReadCodeWithScope() (code string, dr DocumentReader, err error) {
if err := vr.ensureElementValue(bsontype.CodeWithScope, 0, "ReadCodeWithScope"); err != nil {
return "", nil, err
}
totalLength, err := vr.readLength()
if err != nil {
return "", nil, err
}
strLength, err := vr.readLength()
if err != nil {
return "", nil, err
}
if strLength <= 0 {
return "", nil, fmt.Errorf("invalid string length: %d", strLength)
}
strBytes, err := vr.readBytes(strLength)
if err != nil {
return "", nil, err
}
code = string(strBytes[:len(strBytes)-1])
size, err := vr.pushCodeWithScope()
if err != nil {
return "", nil, err
}
// The total length should equal:
// 4 (total length) + strLength + 4 (the length of str itself) + (document length)
componentsLength := int64(4+strLength+4) + size
if int64(totalLength) != componentsLength {
return "", nil, fmt.Errorf(
"length of CodeWithScope does not match lengths of components; total: %d; components: %d",
totalLength, componentsLength,
)
}
return code, vr, nil
}
func (vr *valueReader) ReadDBPointer() (ns string, oid primitive.ObjectID, err error) {
if err := vr.ensureElementValue(bsontype.DBPointer, 0, "ReadDBPointer"); err != nil {
return "", oid, err
}
ns, err = vr.readString()
if err != nil {
return "", oid, err
}
oidbytes, err := vr.readBytes(12)
if err != nil {
return "", oid, err
}
copy(oid[:], oidbytes)
vr.pop()
return ns, oid, nil
}
func (vr *valueReader) ReadDateTime() (int64, error) {
if err := vr.ensureElementValue(bsontype.DateTime, 0, "ReadDateTime"); err != nil {
return 0, err
}
i, err := vr.readi64()
if err != nil {
return 0, err
}
vr.pop()
return i, nil
}
func (vr *valueReader) ReadDecimal128() (primitive.Decimal128, error) {
if err := vr.ensureElementValue(bsontype.Decimal128, 0, "ReadDecimal128"); err != nil {
return primitive.Decimal128{}, err
}
b, err := vr.readBytes(16)
if err != nil {
return primitive.Decimal128{}, err
}
l := binary.LittleEndian.Uint64(b[0:8])
h := binary.LittleEndian.Uint64(b[8:16])
vr.pop()
return primitive.NewDecimal128(h, l), nil
}
func (vr *valueReader) ReadDouble() (float64, error) {
if err := vr.ensureElementValue(bsontype.Double, 0, "ReadDouble"); err != nil {
return 0, err
}
u, err := vr.readu64()
if err != nil {
return 0, err
}
vr.pop()
return math.Float64frombits(u), nil
}
func (vr *valueReader) ReadInt32() (int32, error) {
if err := vr.ensureElementValue(bsontype.Int32, 0, "ReadInt32"); err != nil {
return 0, err
}
vr.pop()
return vr.readi32()
}
func (vr *valueReader) ReadInt64() (int64, error) {
if err := vr.ensureElementValue(bsontype.Int64, 0, "ReadInt64"); err != nil {
return 0, err
}
vr.pop()
return vr.readi64()
}
func (vr *valueReader) ReadJavascript() (code string, err error) {
if err := vr.ensureElementValue(bsontype.JavaScript, 0, "ReadJavascript"); err != nil {
return "", err
}
vr.pop()
return vr.readString()
}
func (vr *valueReader) ReadMaxKey() error {
if err := vr.ensureElementValue(bsontype.MaxKey, 0, "ReadMaxKey"); err != nil {
return err
}
vr.pop()
return nil
}
func (vr *valueReader) ReadMinKey() error {
if err := vr.ensureElementValue(bsontype.MinKey, 0, "ReadMinKey"); err != nil {
return err
}
vr.pop()
return nil
}
func (vr *valueReader) ReadNull() error {
if err := vr.ensureElementValue(bsontype.Null, 0, "ReadNull"); err != nil {
return err
}
vr.pop()
return nil
}
func (vr *valueReader) ReadObjectID() (primitive.ObjectID, error) {
if err := vr.ensureElementValue(bsontype.ObjectID, 0, "ReadObjectID"); err != nil {
return primitive.ObjectID{}, err
}
oidbytes, err := vr.readBytes(12)
if err != nil {
return primitive.ObjectID{}, err
}
var oid primitive.ObjectID
copy(oid[:], oidbytes)
vr.pop()
return oid, nil
}
func (vr *valueReader) ReadRegex() (string, string, error) {
if err := vr.ensureElementValue(bsontype.Regex, 0, "ReadRegex"); err != nil {
return "", "", err
}
pattern, err := vr.readCString()
if err != nil {
return "", "", err
}
options, err := vr.readCString()
if err != nil {
return "", "", err
}
vr.pop()
return pattern, options, nil
}
func (vr *valueReader) ReadString() (string, error) {
if err := vr.ensureElementValue(bsontype.String, 0, "ReadString"); err != nil {
return "", err
}
vr.pop()
return vr.readString()
}
func (vr *valueReader) ReadSymbol() (symbol string, err error) {
if err := vr.ensureElementValue(bsontype.Symbol, 0, "ReadSymbol"); err != nil {
return "", err
}
vr.pop()
return vr.readString()
}
func (vr *valueReader) ReadTimestamp() (t uint32, i uint32, err error) {
if err := vr.ensureElementValue(bsontype.Timestamp, 0, "ReadTimestamp"); err != nil {
return 0, 0, err
}
i, err = vr.readu32()
if err != nil {
return 0, 0, err
}
t, err = vr.readu32()
if err != nil {
return 0, 0, err
}
vr.pop()
return t, i, nil
}
func (vr *valueReader) ReadUndefined() error {
if err := vr.ensureElementValue(bsontype.Undefined, 0, "ReadUndefined"); err != nil {
return err
}
vr.pop()
return nil
}
func (vr *valueReader) ReadElement() (string, ValueReader, error) {
switch vr.stack[vr.frame].mode {
case mTopLevel, mDocument, mCodeWithScope:
default:
return "", nil, vr.invalidTransitionErr(mElement, "ReadElement", []mode{mTopLevel, mDocument, mCodeWithScope})
}
t, err := vr.readByte()
if err != nil {
return "", nil, err
}
if t == 0 {
if vr.offset != vr.stack[vr.frame].end {
return "", nil, vr.invalidDocumentLengthError()
}
vr.pop()
return "", nil, ErrEOD
}
name, err := vr.readCString()
if err != nil {
return "", nil, err
}
vr.pushElement(bsontype.Type(t))
return name, vr, nil
}
func (vr *valueReader) ReadValue() (ValueReader, error) {
switch vr.stack[vr.frame].mode {
case mArray:
default:
return nil, vr.invalidTransitionErr(mValue, "ReadValue", []mode{mArray})
}
t, err := vr.readByte()
if err != nil {
return nil, err
}
if t == 0 {
if vr.offset != vr.stack[vr.frame].end {
return nil, vr.invalidDocumentLengthError()
}
vr.pop()
return nil, ErrEOA
}
_, err = vr.readCString()
if err != nil {
return nil, err
}
vr.pushValue(bsontype.Type(t))
return vr, nil
}
// readBytes reads length bytes from the valueReader starting at the current offset. Note that the
// returned byte slice is a subslice from the valueReader buffer and must be converted or copied
// before returning in an unmarshaled value.
func (vr *valueReader) readBytes(length int32) ([]byte, error) {
if length < 0 {
return nil, fmt.Errorf("invalid length: %d", length)
}
if vr.offset+int64(length) > int64(len(vr.d)) {
return nil, io.EOF
}
start := vr.offset
vr.offset += int64(length)
return vr.d[start : start+int64(length)], nil
}
func (vr *valueReader) appendBytes(dst []byte, length int32) ([]byte, error) {
if vr.offset+int64(length) > int64(len(vr.d)) {
return nil, io.EOF
}
start := vr.offset
vr.offset += int64(length)
return append(dst, vr.d[start:start+int64(length)]...), nil
}
func (vr *valueReader) skipBytes(length int32) error {
if vr.offset+int64(length) > int64(len(vr.d)) {
return io.EOF
}
vr.offset += int64(length)
return nil
}
func (vr *valueReader) readByte() (byte, error) {
if vr.offset+1 > int64(len(vr.d)) {
return 0x0, io.EOF
}
vr.offset++
return vr.d[vr.offset-1], nil
}
func (vr *valueReader) readCString() (string, error) {
idx := bytes.IndexByte(vr.d[vr.offset:], 0x00)
if idx < 0 {
return "", io.EOF
}
start := vr.offset
// idx does not include the null byte
vr.offset += int64(idx) + 1
return string(vr.d[start : start+int64(idx)]), nil
}
func (vr *valueReader) readString() (string, error) {
length, err := vr.readLength()
if err != nil {
return "", err
}
if int64(length)+vr.offset > int64(len(vr.d)) {
return "", io.EOF
}
if length <= 0 {
return "", fmt.Errorf("invalid string length: %d", length)
}
if vr.d[vr.offset+int64(length)-1] != 0x00 {
return "", fmt.Errorf("string does not end with null byte, but with %v", vr.d[vr.offset+int64(length)-1])
}
start := vr.offset
vr.offset += int64(length)
return string(vr.d[start : start+int64(length)-1]), nil
}
func (vr *valueReader) peekLength() (int32, error) {
if vr.offset+4 > int64(len(vr.d)) {
return 0, io.EOF
}
idx := vr.offset
return (int32(vr.d[idx]) | int32(vr.d[idx+1])<<8 | int32(vr.d[idx+2])<<16 | int32(vr.d[idx+3])<<24), nil
}
func (vr *valueReader) readLength() (int32, error) { return vr.readi32() }
func (vr *valueReader) readi32() (int32, error) {
if vr.offset+4 > int64(len(vr.d)) {
return 0, io.EOF
}
idx := vr.offset
vr.offset += 4
return (int32(vr.d[idx]) | int32(vr.d[idx+1])<<8 | int32(vr.d[idx+2])<<16 | int32(vr.d[idx+3])<<24), nil
}
func (vr *valueReader) readu32() (uint32, error) {
if vr.offset+4 > int64(len(vr.d)) {
return 0, io.EOF
}
idx := vr.offset
vr.offset += 4
return (uint32(vr.d[idx]) | uint32(vr.d[idx+1])<<8 | uint32(vr.d[idx+2])<<16 | uint32(vr.d[idx+3])<<24), nil
}
func (vr *valueReader) readi64() (int64, error) {
if vr.offset+8 > int64(len(vr.d)) {
return 0, io.EOF
}
idx := vr.offset
vr.offset += 8
return int64(vr.d[idx]) | int64(vr.d[idx+1])<<8 | int64(vr.d[idx+2])<<16 | int64(vr.d[idx+3])<<24 |
int64(vr.d[idx+4])<<32 | int64(vr.d[idx+5])<<40 | int64(vr.d[idx+6])<<48 | int64(vr.d[idx+7])<<56, nil
}
func (vr *valueReader) readu64() (uint64, error) {
if vr.offset+8 > int64(len(vr.d)) {
return 0, io.EOF
}
idx := vr.offset
vr.offset += 8
return uint64(vr.d[idx]) | uint64(vr.d[idx+1])<<8 | uint64(vr.d[idx+2])<<16 | uint64(vr.d[idx+3])<<24 |
uint64(vr.d[idx+4])<<32 | uint64(vr.d[idx+5])<<40 | uint64(vr.d[idx+6])<<48 | uint64(vr.d[idx+7])<<56, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,608 @@
// 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 bsonrw
import (
"testing"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
type VRWInvoked byte
const (
_ = iota
llvrwReadArray VRWInvoked = 1
llvrwReadBinary
llvrwReadBoolean
llvrwReadDocument
llvrwReadCodeWithScope
llvrwReadDBPointer
llvrwReadDateTime
llvrwReadDecimal128
llvrwReadDouble
llvrwReadInt32
llvrwReadInt64
llvrwReadJavascript
llvrwReadMaxKey
llvrwReadMinKey
llvrwReadNull
llvrwReadObjectID
llvrwReadRegex
llvrwReadString
llvrwReadSymbol
llvrwReadTimestamp
llvrwReadUndefined
llvrwReadElement
llvrwReadValue
llvrwWriteArray
llvrwWriteBinary
llvrwWriteBinaryWithSubtype
llvrwWriteBoolean
llvrwWriteCodeWithScope
llvrwWriteDBPointer
llvrwWriteDateTime
llvrwWriteDecimal128
llvrwWriteDouble
llvrwWriteInt32
llvrwWriteInt64
llvrwWriteJavascript
llvrwWriteMaxKey
llvrwWriteMinKey
llvrwWriteNull
llvrwWriteObjectID
llvrwWriteRegex
llvrwWriteString
llvrwWriteDocument
llvrwWriteSymbol
llvrwWriteTimestamp
llvrwWriteUndefined
llvrwWriteDocumentElement
llvrwWriteDocumentEnd
llvrwWriteArrayElement
llvrwWriteArrayEnd
)
type TestValueReaderWriter struct {
t *testing.T
invoked VRWInvoked
readval interface{}
bsontype bsontype.Type
err error
errAfter VRWInvoked // error after this method is called
}
func (llvrw *TestValueReaderWriter) Type() bsontype.Type {
return llvrw.bsontype
}
func (llvrw *TestValueReaderWriter) Skip() error {
panic("not implemented")
}
func (llvrw *TestValueReaderWriter) ReadArray() (ArrayReader, error) {
llvrw.invoked = llvrwReadArray
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) ReadBinary() (b []byte, btype byte, err error) {
llvrw.invoked = llvrwReadBinary
if llvrw.errAfter == llvrw.invoked {
return nil, 0x00, llvrw.err
}
switch tt := llvrw.readval.(type) {
case bsoncore.Value:
subtype, data, _, ok := bsoncore.ReadBinary(tt.Data)
if !ok {
llvrw.t.Error("Invalid Value provided for return value of ReadBinary.")
return nil, 0x00, nil
}
return data, subtype, nil
default:
llvrw.t.Errorf("Incorrect type provided for return value of ReadBinary: %T", llvrw.readval)
return nil, 0x00, nil
}
}
func (llvrw *TestValueReaderWriter) ReadBoolean() (bool, error) {
llvrw.invoked = llvrwReadBoolean
if llvrw.errAfter == llvrw.invoked {
return false, llvrw.err
}
b, ok := llvrw.readval.(bool)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadBoolean: %T", llvrw.readval)
return false, nil
}
return b, llvrw.err
}
func (llvrw *TestValueReaderWriter) ReadDocument() (DocumentReader, error) {
llvrw.invoked = llvrwReadDocument
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) ReadCodeWithScope() (code string, dr DocumentReader, err error) {
llvrw.invoked = llvrwReadCodeWithScope
if llvrw.errAfter == llvrw.invoked {
return "", nil, llvrw.err
}
return "", llvrw, nil
}
func (llvrw *TestValueReaderWriter) ReadDBPointer() (ns string, oid primitive.ObjectID, err error) {
llvrw.invoked = llvrwReadDBPointer
if llvrw.errAfter == llvrw.invoked {
return "", primitive.ObjectID{}, llvrw.err
}
switch tt := llvrw.readval.(type) {
case bsoncore.Value:
ns, oid, _, ok := bsoncore.ReadDBPointer(tt.Data)
if !ok {
llvrw.t.Error("Invalid Value instance provided for return value of ReadDBPointer")
return "", primitive.ObjectID{}, nil
}
return ns, oid, nil
default:
llvrw.t.Errorf("Incorrect type provided for return value of ReadDBPointer: %T", llvrw.readval)
return "", primitive.ObjectID{}, nil
}
}
func (llvrw *TestValueReaderWriter) ReadDateTime() (int64, error) {
llvrw.invoked = llvrwReadDateTime
if llvrw.errAfter == llvrw.invoked {
return 0, llvrw.err
}
dt, ok := llvrw.readval.(int64)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadDateTime: %T", llvrw.readval)
return 0, nil
}
return dt, nil
}
func (llvrw *TestValueReaderWriter) ReadDecimal128() (primitive.Decimal128, error) {
llvrw.invoked = llvrwReadDecimal128
if llvrw.errAfter == llvrw.invoked {
return primitive.Decimal128{}, llvrw.err
}
d128, ok := llvrw.readval.(primitive.Decimal128)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadDecimal128: %T", llvrw.readval)
return primitive.Decimal128{}, nil
}
return d128, nil
}
func (llvrw *TestValueReaderWriter) ReadDouble() (float64, error) {
llvrw.invoked = llvrwReadDouble
if llvrw.errAfter == llvrw.invoked {
return 0, llvrw.err
}
f64, ok := llvrw.readval.(float64)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadDouble: %T", llvrw.readval)
return 0, nil
}
return f64, nil
}
func (llvrw *TestValueReaderWriter) ReadInt32() (int32, error) {
llvrw.invoked = llvrwReadInt32
if llvrw.errAfter == llvrw.invoked {
return 0, llvrw.err
}
i32, ok := llvrw.readval.(int32)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadInt32: %T", llvrw.readval)
return 0, nil
}
return i32, nil
}
func (llvrw *TestValueReaderWriter) ReadInt64() (int64, error) {
llvrw.invoked = llvrwReadInt64
if llvrw.errAfter == llvrw.invoked {
return 0, llvrw.err
}
i64, ok := llvrw.readval.(int64)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadInt64: %T", llvrw.readval)
return 0, nil
}
return i64, nil
}
func (llvrw *TestValueReaderWriter) ReadJavascript() (code string, err error) {
llvrw.invoked = llvrwReadJavascript
if llvrw.errAfter == llvrw.invoked {
return "", llvrw.err
}
js, ok := llvrw.readval.(string)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadJavascript: %T", llvrw.readval)
return "", nil
}
return js, nil
}
func (llvrw *TestValueReaderWriter) ReadMaxKey() error {
llvrw.invoked = llvrwReadMaxKey
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) ReadMinKey() error {
llvrw.invoked = llvrwReadMinKey
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) ReadNull() error {
llvrw.invoked = llvrwReadNull
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) ReadObjectID() (primitive.ObjectID, error) {
llvrw.invoked = llvrwReadObjectID
if llvrw.errAfter == llvrw.invoked {
return primitive.ObjectID{}, llvrw.err
}
oid, ok := llvrw.readval.(primitive.ObjectID)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadObjectID: %T", llvrw.readval)
return primitive.ObjectID{}, nil
}
return oid, nil
}
func (llvrw *TestValueReaderWriter) ReadRegex() (pattern string, options string, err error) {
llvrw.invoked = llvrwReadRegex
if llvrw.errAfter == llvrw.invoked {
return "", "", llvrw.err
}
switch tt := llvrw.readval.(type) {
case bsoncore.Value:
pattern, options, _, ok := bsoncore.ReadRegex(tt.Data)
if !ok {
llvrw.t.Error("Invalid Value instance provided for ReadRegex")
return "", "", nil
}
return pattern, options, nil
default:
llvrw.t.Errorf("Incorrect type provided for return value of ReadRegex: %T", llvrw.readval)
return "", "", nil
}
}
func (llvrw *TestValueReaderWriter) ReadString() (string, error) {
llvrw.invoked = llvrwReadString
if llvrw.errAfter == llvrw.invoked {
return "", llvrw.err
}
str, ok := llvrw.readval.(string)
if !ok {
llvrw.t.Errorf("Incorrect type provided for return value of ReadString: %T", llvrw.readval)
return "", nil
}
return str, nil
}
func (llvrw *TestValueReaderWriter) ReadSymbol() (symbol string, err error) {
llvrw.invoked = llvrwReadSymbol
if llvrw.errAfter == llvrw.invoked {
return "", llvrw.err
}
switch tt := llvrw.readval.(type) {
case bsoncore.Value:
symbol, _, ok := bsoncore.ReadSymbol(tt.Data)
if !ok {
llvrw.t.Error("Invalid Value instance provided for ReadSymbol")
return "", nil
}
return symbol, nil
default:
llvrw.t.Errorf("Incorrect type provided for return value of ReadSymbol: %T", llvrw.readval)
return "", nil
}
}
func (llvrw *TestValueReaderWriter) ReadTimestamp() (t uint32, i uint32, err error) {
llvrw.invoked = llvrwReadTimestamp
if llvrw.errAfter == llvrw.invoked {
return 0, 0, llvrw.err
}
switch tt := llvrw.readval.(type) {
case bsoncore.Value:
t, i, _, ok := bsoncore.ReadTimestamp(tt.Data)
if !ok {
llvrw.t.Errorf("Invalid Value instance provided for return value of ReadTimestamp")
return 0, 0, nil
}
return t, i, nil
default:
llvrw.t.Errorf("Incorrect type provided for return value of ReadTimestamp: %T", llvrw.readval)
return 0, 0, nil
}
}
func (llvrw *TestValueReaderWriter) ReadUndefined() error {
llvrw.invoked = llvrwReadUndefined
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteArray() (ArrayWriter, error) {
llvrw.invoked = llvrwWriteArray
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) WriteBinary(b []byte) error {
llvrw.invoked = llvrwWriteBinary
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteBinaryWithSubtype(b []byte, btype byte) error {
llvrw.invoked = llvrwWriteBinaryWithSubtype
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteBoolean(bool) error {
llvrw.invoked = llvrwWriteBoolean
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteCodeWithScope(code string) (DocumentWriter, error) {
llvrw.invoked = llvrwWriteCodeWithScope
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) WriteDBPointer(ns string, oid primitive.ObjectID) error {
llvrw.invoked = llvrwWriteDBPointer
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteDateTime(dt int64) error {
llvrw.invoked = llvrwWriteDateTime
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteDecimal128(primitive.Decimal128) error {
llvrw.invoked = llvrwWriteDecimal128
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteDouble(float64) error {
llvrw.invoked = llvrwWriteDouble
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteInt32(int32) error {
llvrw.invoked = llvrwWriteInt32
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteInt64(int64) error {
llvrw.invoked = llvrwWriteInt64
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteJavascript(code string) error {
llvrw.invoked = llvrwWriteJavascript
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteMaxKey() error {
llvrw.invoked = llvrwWriteMaxKey
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteMinKey() error {
llvrw.invoked = llvrwWriteMinKey
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteNull() error {
llvrw.invoked = llvrwWriteNull
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteObjectID(primitive.ObjectID) error {
llvrw.invoked = llvrwWriteObjectID
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteRegex(pattern string, options string) error {
llvrw.invoked = llvrwWriteRegex
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteString(string) error {
llvrw.invoked = llvrwWriteString
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteDocument() (DocumentWriter, error) {
llvrw.invoked = llvrwWriteDocument
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) WriteSymbol(symbol string) error {
llvrw.invoked = llvrwWriteSymbol
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteTimestamp(t uint32, i uint32) error {
llvrw.invoked = llvrwWriteTimestamp
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) WriteUndefined() error {
llvrw.invoked = llvrwWriteUndefined
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) ReadElement() (string, ValueReader, error) {
llvrw.invoked = llvrwReadElement
if llvrw.errAfter == llvrw.invoked {
return "", nil, llvrw.err
}
return "", llvrw, nil
}
func (llvrw *TestValueReaderWriter) WriteDocumentElement(string) (ValueWriter, error) {
llvrw.invoked = llvrwWriteDocumentElement
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) WriteDocumentEnd() error {
llvrw.invoked = llvrwWriteDocumentEnd
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}
func (llvrw *TestValueReaderWriter) ReadValue() (ValueReader, error) {
llvrw.invoked = llvrwReadValue
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) WriteArrayElement() (ValueWriter, error) {
llvrw.invoked = llvrwWriteArrayElement
if llvrw.errAfter == llvrw.invoked {
return nil, llvrw.err
}
return llvrw, nil
}
func (llvrw *TestValueReaderWriter) WriteArrayEnd() error {
llvrw.invoked = llvrwWriteArrayEnd
if llvrw.errAfter == llvrw.invoked {
return llvrw.err
}
return nil
}

View File

@ -0,0 +1,606 @@
// 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 bsonrw
import (
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"sync"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
var _ ValueWriter = (*valueWriter)(nil)
var vwPool = sync.Pool{
New: func() interface{} {
return new(valueWriter)
},
}
// BSONValueWriterPool is a pool for BSON ValueWriters.
type BSONValueWriterPool struct {
pool sync.Pool
}
// NewBSONValueWriterPool creates a new pool for ValueWriter instances that write to BSON.
func NewBSONValueWriterPool() *BSONValueWriterPool {
return &BSONValueWriterPool{
pool: sync.Pool{
New: func() interface{} {
return new(valueWriter)
},
},
}
}
// Get retrieves a BSON ValueWriter from the pool and resets it to use w as the destination.
func (bvwp *BSONValueWriterPool) Get(w io.Writer) ValueWriter {
vw := bvwp.pool.Get().(*valueWriter)
// TODO: Having to call reset here with the same buffer doesn't really make sense.
vw.reset(vw.buf)
vw.buf = vw.buf[:0]
vw.w = w
return vw
}
// GetAtModeElement retrieves a ValueWriterFlusher from the pool and resets it to use w as the destination.
func (bvwp *BSONValueWriterPool) GetAtModeElement(w io.Writer) ValueWriterFlusher {
vw := bvwp.Get(w).(*valueWriter)
vw.push(mElement)
return vw
}
// Put inserts a ValueWriter into the pool. If the ValueWriter is not a BSON ValueWriter, nothing
// happens and ok will be false.
func (bvwp *BSONValueWriterPool) Put(vw ValueWriter) (ok bool) {
bvw, ok := vw.(*valueWriter)
if !ok {
return false
}
bvwp.pool.Put(bvw)
return true
}
// This is here so that during testing we can change it and not require
// allocating a 4GB slice.
var maxSize = math.MaxInt32
var errNilWriter = errors.New("cannot create a ValueWriter from a nil io.Writer")
type errMaxDocumentSizeExceeded struct {
size int64
}
func (mdse errMaxDocumentSizeExceeded) Error() string {
return fmt.Sprintf("document size (%d) is larger than the max int32", mdse.size)
}
type vwMode int
const (
_ vwMode = iota
vwTopLevel
vwDocument
vwArray
vwValue
vwElement
vwCodeWithScope
)
func (vm vwMode) String() string {
var str string
switch vm {
case vwTopLevel:
str = "TopLevel"
case vwDocument:
str = "DocumentMode"
case vwArray:
str = "ArrayMode"
case vwValue:
str = "ValueMode"
case vwElement:
str = "ElementMode"
case vwCodeWithScope:
str = "CodeWithScopeMode"
default:
str = "UnknownMode"
}
return str
}
type vwState struct {
mode mode
key string
arrkey int
start int32
}
type valueWriter struct {
w io.Writer
buf []byte
stack []vwState
frame int64
}
func (vw *valueWriter) advanceFrame() {
if vw.frame+1 >= int64(len(vw.stack)) { // We need to grow the stack
length := len(vw.stack)
if length+1 >= cap(vw.stack) {
// double it
buf := make([]vwState, 2*cap(vw.stack)+1)
copy(buf, vw.stack)
vw.stack = buf
}
vw.stack = vw.stack[:length+1]
}
vw.frame++
}
func (vw *valueWriter) push(m mode) {
vw.advanceFrame()
// Clean the stack
vw.stack[vw.frame].mode = m
vw.stack[vw.frame].key = ""
vw.stack[vw.frame].arrkey = 0
vw.stack[vw.frame].start = 0
vw.stack[vw.frame].mode = m
switch m {
case mDocument, mArray, mCodeWithScope:
vw.reserveLength()
}
}
func (vw *valueWriter) reserveLength() {
vw.stack[vw.frame].start = int32(len(vw.buf))
vw.buf = append(vw.buf, 0x00, 0x00, 0x00, 0x00)
}
func (vw *valueWriter) pop() {
switch vw.stack[vw.frame].mode {
case mElement, mValue:
vw.frame--
case mDocument, mArray, mCodeWithScope:
vw.frame -= 2 // we pop twice to jump over the mElement: mDocument -> mElement -> mDocument/mTopLevel/etc...
}
}
// NewBSONValueWriter creates a ValueWriter that writes BSON to w.
//
// This ValueWriter will only write entire documents to the io.Writer and it
// will buffer the document as it is built.
func NewBSONValueWriter(w io.Writer) (ValueWriter, error) {
if w == nil {
return nil, errNilWriter
}
return newValueWriter(w), nil
}
func newValueWriter(w io.Writer) *valueWriter {
vw := new(valueWriter)
stack := make([]vwState, 1, 5)
stack[0] = vwState{mode: mTopLevel}
vw.w = w
vw.stack = stack
return vw
}
func newValueWriterFromSlice(buf []byte) *valueWriter {
vw := new(valueWriter)
stack := make([]vwState, 1, 5)
stack[0] = vwState{mode: mTopLevel}
vw.stack = stack
vw.buf = buf
return vw
}
func (vw *valueWriter) reset(buf []byte) {
if vw.stack == nil {
vw.stack = make([]vwState, 1, 5)
}
vw.stack = vw.stack[:1]
vw.stack[0] = vwState{mode: mTopLevel}
vw.buf = buf
vw.frame = 0
vw.w = nil
}
func (vw *valueWriter) invalidTransitionError(destination mode, name string, modes []mode) error {
te := TransitionError{
name: name,
current: vw.stack[vw.frame].mode,
destination: destination,
modes: modes,
action: "write",
}
if vw.frame != 0 {
te.parent = vw.stack[vw.frame-1].mode
}
return te
}
func (vw *valueWriter) writeElementHeader(t bsontype.Type, destination mode, callerName string, addmodes ...mode) error {
switch vw.stack[vw.frame].mode {
case mElement:
key := vw.stack[vw.frame].key
if !isValidCString(key) {
return errors.New("BSON element key cannot contain null bytes")
}
vw.buf = bsoncore.AppendHeader(vw.buf, t, key)
case mValue:
// TODO: Do this with a cache of the first 1000 or so array keys.
vw.buf = bsoncore.AppendHeader(vw.buf, t, strconv.Itoa(vw.stack[vw.frame].arrkey))
default:
modes := []mode{mElement, mValue}
if addmodes != nil {
modes = append(modes, addmodes...)
}
return vw.invalidTransitionError(destination, callerName, modes)
}
return nil
}
func (vw *valueWriter) WriteValueBytes(t bsontype.Type, b []byte) error {
if err := vw.writeElementHeader(t, mode(0), "WriteValueBytes"); err != nil {
return err
}
vw.buf = append(vw.buf, b...)
vw.pop()
return nil
}
func (vw *valueWriter) WriteArray() (ArrayWriter, error) {
if err := vw.writeElementHeader(bsontype.Array, mArray, "WriteArray"); err != nil {
return nil, err
}
vw.push(mArray)
return vw, nil
}
func (vw *valueWriter) WriteBinary(b []byte) error {
return vw.WriteBinaryWithSubtype(b, 0x00)
}
func (vw *valueWriter) WriteBinaryWithSubtype(b []byte, btype byte) error {
if err := vw.writeElementHeader(bsontype.Binary, mode(0), "WriteBinaryWithSubtype"); err != nil {
return err
}
vw.buf = bsoncore.AppendBinary(vw.buf, btype, b)
vw.pop()
return nil
}
func (vw *valueWriter) WriteBoolean(b bool) error {
if err := vw.writeElementHeader(bsontype.Boolean, mode(0), "WriteBoolean"); err != nil {
return err
}
vw.buf = bsoncore.AppendBoolean(vw.buf, b)
vw.pop()
return nil
}
func (vw *valueWriter) WriteCodeWithScope(code string) (DocumentWriter, error) {
if err := vw.writeElementHeader(bsontype.CodeWithScope, mCodeWithScope, "WriteCodeWithScope"); err != nil {
return nil, err
}
// CodeWithScope is a different than other types because we need an extra
// frame on the stack. In the EndDocument code, we write the document
// length, pop, write the code with scope length, and pop. To simplify the
// pop code, we push a spacer frame that we'll always jump over.
vw.push(mCodeWithScope)
vw.buf = bsoncore.AppendString(vw.buf, code)
vw.push(mSpacer)
vw.push(mDocument)
return vw, nil
}
func (vw *valueWriter) WriteDBPointer(ns string, oid primitive.ObjectID) error {
if err := vw.writeElementHeader(bsontype.DBPointer, mode(0), "WriteDBPointer"); err != nil {
return err
}
vw.buf = bsoncore.AppendDBPointer(vw.buf, ns, oid)
vw.pop()
return nil
}
func (vw *valueWriter) WriteDateTime(dt int64) error {
if err := vw.writeElementHeader(bsontype.DateTime, mode(0), "WriteDateTime"); err != nil {
return err
}
vw.buf = bsoncore.AppendDateTime(vw.buf, dt)
vw.pop()
return nil
}
func (vw *valueWriter) WriteDecimal128(d128 primitive.Decimal128) error {
if err := vw.writeElementHeader(bsontype.Decimal128, mode(0), "WriteDecimal128"); err != nil {
return err
}
vw.buf = bsoncore.AppendDecimal128(vw.buf, d128)
vw.pop()
return nil
}
func (vw *valueWriter) WriteDouble(f float64) error {
if err := vw.writeElementHeader(bsontype.Double, mode(0), "WriteDouble"); err != nil {
return err
}
vw.buf = bsoncore.AppendDouble(vw.buf, f)
vw.pop()
return nil
}
func (vw *valueWriter) WriteInt32(i32 int32) error {
if err := vw.writeElementHeader(bsontype.Int32, mode(0), "WriteInt32"); err != nil {
return err
}
vw.buf = bsoncore.AppendInt32(vw.buf, i32)
vw.pop()
return nil
}
func (vw *valueWriter) WriteInt64(i64 int64) error {
if err := vw.writeElementHeader(bsontype.Int64, mode(0), "WriteInt64"); err != nil {
return err
}
vw.buf = bsoncore.AppendInt64(vw.buf, i64)
vw.pop()
return nil
}
func (vw *valueWriter) WriteJavascript(code string) error {
if err := vw.writeElementHeader(bsontype.JavaScript, mode(0), "WriteJavascript"); err != nil {
return err
}
vw.buf = bsoncore.AppendJavaScript(vw.buf, code)
vw.pop()
return nil
}
func (vw *valueWriter) WriteMaxKey() error {
if err := vw.writeElementHeader(bsontype.MaxKey, mode(0), "WriteMaxKey"); err != nil {
return err
}
vw.pop()
return nil
}
func (vw *valueWriter) WriteMinKey() error {
if err := vw.writeElementHeader(bsontype.MinKey, mode(0), "WriteMinKey"); err != nil {
return err
}
vw.pop()
return nil
}
func (vw *valueWriter) WriteNull() error {
if err := vw.writeElementHeader(bsontype.Null, mode(0), "WriteNull"); err != nil {
return err
}
vw.pop()
return nil
}
func (vw *valueWriter) WriteObjectID(oid primitive.ObjectID) error {
if err := vw.writeElementHeader(bsontype.ObjectID, mode(0), "WriteObjectID"); err != nil {
return err
}
vw.buf = bsoncore.AppendObjectID(vw.buf, oid)
vw.pop()
return nil
}
func (vw *valueWriter) WriteRegex(pattern string, options string) error {
if !isValidCString(pattern) || !isValidCString(options) {
return errors.New("BSON regex values cannot contain null bytes")
}
if err := vw.writeElementHeader(bsontype.Regex, mode(0), "WriteRegex"); err != nil {
return err
}
vw.buf = bsoncore.AppendRegex(vw.buf, pattern, sortStringAlphebeticAscending(options))
vw.pop()
return nil
}
func (vw *valueWriter) WriteString(s string) error {
if err := vw.writeElementHeader(bsontype.String, mode(0), "WriteString"); err != nil {
return err
}
vw.buf = bsoncore.AppendString(vw.buf, s)
vw.pop()
return nil
}
func (vw *valueWriter) WriteDocument() (DocumentWriter, error) {
if vw.stack[vw.frame].mode == mTopLevel {
vw.reserveLength()
return vw, nil
}
if err := vw.writeElementHeader(bsontype.EmbeddedDocument, mDocument, "WriteDocument", mTopLevel); err != nil {
return nil, err
}
vw.push(mDocument)
return vw, nil
}
func (vw *valueWriter) WriteSymbol(symbol string) error {
if err := vw.writeElementHeader(bsontype.Symbol, mode(0), "WriteSymbol"); err != nil {
return err
}
vw.buf = bsoncore.AppendSymbol(vw.buf, symbol)
vw.pop()
return nil
}
func (vw *valueWriter) WriteTimestamp(t uint32, i uint32) error {
if err := vw.writeElementHeader(bsontype.Timestamp, mode(0), "WriteTimestamp"); err != nil {
return err
}
vw.buf = bsoncore.AppendTimestamp(vw.buf, t, i)
vw.pop()
return nil
}
func (vw *valueWriter) WriteUndefined() error {
if err := vw.writeElementHeader(bsontype.Undefined, mode(0), "WriteUndefined"); err != nil {
return err
}
vw.pop()
return nil
}
func (vw *valueWriter) WriteDocumentElement(key string) (ValueWriter, error) {
switch vw.stack[vw.frame].mode {
case mTopLevel, mDocument:
default:
return nil, vw.invalidTransitionError(mElement, "WriteDocumentElement", []mode{mTopLevel, mDocument})
}
vw.push(mElement)
vw.stack[vw.frame].key = key
return vw, nil
}
func (vw *valueWriter) WriteDocumentEnd() error {
switch vw.stack[vw.frame].mode {
case mTopLevel, mDocument:
default:
return fmt.Errorf("incorrect mode to end document: %s", vw.stack[vw.frame].mode)
}
vw.buf = append(vw.buf, 0x00)
err := vw.writeLength()
if err != nil {
return err
}
if vw.stack[vw.frame].mode == mTopLevel {
if err = vw.Flush(); err != nil {
return err
}
}
vw.pop()
if vw.stack[vw.frame].mode == mCodeWithScope {
// We ignore the error here because of the guarantee of writeLength.
// See the docs for writeLength for more info.
_ = vw.writeLength()
vw.pop()
}
return nil
}
func (vw *valueWriter) Flush() error {
if vw.w == nil {
return nil
}
if _, err := vw.w.Write(vw.buf); err != nil {
return err
}
// reset buffer
vw.buf = vw.buf[:0]
return nil
}
func (vw *valueWriter) WriteArrayElement() (ValueWriter, error) {
if vw.stack[vw.frame].mode != mArray {
return nil, vw.invalidTransitionError(mValue, "WriteArrayElement", []mode{mArray})
}
arrkey := vw.stack[vw.frame].arrkey
vw.stack[vw.frame].arrkey++
vw.push(mValue)
vw.stack[vw.frame].arrkey = arrkey
return vw, nil
}
func (vw *valueWriter) WriteArrayEnd() error {
if vw.stack[vw.frame].mode != mArray {
return fmt.Errorf("incorrect mode to end array: %s", vw.stack[vw.frame].mode)
}
vw.buf = append(vw.buf, 0x00)
err := vw.writeLength()
if err != nil {
return err
}
vw.pop()
return nil
}
// NOTE: We assume that if we call writeLength more than once the same function
// within the same function without altering the vw.buf that this method will
// not return an error. If this changes ensure that the following methods are
// updated:
//
// - WriteDocumentEnd
func (vw *valueWriter) writeLength() error {
length := len(vw.buf)
if length > maxSize {
return errMaxDocumentSizeExceeded{size: int64(len(vw.buf))}
}
length = length - int(vw.stack[vw.frame].start)
start := vw.stack[vw.frame].start
vw.buf[start+0] = byte(length)
vw.buf[start+1] = byte(length >> 8)
vw.buf[start+2] = byte(length >> 16)
vw.buf[start+3] = byte(length >> 24)
return nil
}
func isValidCString(cs string) bool {
return !strings.ContainsRune(cs, '\x00')
}

View File

@ -0,0 +1,368 @@
// 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 bsonrw
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"math"
"reflect"
"strings"
"testing"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
func TestNewBSONValueWriter(t *testing.T) {
_, got := NewBSONValueWriter(nil)
want := errNilWriter
if !compareErrors(got, want) {
t.Errorf("Returned error did not match what was expected. got %v; want %v", got, want)
}
vw, got := NewBSONValueWriter(errWriter{})
want = nil
if !compareErrors(got, want) {
t.Errorf("Returned error did not match what was expected. got %v; want %v", got, want)
}
if vw == nil {
t.Errorf("Expected non-nil ValueWriter to be returned from NewBSONValueWriter")
}
}
func TestValueWriter(t *testing.T) {
header := []byte{0x00, 0x00, 0x00, 0x00}
oid := primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}
testCases := []struct {
name string
fn interface{}
params []interface{}
want []byte
}{
{
"WriteBinary",
(*valueWriter).WriteBinary,
[]interface{}{[]byte{0x01, 0x02, 0x03}},
bsoncore.AppendBinaryElement(header, "foo", 0x00, []byte{0x01, 0x02, 0x03}),
},
{
"WriteBinaryWithSubtype (not 0x02)",
(*valueWriter).WriteBinaryWithSubtype,
[]interface{}{[]byte{0x01, 0x02, 0x03}, byte(0xFF)},
bsoncore.AppendBinaryElement(header, "foo", 0xFF, []byte{0x01, 0x02, 0x03}),
},
{
"WriteBinaryWithSubtype (0x02)",
(*valueWriter).WriteBinaryWithSubtype,
[]interface{}{[]byte{0x01, 0x02, 0x03}, byte(0x02)},
bsoncore.AppendBinaryElement(header, "foo", 0x02, []byte{0x01, 0x02, 0x03}),
},
{
"WriteBoolean",
(*valueWriter).WriteBoolean,
[]interface{}{true},
bsoncore.AppendBooleanElement(header, "foo", true),
},
{
"WriteDBPointer",
(*valueWriter).WriteDBPointer,
[]interface{}{"bar", oid},
bsoncore.AppendDBPointerElement(header, "foo", "bar", oid),
},
{
"WriteDateTime",
(*valueWriter).WriteDateTime,
[]interface{}{int64(12345678)},
bsoncore.AppendDateTimeElement(header, "foo", 12345678),
},
{
"WriteDecimal128",
(*valueWriter).WriteDecimal128,
[]interface{}{primitive.NewDecimal128(10, 20)},
bsoncore.AppendDecimal128Element(header, "foo", primitive.NewDecimal128(10, 20)),
},
{
"WriteDouble",
(*valueWriter).WriteDouble,
[]interface{}{float64(3.14159)},
bsoncore.AppendDoubleElement(header, "foo", 3.14159),
},
{
"WriteInt32",
(*valueWriter).WriteInt32,
[]interface{}{int32(123456)},
bsoncore.AppendInt32Element(header, "foo", 123456),
},
{
"WriteInt64",
(*valueWriter).WriteInt64,
[]interface{}{int64(1234567890)},
bsoncore.AppendInt64Element(header, "foo", 1234567890),
},
{
"WriteJavascript",
(*valueWriter).WriteJavascript,
[]interface{}{"var foo = 'bar';"},
bsoncore.AppendJavaScriptElement(header, "foo", "var foo = 'bar';"),
},
{
"WriteMaxKey",
(*valueWriter).WriteMaxKey,
[]interface{}{},
bsoncore.AppendMaxKeyElement(header, "foo"),
},
{
"WriteMinKey",
(*valueWriter).WriteMinKey,
[]interface{}{},
bsoncore.AppendMinKeyElement(header, "foo"),
},
{
"WriteNull",
(*valueWriter).WriteNull,
[]interface{}{},
bsoncore.AppendNullElement(header, "foo"),
},
{
"WriteObjectID",
(*valueWriter).WriteObjectID,
[]interface{}{oid},
bsoncore.AppendObjectIDElement(header, "foo", oid),
},
{
"WriteRegex",
(*valueWriter).WriteRegex,
[]interface{}{"bar", "baz"},
bsoncore.AppendRegexElement(header, "foo", "bar", "abz"),
},
{
"WriteString",
(*valueWriter).WriteString,
[]interface{}{"hello, world!"},
bsoncore.AppendStringElement(header, "foo", "hello, world!"),
},
{
"WriteSymbol",
(*valueWriter).WriteSymbol,
[]interface{}{"symbollolz"},
bsoncore.AppendSymbolElement(header, "foo", "symbollolz"),
},
{
"WriteTimestamp",
(*valueWriter).WriteTimestamp,
[]interface{}{uint32(10), uint32(20)},
bsoncore.AppendTimestampElement(header, "foo", 10, 20),
},
{
"WriteUndefined",
(*valueWriter).WriteUndefined,
[]interface{}{},
bsoncore.AppendUndefinedElement(header, "foo"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fn := reflect.ValueOf(tc.fn)
if fn.Kind() != reflect.Func {
t.Fatalf("fn must be of kind Func but it is a %v", fn.Kind())
}
if fn.Type().NumIn() != len(tc.params)+1 || fn.Type().In(0) != reflect.TypeOf((*valueWriter)(nil)) {
t.Fatalf("fn must have at least one parameter and the first parameter must be a *valueWriter")
}
if fn.Type().NumOut() != 1 || fn.Type().Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
t.Fatalf("fn must have one return value and it must be an error.")
}
params := make([]reflect.Value, 1, len(tc.params)+1)
vw := newValueWriter(ioutil.Discard)
params[0] = reflect.ValueOf(vw)
for _, param := range tc.params {
params = append(params, reflect.ValueOf(param))
}
_, err := vw.WriteDocument()
noerr(t, err)
_, err = vw.WriteDocumentElement("foo")
noerr(t, err)
results := fn.Call(params)
if !results[0].IsValid() {
err = results[0].Interface().(error)
} else {
err = nil
}
noerr(t, err)
got := vw.buf
want := tc.want
if !bytes.Equal(got, want) {
t.Errorf("Bytes are not equal.\n\tgot %v\n\twant %v", got, want)
}
t.Run("incorrect transition", func(t *testing.T) {
vw = newValueWriter(ioutil.Discard)
results := fn.Call(params)
got := results[0].Interface().(error)
fnName := tc.name
if strings.Contains(fnName, "WriteBinary") {
fnName = "WriteBinaryWithSubtype"
}
want := TransitionError{current: mTopLevel, name: fnName, modes: []mode{mElement, mValue},
action: "write"}
if !compareErrors(got, want) {
t.Errorf("Errors do not match. got %v; want %v", got, want)
}
})
})
}
t.Run("WriteArray", func(t *testing.T) {
vw := newValueWriter(ioutil.Discard)
vw.push(mArray)
want := TransitionError{current: mArray, destination: mArray, parent: mTopLevel,
name: "WriteArray", modes: []mode{mElement, mValue}, action: "write"}
_, got := vw.WriteArray()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteCodeWithScope", func(t *testing.T) {
vw := newValueWriter(ioutil.Discard)
vw.push(mArray)
want := TransitionError{current: mArray, destination: mCodeWithScope, parent: mTopLevel,
name: "WriteCodeWithScope", modes: []mode{mElement, mValue}, action: "write"}
_, got := vw.WriteCodeWithScope("")
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteDocument", func(t *testing.T) {
vw := newValueWriter(ioutil.Discard)
vw.push(mArray)
want := TransitionError{current: mArray, destination: mDocument, parent: mTopLevel,
name: "WriteDocument", modes: []mode{mElement, mValue, mTopLevel}, action: "write"}
_, got := vw.WriteDocument()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteDocumentElement", func(t *testing.T) {
vw := newValueWriter(ioutil.Discard)
vw.push(mElement)
want := TransitionError{current: mElement,
destination: mElement,
parent: mTopLevel,
name: "WriteDocumentElement",
modes: []mode{mTopLevel, mDocument},
action: "write"}
_, got := vw.WriteDocumentElement("")
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteDocumentEnd", func(t *testing.T) {
vw := newValueWriter(ioutil.Discard)
vw.push(mElement)
want := fmt.Errorf("incorrect mode to end document: %s", mElement)
got := vw.WriteDocumentEnd()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
vw.pop()
vw.buf = append(vw.buf, make([]byte, 1023)...)
maxSize = 512
want = errMaxDocumentSizeExceeded{size: 1024}
got = vw.WriteDocumentEnd()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
maxSize = math.MaxInt32
want = errors.New("what a nice fake error we have here")
vw.w = errWriter{err: want}
got = vw.WriteDocumentEnd()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteArrayElement", func(t *testing.T) {
vw := newValueWriter(ioutil.Discard)
vw.push(mElement)
want := TransitionError{current: mElement,
destination: mValue,
parent: mTopLevel,
name: "WriteArrayElement",
modes: []mode{mArray},
action: "write"}
_, got := vw.WriteArrayElement()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
})
t.Run("WriteArrayEnd", func(t *testing.T) {
vw := newValueWriter(ioutil.Discard)
vw.push(mElement)
want := fmt.Errorf("incorrect mode to end array: %s", mElement)
got := vw.WriteArrayEnd()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
vw.push(mArray)
vw.buf = append(vw.buf, make([]byte, 1019)...)
maxSize = 512
want = errMaxDocumentSizeExceeded{size: 1024}
got = vw.WriteArrayEnd()
if !compareErrors(got, want) {
t.Errorf("Did not get expected error. got %v; want %v", got, want)
}
maxSize = math.MaxInt32
})
t.Run("WriteBytes", func(t *testing.T) {
t.Run("writeElementHeader error", func(t *testing.T) {
vw := newValueWriterFromSlice(nil)
want := TransitionError{current: mTopLevel, destination: mode(0),
name: "WriteValueBytes", modes: []mode{mElement, mValue}, action: "write"}
got := vw.WriteValueBytes(bsontype.EmbeddedDocument, nil)
if !compareErrors(got, want) {
t.Errorf("Did not received expected error. got %v; want %v", got, want)
}
})
t.Run("success", func(t *testing.T) {
index, doc := bsoncore.ReserveLength(nil)
doc = bsoncore.AppendStringElement(doc, "hello", "world")
doc = append(doc, 0x00)
doc = bsoncore.UpdateLength(doc, index, int32(len(doc)))
index, want := bsoncore.ReserveLength(nil)
want = bsoncore.AppendDocumentElement(want, "foo", doc)
want = append(want, 0x00)
want = bsoncore.UpdateLength(want, index, int32(len(want)))
vw := newValueWriterFromSlice(make([]byte, 0, 512))
_, err := vw.WriteDocument()
noerr(t, err)
_, err = vw.WriteDocumentElement("foo")
noerr(t, err)
err = vw.WriteValueBytes(bsontype.EmbeddedDocument, doc)
noerr(t, err)
err = vw.WriteDocumentEnd()
noerr(t, err)
got := vw.buf
if !bytes.Equal(got, want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, want)
}
})
})
}
type errWriter struct {
err error
}
func (ew errWriter) Write([]byte) (int, error) { return 0, ew.err }

View File

@ -0,0 +1,78 @@
// 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 bsonrw
import (
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ArrayWriter is the interface used to create a BSON or BSON adjacent array.
// Callers must ensure they call WriteArrayEnd when they have finished creating
// the array.
type ArrayWriter interface {
WriteArrayElement() (ValueWriter, error)
WriteArrayEnd() error
}
// DocumentWriter is the interface used to create a BSON or BSON adjacent
// document. Callers must ensure they call WriteDocumentEnd when they have
// finished creating the document.
type DocumentWriter interface {
WriteDocumentElement(string) (ValueWriter, error)
WriteDocumentEnd() error
}
// ValueWriter is the interface used to write BSON values. Implementations of
// this interface handle creating BSON or BSON adjacent representations of the
// values.
type ValueWriter interface {
WriteArray() (ArrayWriter, error)
WriteBinary(b []byte) error
WriteBinaryWithSubtype(b []byte, btype byte) error
WriteBoolean(bool) error
WriteCodeWithScope(code string) (DocumentWriter, error)
WriteDBPointer(ns string, oid primitive.ObjectID) error
WriteDateTime(dt int64) error
WriteDecimal128(primitive.Decimal128) error
WriteDouble(float64) error
WriteInt32(int32) error
WriteInt64(int64) error
WriteJavascript(code string) error
WriteMaxKey() error
WriteMinKey() error
WriteNull() error
WriteObjectID(primitive.ObjectID) error
WriteRegex(pattern, options string) error
WriteString(string) error
WriteDocument() (DocumentWriter, error)
WriteSymbol(symbol string) error
WriteTimestamp(t, i uint32) error
WriteUndefined() error
}
// ValueWriterFlusher is a superset of ValueWriter that exposes functionality to flush to the underlying buffer.
type ValueWriterFlusher interface {
ValueWriter
Flush() error
}
// BytesWriter is the interface used to write BSON bytes to a ValueWriter.
// This interface is meant to be a superset of ValueWriter, so that types that
// implement ValueWriter may also implement this interface.
type BytesWriter interface {
WriteValueBytes(t bsontype.Type, b []byte) error
}
// SliceWriter allows a pointer to a slice of bytes to be used as an io.Writer.
type SliceWriter []byte
func (sw *SliceWriter) Write(p []byte) (int, error) {
written := len(p)
*sw = append(*sw, p...)
return written, nil
}

View File

@ -0,0 +1,97 @@
// 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 bsontype is a utility package that contains types for each BSON type and the
// a stringifier for the Type to enable easier debugging when working with BSON.
package bsontype // import "go.mongodb.org/mongo-driver/bson/bsontype"
// These constants uniquely refer to each BSON type.
const (
Double Type = 0x01
String Type = 0x02
EmbeddedDocument Type = 0x03
Array Type = 0x04
Binary Type = 0x05
Undefined Type = 0x06
ObjectID Type = 0x07
Boolean Type = 0x08
DateTime Type = 0x09
Null Type = 0x0A
Regex Type = 0x0B
DBPointer Type = 0x0C
JavaScript Type = 0x0D
Symbol Type = 0x0E
CodeWithScope Type = 0x0F
Int32 Type = 0x10
Timestamp Type = 0x11
Int64 Type = 0x12
Decimal128 Type = 0x13
MinKey Type = 0xFF
MaxKey Type = 0x7F
BinaryGeneric byte = 0x00
BinaryFunction byte = 0x01
BinaryBinaryOld byte = 0x02
BinaryUUIDOld byte = 0x03
BinaryUUID byte = 0x04
BinaryMD5 byte = 0x05
BinaryEncrypted byte = 0x06
BinaryColumn byte = 0x07
BinaryUserDefined byte = 0x80
)
// Type represents a BSON type.
type Type byte
// String returns the string representation of the BSON type's name.
func (bt Type) String() string {
switch bt {
case '\x01':
return "double"
case '\x02':
return "string"
case '\x03':
return "embedded document"
case '\x04':
return "array"
case '\x05':
return "binary"
case '\x06':
return "undefined"
case '\x07':
return "objectID"
case '\x08':
return "boolean"
case '\x09':
return "UTC datetime"
case '\x0A':
return "null"
case '\x0B':
return "regex"
case '\x0C':
return "dbPointer"
case '\x0D':
return "javascript"
case '\x0E':
return "symbol"
case '\x0F':
return "code with scope"
case '\x10':
return "32-bit integer"
case '\x11':
return "timestamp"
case '\x12':
return "64-bit integer"
case '\x13':
return "128-bit decimal"
case '\xFF':
return "min key"
case '\x7F':
return "max key"
default:
return "invalid"
}
}

View File

@ -0,0 +1,49 @@
// 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 bsontype
import "testing"
func TestType(t *testing.T) {
testCases := []struct {
name string
t Type
want string
}{
{"double", Double, "double"},
{"string", String, "string"},
{"embedded document", EmbeddedDocument, "embedded document"},
{"array", Array, "array"},
{"binary", Binary, "binary"},
{"undefined", Undefined, "undefined"},
{"objectID", ObjectID, "objectID"},
{"boolean", Boolean, "boolean"},
{"UTC datetime", DateTime, "UTC datetime"},
{"null", Null, "null"},
{"regex", Regex, "regex"},
{"dbPointer", DBPointer, "dbPointer"},
{"javascript", JavaScript, "javascript"},
{"symbol", Symbol, "symbol"},
{"code with scope", CodeWithScope, "code with scope"},
{"32-bit integer", Int32, "32-bit integer"},
{"timestamp", Timestamp, "timestamp"},
{"64-bit integer", Int64, "64-bit integer"},
{"128-bit decimal", Decimal128, "128-bit decimal"},
{"min key", MinKey, "min key"},
{"max key", MaxKey, "max key"},
{"invalid", (0), "invalid"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := tc.t.String()
if got != tc.want {
t.Errorf("String outputs do not match. got %s; want %s", got, tc.want)
}
})
}
}

141
mongo/bson/decoder.go Normal file
View File

@ -0,0 +1,141 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"errors"
"fmt"
"reflect"
"sync"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
)
// ErrDecodeToNil is the error returned when trying to decode to a nil value
var ErrDecodeToNil = errors.New("cannot Decode to nil value")
// This pool is used to keep the allocations of Decoders down. This is only used for the Marshal*
// methods and is not consumable from outside of this package. The Decoders retrieved from this pool
// must have both Reset and SetRegistry called on them.
var decPool = sync.Pool{
New: func() interface{} {
return new(Decoder)
},
}
// A Decoder reads and decodes BSON documents from a stream. It reads from a bsonrw.ValueReader as
// the source of BSON data.
type Decoder struct {
dc bsoncodec.DecodeContext
vr bsonrw.ValueReader
// We persist defaultDocumentM and defaultDocumentD on the Decoder to prevent overwriting from
// (*Decoder).SetContext.
defaultDocumentM bool
defaultDocumentD bool
}
// NewDecoder returns a new decoder that uses the DefaultRegistry to read from vr.
func NewDecoder(vr bsonrw.ValueReader) (*Decoder, error) {
if vr == nil {
return nil, errors.New("cannot create a new Decoder with a nil ValueReader")
}
return &Decoder{
dc: bsoncodec.DecodeContext{Registry: DefaultRegistry},
vr: vr,
}, nil
}
// NewDecoderWithContext returns a new decoder that uses DecodeContext dc to read from vr.
func NewDecoderWithContext(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader) (*Decoder, error) {
if dc.Registry == nil {
dc.Registry = DefaultRegistry
}
if vr == nil {
return nil, errors.New("cannot create a new Decoder with a nil ValueReader")
}
return &Decoder{
dc: dc,
vr: vr,
}, nil
}
// Decode reads the next BSON document from the stream and decodes it into the
// value pointed to by val.
//
// The documentation for Unmarshal contains details about of BSON into a Go
// value.
func (d *Decoder) Decode(val interface{}) error {
if unmarshaler, ok := val.(Unmarshaler); ok {
// TODO(skriptble): Reuse a []byte here and use the AppendDocumentBytes method.
buf, err := bsonrw.Copier{}.CopyDocumentToBytes(d.vr)
if err != nil {
return err
}
return unmarshaler.UnmarshalBSON(buf)
}
rval := reflect.ValueOf(val)
switch rval.Kind() {
case reflect.Ptr:
if rval.IsNil() {
return ErrDecodeToNil
}
rval = rval.Elem()
case reflect.Map:
if rval.IsNil() {
return ErrDecodeToNil
}
default:
return fmt.Errorf("argument to Decode must be a pointer or a map, but got %v", rval)
}
decoder, err := d.dc.LookupDecoder(rval.Type())
if err != nil {
return err
}
if d.defaultDocumentM {
d.dc.DefaultDocumentM()
}
if d.defaultDocumentD {
d.dc.DefaultDocumentD()
}
return decoder.DecodeValue(d.dc, d.vr, rval)
}
// Reset will reset the state of the decoder, using the same *DecodeContext used in
// the original construction but using vr for reading.
func (d *Decoder) Reset(vr bsonrw.ValueReader) error {
d.vr = vr
return nil
}
// SetRegistry replaces the current registry of the decoder with r.
func (d *Decoder) SetRegistry(r *bsoncodec.Registry) error {
d.dc.Registry = r
return nil
}
// SetContext replaces the current registry of the decoder with dc.
func (d *Decoder) SetContext(dc bsoncodec.DecodeContext) error {
d.dc = dc
return nil
}
// DefaultDocumentM will decode empty documents using the primitive.M type. This behavior is restricted to data typed as
// "interface{}" or "map[string]interface{}".
func (d *Decoder) DefaultDocumentM() {
d.defaultDocumentM = true
}
// DefaultDocumentD will decode empty documents using the primitive.D type. This behavior is restricted to data typed as
// "interface{}" or "map[string]interface{}".
func (d *Decoder) DefaultDocumentD() {
d.defaultDocumentD = true
}

435
mongo/bson/decoder_test.go Normal file
View File

@ -0,0 +1,435 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bytes"
"errors"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
func TestBasicDecode(t *testing.T) {
t.Parallel()
for _, tc := range unmarshalingTestCases() {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := reflect.New(tc.sType).Elem()
vr := bsonrw.NewBSONDocumentReader(tc.data)
reg := DefaultRegistry
decoder, err := reg.LookupDecoder(reflect.TypeOf(got))
noerr(t, err)
err = decoder.DecodeValue(bsoncodec.DecodeContext{Registry: reg}, vr, got)
noerr(t, err)
assert.Equal(t, tc.want, got.Addr().Interface(), "Results do not match.")
})
}
}
func TestDecoderv2(t *testing.T) {
t.Parallel()
t.Run("Decode", func(t *testing.T) {
t.Parallel()
for _, tc := range unmarshalingTestCases() {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := reflect.New(tc.sType).Interface()
vr := bsonrw.NewBSONDocumentReader(tc.data)
dec, err := NewDecoderWithContext(bsoncodec.DecodeContext{Registry: DefaultRegistry}, vr)
noerr(t, err)
err = dec.Decode(got)
noerr(t, err)
assert.Equal(t, tc.want, got, "Results do not match.")
})
}
t.Run("lookup error", func(t *testing.T) {
t.Parallel()
type certainlydoesntexistelsewhereihope func(string, string) string
// Avoid unused code lint error.
_ = certainlydoesntexistelsewhereihope(func(string, string) string { return "" })
cdeih := func(string, string) string { return "certainlydoesntexistelsewhereihope" }
dec, err := NewDecoder(bsonrw.NewBSONDocumentReader([]byte{}))
noerr(t, err)
want := bsoncodec.ErrNoDecoder{Type: reflect.TypeOf(cdeih)}
got := dec.Decode(&cdeih)
assert.Equal(t, want, got, "Received unexpected error.")
})
t.Run("Unmarshaler", func(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
err error
vr bsonrw.ValueReader
invoked bool
}{
{
"error",
errors.New("Unmarshaler error"),
&bsonrwtest.ValueReaderWriter{BSONType: bsontype.EmbeddedDocument, Err: bsonrw.ErrEOD, ErrAfter: bsonrwtest.ReadElement},
true,
},
{
"copy error",
errors.New("copy error"),
&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadDocument},
false,
},
{
"success",
nil,
&bsonrwtest.ValueReaderWriter{BSONType: bsontype.EmbeddedDocument, Err: bsonrw.ErrEOD, ErrAfter: bsonrwtest.ReadElement},
true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
unmarshaler := &testUnmarshaler{err: tc.err}
dec, err := NewDecoder(tc.vr)
noerr(t, err)
got := dec.Decode(unmarshaler)
want := tc.err
if !compareErrors(got, want) {
t.Errorf("Did not receive expected error. got %v; want %v", got, want)
}
if unmarshaler.invoked != tc.invoked {
if tc.invoked {
t.Error("Expected to have UnmarshalBSON invoked, but it wasn't.")
} else {
t.Error("Expected UnmarshalBSON to not be invoked, but it was.")
}
}
})
}
t.Run("Unmarshaler/success bsonrw.ValueReader", func(t *testing.T) {
t.Parallel()
want := bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))
unmarshaler := &testUnmarshaler{}
vr := bsonrw.NewBSONDocumentReader(want)
dec, err := NewDecoder(vr)
noerr(t, err)
err = dec.Decode(unmarshaler)
noerr(t, err)
got := unmarshaler.data
if !bytes.Equal(got, want) {
t.Errorf("Did not unmarshal properly. got %v; want %v", got, want)
}
})
})
})
t.Run("NewDecoder", func(t *testing.T) {
t.Parallel()
t.Run("error", func(t *testing.T) {
t.Parallel()
_, got := NewDecoder(nil)
want := errors.New("cannot create a new Decoder with a nil ValueReader")
if !cmp.Equal(got, want, cmp.Comparer(compareErrors)) {
t.Errorf("Was expecting error but got different error. got %v; want %v", got, want)
}
})
t.Run("success", func(t *testing.T) {
t.Parallel()
got, err := NewDecoder(bsonrw.NewBSONDocumentReader([]byte{}))
noerr(t, err)
if got == nil {
t.Errorf("Was expecting a non-nil Decoder, but got <nil>")
}
})
})
t.Run("NewDecoderWithContext", func(t *testing.T) {
t.Parallel()
t.Run("errors", func(t *testing.T) {
t.Parallel()
dc := bsoncodec.DecodeContext{Registry: DefaultRegistry}
_, got := NewDecoderWithContext(dc, nil)
want := errors.New("cannot create a new Decoder with a nil ValueReader")
if !cmp.Equal(got, want, cmp.Comparer(compareErrors)) {
t.Errorf("Was expecting error but got different error. got %v; want %v", got, want)
}
})
t.Run("success", func(t *testing.T) {
t.Parallel()
got, err := NewDecoderWithContext(bsoncodec.DecodeContext{}, bsonrw.NewBSONDocumentReader([]byte{}))
noerr(t, err)
if got == nil {
t.Errorf("Was expecting a non-nil Decoder, but got <nil>")
}
dc := bsoncodec.DecodeContext{Registry: DefaultRegistry}
got, err = NewDecoderWithContext(dc, bsonrw.NewBSONDocumentReader([]byte{}))
noerr(t, err)
if got == nil {
t.Errorf("Was expecting a non-nil Decoder, but got <nil>")
}
})
})
t.Run("Decode doesn't zero struct", func(t *testing.T) {
t.Parallel()
type foo struct {
Item string
Qty int
Bonus int
}
var got foo
got.Item = "apple"
got.Bonus = 2
data := docToBytes(D{{"item", "canvas"}, {"qty", 4}})
vr := bsonrw.NewBSONDocumentReader(data)
dec, err := NewDecoder(vr)
noerr(t, err)
err = dec.Decode(&got)
noerr(t, err)
want := foo{Item: "canvas", Qty: 4, Bonus: 2}
assert.Equal(t, want, got, "Results do not match.")
})
t.Run("Reset", func(t *testing.T) {
t.Parallel()
vr1, vr2 := bsonrw.NewBSONDocumentReader([]byte{}), bsonrw.NewBSONDocumentReader([]byte{})
dc := bsoncodec.DecodeContext{Registry: DefaultRegistry}
dec, err := NewDecoderWithContext(dc, vr1)
noerr(t, err)
if dec.vr != vr1 {
t.Errorf("Decoder should use the value reader provided. got %v; want %v", dec.vr, vr1)
}
err = dec.Reset(vr2)
noerr(t, err)
if dec.vr != vr2 {
t.Errorf("Decoder should use the value reader provided. got %v; want %v", dec.vr, vr2)
}
})
t.Run("SetContext", func(t *testing.T) {
t.Parallel()
dc1 := bsoncodec.DecodeContext{Registry: DefaultRegistry}
dc2 := bsoncodec.DecodeContext{Registry: NewRegistryBuilder().Build()}
dec, err := NewDecoderWithContext(dc1, bsonrw.NewBSONDocumentReader([]byte{}))
noerr(t, err)
if !reflect.DeepEqual(dec.dc, dc1) {
t.Errorf("Decoder should use the Registry provided. got %v; want %v", dec.dc, dc1)
}
err = dec.SetContext(dc2)
noerr(t, err)
if !reflect.DeepEqual(dec.dc, dc2) {
t.Errorf("Decoder should use the Registry provided. got %v; want %v", dec.dc, dc2)
}
})
t.Run("SetRegistry", func(t *testing.T) {
t.Parallel()
r1, r2 := DefaultRegistry, NewRegistryBuilder().Build()
dc1 := bsoncodec.DecodeContext{Registry: r1}
dc2 := bsoncodec.DecodeContext{Registry: r2}
dec, err := NewDecoder(bsonrw.NewBSONDocumentReader([]byte{}))
noerr(t, err)
if !reflect.DeepEqual(dec.dc, dc1) {
t.Errorf("Decoder should use the Registry provided. got %v; want %v", dec.dc, dc1)
}
err = dec.SetRegistry(r2)
noerr(t, err)
if !reflect.DeepEqual(dec.dc, dc2) {
t.Errorf("Decoder should use the Registry provided. got %v; want %v", dec.dc, dc2)
}
})
t.Run("DecodeToNil", func(t *testing.T) {
t.Parallel()
data := docToBytes(D{{"item", "canvas"}, {"qty", 4}})
vr := bsonrw.NewBSONDocumentReader(data)
dec, err := NewDecoder(vr)
noerr(t, err)
var got *D
err = dec.Decode(got)
if err != ErrDecodeToNil {
t.Fatalf("Decode error mismatch; expected %v, got %v", ErrDecodeToNil, err)
}
})
t.Run("DefaultDocuemntD embedded map as empty interface", func(t *testing.T) {
t.Parallel()
type someMap map[string]interface{}
in := make(someMap)
in["foo"] = map[string]interface{}{"bar": "baz"}
bytes, err := Marshal(in)
if err != nil {
t.Fatal(err)
}
var bsonOut someMap
dec, err := NewDecoder(bsonrw.NewBSONDocumentReader(bytes))
if err != nil {
t.Fatal(err)
}
dec.DefaultDocumentM()
if err := dec.Decode(&bsonOut); err != nil {
t.Fatal(err)
}
// Ensure that interface{}-typed top-level data is converted to the document type.
bsonOutType := reflect.TypeOf(bsonOut)
inType := reflect.TypeOf(in)
assert.Equal(t, inType, bsonOutType, "expected %v to equal %v", inType.String(), bsonOutType.String())
// Ensure that the embedded type is a primitive map.
mType := reflect.TypeOf(primitive.M{})
bsonFooOutType := reflect.TypeOf(bsonOut["foo"])
assert.Equal(t, mType, bsonFooOutType, "expected %v to equal %v", mType.String(), bsonFooOutType.String())
})
t.Run("DefaultDocuemntD for decoding into interface{} alias", func(t *testing.T) {
t.Parallel()
var in interface{} = map[string]interface{}{"bar": "baz"}
bytes, err := Marshal(in)
if err != nil {
t.Fatal(err)
}
var bsonOut interface{}
dec, err := NewDecoder(bsonrw.NewBSONDocumentReader(bytes))
if err != nil {
t.Fatal(err)
}
dec.DefaultDocumentD()
if err := dec.Decode(&bsonOut); err != nil {
t.Fatal(err)
}
// Ensure that interface{}-typed top-level data is converted to the document type.
dType := reflect.TypeOf(primitive.D{})
bsonOutType := reflect.TypeOf(bsonOut)
assert.Equal(t, dType, bsonOutType,
"expected %v to equal %v", dType.String(), bsonOutType.String())
})
t.Run("DefaultDocuemntD for decoding into non-interface{} alias", func(t *testing.T) {
t.Parallel()
var in interface{} = map[string]interface{}{"bar": "baz"}
bytes, err := Marshal(in)
if err != nil {
t.Fatal(err)
}
var bsonOut struct{}
dec, err := NewDecoder(bsonrw.NewBSONDocumentReader(bytes))
if err != nil {
t.Fatal(err)
}
dec.DefaultDocumentD()
if err := dec.Decode(&bsonOut); err != nil {
t.Fatal(err)
}
// Ensure that typed top-level data is not converted to the document type.
dType := reflect.TypeOf(primitive.D{})
bsonOutType := reflect.TypeOf(bsonOut)
assert.NotEqual(t, dType, bsonOutType,
"expected %v to not equal %v", dType.String(), bsonOutType.String())
})
t.Run("DefaultDocumentD for deep struct values", func(t *testing.T) {
t.Parallel()
type emb struct {
Foo map[int]interface{} `bson:"foo"`
}
objID := primitive.NewObjectID()
in := emb{
Foo: map[int]interface{}{
1: map[string]interface{}{"bar": "baz"},
2: map[int]interface{}{
3: map[string]interface{}{"bar": "baz"},
},
4: map[primitive.ObjectID]interface{}{
objID: map[string]interface{}{"bar": "baz"},
},
},
}
bytes, err := Marshal(in)
if err != nil {
t.Fatal(err)
}
dec, err := NewDecoder(bsonrw.NewBSONDocumentReader(bytes))
if err != nil {
t.Fatal(err)
}
dec.DefaultDocumentD()
var out emb
if err := dec.Decode(&out); err != nil {
t.Fatal(err)
}
mType := reflect.TypeOf(primitive.M{})
bsonOutType := reflect.TypeOf(out)
assert.NotEqual(t, mType, bsonOutType,
"expected %v to not equal %v", mType.String(), bsonOutType.String())
want := emb{
Foo: map[int]interface{}{
1: primitive.D{{Key: "bar", Value: "baz"}},
2: primitive.D{{Key: "3", Value: primitive.D{{Key: "bar", Value: "baz"}}}},
4: primitive.D{{Key: objID.Hex(), Value: primitive.D{{Key: "bar", Value: "baz"}}}},
},
}
assert.Equal(t, want, out, "expected %v, got %v", want, out)
})
}
type testUnmarshaler struct {
invoked bool
err error
data []byte
}
func (tu *testUnmarshaler) UnmarshalBSON(d []byte) error {
tu.invoked = true
tu.data = d
return tu.err
}

141
mongo/bson/doc.go Normal file
View File

@ -0,0 +1,141 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Package bson is a library for reading, writing, and manipulating BSON. BSON is a binary serialization format used to
// store documents and make remote procedure calls in MongoDB. The BSON specification is located at https://bsonspec.org.
// The BSON library handles marshalling and unmarshalling of values through a configurable codec system. For a description
// of the codec system and examples of registering custom codecs, see the bsoncodec package.
//
// # Raw BSON
//
// The Raw family of types is used to validate and retrieve elements from a slice of bytes. This
// type is most useful when you want do lookups on BSON bytes without unmarshaling it into another
// type.
//
// Example:
//
// var raw bson.Raw = ... // bytes from somewhere
// err := raw.Validate()
// if err != nil { return err }
// val := raw.Lookup("foo")
// i32, ok := val.Int32OK()
// // do something with i32...
//
// # Native Go Types
//
// The D and M types defined in this package can be used to build representations of BSON using native Go types. D is a
// slice and M is a map. For more information about the use cases for these types, see the documentation on the type
// definitions.
//
// Note that a D should not be constructed with duplicate key names, as that can cause undefined server behavior.
//
// Example:
//
// bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
// bson.M{"foo": "bar", "hello": "world", "pi": 3.14159}
//
// When decoding BSON to a D or M, the following type mappings apply when unmarshalling:
//
// 1. BSON int32 unmarshals to an int32.
// 2. BSON int64 unmarshals to an int64.
// 3. BSON double unmarshals to a float64.
// 4. BSON string unmarshals to a string.
// 5. BSON boolean unmarshals to a bool.
// 6. BSON embedded document unmarshals to the parent type (i.e. D for a D, M for an M).
// 7. BSON array unmarshals to a bson.A.
// 8. BSON ObjectId unmarshals to a primitive.ObjectID.
// 9. BSON datetime unmarshals to a primitive.DateTime.
// 10. BSON binary unmarshals to a primitive.Binary.
// 11. BSON regular expression unmarshals to a primitive.Regex.
// 12. BSON JavaScript unmarshals to a primitive.JavaScript.
// 13. BSON code with scope unmarshals to a primitive.CodeWithScope.
// 14. BSON timestamp unmarshals to an primitive.Timestamp.
// 15. BSON 128-bit decimal unmarshals to an primitive.Decimal128.
// 16. BSON min key unmarshals to an primitive.MinKey.
// 17. BSON max key unmarshals to an primitive.MaxKey.
// 18. BSON undefined unmarshals to a primitive.Undefined.
// 19. BSON null unmarshals to nil.
// 20. BSON DBPointer unmarshals to a primitive.DBPointer.
// 21. BSON symbol unmarshals to a primitive.Symbol.
//
// The above mappings also apply when marshalling a D or M to BSON. Some other useful marshalling mappings are:
//
// 1. time.Time marshals to a BSON datetime.
// 2. int8, int16, and int32 marshal to a BSON int32.
// 3. int marshals to a BSON int32 if the value is between math.MinInt32 and math.MaxInt32, inclusive, and a BSON int64
// otherwise.
// 4. int64 marshals to BSON int64.
// 5. uint8 and uint16 marshal to a BSON int32.
// 6. uint, uint32, and uint64 marshal to a BSON int32 if the value is between math.MinInt32 and math.MaxInt32,
// inclusive, and BSON int64 otherwise.
// 7. BSON null and undefined values will unmarshal into the zero value of a field (e.g. unmarshalling a BSON null or
// undefined value into a string will yield the empty string.).
//
// # Structs
//
// Structs can be marshalled/unmarshalled to/from BSON or Extended JSON. When transforming structs to/from BSON or Extended
// JSON, the following rules apply:
//
// 1. Only exported fields in structs will be marshalled or unmarshalled.
//
// 2. When marshalling a struct, each field will be lowercased to generate the key for the corresponding BSON element.
// For example, a struct field named "Foo" will generate key "foo". This can be overridden via a struct tag (e.g.
// `bson:"fooField"` to generate key "fooField" instead).
//
// 3. An embedded struct field is marshalled as a subdocument. The key will be the lowercased name of the field's type.
//
// 4. A pointer field is marshalled as the underlying type if the pointer is non-nil. If the pointer is nil, it is
// marshalled as a BSON null value.
//
// 5. When unmarshalling, a field of type interface{} will follow the D/M type mappings listed above. BSON documents
// unmarshalled into an interface{} field will be unmarshalled as a D.
//
// The encoding of each struct field can be customized by the "bson" struct tag.
//
// This tag behavior is configurable, and different struct tag behavior can be configured by initializing a new
// bsoncodec.StructCodec with the desired tag parser and registering that StructCodec onto the Registry. By default, JSON tags
// are not honored, but that can be enabled by creating a StructCodec with JSONFallbackStructTagParser, like below:
//
// Example:
//
// structcodec, _ := bsoncodec.NewStructCodec(bsoncodec.JSONFallbackStructTagParser)
//
// The bson tag gives the name of the field, possibly followed by a comma-separated list of options.
// The name may be empty in order to specify options without overriding the default field name. The following options can be used
// to configure behavior:
//
// 1. omitempty: If the omitempty struct tag is specified on a field, the field will not be marshalled if it is set to
// the zero value. Fields with language primitive types such as integers, booleans, and strings are considered empty if
// their value is equal to the zero value for the type (i.e. 0 for integers, false for booleans, and "" for strings).
// Slices, maps, and arrays are considered empty if they are of length zero. Interfaces and pointers are considered
// empty if their value is nil. By default, structs are only considered empty if the struct type implements the
// bsoncodec.Zeroer interface and the IsZero method returns true. Struct fields whose types do not implement Zeroer are
// never considered empty and will be marshalled as embedded documents.
// NOTE: It is recommended that this tag be used for all slice and map fields.
//
// 2. minsize: If the minsize struct tag is specified on a field of type int64, uint, uint32, or uint64 and the value of
// the field can fit in a signed int32, the field will be serialized as a BSON int32 rather than a BSON int64. For other
// types, this tag is ignored.
//
// 3. truncate: If the truncate struct tag is specified on a field with a non-float numeric type, BSON doubles unmarshalled
// into that field will be truncated at the decimal point. For example, if 3.14 is unmarshalled into a field of type int,
// it will be unmarshalled as 3. If this tag is not specified, the decoder will throw an error if the value cannot be
// decoded without losing precision. For float64 or non-numeric types, this tag is ignored.
//
// 4. inline: If the inline struct tag is specified for a struct or map field, the field will be "flattened" when
// marshalling and "un-flattened" when unmarshalling. This means that all of the fields in that struct/map will be
// pulled up one level and will become top-level fields rather than being fields in a nested document. For example, if a
// map field named "Map" with value map[string]interface{}{"foo": "bar"} is inlined, the resulting document will be
// {"foo": "bar"} instead of {"map": {"foo": "bar"}}. There can only be one inlined map field in a struct. If there are
// duplicated fields in the resulting document when an inlined struct is marshalled, the inlined field will be overwritten.
// If there are duplicated fields in the resulting document when an inlined map is marshalled, an error will be returned.
// This tag can be used with fields that are pointers to structs. If an inlined pointer field is nil, it will not be
// marshalled. For fields that are not maps or structs, this tag is ignored.
//
// # Marshalling and Unmarshalling
//
// Manually marshalling and unmarshalling can be done with the Marshal and Unmarshal family of functions.
package bson

99
mongo/bson/encoder.go Normal file
View File

@ -0,0 +1,99 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"errors"
"reflect"
"sync"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
)
// This pool is used to keep the allocations of Encoders down. This is only used for the Marshal*
// methods and is not consumable from outside of this package. The Encoders retrieved from this pool
// must have both Reset and SetRegistry called on them.
var encPool = sync.Pool{
New: func() interface{} {
return new(Encoder)
},
}
// An Encoder writes a serialization format to an output stream. It writes to a bsonrw.ValueWriter
// as the destination of BSON data.
type Encoder struct {
ec bsoncodec.EncodeContext
vw bsonrw.ValueWriter
}
// NewEncoder returns a new encoder that uses the DefaultRegistry to write to vw.
func NewEncoder(vw bsonrw.ValueWriter) (*Encoder, error) {
if vw == nil {
return nil, errors.New("cannot create a new Encoder with a nil ValueWriter")
}
return &Encoder{
ec: bsoncodec.EncodeContext{Registry: DefaultRegistry},
vw: vw,
}, nil
}
// NewEncoderWithContext returns a new encoder that uses EncodeContext ec to write to vw.
func NewEncoderWithContext(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter) (*Encoder, error) {
if ec.Registry == nil {
ec = bsoncodec.EncodeContext{Registry: DefaultRegistry}
}
if vw == nil {
return nil, errors.New("cannot create a new Encoder with a nil ValueWriter")
}
return &Encoder{
ec: ec,
vw: vw,
}, nil
}
// Encode writes the BSON encoding of val to the stream.
//
// The documentation for Marshal contains details about the conversion of Go
// values to BSON.
func (e *Encoder) Encode(val interface{}) error {
if marshaler, ok := val.(Marshaler); ok {
// TODO(skriptble): Should we have a MarshalAppender interface so that we can have []byte reuse?
buf, err := marshaler.MarshalBSON()
if err != nil {
return err
}
return bsonrw.Copier{}.CopyDocumentFromBytes(e.vw, buf)
}
encoder, err := e.ec.LookupEncoder(reflect.TypeOf(val))
if err != nil {
return err
}
return encoder.EncodeValue(e.ec, e.vw, reflect.ValueOf(val))
}
// Reset will reset the state of the encoder, using the same *EncodeContext used in
// the original construction but using vw.
func (e *Encoder) Reset(vw bsonrw.ValueWriter) error {
e.vw = vw
return nil
}
// SetRegistry replaces the current registry of the encoder with r.
func (e *Encoder) SetRegistry(r *bsoncodec.Registry) error {
e.ec.Registry = r
return nil
}
// SetContext replaces the current EncodeContext of the encoder with er.
func (e *Encoder) SetContext(ec bsoncodec.EncodeContext) error {
e.ec = ec
return nil
}

135
mongo/bson/encoder_test.go Normal file
View File

@ -0,0 +1,135 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bytes"
"errors"
"reflect"
"testing"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
)
func TestBasicEncode(t *testing.T) {
for _, tc := range marshalingTestCases {
t.Run(tc.name, func(t *testing.T) {
got := make(bsonrw.SliceWriter, 0, 1024)
vw, err := bsonrw.NewBSONValueWriter(&got)
noerr(t, err)
reg := DefaultRegistry
encoder, err := reg.LookupEncoder(reflect.TypeOf(tc.val))
noerr(t, err)
err = encoder.EncodeValue(bsoncodec.EncodeContext{Registry: reg}, vw, reflect.ValueOf(tc.val))
noerr(t, err)
if !bytes.Equal(got, tc.want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
t.Errorf("Bytes:\n%v\n%v", got, tc.want)
}
})
}
}
func TestEncoderEncode(t *testing.T) {
for _, tc := range marshalingTestCases {
t.Run(tc.name, func(t *testing.T) {
got := make(bsonrw.SliceWriter, 0, 1024)
vw, err := bsonrw.NewBSONValueWriter(&got)
noerr(t, err)
enc, err := NewEncoder(vw)
noerr(t, err)
err = enc.Encode(tc.val)
noerr(t, err)
if !bytes.Equal(got, tc.want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
t.Errorf("Bytes:\n%v\n%v", got, tc.want)
}
})
}
t.Run("Marshaler", func(t *testing.T) {
testCases := []struct {
name string
buf []byte
err error
wanterr error
vw bsonrw.ValueWriter
}{
{
"error",
nil,
errors.New("Marshaler error"),
errors.New("Marshaler error"),
&bsonrwtest.ValueReaderWriter{},
},
{
"copy error",
[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
nil,
errors.New("copy error"),
&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.WriteDocument},
},
{
"success",
[]byte{0x07, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00},
nil,
nil,
nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
marshaler := testMarshaler{buf: tc.buf, err: tc.err}
var vw bsonrw.ValueWriter
var err error
b := make(bsonrw.SliceWriter, 0, 100)
compareVW := false
if tc.vw != nil {
vw = tc.vw
} else {
compareVW = true
vw, err = bsonrw.NewBSONValueWriter(&b)
noerr(t, err)
}
enc, err := NewEncoder(vw)
noerr(t, err)
got := enc.Encode(marshaler)
want := tc.wanterr
if !compareErrors(got, want) {
t.Errorf("Did not receive expected error. got %v; want %v", got, want)
}
if compareVW {
buf := b
if !bytes.Equal(buf, tc.buf) {
t.Errorf("Copied bytes do not match. got %v; want %v", buf, tc.buf)
}
}
})
}
})
}
type testMarshaler struct {
buf []byte
err error
}
func (tm testMarshaler) MarshalBSON() ([]byte, error) { return tm.buf, tm.err }
func docToBytes(d interface{}) []byte {
b, err := Marshal(d)
if err != nil {
panic(err)
}
return b
}

View File

@ -0,0 +1,47 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"fmt"
"testing"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)
func TestExtJSON(t *testing.T) {
timestampNegativeInt32Err := fmt.Errorf("$timestamp i number should be uint32: -1")
timestampNegativeInt64Err := fmt.Errorf("$timestamp i number should be uint32: -2147483649")
timestampLargeValueErr := fmt.Errorf("$timestamp i number should be uint32: 4294967296")
testCases := []struct {
name string
input string
canonical bool
err error
}{
{"timestamp - negative int32 value", `{"":{"$timestamp":{"t":0,"i":-1}}}`, false, timestampNegativeInt32Err},
{"timestamp - negative int64 value", `{"":{"$timestamp":{"t":0,"i":-2147483649}}}`, false, timestampNegativeInt64Err},
{"timestamp - value overflows uint32", `{"":{"$timestamp":{"t":0,"i":4294967296}}}`, false, timestampLargeValueErr},
{"top level key is not treated as special", `{"$code": "foo"}`, false, nil},
{"escaped single quote errors", `{"f\'oo": "bar"}`, false, bsonrw.ErrInvalidJSON},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var res Raw
err := UnmarshalExtJSON([]byte(tc.input), tc.canonical, &res)
if tc.err == nil {
assert.Nil(t, err, "UnmarshalExtJSON error: %v", err)
return
}
assert.NotNil(t, err, "expected error %v, got nil", tc.err)
assert.Equal(t, tc.err.Error(), err.Error(), "expected error %v, got %v", tc.err, err)
})
}
}

40
mongo/bson/fuzz_test.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright (C) MongoDB, Inc. 2023-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"testing"
)
func FuzzDecode(f *testing.F) {
seedBSONCorpus(f)
f.Fuzz(func(t *testing.T, data []byte) {
for _, typ := range []func() interface{}{
func() interface{} { return new(D) },
func() interface{} { return new([]E) },
func() interface{} { return new(M) },
func() interface{} { return new(interface{}) },
func() interface{} { return make(map[string]interface{}) },
func() interface{} { return new([]interface{}) },
} {
i := typ()
if err := Unmarshal(data, i); err != nil {
return
}
encoded, err := Marshal(i)
if err != nil {
t.Fatal("failed to marshal", err)
}
if err := Unmarshal(encoded, i); err != nil {
t.Fatal("failed to unmarshal", err)
}
}
})
}

248
mongo/bson/marshal.go Normal file
View File

@ -0,0 +1,248 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bytes"
"encoding/json"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
const defaultDstCap = 256
var bvwPool = bsonrw.NewBSONValueWriterPool()
var extjPool = bsonrw.NewExtJSONValueWriterPool()
// Marshaler is an interface implemented by types that can marshal themselves
// into a BSON document represented as bytes. The bytes returned must be a valid
// BSON document if the error is nil.
type Marshaler interface {
MarshalBSON() ([]byte, error)
}
// ValueMarshaler is an interface implemented by types that can marshal
// themselves into a BSON value as bytes. The type must be the valid type for
// the bytes returned. The bytes and byte type together must be valid if the
// error is nil.
type ValueMarshaler interface {
MarshalBSONValue() (bsontype.Type, []byte, error)
}
// Marshal returns the BSON encoding of val as a BSON document. If val is not a type that can be transformed into a
// document, MarshalValue should be used instead.
//
// Marshal will use the default registry created by NewRegistry to recursively
// marshal val into a []byte. Marshal will inspect struct tags and alter the
// marshaling process accordingly.
func Marshal(val interface{}) ([]byte, error) {
return MarshalWithRegistry(DefaultRegistry, val)
}
// MarshalAppend will encode val as a BSON document and append the bytes to dst. If dst is not large enough to hold the
// bytes, it will be grown. If val is not a type that can be transformed into a document, MarshalValueAppend should be
// used instead.
func MarshalAppend(dst []byte, val interface{}) ([]byte, error) {
return MarshalAppendWithRegistry(DefaultRegistry, dst, val)
}
// MarshalWithRegistry returns the BSON encoding of val as a BSON document. If val is not a type that can be transformed
// into a document, MarshalValueWithRegistry should be used instead.
func MarshalWithRegistry(r *bsoncodec.Registry, val interface{}) ([]byte, error) {
dst := make([]byte, 0)
return MarshalAppendWithRegistry(r, dst, val)
}
// MarshalWithContext returns the BSON encoding of val as a BSON document using EncodeContext ec. If val is not a type
// that can be transformed into a document, MarshalValueWithContext should be used instead.
func MarshalWithContext(ec bsoncodec.EncodeContext, val interface{}) ([]byte, error) {
dst := make([]byte, 0)
return MarshalAppendWithContext(ec, dst, val)
}
// MarshalAppendWithRegistry will encode val as a BSON document using Registry r and append the bytes to dst. If dst is
// not large enough to hold the bytes, it will be grown. If val is not a type that can be transformed into a document,
// MarshalValueAppendWithRegistry should be used instead.
func MarshalAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{}) ([]byte, error) {
return MarshalAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val)
}
// MarshalAppendWithContext will encode val as a BSON document using Registry r and EncodeContext ec and append the
// bytes to dst. If dst is not large enough to hold the bytes, it will be grown. If val is not a type that can be
// transformed into a document, MarshalValueAppendWithContext should be used instead.
func MarshalAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interface{}) ([]byte, error) {
sw := new(bsonrw.SliceWriter)
*sw = dst
vw := bvwPool.Get(sw)
defer bvwPool.Put(vw)
enc := encPool.Get().(*Encoder)
defer encPool.Put(enc)
err := enc.Reset(vw)
if err != nil {
return nil, err
}
err = enc.SetContext(ec)
if err != nil {
return nil, err
}
err = enc.Encode(val)
if err != nil {
return nil, err
}
return *sw, nil
}
// MarshalValue returns the BSON encoding of val.
//
// MarshalValue will use bson.DefaultRegistry to transform val into a BSON value. If val is a struct, this function will
// inspect struct tags and alter the marshalling process accordingly.
func MarshalValue(val interface{}) (bsontype.Type, []byte, error) {
return MarshalValueWithRegistry(DefaultRegistry, val)
}
// MarshalValueAppend will append the BSON encoding of val to dst. If dst is not large enough to hold the BSON encoding
// of val, dst will be grown.
func MarshalValueAppend(dst []byte, val interface{}) (bsontype.Type, []byte, error) {
return MarshalValueAppendWithRegistry(DefaultRegistry, dst, val)
}
// MarshalValueWithRegistry returns the BSON encoding of val using Registry r.
func MarshalValueWithRegistry(r *bsoncodec.Registry, val interface{}) (bsontype.Type, []byte, error) {
dst := make([]byte, 0)
return MarshalValueAppendWithRegistry(r, dst, val)
}
// MarshalValueWithContext returns the BSON encoding of val using EncodeContext ec.
func MarshalValueWithContext(ec bsoncodec.EncodeContext, val interface{}) (bsontype.Type, []byte, error) {
dst := make([]byte, 0)
return MarshalValueAppendWithContext(ec, dst, val)
}
// MarshalValueAppendWithRegistry will append the BSON encoding of val to dst using Registry r. If dst is not large
// enough to hold the BSON encoding of val, dst will be grown.
func MarshalValueAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{}) (bsontype.Type, []byte, error) {
return MarshalValueAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val)
}
// MarshalValueAppendWithContext will append the BSON encoding of val to dst using EncodeContext ec. If dst is not large
// enough to hold the BSON encoding of val, dst will be grown.
func MarshalValueAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interface{}) (bsontype.Type, []byte, error) {
// get a ValueWriter configured to write to dst
sw := new(bsonrw.SliceWriter)
*sw = dst
vwFlusher := bvwPool.GetAtModeElement(sw)
// get an Encoder and encode the value
enc := encPool.Get().(*Encoder)
defer encPool.Put(enc)
if err := enc.Reset(vwFlusher); err != nil {
return 0, nil, err
}
if err := enc.SetContext(ec); err != nil {
return 0, nil, err
}
if err := enc.Encode(val); err != nil {
return 0, nil, err
}
// flush the bytes written because we cannot guarantee that a full document has been written
// after the flush, *sw will be in the format
// [value type, 0 (null byte to indicate end of empty element name), value bytes..]
if err := vwFlusher.Flush(); err != nil {
return 0, nil, err
}
buffer := *sw
return bsontype.Type(buffer[0]), buffer[2:], nil
}
// MarshalExtJSON returns the extended JSON encoding of val.
func MarshalExtJSON(val interface{}, canonical, escapeHTML bool) ([]byte, error) {
return MarshalExtJSONWithRegistry(DefaultRegistry, val, canonical, escapeHTML)
}
// MarshalExtJSONAppend will append the extended JSON encoding of val to dst.
// If dst is not large enough to hold the extended JSON encoding of val, dst
// will be grown.
func MarshalExtJSONAppend(dst []byte, val interface{}, canonical, escapeHTML bool) ([]byte, error) {
return MarshalExtJSONAppendWithRegistry(DefaultRegistry, dst, val, canonical, escapeHTML)
}
// MarshalExtJSONWithRegistry returns the extended JSON encoding of val using Registry r.
func MarshalExtJSONWithRegistry(r *bsoncodec.Registry, val interface{}, canonical, escapeHTML bool) ([]byte, error) {
dst := make([]byte, 0, defaultDstCap)
return MarshalExtJSONAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val, canonical, escapeHTML)
}
// MarshalExtJSONWithContext returns the extended JSON encoding of val using Registry r.
func MarshalExtJSONWithContext(ec bsoncodec.EncodeContext, val interface{}, canonical, escapeHTML bool) ([]byte, error) {
dst := make([]byte, 0, defaultDstCap)
return MarshalExtJSONAppendWithContext(ec, dst, val, canonical, escapeHTML)
}
// MarshalExtJSONAppendWithRegistry will append the extended JSON encoding of
// val to dst using Registry r. If dst is not large enough to hold the BSON
// encoding of val, dst will be grown.
func MarshalExtJSONAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{}, canonical, escapeHTML bool) ([]byte, error) {
return MarshalExtJSONAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val, canonical, escapeHTML)
}
// MarshalExtJSONAppendWithContext will append the extended JSON encoding of
// val to dst using Registry r. If dst is not large enough to hold the BSON
// encoding of val, dst will be grown.
func MarshalExtJSONAppendWithContext(ec bsoncodec.EncodeContext, dst []byte, val interface{}, canonical, escapeHTML bool) ([]byte, error) {
sw := new(bsonrw.SliceWriter)
*sw = dst
ejvw := extjPool.Get(sw, canonical, escapeHTML)
defer extjPool.Put(ejvw)
enc := encPool.Get().(*Encoder)
defer encPool.Put(enc)
err := enc.Reset(ejvw)
if err != nil {
return nil, err
}
err = enc.SetContext(ec)
if err != nil {
return nil, err
}
err = enc.Encode(val)
if err != nil {
return nil, err
}
return *sw, nil
}
// IndentExtJSON will prefix and indent the provided extended JSON src and append it to dst.
func IndentExtJSON(dst *bytes.Buffer, src []byte, prefix, indent string) error {
return json.Indent(dst, src, prefix, indent)
}
// MarshalExtJSONIndent returns the extended JSON encoding of val with each line with prefixed
// and indented.
func MarshalExtJSONIndent(val interface{}, canonical, escapeHTML bool, prefix, indent string) ([]byte, error) {
marshaled, err := MarshalExtJSON(val, canonical, escapeHTML)
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = IndentExtJSON(&buf, marshaled, prefix, indent)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

382
mongo/bson/marshal_test.go Normal file
View File

@ -0,0 +1,382 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"bytes"
"errors"
"fmt"
"reflect"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
var tInt32 = reflect.TypeOf(int32(0))
func TestMarshalAppendWithRegistry(t *testing.T) {
for _, tc := range marshalingTestCases {
t.Run(tc.name, func(t *testing.T) {
dst := make([]byte, 0, 1024)
var reg *bsoncodec.Registry
if tc.reg != nil {
reg = tc.reg
} else {
reg = DefaultRegistry
}
got, err := MarshalAppendWithRegistry(reg, dst, tc.val)
noerr(t, err)
if !bytes.Equal(got, tc.want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
t.Errorf("Bytes:\n%v\n%v", got, tc.want)
}
})
}
}
func TestMarshalAppendWithContext(t *testing.T) {
for _, tc := range marshalingTestCases {
t.Run(tc.name, func(t *testing.T) {
dst := make([]byte, 0, 1024)
var reg *bsoncodec.Registry
if tc.reg != nil {
reg = tc.reg
} else {
reg = DefaultRegistry
}
ec := bsoncodec.EncodeContext{Registry: reg}
got, err := MarshalAppendWithContext(ec, dst, tc.val)
noerr(t, err)
if !bytes.Equal(got, tc.want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
t.Errorf("Bytes:\n%v\n%v", got, tc.want)
}
})
}
}
func TestMarshalWithRegistry(t *testing.T) {
for _, tc := range marshalingTestCases {
t.Run(tc.name, func(t *testing.T) {
var reg *bsoncodec.Registry
if tc.reg != nil {
reg = tc.reg
} else {
reg = DefaultRegistry
}
got, err := MarshalWithRegistry(reg, tc.val)
noerr(t, err)
if !bytes.Equal(got, tc.want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
t.Errorf("Bytes:\n%v\n%v", got, tc.want)
}
})
}
}
func TestMarshalWithContext(t *testing.T) {
for _, tc := range marshalingTestCases {
t.Run(tc.name, func(t *testing.T) {
var reg *bsoncodec.Registry
if tc.reg != nil {
reg = tc.reg
} else {
reg = DefaultRegistry
}
ec := bsoncodec.EncodeContext{Registry: reg}
got, err := MarshalWithContext(ec, tc.val)
noerr(t, err)
if !bytes.Equal(got, tc.want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
t.Errorf("Bytes:\n%v\n%v", got, tc.want)
}
})
}
}
func TestMarshalAppend(t *testing.T) {
for _, tc := range marshalingTestCases {
t.Run(tc.name, func(t *testing.T) {
if tc.reg != nil {
t.Skip() // test requires custom registry
}
dst := make([]byte, 0, 1024)
got, err := MarshalAppend(dst, tc.val)
noerr(t, err)
if !bytes.Equal(got, tc.want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, tc.want)
t.Errorf("Bytes:\n%v\n%v", got, tc.want)
}
})
}
}
func TestMarshalExtJSONAppendWithContext(t *testing.T) {
t.Run("MarshalExtJSONAppendWithContext", func(t *testing.T) {
dst := make([]byte, 0, 1024)
type teststruct struct{ Foo int }
val := teststruct{1}
ec := bsoncodec.EncodeContext{Registry: DefaultRegistry}
got, err := MarshalExtJSONAppendWithContext(ec, dst, val, true, false)
noerr(t, err)
want := []byte(`{"foo":{"$numberInt":"1"}}`)
if !bytes.Equal(got, want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, want)
t.Errorf("Bytes:\n%s\n%s", got, want)
}
})
}
func TestMarshalExtJSONWithContext(t *testing.T) {
t.Run("MarshalExtJSONWithContext", func(t *testing.T) {
type teststruct struct{ Foo int }
val := teststruct{1}
ec := bsoncodec.EncodeContext{Registry: DefaultRegistry}
got, err := MarshalExtJSONWithContext(ec, val, true, false)
noerr(t, err)
want := []byte(`{"foo":{"$numberInt":"1"}}`)
if !bytes.Equal(got, want) {
t.Errorf("Bytes are not equal. got %v; want %v", got, want)
t.Errorf("Bytes:\n%s\n%s", got, want)
}
})
}
func TestMarshal_roundtripFromBytes(t *testing.T) {
before := []byte{
// length
0x1c, 0x0, 0x0, 0x0,
// --- begin array ---
// type - document
0x3,
// key - "foo"
0x66, 0x6f, 0x6f, 0x0,
// length
0x12, 0x0, 0x0, 0x0,
// type - string
0x2,
// key - "bar"
0x62, 0x61, 0x72, 0x0,
// value - string length
0x4, 0x0, 0x0, 0x0,
// value - "baz"
0x62, 0x61, 0x7a, 0x0,
// null terminator
0x0,
// --- end array ---
// null terminator
0x0,
}
var doc D
require.NoError(t, Unmarshal(before, &doc))
after, err := Marshal(doc)
require.NoError(t, err)
require.True(t, bytes.Equal(before, after))
}
func TestMarshal_roundtripFromDoc(t *testing.T) {
before := D{
{"foo", "bar"},
{"baz", int64(-27)},
{"bing", A{nil, primitive.Regex{Pattern: "word", Options: "i"}}},
}
b, err := Marshal(before)
require.NoError(t, err)
var after D
require.NoError(t, Unmarshal(b, &after))
if !cmp.Equal(after, before) {
t.Errorf("Documents to not match. got %v; want %v", after, before)
}
}
func TestCachingEncodersNotSharedAcrossRegistries(t *testing.T) {
// Encoders that have caches for recursive encoder lookup should not be shared across Registry instances. Otherwise,
// the first EncodeValue call would cache an encoder and a subsequent call would see that encoder even if a
// different Registry is used.
// Create a custom Registry that negates int32 values when encoding.
var encodeInt32 bsoncodec.ValueEncoderFunc = func(_ bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if val.Kind() != reflect.Int32 {
return fmt.Errorf("expected kind to be int32, got %v", val.Kind())
}
return vw.WriteInt32(int32(val.Int()) * -1)
}
customReg := NewRegistryBuilder().
RegisterTypeEncoder(tInt32, encodeInt32).
Build()
// Helper function to run the test and make assertions. The provided original value should result in the document
// {"x": {$numberInt: 1}} when marshalled with the default registry.
verifyResults := func(t *testing.T, original interface{}) {
// Marshal using the default and custom registries. Assert that the result is {x: 1} and {x: -1}, respectively.
first, err := Marshal(original)
assert.Nil(t, err, "Marshal error: %v", err)
expectedFirst := Raw(bsoncore.BuildDocumentFromElements(
nil,
bsoncore.AppendInt32Element(nil, "x", 1),
))
assert.Equal(t, expectedFirst, Raw(first), "expected document %v, got %v", expectedFirst, Raw(first))
second, err := MarshalWithRegistry(customReg, original)
assert.Nil(t, err, "Marshal error: %v", err)
expectedSecond := Raw(bsoncore.BuildDocumentFromElements(
nil,
bsoncore.AppendInt32Element(nil, "x", -1),
))
assert.Equal(t, expectedSecond, Raw(second), "expected document %v, got %v", expectedSecond, Raw(second))
}
t.Run("struct", func(t *testing.T) {
type Struct struct {
X int32
}
verifyResults(t, Struct{
X: 1,
})
})
t.Run("pointer", func(t *testing.T) {
i32 := int32(1)
verifyResults(t, M{
"x": &i32,
})
})
}
func TestNullBytes(t *testing.T) {
t.Run("element keys", func(t *testing.T) {
doc := D{{"a\x00", "foobar"}}
res, err := Marshal(doc)
want := errors.New("BSON element key cannot contain null bytes")
assert.Equal(t, want, err, "expected Marshal error %v, got error %v with result %q", want, err, Raw(res))
})
t.Run("regex values", func(t *testing.T) {
wantErr := errors.New("BSON regex values cannot contain null bytes")
testCases := []struct {
name string
pattern string
options string
}{
{"null bytes in pattern", "a\x00", "i"},
{"null bytes in options", "pattern", "i\x00"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
regex := primitive.Regex{
Pattern: tc.pattern,
Options: tc.options,
}
res, err := Marshal(D{{"foo", regex}})
assert.Equal(t, wantErr, err, "expected Marshal error %v, got error %v with result %q", wantErr, err, Raw(res))
})
}
})
t.Run("sub document field name", func(t *testing.T) {
doc := D{{"foo", D{{"foobar", D{{"a\x00", "foobar"}}}}}}
res, err := Marshal(doc)
wantErr := errors.New("BSON element key cannot contain null bytes")
assert.Equal(t, wantErr, err, "expected Marshal error %v, got error %v with result %q", wantErr, err, Raw(res))
})
}
func TestMarshalExtJSONIndent(t *testing.T) {
type indentTestCase struct {
name string
val interface{}
expectedExtJSON string
}
// expectedExtJSON must be written as below because single-quoted
// literal strings capture undesired code formatting tabs
testCases := []indentTestCase{
{
"empty val",
struct{}{},
`{}`,
},
{
"embedded struct",
struct {
Embedded interface{} `json:"embedded"`
Foo string `json:"foo"`
}{
Embedded: struct {
Name string `json:"name"`
Word string `json:"word"`
}{
Name: "test",
Word: "word",
},
Foo: "bar",
},
"{\n\t\"embedded\": {\n\t\t\"name\": \"test\",\n\t\t\"word\": \"word\"\n\t},\n\t\"foo\": \"bar\"\n}",
},
{
"date struct",
struct {
Foo string `json:"foo"`
Date time.Time `json:"date"`
}{
Foo: "bar",
Date: time.Date(2000, time.January, 1, 12, 0, 0, 0, time.UTC),
},
"{\n\t\"foo\": \"bar\",\n\t\"date\": {\n\t\t\"$date\": {\n\t\t\t\"$numberLong\": \"946728000000\"\n\t\t}\n\t}\n}",
},
{
"float struct",
struct {
Foo string `json:"foo"`
Float float32 `json:"float"`
}{
Foo: "bar",
Float: 3.14,
},
"{\n\t\"foo\": \"bar\",\n\t\"float\": {\n\t\t\"$numberDouble\": \"3.140000104904175\"\n\t}\n}",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
extJSONBytes, err := MarshalExtJSONIndent(tc.val, true, false, "", "\t")
assert.Nil(t, err, "Marshal indent error: %v", err)
expectedExtJSONBytes := []byte(tc.expectedExtJSON)
assert.Equal(t, expectedExtJSONBytes, extJSONBytes, "expected:\n%s\ngot:\n%s", expectedExtJSONBytes, extJSONBytes)
})
}
}

View File

@ -0,0 +1,168 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"io"
"testing"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
// helper type for testing MarshalValue that implements io.Reader
type marshalValueInterfaceInner struct {
Foo int
}
var _ io.Reader = marshalValueInterfaceInner{}
func (marshalValueInterfaceInner) Read([]byte) (int, error) {
return 0, nil
}
// helper type for testing MarshalValue that contains an interface
type marshalValueInterfaceOuter struct {
Reader io.Reader
}
// helper type for testing MarshalValue that implements ValueMarshaler
type marshalValueMarshaler struct {
Foo int
}
var _ ValueMarshaler = marshalValueMarshaler{}
func (mvi marshalValueMarshaler) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bsontype.Int32, bsoncore.AppendInt32(nil, int32(mvi.Foo)), nil
}
type marshalValueStruct struct {
Foo int
}
type marshalValueTestCase struct {
name string
val interface{}
expectedType bsontype.Type
expectedBytes []byte
}
func TestMarshalValue(t *testing.T) {
oid := primitive.NewObjectID()
regex := primitive.Regex{Pattern: "pattern", Options: "imx"}
dbPointer := primitive.DBPointer{DB: "db", Pointer: primitive.NewObjectID()}
codeWithScope := primitive.CodeWithScope{Code: "code", Scope: D{{"a", "b"}}}
idx, scopeCore := bsoncore.AppendDocumentStart(nil)
scopeCore = bsoncore.AppendStringElement(scopeCore, "a", "b")
scopeCore, _ = bsoncore.AppendDocumentEnd(scopeCore, idx)
decimal128 := primitive.NewDecimal128(5, 10)
interfaceTest := marshalValueInterfaceOuter{
Reader: marshalValueInterfaceInner{
Foo: 10,
},
}
interfaceCore, err := Marshal(interfaceTest)
assert.Nil(t, err, "Marshal error: %v", err)
structTest := marshalValueStruct{Foo: 10}
structCore, err := Marshal(structTest)
assert.Nil(t, err, "Marshal error: %v", err)
marshalValueTestCases := []marshalValueTestCase{
{"double", 3.14, bsontype.Double, bsoncore.AppendDouble(nil, 3.14)},
{"string", "hello world", bsontype.String, bsoncore.AppendString(nil, "hello world")},
{"binary", primitive.Binary{1, []byte{1, 2}}, bsontype.Binary, bsoncore.AppendBinary(nil, 1, []byte{1, 2})},
{"undefined", primitive.Undefined{}, bsontype.Undefined, []byte{}},
{"object id", oid, bsontype.ObjectID, bsoncore.AppendObjectID(nil, oid)},
{"boolean", true, bsontype.Boolean, bsoncore.AppendBoolean(nil, true)},
{"datetime", primitive.DateTime(5), bsontype.DateTime, bsoncore.AppendDateTime(nil, 5)},
{"null", primitive.Null{}, bsontype.Null, []byte{}},
{"regex", regex, bsontype.Regex, bsoncore.AppendRegex(nil, regex.Pattern, regex.Options)},
{"dbpointer", dbPointer, bsontype.DBPointer, bsoncore.AppendDBPointer(nil, dbPointer.DB, dbPointer.Pointer)},
{"javascript", primitive.JavaScript("js"), bsontype.JavaScript, bsoncore.AppendJavaScript(nil, "js")},
{"symbol", primitive.Symbol("symbol"), bsontype.Symbol, bsoncore.AppendSymbol(nil, "symbol")},
{"code with scope", codeWithScope, bsontype.CodeWithScope, bsoncore.AppendCodeWithScope(nil, "code", scopeCore)},
{"int32", 5, bsontype.Int32, bsoncore.AppendInt32(nil, 5)},
{"int64", int64(5), bsontype.Int64, bsoncore.AppendInt64(nil, 5)},
{"timestamp", primitive.Timestamp{T: 1, I: 5}, bsontype.Timestamp, bsoncore.AppendTimestamp(nil, 1, 5)},
{"decimal128", decimal128, bsontype.Decimal128, bsoncore.AppendDecimal128(nil, decimal128)},
{"min key", primitive.MinKey{}, bsontype.MinKey, []byte{}},
{"max key", primitive.MaxKey{}, bsontype.MaxKey, []byte{}},
{"struct", structTest, bsontype.EmbeddedDocument, structCore},
{"interface", interfaceTest, bsontype.EmbeddedDocument, interfaceCore},
{"D", D{{"foo", 10}}, bsontype.EmbeddedDocument, structCore},
{"M", M{"foo": 10}, bsontype.EmbeddedDocument, structCore},
{"ValueMarshaler", marshalValueMarshaler{Foo: 10}, bsontype.Int32, bsoncore.AppendInt32(nil, 10)},
}
t.Run("MarshalValue", func(t *testing.T) {
for _, tc := range marshalValueTestCases {
t.Run(tc.name, func(t *testing.T) {
valueType, valueBytes, err := MarshalValue(tc.val)
assert.Nil(t, err, "MarshalValue error: %v", err)
compareMarshalValueResults(t, tc, valueType, valueBytes)
})
}
})
t.Run("MarshalValueAppend", func(t *testing.T) {
for _, tc := range marshalValueTestCases {
t.Run(tc.name, func(t *testing.T) {
valueType, valueBytes, err := MarshalValueAppend(nil, tc.val)
assert.Nil(t, err, "MarshalValueAppend error: %v", err)
compareMarshalValueResults(t, tc, valueType, valueBytes)
})
}
})
t.Run("MarshalValueWithRegistry", func(t *testing.T) {
for _, tc := range marshalValueTestCases {
t.Run(tc.name, func(t *testing.T) {
valueType, valueBytes, err := MarshalValueWithRegistry(DefaultRegistry, tc.val)
assert.Nil(t, err, "MarshalValueWithRegistry error: %v", err)
compareMarshalValueResults(t, tc, valueType, valueBytes)
})
}
})
t.Run("MarshalValueWithContext", func(t *testing.T) {
ec := bsoncodec.EncodeContext{Registry: DefaultRegistry}
for _, tc := range marshalValueTestCases {
t.Run(tc.name, func(t *testing.T) {
valueType, valueBytes, err := MarshalValueWithContext(ec, tc.val)
assert.Nil(t, err, "MarshalValueWithContext error: %v", err)
compareMarshalValueResults(t, tc, valueType, valueBytes)
})
}
})
t.Run("MarshalValueAppendWithRegistry", func(t *testing.T) {
for _, tc := range marshalValueTestCases {
t.Run(tc.name, func(t *testing.T) {
valueType, valueBytes, err := MarshalValueAppendWithRegistry(DefaultRegistry, nil, tc.val)
assert.Nil(t, err, "MarshalValueAppendWithRegistry error: %v", err)
compareMarshalValueResults(t, tc, valueType, valueBytes)
})
}
})
t.Run("MarshalValueAppendWithContext", func(t *testing.T) {
ec := bsoncodec.EncodeContext{Registry: DefaultRegistry}
for _, tc := range marshalValueTestCases {
t.Run(tc.name, func(t *testing.T) {
valueType, valueBytes, err := MarshalValueAppendWithContext(ec, nil, tc.val)
assert.Nil(t, err, "MarshalValueWithContext error: %v", err)
compareMarshalValueResults(t, tc, valueType, valueBytes)
})
}
})
}
func compareMarshalValueResults(t *testing.T, tc marshalValueTestCase, gotType bsontype.Type, gotBytes []byte) {
t.Helper()
expectedValue := RawValue{Type: tc.expectedType, Value: tc.expectedBytes}
gotValue := RawValue{Type: gotType, Value: gotBytes}
assert.Equal(t, expectedValue, gotValue, "value mismatch; expected %s, got %s", expectedValue, gotValue)
}

View File

@ -0,0 +1,29 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"go.mongodb.org/mongo-driver/bson/bsoncodec"
)
type marshalingTestCase struct {
name string
reg *bsoncodec.Registry
val interface{}
want []byte
}
var marshalingTestCases = []marshalingTestCase{
{
"small struct",
nil,
struct {
Foo bool
}{Foo: true},
docToBytes(D{{"foo", true}}),
},
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
// 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 mgocompat provides Registry, a BSON registry compatible with globalsign/mgo's BSON,
// with some remaining differences. It also provides RegistryRespectNilValues for compatibility
// with mgo's BSON with RespectNilValues set to true. A registry can be configured on a
// mongo.Client with the SetRegistry option. See the bsoncodec docs for more details on registries.
//
// Registry supports Getter and Setter equivalents by registering hooks. Note that if a value
// matches the hook for bsoncodec.Marshaler, bsoncodec.ValueMarshaler, or bsoncodec.Proxy, that
// hook will take priority over the Getter hook. The same is true for the hooks for
// bsoncodec.Unmarshaler and bsoncodec.ValueUnmarshaler and the Setter hook.
//
// The functional differences between Registry and globalsign/mgo's BSON library are:
//
// 1) Registry errors instead of silently skipping mismatched types when decoding.
//
// 2) Registry does not have special handling for marshaling array ops ("$in", "$nin", "$all").
//
// The driver uses different types than mgo's bson. The differences are:
//
// 1. The driver's bson.RawValue is equivalent to mgo's bson.Raw, but uses Value instead of Data and uses Type,
// which is a bsontype.Type object that wraps a byte, instead of bson.Raw's Kind, a byte.
//
// 2. The driver uses primitive.ObjectID, which is a [12]byte instead of mgo's
// bson.ObjectId, a string. Due to this, the zero value marshals and unmarshals differently
// for Extended JSON, with the driver marshaling as {"ID":"000000000000000000000000"} and
// mgo as {"Id":""}. The driver can unmarshal {"ID":""} to a primitive.ObjectID.
//
// 3. The driver's primitive.Symbol is equivalent to mgo's bson.Symbol.
//
// 4. The driver uses primitive.Timestamp instead of mgo's bson.MongoTimestamp. While
// MongoTimestamp is an int64, primitive.Timestamp stores the time and counter as two separate
// uint32 values, T and I respectively.
//
// 5. The driver uses primitive.MinKey and primitive.MaxKey, which are struct{}, instead
// of mgo's bson.MinKey and bson.MaxKey, which are int64.
//
// 6. The driver's primitive.Undefined is equivalent to mgo's bson.Undefined.
//
// 7. The driver's primitive.Binary is equivalent to mgo's bson.Binary, with variables named Subtype
// and Data instead of Kind and Data.
//
// 8. The driver's primitive.Regex is equivalent to mgo's bson.RegEx.
//
// 9. The driver's primitive.JavaScript is equivalent to mgo's bson.JavaScript with no
// scope and primitive.CodeWithScope is equivalent to mgo's bson.JavaScript with scope.
//
// 10. The driver's primitive.DBPointer is equivalent to mgo's bson.DBPointer, with variables
// named DB and Pointer instead of Namespace and Id.
//
// 11. When implementing the Setter interface, mgocompat.ErrSetZero is equivalent to mgo's
// bson.ErrSetZero.
package mgocompat

View File

@ -0,0 +1,114 @@
// 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 mgocompat
import (
"errors"
"reflect"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonoptions"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
var (
// ErrSetZero may be returned from a SetBSON method to have the value set to its respective zero value.
ErrSetZero = errors.New("set to zero")
tInt = reflect.TypeOf(int(0))
tTime = reflect.TypeOf(time.Time{})
tM = reflect.TypeOf(bson.M{})
tInterfaceSlice = reflect.TypeOf([]interface{}{})
tByteSlice = reflect.TypeOf([]byte{})
tEmpty = reflect.TypeOf((*interface{})(nil)).Elem()
tGetter = reflect.TypeOf((*Getter)(nil)).Elem()
tSetter = reflect.TypeOf((*Setter)(nil)).Elem()
)
// Registry is the mgo compatible bsoncodec.Registry. It contains the default and
// primitive codecs with mgo compatible options.
var Registry = NewRegistryBuilder().Build()
// RegistryRespectNilValues is the bsoncodec.Registry compatible with mgo withSetRespectNilValues set to true.
var RegistryRespectNilValues = NewRespectNilValuesRegistryBuilder().Build()
// NewRegistryBuilder creates a new bsoncodec.RegistryBuilder configured with the default encoders and
// decoders from the bsoncodec.DefaultValueEncoders and bsoncodec.DefaultValueDecoders types and the
// PrimitiveCodecs type in this package.
func NewRegistryBuilder() *bsoncodec.RegistryBuilder {
rb := bsoncodec.NewRegistryBuilder()
bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
bson.PrimitiveCodecs{}.RegisterPrimitiveCodecs(rb)
structcodec, _ := bsoncodec.NewStructCodec(bsoncodec.DefaultStructTagParser,
bsonoptions.StructCodec().
SetDecodeZeroStruct(true).
SetEncodeOmitDefaultStruct(true).
SetOverwriteDuplicatedInlinedFields(false).
SetAllowUnexportedFields(true))
emptyInterCodec := bsoncodec.NewEmptyInterfaceCodec(
bsonoptions.EmptyInterfaceCodec().
SetDecodeBinaryAsSlice(true))
mapCodec := bsoncodec.NewMapCodec(
bsonoptions.MapCodec().
SetDecodeZerosMap(true).
SetEncodeNilAsEmpty(true).
SetEncodeKeysWithStringer(true))
uintcodec := bsoncodec.NewUIntCodec(bsonoptions.UIntCodec().SetEncodeToMinSize(true))
rb.RegisterTypeDecoder(tEmpty, emptyInterCodec).
RegisterDefaultDecoder(reflect.String, bsoncodec.NewStringCodec(bsonoptions.StringCodec().SetDecodeObjectIDAsHex(false))).
RegisterDefaultDecoder(reflect.Struct, structcodec).
RegisterDefaultDecoder(reflect.Map, mapCodec).
RegisterTypeEncoder(tByteSlice, bsoncodec.NewByteSliceCodec(bsonoptions.ByteSliceCodec().SetEncodeNilAsEmpty(true))).
RegisterDefaultEncoder(reflect.Struct, structcodec).
RegisterDefaultEncoder(reflect.Slice, bsoncodec.NewSliceCodec(bsonoptions.SliceCodec().SetEncodeNilAsEmpty(true))).
RegisterDefaultEncoder(reflect.Map, mapCodec).
RegisterDefaultEncoder(reflect.Uint, uintcodec).
RegisterDefaultEncoder(reflect.Uint8, uintcodec).
RegisterDefaultEncoder(reflect.Uint16, uintcodec).
RegisterDefaultEncoder(reflect.Uint32, uintcodec).
RegisterDefaultEncoder(reflect.Uint64, uintcodec).
RegisterTypeMapEntry(bsontype.Int32, tInt).
RegisterTypeMapEntry(bsontype.DateTime, tTime).
RegisterTypeMapEntry(bsontype.Array, tInterfaceSlice).
RegisterTypeMapEntry(bsontype.Type(0), tM).
RegisterTypeMapEntry(bsontype.EmbeddedDocument, tM).
RegisterHookEncoder(tGetter, bsoncodec.ValueEncoderFunc(GetterEncodeValue)).
RegisterHookDecoder(tSetter, bsoncodec.ValueDecoderFunc(SetterDecodeValue))
return rb
}
// NewRespectNilValuesRegistryBuilder creates a new bsoncodec.RegistryBuilder configured to behave like mgo/bson
// with RespectNilValues set to true.
func NewRespectNilValuesRegistryBuilder() *bsoncodec.RegistryBuilder {
rb := NewRegistryBuilder()
structcodec, _ := bsoncodec.NewStructCodec(bsoncodec.DefaultStructTagParser,
bsonoptions.StructCodec().
SetDecodeZeroStruct(true).
SetEncodeOmitDefaultStruct(true).
SetOverwriteDuplicatedInlinedFields(false).
SetAllowUnexportedFields(true))
mapCodec := bsoncodec.NewMapCodec(
bsonoptions.MapCodec().
SetDecodeZerosMap(true).
SetEncodeNilAsEmpty(false))
rb.RegisterDefaultDecoder(reflect.Struct, structcodec).
RegisterDefaultDecoder(reflect.Map, mapCodec).
RegisterTypeEncoder(tByteSlice, bsoncodec.NewByteSliceCodec(bsonoptions.ByteSliceCodec().SetEncodeNilAsEmpty(false))).
RegisterDefaultEncoder(reflect.Struct, structcodec).
RegisterDefaultEncoder(reflect.Slice, bsoncodec.NewSliceCodec(bsonoptions.SliceCodec().SetEncodeNilAsEmpty(false))).
RegisterDefaultEncoder(reflect.Map, mapCodec)
return rb
}

View File

@ -0,0 +1,127 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package mgocompat
import (
"reflect"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
)
// Setter interface: a value implementing the bson.Setter interface will receive the BSON
// value via the SetBSON method during unmarshaling, and the object
// itself will not be changed as usual.
//
// If setting the value works, the method should return nil or alternatively
// mgocompat.ErrSetZero to set the respective field to its zero value (nil for
// pointer types). If SetBSON returns a non-nil error, the unmarshalling
// procedure will stop and error out with the provided value.
//
// This interface is generally useful in pointer receivers, since the method
// will want to change the receiver. A type field that implements the Setter
// interface doesn't have to be a pointer, though.
//
// For example:
//
// type MyString string
//
// func (s *MyString) SetBSON(raw bson.RawValue) error {
// return raw.Unmarshal(s)
// }
type Setter interface {
SetBSON(raw bson.RawValue) error
}
// Getter interface: a value implementing the bson.Getter interface will have its GetBSON
// method called when the given value has to be marshalled, and the result
// of this method will be marshaled in place of the actual object.
//
// If GetBSON returns return a non-nil error, the marshalling procedure
// will stop and error out with the provided value.
type Getter interface {
GetBSON() (interface{}, error)
}
// SetterDecodeValue is the ValueDecoderFunc for Setter types.
func SetterDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.IsValid() || (!val.Type().Implements(tSetter) && !reflect.PtrTo(val.Type()).Implements(tSetter)) {
return bsoncodec.ValueDecoderError{Name: "SetterDecodeValue", Types: []reflect.Type{tSetter}, Received: val}
}
if val.Kind() == reflect.Ptr && val.IsNil() {
if !val.CanSet() {
return bsoncodec.ValueDecoderError{Name: "SetterDecodeValue", Types: []reflect.Type{tSetter}, Received: val}
}
val.Set(reflect.New(val.Type().Elem()))
}
if !val.Type().Implements(tSetter) {
if !val.CanAddr() {
return bsoncodec.ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tSetter}, Received: val}
}
val = val.Addr() // If the type doesn't implement the interface, a pointer to it must.
}
t, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
if err != nil {
return err
}
fn := val.Convert(tSetter).MethodByName("SetBSON")
errVal := fn.Call([]reflect.Value{reflect.ValueOf(bson.RawValue{Type: t, Value: src})})[0]
if !errVal.IsNil() {
err = errVal.Interface().(error)
if err == ErrSetZero {
val.Set(reflect.Zero(val.Type()))
return nil
}
return err
}
return nil
}
// GetterEncodeValue is the ValueEncoderFunc for Getter types.
func GetterEncodeValue(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
// Either val or a pointer to val must implement Getter
switch {
case !val.IsValid():
return bsoncodec.ValueEncoderError{Name: "GetterEncodeValue", Types: []reflect.Type{tGetter}, Received: val}
case val.Type().Implements(tGetter):
// If Getter is implemented on a concrete type, make sure that val isn't a nil pointer
if isImplementationNil(val, tGetter) {
return vw.WriteNull()
}
case reflect.PtrTo(val.Type()).Implements(tGetter) && val.CanAddr():
val = val.Addr()
default:
return bsoncodec.ValueEncoderError{Name: "GetterEncodeValue", Types: []reflect.Type{tGetter}, Received: val}
}
fn := val.Convert(tGetter).MethodByName("GetBSON")
returns := fn.Call(nil)
if !returns[1].IsNil() {
return returns[1].Interface().(error)
}
intermediate := returns[0]
encoder, err := ec.Registry.LookupEncoder(intermediate.Type())
if err != nil {
return err
}
return encoder.EncodeValue(ec, vw, intermediate)
}
// isImplementationNil returns if val is a nil pointer and inter is implemented on a concrete type
func isImplementationNil(val reflect.Value, inter reflect.Type) bool {
vt := val.Type()
for vt.Kind() == reflect.Ptr {
vt = vt.Elem()
}
return vt.Implements(inter) && val.Kind() == reflect.Ptr && val.IsNil()
}

View File

@ -0,0 +1,437 @@
// 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 gopkg.in/mgo.v2/bson by Gustavo Niemeyer
// See THIRD-PARTY-NOTICES for original license terms.
package primitive
import (
"encoding/json"
"errors"
"fmt"
"math/big"
"regexp"
"strconv"
"strings"
)
// These constants are the maximum and minimum values for the exponent field in a decimal128 value.
const (
MaxDecimal128Exp = 6111
MinDecimal128Exp = -6176
)
// These errors are returned when an invalid value is parsed as a big.Int.
var (
ErrParseNaN = errors.New("cannot parse NaN as a *big.Int")
ErrParseInf = errors.New("cannot parse Infinity as a *big.Int")
ErrParseNegInf = errors.New("cannot parse -Infinity as a *big.Int")
)
// Decimal128 holds decimal128 BSON values.
type Decimal128 struct {
h, l uint64
}
// NewDecimal128 creates a Decimal128 using the provide high and low uint64s.
func NewDecimal128(h, l uint64) Decimal128 {
return Decimal128{h: h, l: l}
}
// GetBytes returns the underlying bytes of the BSON decimal value as two uint64 values. The first
// contains the most first 8 bytes of the value and the second contains the latter.
func (d Decimal128) GetBytes() (uint64, uint64) {
return d.h, d.l
}
// String returns a string representation of the decimal value.
func (d Decimal128) String() string {
var posSign int // positive sign
var exp int // exponent
var high, low uint64 // significand high/low
if d.h>>63&1 == 0 {
posSign = 1
}
switch d.h >> 58 & (1<<5 - 1) {
case 0x1F:
return "NaN"
case 0x1E:
return "-Infinity"[posSign:]
}
low = d.l
if d.h>>61&3 == 3 {
// Bits: 1*sign 2*ignored 14*exponent 111*significand.
// Implicit 0b100 prefix in significand.
exp = int(d.h >> 47 & (1<<14 - 1))
//high = 4<<47 | d.h&(1<<47-1)
// Spec says all of these values are out of range.
high, low = 0, 0
} else {
// Bits: 1*sign 14*exponent 113*significand
exp = int(d.h >> 49 & (1<<14 - 1))
high = d.h & (1<<49 - 1)
}
exp += MinDecimal128Exp
// Would be handled by the logic below, but that's trivial and common.
if high == 0 && low == 0 && exp == 0 {
return "-0"[posSign:]
}
var repr [48]byte // Loop 5 times over 9 digits plus dot, negative sign, and leading zero.
var last = len(repr)
var i = len(repr)
var dot = len(repr) + exp
var rem uint32
Loop:
for d9 := 0; d9 < 5; d9++ {
high, low, rem = divmod(high, low, 1e9)
for d1 := 0; d1 < 9; d1++ {
// Handle "-0.0", "0.00123400", "-1.00E-6", "1.050E+3", etc.
if i < len(repr) && (dot == i || low == 0 && high == 0 && rem > 0 && rem < 10 && (dot < i-6 || exp > 0)) {
exp += len(repr) - i
i--
repr[i] = '.'
last = i - 1
dot = len(repr) // Unmark.
}
c := '0' + byte(rem%10)
rem /= 10
i--
repr[i] = c
// Handle "0E+3", "1E+3", etc.
if low == 0 && high == 0 && rem == 0 && i == len(repr)-1 && (dot < i-5 || exp > 0) {
last = i
break Loop
}
if c != '0' {
last = i
}
// Break early. Works without it, but why.
if dot > i && low == 0 && high == 0 && rem == 0 {
break Loop
}
}
}
repr[last-1] = '-'
last--
if exp > 0 {
return string(repr[last+posSign:]) + "E+" + strconv.Itoa(exp)
}
if exp < 0 {
return string(repr[last+posSign:]) + "E" + strconv.Itoa(exp)
}
return string(repr[last+posSign:])
}
// BigInt returns significand as big.Int and exponent, bi * 10 ^ exp.
func (d Decimal128) BigInt() (*big.Int, int, error) {
high, low := d.GetBytes()
posSign := high>>63&1 == 0 // positive sign
switch high >> 58 & (1<<5 - 1) {
case 0x1F:
return nil, 0, ErrParseNaN
case 0x1E:
if posSign {
return nil, 0, ErrParseInf
}
return nil, 0, ErrParseNegInf
}
var exp int
if high>>61&3 == 3 {
// Bits: 1*sign 2*ignored 14*exponent 111*significand.
// Implicit 0b100 prefix in significand.
exp = int(high >> 47 & (1<<14 - 1))
//high = 4<<47 | d.h&(1<<47-1)
// Spec says all of these values are out of range.
high, low = 0, 0
} else {
// Bits: 1*sign 14*exponent 113*significand
exp = int(high >> 49 & (1<<14 - 1))
high = high & (1<<49 - 1)
}
exp += MinDecimal128Exp
// Would be handled by the logic below, but that's trivial and common.
if high == 0 && low == 0 && exp == 0 {
if posSign {
return new(big.Int), 0, nil
}
return new(big.Int), 0, nil
}
bi := big.NewInt(0)
const host32bit = ^uint(0)>>32 == 0
if host32bit {
bi.SetBits([]big.Word{big.Word(low), big.Word(low >> 32), big.Word(high), big.Word(high >> 32)})
} else {
bi.SetBits([]big.Word{big.Word(low), big.Word(high)})
}
if !posSign {
return bi.Neg(bi), exp, nil
}
return bi, exp, nil
}
// IsNaN returns whether d is NaN.
func (d Decimal128) IsNaN() bool {
return d.h>>58&(1<<5-1) == 0x1F
}
// IsInf returns:
//
// +1 d == Infinity
// 0 other case
// -1 d == -Infinity
func (d Decimal128) IsInf() int {
if d.h>>58&(1<<5-1) != 0x1E {
return 0
}
if d.h>>63&1 == 0 {
return 1
}
return -1
}
// IsZero returns true if d is the empty Decimal128.
func (d Decimal128) IsZero() bool {
return d.h == 0 && d.l == 0
}
// MarshalJSON returns Decimal128 as a string.
func (d Decimal128) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
// UnmarshalJSON creates a primitive.Decimal128 from a JSON string, an extended JSON $numberDecimal value, or the string
// "null". If b is a JSON string or extended JSON value, d will have the value of that string, and if b is "null", d will
// be unchanged.
func (d *Decimal128) UnmarshalJSON(b []byte) error {
// Ignore "null" to keep parity with the standard library. Decoding a JSON null into a non-pointer Decimal128 field
// will leave the field unchanged. For pointer values, encoding/json will set the pointer to nil and will not
// enter the UnmarshalJSON hook.
if string(b) == "null" {
return nil
}
var res interface{}
err := json.Unmarshal(b, &res)
if err != nil {
return err
}
str, ok := res.(string)
// Extended JSON
if !ok {
m, ok := res.(map[string]interface{})
if !ok {
return errors.New("not an extended JSON Decimal128: expected document")
}
d128, ok := m["$numberDecimal"]
if !ok {
return errors.New("not an extended JSON Decimal128: expected key $numberDecimal")
}
str, ok = d128.(string)
if !ok {
return errors.New("not an extended JSON Decimal128: expected decimal to be string")
}
}
*d, err = ParseDecimal128(str)
return err
}
func divmod(h, l uint64, div uint32) (qh, ql uint64, rem uint32) {
div64 := uint64(div)
a := h >> 32
aq := a / div64
ar := a % div64
b := ar<<32 + h&(1<<32-1)
bq := b / div64
br := b % div64
c := br<<32 + l>>32
cq := c / div64
cr := c % div64
d := cr<<32 + l&(1<<32-1)
dq := d / div64
dr := d % div64
return (aq<<32 | bq), (cq<<32 | dq), uint32(dr)
}
var dNaN = Decimal128{0x1F << 58, 0}
var dPosInf = Decimal128{0x1E << 58, 0}
var dNegInf = Decimal128{0x3E << 58, 0}
func dErr(s string) (Decimal128, error) {
return dNaN, fmt.Errorf("cannot parse %q as a decimal128", s)
}
// match scientific notation number, example -10.15e-18
var normalNumber = regexp.MustCompile(`^(?P<int>[-+]?\d*)?(?:\.(?P<dec>\d*))?(?:[Ee](?P<exp>[-+]?\d+))?$`)
// ParseDecimal128 takes the given string and attempts to parse it into a valid
// Decimal128 value.
func ParseDecimal128(s string) (Decimal128, error) {
if s == "" {
return dErr(s)
}
matches := normalNumber.FindStringSubmatch(s)
if len(matches) == 0 {
orig := s
neg := s[0] == '-'
if neg || s[0] == '+' {
s = s[1:]
}
if s == "NaN" || s == "nan" || strings.EqualFold(s, "nan") {
return dNaN, nil
}
if s == "Inf" || s == "inf" || strings.EqualFold(s, "inf") || strings.EqualFold(s, "infinity") {
if neg {
return dNegInf, nil
}
return dPosInf, nil
}
return dErr(orig)
}
intPart := matches[1]
decPart := matches[2]
expPart := matches[3]
var err error
exp := 0
if expPart != "" {
exp, err = strconv.Atoi(expPart)
if err != nil {
return dErr(s)
}
}
if decPart != "" {
exp -= len(decPart)
}
if len(strings.Trim(intPart+decPart, "-0")) > 35 {
return dErr(s)
}
// Parse the significand (i.e. the non-exponent part) as a big.Int.
bi, ok := new(big.Int).SetString(intPart+decPart, 10)
if !ok {
return dErr(s)
}
d, ok := ParseDecimal128FromBigInt(bi, exp)
if !ok {
return dErr(s)
}
if bi.Sign() == 0 && s[0] == '-' {
d.h |= 1 << 63
}
return d, nil
}
var (
ten = big.NewInt(10)
zero = new(big.Int)
maxS, _ = new(big.Int).SetString("9999999999999999999999999999999999", 10)
)
// ParseDecimal128FromBigInt attempts to parse the given significand and exponent into a valid Decimal128 value.
func ParseDecimal128FromBigInt(bi *big.Int, exp int) (Decimal128, bool) {
//copy
bi = new(big.Int).Set(bi)
q := new(big.Int)
r := new(big.Int)
// If the significand is zero, the logical value will always be zero, independent of the
// exponent. However, the loops for handling out-of-range exponent values below may be extremely
// slow for zero values because the significand never changes. Limit the exponent value to the
// supported range here to prevent entering the loops below.
if bi.Cmp(zero) == 0 {
if exp > MaxDecimal128Exp {
exp = MaxDecimal128Exp
}
if exp < MinDecimal128Exp {
exp = MinDecimal128Exp
}
}
for bigIntCmpAbs(bi, maxS) == 1 {
bi, _ = q.QuoRem(bi, ten, r)
if r.Cmp(zero) != 0 {
return Decimal128{}, false
}
exp++
if exp > MaxDecimal128Exp {
return Decimal128{}, false
}
}
for exp < MinDecimal128Exp {
// Subnormal.
bi, _ = q.QuoRem(bi, ten, r)
if r.Cmp(zero) != 0 {
return Decimal128{}, false
}
exp++
}
for exp > MaxDecimal128Exp {
// Clamped.
bi.Mul(bi, ten)
if bigIntCmpAbs(bi, maxS) == 1 {
return Decimal128{}, false
}
exp--
}
b := bi.Bytes()
var h, l uint64
for i := 0; i < len(b); i++ {
if i < len(b)-8 {
h = h<<8 | uint64(b[i])
continue
}
l = l<<8 | uint64(b[i])
}
h |= uint64(exp-MinDecimal128Exp) & uint64(1<<14-1) << 49
if bi.Sign() == -1 {
h |= 1 << 63
}
return Decimal128{h: h, l: l}, true
}
// bigIntCmpAbs computes big.Int.Cmp(absoluteValue(x), absoluteValue(y)).
func bigIntCmpAbs(x, y *big.Int) int {
xAbs := bigIntAbsValue(x)
yAbs := bigIntAbsValue(y)
return xAbs.Cmp(yAbs)
}
// bigIntAbsValue returns a big.Int containing the absolute value of b.
// If b is already a non-negative number, it is returned without any changes or copies.
func bigIntAbsValue(b *big.Int) *big.Int {
if b.Sign() >= 0 {
return b // already positive
}
return new(big.Int).Abs(b)
}

View File

@ -0,0 +1,236 @@
// 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 primitive
import (
"encoding/json"
"fmt"
"math/big"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type bigIntTestCase struct {
s string
h uint64
l uint64
bi *big.Int
exp int
remark string
}
func parseBigInt(s string) *big.Int {
bi, _ := new(big.Int).SetString(s, 10)
return bi
}
var (
one = big.NewInt(1)
biMaxS = new(big.Int).SetBytes([]byte{0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})
biNMaxS = new(big.Int).Neg(biMaxS)
biOverflow = new(big.Int).Add(biMaxS, one)
biNOverflow = new(big.Int).Neg(biOverflow)
bi12345 = parseBigInt("12345")
biN12345 = parseBigInt("-12345")
bi9_14 = parseBigInt("90123456789012")
biN9_14 = parseBigInt("-90123456789012")
bi9_34 = parseBigInt("9999999999999999999999999999999999")
biN9_34 = parseBigInt("-9999999999999999999999999999999999")
)
var bigIntTestCases = []bigIntTestCase{
{s: "12345", h: 0x3040000000000000, l: 12345, bi: bi12345},
{s: "-12345", h: 0xB040000000000000, l: 12345, bi: biN12345},
{s: "90123456.789012", h: 0x3034000000000000, l: 90123456789012, bi: bi9_14, exp: -6},
{s: "-90123456.789012", h: 0xB034000000000000, l: 90123456789012, bi: biN9_14, exp: -6},
{s: "9.0123456789012E+22", h: 0x3052000000000000, l: 90123456789012, bi: bi9_14, exp: 9},
{s: "-9.0123456789012E+22", h: 0xB052000000000000, l: 90123456789012, bi: biN9_14, exp: 9},
{s: "9.0123456789012E-8", h: 0x3016000000000000, l: 90123456789012, bi: bi9_14, exp: -21},
{s: "-9.0123456789012E-8", h: 0xB016000000000000, l: 90123456789012, bi: biN9_14, exp: -21},
{s: "9999999999999999999999999999999999", h: 3477321013416265664, l: 4003012203950112767, bi: bi9_34},
{s: "-9999999999999999999999999999999999", h: 12700693050271041472, l: 4003012203950112767, bi: biN9_34},
{s: "0.9999999999999999999999999999999999", h: 3458180714999941056, l: 4003012203950112767, bi: bi9_34, exp: -34},
{s: "-0.9999999999999999999999999999999999", h: 12681552751854716864, l: 4003012203950112767, bi: biN9_34, exp: -34},
{s: "99999999999999999.99999999999999999", h: 3467750864208103360, l: 4003012203950112767, bi: bi9_34, exp: -17},
{s: "-99999999999999999.99999999999999999", h: 12691122901062879168, l: 4003012203950112767, bi: biN9_34, exp: -17},
{s: "9.999999999999999999999999999999999E+35", h: 3478446913323108288, l: 4003012203950112767, bi: bi9_34, exp: 2},
{s: "-9.999999999999999999999999999999999E+35", h: 12701818950177884096, l: 4003012203950112767, bi: biN9_34, exp: 2},
{s: "9.999999999999999999999999999999999E+40", h: 3481261663090214848, l: 4003012203950112767, bi: bi9_34, exp: 7},
{s: "-9.999999999999999999999999999999999E+40", h: 12704633699944990656, l: 4003012203950112767, bi: biN9_34, exp: 7},
{s: "99999999999999999999999999999.99999", h: 3474506263649159104, l: 4003012203950112767, bi: bi9_34, exp: -5},
{s: "-99999999999999999999999999999.99999", h: 12697878300503934912, l: 4003012203950112767, bi: biN9_34, exp: -5},
{s: "1.038459371706965525706099265844019E-6143", remark: "subnormal", h: 0x333333333333, l: 0x3333333333333333, bi: parseBigInt("10384593717069655257060992658440190"), exp: MinDecimal128Exp - 1},
{s: "-1.038459371706965525706099265844019E-6143", remark: "subnormal", h: 0x8000333333333333, l: 0x3333333333333333, bi: parseBigInt("-10384593717069655257060992658440190"), exp: MinDecimal128Exp - 1},
{s: "rounding overflow 1", remark: "overflow", bi: parseBigInt("103845937170696552570609926584401910"), exp: MaxDecimal128Exp},
{s: "rounding overflow 2", remark: "overflow", bi: parseBigInt("103845937170696552570609926584401910"), exp: MaxDecimal128Exp},
{s: "subnormal overflow 1", remark: "overflow", bi: biMaxS, exp: MinDecimal128Exp - 1},
{s: "subnormal overflow 2", remark: "overflow", bi: biNMaxS, exp: MinDecimal128Exp - 1},
{s: "clamped overflow 1", remark: "overflow", bi: biMaxS, exp: MaxDecimal128Exp + 1},
{s: "clamped overflow 2", remark: "overflow", bi: biNMaxS, exp: MaxDecimal128Exp + 1},
{s: "biMaxS+1 overflow", remark: "overflow", bi: biOverflow, exp: MaxDecimal128Exp},
{s: "biNMaxS-1 overflow", remark: "overflow", bi: biNOverflow, exp: MaxDecimal128Exp},
{s: "NaN", h: 0x7c00000000000000, l: 0, remark: "NaN"},
{s: "Infinity", h: 0x7800000000000000, l: 0, remark: "Infinity"},
{s: "-Infinity", h: 0xf800000000000000, l: 0, remark: "-Infinity"},
}
func TestDecimal128_BigInt(t *testing.T) {
for _, c := range bigIntTestCases {
t.Run(c.s, func(t *testing.T) {
switch c.remark {
case "NaN", "Infinity", "-Infinity":
d128 := NewDecimal128(c.h, c.l)
_, _, err := d128.BigInt()
require.Error(t, err, "case %s", c.s)
case "":
d128 := NewDecimal128(c.h, c.l)
bi, e, err := d128.BigInt()
require.NoError(t, err, "case %s", c.s)
require.Equal(t, 0, c.bi.Cmp(bi), "case %s e:%s a:%s", c.s, c.bi.String(), bi.String())
require.Equal(t, c.exp, e, "case %s", c.s, d128.String())
}
})
}
}
func TestParseDecimal128FromBigInt(t *testing.T) {
for _, c := range bigIntTestCases {
switch c.remark {
case "overflow":
d128, ok := ParseDecimal128FromBigInt(c.bi, c.exp)
require.Equal(t, false, ok, "case %s %s", c.s, d128.String(), c.remark)
case "", "rounding", "subnormal", "clamped":
d128, ok := ParseDecimal128FromBigInt(c.bi, c.exp)
require.Equal(t, true, ok, "case %s", c.s)
require.Equal(t, c.s, d128.String(), "case %s", c.s)
require.Equal(t, c.h, d128.h, "case %s", c.s, d128.l)
require.Equal(t, c.l, d128.l, "case %s", c.s, d128.h)
}
}
}
func TestParseDecimal128(t *testing.T) {
cases := make([]bigIntTestCase, 0, len(bigIntTestCases))
cases = append(cases, bigIntTestCases...)
cases = append(cases,
bigIntTestCase{s: "-0001231.453454000000565600000000E-21", h: 0xafe6000003faa269, l: 0x81cfeceaabdb1800},
bigIntTestCase{s: "12345E+21", h: 0x306a000000000000, l: 12345},
bigIntTestCase{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"},
bigIntTestCase{s: ".125e1", h: 0x303c000000000000, l: 125},
bigIntTestCase{s: ".125", h: 0x303a000000000000, l: 125},
// Test that parsing negative zero returns negative zero with a zero exponent.
bigIntTestCase{s: "-0", h: 0xb040000000000000, l: 0},
// Test that parsing negative zero with an in-range exponent returns negative zero and
// preserves the specified exponent value.
bigIntTestCase{s: "-0E999", h: 0xb80e000000000000, l: 0},
// Test that parsing zero with an out-of-range positive exponent returns zero with the
// maximum positive exponent (i.e. 0e+6111).
bigIntTestCase{s: "0E2000000000000", h: 0x5ffe000000000000, l: 0},
// Test that parsing zero with an out-of-range negative exponent returns zero with the
// minimum negative exponent (i.e. 0e-6176).
bigIntTestCase{s: "-0E2000000000000", h: 0xdffe000000000000, l: 0},
bigIntTestCase{s: "", remark: "parse fail"})
for _, c := range cases {
t.Run(c.s, func(t *testing.T) {
switch c.remark {
case "overflow", "parse fail":
_, err := ParseDecimal128(c.s)
assert.Error(t, err, "ParseDecimal128(%q) should return an error", c.s)
default:
got, err := ParseDecimal128(c.s)
require.NoError(t, err, "ParseDecimal128(%q) error", c.s)
want := Decimal128{h: c.h, l: c.l}
// Decimal128 doesn't implement an equality function, so compare the expected
// low/high uint64 values directly. Also print the string representation of each
// number to make debugging failures easier.
assert.Equal(t, want, got, "ParseDecimal128(%q) = %s, want %s", c.s, got, want)
}
})
}
}
func TestDecimal128_JSON(t *testing.T) {
t.Run("roundTrip", func(t *testing.T) {
decimal := NewDecimal128(0x3040000000000000, 12345)
bytes, err := json.Marshal(decimal)
assert.Nil(t, err, "json.Marshal error: %v", err)
got := NewDecimal128(0, 0)
err = json.Unmarshal(bytes, &got)
assert.Nil(t, err, "json.Unmarshal error: %v", err)
assert.Equal(t, decimal.h, got.h, "expected h: %v got: %v", decimal.h, got.h)
assert.Equal(t, decimal.l, got.l, "expected l: %v got: %v", decimal.l, got.l)
})
t.Run("unmarshal extendedJSON", func(t *testing.T) {
want := NewDecimal128(0x3040000000000000, 12345)
extJSON := fmt.Sprintf(`{"$numberDecimal": %q}`, want.String())
got := NewDecimal128(0, 0)
err := json.Unmarshal([]byte(extJSON), &got)
assert.Nil(t, err, "json.Unmarshal error: %v", err)
assert.Equal(t, want.h, got.h, "expected h: %v got: %v", want.h, got.h)
assert.Equal(t, want.l, got.l, "expected l: %v got: %v", want.l, got.l)
})
t.Run("unmarshal null", func(t *testing.T) {
want := NewDecimal128(0, 0)
extJSON := `null`
got := NewDecimal128(0, 0)
err := json.Unmarshal([]byte(extJSON), &got)
assert.Nil(t, err, "json.Unmarshal error: %v", err)
assert.Equal(t, want.h, got.h, "expected h: %v got: %v", want.h, got.h)
assert.Equal(t, want.l, got.l, "expected l: %v got: %v", want.l, got.l)
})
t.Run("unmarshal", func(t *testing.T) {
cases := make([]bigIntTestCase, 0, len(bigIntTestCases))
cases = append(cases, bigIntTestCases...)
cases = append(cases,
bigIntTestCase{s: "-0001231.453454000000565600000000E-21", h: 0xafe6000003faa269, l: 0x81cfeceaabdb1800},
bigIntTestCase{s: "12345E+21", h: 0x306a000000000000, l: 12345},
bigIntTestCase{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"},
bigIntTestCase{s: ".125e1", h: 0x303c000000000000, l: 125},
bigIntTestCase{s: ".125", h: 0x303a000000000000, l: 125})
for _, c := range cases {
t.Run(c.s, func(t *testing.T) {
input := fmt.Sprintf(`{"foo": %q}`, c.s)
var got map[string]Decimal128
err := json.Unmarshal([]byte(input), &got)
switch c.remark {
case "overflow", "parse fail":
assert.NotNil(t, err, "expected Unmarshal error, got nil")
default:
assert.Nil(t, err, "Unmarshal error: %v", err)
gotDecimal := got["foo"]
assert.Equal(t, c.h, gotDecimal.h, "expected h: %v got: %v", c.h, gotDecimal.l)
assert.Equal(t, c.l, gotDecimal.l, "expected l: %v got: %v", c.l, gotDecimal.h)
}
})
}
})
}

View File

@ -0,0 +1,206 @@
// 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 gopkg.in/mgo.v2/bson by Gustavo Niemeyer
// See THIRD-PARTY-NOTICES for original license terms.
package primitive
import (
"crypto/rand"
"encoding"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"sync/atomic"
"time"
)
// ErrInvalidHex indicates that a hex string cannot be converted to an ObjectID.
var ErrInvalidHex = errors.New("the provided hex string is not a valid ObjectID")
// ObjectID is the BSON ObjectID type.
type ObjectID [12]byte
// NilObjectID is the zero value for ObjectID.
var NilObjectID ObjectID
var objectIDCounter = readRandomUint32()
var processUnique = processUniqueBytes()
var _ encoding.TextMarshaler = ObjectID{}
var _ encoding.TextUnmarshaler = &ObjectID{}
// NewObjectID generates a new ObjectID.
func NewObjectID() ObjectID {
return NewObjectIDFromTimestamp(time.Now())
}
// NewObjectIDFromTimestamp generates a new ObjectID based on the given time.
func NewObjectIDFromTimestamp(timestamp time.Time) ObjectID {
var b [12]byte
binary.BigEndian.PutUint32(b[0:4], uint32(timestamp.Unix()))
copy(b[4:9], processUnique[:])
putUint24(b[9:12], atomic.AddUint32(&objectIDCounter, 1))
return b
}
// Timestamp extracts the time part of the ObjectId.
func (id ObjectID) Timestamp() time.Time {
unixSecs := binary.BigEndian.Uint32(id[0:4])
return time.Unix(int64(unixSecs), 0).UTC()
}
// Hex returns the hex encoding of the ObjectID as a string.
func (id ObjectID) Hex() string {
var buf [24]byte
hex.Encode(buf[:], id[:])
return string(buf[:])
}
func (id ObjectID) String() string {
return fmt.Sprintf("ObjectID(%q)", id.Hex())
}
// IsZero returns true if id is the empty ObjectID.
func (id ObjectID) IsZero() bool {
return id == NilObjectID
}
// ObjectIDFromHex creates a new ObjectID from a hex string. It returns an error if the hex string is not a
// valid ObjectID.
func ObjectIDFromHex(s string) (ObjectID, error) {
if len(s) != 24 {
return NilObjectID, ErrInvalidHex
}
b, err := hex.DecodeString(s)
if err != nil {
return NilObjectID, err
}
var oid [12]byte
copy(oid[:], b)
return oid, nil
}
// IsValidObjectID returns true if the provided hex string represents a valid ObjectID and false if not.
func IsValidObjectID(s string) bool {
_, err := ObjectIDFromHex(s)
return err == nil
}
// MarshalText returns the ObjectID as UTF-8-encoded text. Implementing this allows us to use ObjectID
// as a map key when marshalling JSON. See https://pkg.go.dev/encoding#TextMarshaler
func (id ObjectID) MarshalText() ([]byte, error) {
return []byte(id.Hex()), nil
}
// UnmarshalText populates the byte slice with the ObjectID. Implementing this allows us to use ObjectID
// as a map key when unmarshalling JSON. See https://pkg.go.dev/encoding#TextUnmarshaler
func (id *ObjectID) UnmarshalText(b []byte) error {
oid, err := ObjectIDFromHex(string(b))
if err != nil {
return err
}
*id = oid
return nil
}
// MarshalJSON returns the ObjectID as a string
func (id ObjectID) MarshalJSON() ([]byte, error) {
return json.Marshal(id.Hex())
}
// UnmarshalJSON populates the byte slice with the ObjectID. If the byte slice is 24 bytes long, it
// will be populated with the hex representation of the ObjectID. If the byte slice is twelve bytes
// long, it will be populated with the BSON representation of the ObjectID. This method also accepts empty strings and
// decodes them as NilObjectID. For any other inputs, an error will be returned.
func (id *ObjectID) UnmarshalJSON(b []byte) error {
// Ignore "null" to keep parity with the standard library. Decoding a JSON null into a non-pointer ObjectID field
// will leave the field unchanged. For pointer values, encoding/json will set the pointer to nil and will not
// enter the UnmarshalJSON hook.
if string(b) == "null" {
return nil
}
var err error
switch len(b) {
case 12:
copy(id[:], b)
default:
// Extended JSON
var res interface{}
err := json.Unmarshal(b, &res)
if err != nil {
return err
}
str, ok := res.(string)
if !ok {
m, ok := res.(map[string]interface{})
if !ok {
return errors.New("not an extended JSON ObjectID")
}
oid, ok := m["$oid"]
if !ok {
return errors.New("not an extended JSON ObjectID")
}
str, ok = oid.(string)
if !ok {
return errors.New("not an extended JSON ObjectID")
}
}
// An empty string is not a valid ObjectID, but we treat it as a special value that decodes as NilObjectID.
if len(str) == 0 {
copy(id[:], NilObjectID[:])
return nil
}
if len(str) != 24 {
return fmt.Errorf("cannot unmarshal into an ObjectID, the length must be 24 but it is %d", len(str))
}
_, err = hex.Decode(id[:], []byte(str))
if err != nil {
return err
}
}
return err
}
func processUniqueBytes() [5]byte {
var b [5]byte
_, err := io.ReadFull(rand.Reader, b[:])
if err != nil {
panic(fmt.Errorf("cannot initialize objectid package with crypto.rand.Reader: %v", err))
}
return b
}
func readRandomUint32() uint32 {
var b [4]byte
_, err := io.ReadFull(rand.Reader, b[:])
if err != nil {
panic(fmt.Errorf("cannot initialize objectid package with crypto.rand.Reader: %v", err))
}
return (uint32(b[0]) << 0) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)
}
func putUint24(b []byte, v uint32) {
b[0] = byte(v >> 16)
b[1] = byte(v >> 8)
b[2] = byte(v)
}

View File

@ -0,0 +1,259 @@
// 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 primitive
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)
func TestNew(t *testing.T) {
// Ensure that objectid.NewObjectID() doesn't panic.
NewObjectID()
}
func TestString(t *testing.T) {
id := NewObjectID()
require.Contains(t, id.String(), id.Hex())
}
func BenchmarkHex(b *testing.B) {
id := NewObjectID()
for i := 0; i < b.N; i++ {
id.Hex()
}
}
func TestFromHex_RoundTrip(t *testing.T) {
before := NewObjectID()
after, err := ObjectIDFromHex(before.Hex())
require.NoError(t, err)
require.Equal(t, before, after)
}
func TestFromHex_InvalidHex(t *testing.T) {
_, err := ObjectIDFromHex("this is not a valid hex string!")
require.Error(t, err)
}
func TestFromHex_WrongLength(t *testing.T) {
_, err := ObjectIDFromHex("deadbeef")
require.Equal(t, ErrInvalidHex, err)
}
func TestIsValidObjectID(t *testing.T) {
testCases := []struct {
givenID string
expected bool
}{
{
givenID: "5ef7fdd91c19e3222b41b839",
expected: true,
},
{
givenID: "5ef7fdd91c19e3222b41b83",
expected: false,
},
}
for _, testcase := range testCases {
got := IsValidObjectID(testcase.givenID)
assert.Equal(t, testcase.expected, got, "expected hex string to be valid ObjectID: %v, got %v", testcase.expected, got)
}
}
func TestTimeStamp(t *testing.T) {
testCases := []struct {
Hex string
Expected string
}{
{
"000000001111111111111111",
"1970-01-01 00:00:00 +0000 UTC",
},
{
"7FFFFFFF1111111111111111",
"2038-01-19 03:14:07 +0000 UTC",
},
{
"800000001111111111111111",
"2038-01-19 03:14:08 +0000 UTC",
},
{
"FFFFFFFF1111111111111111",
"2106-02-07 06:28:15 +0000 UTC",
},
}
for _, testcase := range testCases {
id, err := ObjectIDFromHex(testcase.Hex)
require.NoError(t, err)
secs := int64(binary.BigEndian.Uint32(id[0:4]))
timestamp := time.Unix(secs, 0).UTC()
require.Equal(t, testcase.Expected, timestamp.String())
}
}
func TestCreateFromTime(t *testing.T) {
testCases := []struct {
time string
Expected string
}{
{
"1970-01-01T00:00:00.000Z",
"00000000",
},
{
"2038-01-19T03:14:07.000Z",
"7fffffff",
},
{
"2038-01-19T03:14:08.000Z",
"80000000",
},
{
"2106-02-07T06:28:15.000Z",
"ffffffff",
},
}
layout := "2006-01-02T15:04:05.000Z"
for _, testcase := range testCases {
time, err := time.Parse(layout, testcase.time)
require.NoError(t, err)
id := NewObjectIDFromTimestamp(time)
timeStr := hex.EncodeToString(id[0:4])
require.Equal(t, testcase.Expected, timeStr)
}
}
func TestGenerationTime(t *testing.T) {
testCases := []struct {
hex string
Expected string
}{
{
"000000001111111111111111",
"1970-01-01 00:00:00 +0000 UTC",
},
{
"7FFFFFFF1111111111111111",
"2038-01-19 03:14:07 +0000 UTC",
},
{
"800000001111111111111111",
"2038-01-19 03:14:08 +0000 UTC",
},
{
"FFFFFFFF1111111111111111",
"2106-02-07 06:28:15 +0000 UTC",
},
}
for _, testcase := range testCases {
id, err := ObjectIDFromHex(testcase.hex)
require.NoError(t, err)
genTime := id.Timestamp()
require.Equal(t, testcase.Expected, genTime.String())
}
}
func TestCounterOverflow(t *testing.T) {
objectIDCounter = 0xFFFFFFFF
NewObjectID()
require.Equal(t, uint32(0), objectIDCounter)
}
func TestObjectID_MarshalJSONMap(t *testing.T) {
type mapOID struct {
Map map[ObjectID]string
}
oid := NewObjectID()
expectedJSON := []byte(fmt.Sprintf(`{"Map":{%q:"foo"}}`, oid.Hex()))
data := mapOID{
Map: map[ObjectID]string{oid: "foo"},
}
out, err := json.Marshal(&data)
require.NoError(t, err)
require.Equal(t, expectedJSON, out)
}
func TestObjectID_UnmarshalJSONMap(t *testing.T) {
type mapOID struct {
Map map[ObjectID]string
}
oid := NewObjectID()
mapOIDJSON := []byte(fmt.Sprintf(`{"Map":{%q:"foo"}}`, oid.Hex()))
expectedData := mapOID{
Map: map[ObjectID]string{oid: "foo"},
}
data := mapOID{}
err := json.Unmarshal(mapOIDJSON, &data)
require.NoError(t, err)
require.Equal(t, expectedData, data)
}
func TestObjectID_UnmarshalJSON(t *testing.T) {
oid := NewObjectID()
hexJSON := fmt.Sprintf(`{"foo": %q}`, oid.Hex())
extJSON := fmt.Sprintf(`{"foo": {"$oid": %q}}`, oid.Hex())
emptyStringJSON := `{"foo": ""}`
nullJSON := `{"foo": null}`
testCases := []struct {
name string
jsonString string
expected ObjectID
}{
{"hex bytes", hexJSON, oid},
{"extended JSON", extJSON, oid},
{"empty string", emptyStringJSON, NilObjectID},
{"null", nullJSON, NilObjectID},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var got map[string]ObjectID
err := json.Unmarshal([]byte(tc.jsonString), &got)
assert.Nil(t, err, "Unmarshal error: %v", err)
gotOid := got["foo"]
assert.Equal(t, tc.expected, gotOid, "expected ObjectID %s, got %s", tc.expected, gotOid)
})
}
}
func TestObjectID_MarshalText(t *testing.T) {
oid := ObjectID{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB}
b, err := oid.MarshalText()
assert.Nil(t, err, "MarshalText error: %v", err)
want := "000102030405060708090a0b"
got := string(b)
assert.Equal(t, want, got, "want %v, got %v", want, got)
}
func TestObjectID_UnmarshalText(t *testing.T) {
var oid ObjectID
err := oid.UnmarshalText([]byte("000102030405060708090a0b"))
assert.Nil(t, err, "UnmarshalText error: %v", err)
want := ObjectID{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB}
assert.Equal(t, want, oid, "want %v, got %v", want, oid)
}

View File

@ -0,0 +1,217 @@
// 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 primitive contains types similar to Go primitives for BSON types that do not have direct
// Go primitive representations.
package primitive // import "go.mongodb.org/mongo-driver/bson/primitive"
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
// Binary represents a BSON binary value.
type Binary struct {
Subtype byte
Data []byte
}
// Equal compares bp to bp2 and returns true if they are equal.
func (bp Binary) Equal(bp2 Binary) bool {
if bp.Subtype != bp2.Subtype {
return false
}
return bytes.Equal(bp.Data, bp2.Data)
}
// IsZero returns if bp is the empty Binary.
func (bp Binary) IsZero() bool {
return bp.Subtype == 0 && len(bp.Data) == 0
}
// Undefined represents the BSON undefined value type.
type Undefined struct{}
// DateTime represents the BSON datetime value.
type DateTime int64
var _ json.Marshaler = DateTime(0)
var _ json.Unmarshaler = (*DateTime)(nil)
// MarshalJSON marshal to time type.
func (d DateTime) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Time())
}
// UnmarshalJSON creates a primitive.DateTime from a JSON string.
func (d *DateTime) UnmarshalJSON(data []byte) error {
// Ignore "null" to keep parity with the time.Time type and the standard library. Decoding "null" into a non-pointer
// DateTime field will leave the field unchanged. For pointer values, the encoding/json will set the pointer to nil
// and will not defer to the UnmarshalJSON hook.
if string(data) == "null" {
return nil
}
var tempTime time.Time
if err := json.Unmarshal(data, &tempTime); err != nil {
return err
}
*d = NewDateTimeFromTime(tempTime)
return nil
}
// Time returns the date as a time type.
func (d DateTime) Time() time.Time {
return time.Unix(int64(d)/1000, int64(d)%1000*1000000)
}
// NewDateTimeFromTime creates a new DateTime from a Time.
func NewDateTimeFromTime(t time.Time) DateTime {
return DateTime(t.Unix()*1e3 + int64(t.Nanosecond())/1e6)
}
// Null represents the BSON null value.
type Null struct{}
// Regex represents a BSON regex value.
type Regex struct {
Pattern string
Options string
}
func (rp Regex) String() string {
return fmt.Sprintf(`{"pattern": "%s", "options": "%s"}`, rp.Pattern, rp.Options)
}
// Equal compares rp to rp2 and returns true if they are equal.
func (rp Regex) Equal(rp2 Regex) bool {
return rp.Pattern == rp2.Pattern && rp.Options == rp2.Options
}
// IsZero returns if rp is the empty Regex.
func (rp Regex) IsZero() bool {
return rp.Pattern == "" && rp.Options == ""
}
// DBPointer represents a BSON dbpointer value.
type DBPointer struct {
DB string
Pointer ObjectID
}
func (d DBPointer) String() string {
return fmt.Sprintf(`{"db": "%s", "pointer": "%s"}`, d.DB, d.Pointer)
}
// Equal compares d to d2 and returns true if they are equal.
func (d DBPointer) Equal(d2 DBPointer) bool {
return d == d2
}
// IsZero returns if d is the empty DBPointer.
func (d DBPointer) IsZero() bool {
return d.DB == "" && d.Pointer.IsZero()
}
// JavaScript represents a BSON JavaScript code value.
type JavaScript string
// Symbol represents a BSON symbol value.
type Symbol string
// CodeWithScope represents a BSON JavaScript code with scope value.
type CodeWithScope struct {
Code JavaScript
Scope interface{}
}
func (cws CodeWithScope) String() string {
return fmt.Sprintf(`{"code": "%s", "scope": %v}`, cws.Code, cws.Scope)
}
// Timestamp represents a BSON timestamp value.
type Timestamp struct {
T uint32
I uint32
}
// Equal compares tp to tp2 and returns true if they are equal.
func (tp Timestamp) Equal(tp2 Timestamp) bool {
return tp.T == tp2.T && tp.I == tp2.I
}
// IsZero returns if tp is the zero Timestamp.
func (tp Timestamp) IsZero() bool {
return tp.T == 0 && tp.I == 0
}
// CompareTimestamp returns an integer comparing two Timestamps, where T is compared first, followed by I.
// Returns 0 if tp = tp2, 1 if tp > tp2, -1 if tp < tp2.
func CompareTimestamp(tp, tp2 Timestamp) int {
if tp.Equal(tp2) {
return 0
}
if tp.T > tp2.T {
return 1
}
if tp.T < tp2.T {
return -1
}
// Compare I values because T values are equal
if tp.I > tp2.I {
return 1
}
return -1
}
// MinKey represents the BSON minkey value.
type MinKey struct{}
// MaxKey represents the BSON maxkey value.
type MaxKey struct{}
// D is an ordered representation of a BSON document. This type should be used when the order of the elements matters,
// such as MongoDB command documents. If the order of the elements does not matter, an M should be used instead.
//
// Example usage:
//
// bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
type D []E
// Map creates a map from the elements of the D.
func (d D) Map() M {
m := make(M, len(d))
for _, e := range d {
m[e.Key] = e.Value
}
return m
}
// E represents a BSON element for a D. It is usually used inside a D.
type E struct {
Key string
Value interface{}
}
// M is an unordered representation of a BSON document. This type should be used when the order of the elements does not
// matter. This type is handled as a regular map[string]interface{} when encoding and decoding. Elements will be
// serialized in an undefined, random order. If the order of the elements matters, a D should be used instead.
//
// Example usage:
//
// bson.M{"foo": "bar", "hello": "world", "pi": 3.14159}
type M map[string]interface{}
// An A is an ordered representation of a BSON array.
//
// Example usage:
//
// bson.A{"bar", "world", 3.14159, bson.D{{"qux", 12345}}}
type A []interface{}

View File

@ -0,0 +1,122 @@
// 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 primitive
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)
// The same interface as bsoncodec.Zeroer implemented for tests.
type zeroer interface {
IsZero() bool
}
func TestTimestampCompare(t *testing.T) {
testcases := []struct {
name string
tp Timestamp
tp2 Timestamp
expected int
}{
{"equal", Timestamp{T: 12345, I: 67890}, Timestamp{T: 12345, I: 67890}, 0},
{"T greater than", Timestamp{T: 12345, I: 67890}, Timestamp{T: 2345, I: 67890}, 1},
{"I greater than", Timestamp{T: 12345, I: 67890}, Timestamp{T: 12345, I: 7890}, 1},
{"T less than", Timestamp{T: 12345, I: 67890}, Timestamp{T: 112345, I: 67890}, -1},
{"I less than", Timestamp{T: 12345, I: 67890}, Timestamp{T: 12345, I: 167890}, -1},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
result := CompareTimestamp(tc.tp, tc.tp2)
require.Equal(t, tc.expected, result)
})
}
}
func TestPrimitiveIsZero(t *testing.T) {
testcases := []struct {
name string
zero zeroer
nonzero zeroer
}{
{"binary", Binary{}, Binary{Data: []byte{0x01, 0x02, 0x03}, Subtype: 0xFF}},
{"decimal128", Decimal128{}, NewDecimal128(1, 2)},
{"objectID", ObjectID{}, NewObjectID()},
{"regex", Regex{}, Regex{Pattern: "foo", Options: "bar"}},
{"dbPointer", DBPointer{}, DBPointer{DB: "foobar", Pointer: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}}},
{"timestamp", Timestamp{}, Timestamp{T: 12345, I: 67890}},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
require.True(t, tc.zero.IsZero())
require.False(t, tc.nonzero.IsZero())
})
}
}
func TestRegexCompare(t *testing.T) {
testcases := []struct {
name string
r1 Regex
r2 Regex
eq bool
}{
{"equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo1", Options: "bar1"}, true},
{"not equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo2", Options: "bar2"}, false},
{"not equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo1", Options: "bar2"}, false},
{"not equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo2", Options: "bar1"}, false},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
require.True(t, tc.r1.Equal(tc.r2) == tc.eq)
})
}
}
func TestDateTime(t *testing.T) {
t.Run("json", func(t *testing.T) {
t.Run("round trip", func(t *testing.T) {
original := DateTime(1000)
jsonBytes, err := json.Marshal(original)
assert.Nil(t, err, "Marshal error: %v", err)
var unmarshalled DateTime
err = json.Unmarshal(jsonBytes, &unmarshalled)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, original, unmarshalled, "expected DateTime %v, got %v", original, unmarshalled)
})
t.Run("decode null", func(t *testing.T) {
jsonBytes := []byte("null")
var dt DateTime
err := json.Unmarshal(jsonBytes, &dt)
assert.Nil(t, err, "Unmarshal error: %v", err)
assert.Equal(t, DateTime(0), dt, "expected DateTime value to be 0, got %v", dt)
})
})
t.Run("NewDateTimeFromTime", func(t *testing.T) {
t.Run("range is not limited", func(t *testing.T) {
// If the implementation internally calls time.Time.UnixNano(), the constructor cannot handle times after
// the year 2262.
timeFormat := "2006-01-02T15:04:05.999Z07:00"
timeString := "3001-01-01T00:00:00Z"
tt, err := time.Parse(timeFormat, timeString)
assert.Nil(t, err, "Parse error: %v", err)
dt := NewDateTimeFromTime(tt)
assert.True(t, dt > 0, "expected a valid DateTime greater than 0, got %v", dt)
})
})
}

View File

@ -0,0 +1,92 @@
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
package bson
import (
"errors"
"reflect"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
)
var tRawValue = reflect.TypeOf(RawValue{})
var tRaw = reflect.TypeOf(Raw(nil))
var primitiveCodecs PrimitiveCodecs
// PrimitiveCodecs is a namespace for all of the default bsoncodec.Codecs for the primitive types
// defined in this package.
type PrimitiveCodecs struct{}
// RegisterPrimitiveCodecs will register the encode and decode methods attached to PrimitiveCodecs
// with the provided RegistryBuilder. if rb is nil, a new empty RegistryBuilder will be created.
func (pc PrimitiveCodecs) RegisterPrimitiveCodecs(rb *bsoncodec.RegistryBuilder) {
if rb == nil {
panic(errors.New("argument to RegisterPrimitiveCodecs must not be nil"))
}
rb.
RegisterTypeEncoder(tRawValue, bsoncodec.ValueEncoderFunc(pc.RawValueEncodeValue)).
RegisterTypeEncoder(tRaw, bsoncodec.ValueEncoderFunc(pc.RawEncodeValue)).
RegisterTypeDecoder(tRawValue, bsoncodec.ValueDecoderFunc(pc.RawValueDecodeValue)).
RegisterTypeDecoder(tRaw, bsoncodec.ValueDecoderFunc(pc.RawDecodeValue))
}
// RawValueEncodeValue is the ValueEncoderFunc for RawValue.
func (PrimitiveCodecs) RawValueEncodeValue(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tRawValue {
return bsoncodec.ValueEncoderError{Name: "RawValueEncodeValue", Types: []reflect.Type{tRawValue}, Received: val}
}
rawvalue := val.Interface().(RawValue)
return bsonrw.Copier{}.CopyValueFromBytes(vw, rawvalue.Type, rawvalue.Value)
}
// RawValueDecodeValue is the ValueDecoderFunc for RawValue.
func (PrimitiveCodecs) RawValueDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != tRawValue {
return bsoncodec.ValueDecoderError{Name: "RawValueDecodeValue", Types: []reflect.Type{tRawValue}, Received: val}
}
t, value, err := bsonrw.Copier{}.CopyValueToBytes(vr)
if err != nil {
return err
}
val.Set(reflect.ValueOf(RawValue{Type: t, Value: value}))
return nil
}
// RawEncodeValue is the ValueEncoderFunc for Reader.
func (PrimitiveCodecs) RawEncodeValue(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
if !val.IsValid() || val.Type() != tRaw {
return bsoncodec.ValueEncoderError{Name: "RawEncodeValue", Types: []reflect.Type{tRaw}, Received: val}
}
rdr := val.Interface().(Raw)
return bsonrw.Copier{}.CopyDocumentFromBytes(vw, rdr)
}
// RawDecodeValue is the ValueDecoderFunc for Reader.
func (PrimitiveCodecs) RawDecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
if !val.CanSet() || val.Type() != tRaw {
return bsoncodec.ValueDecoderError{Name: "RawDecodeValue", Types: []reflect.Type{tRaw}, Received: val}
}
if val.IsNil() {
val.Set(reflect.MakeSlice(val.Type(), 0, 0))
}
val.SetLen(0)
rdr, err := bsonrw.Copier{}.AppendDocumentBytes(val.Interface().(Raw), vr)
val.Set(reflect.ValueOf(rdr))
return err
}

Some files were not shown because too many files have changed in this diff Show More