Copied mongo repo (to patch it)
This commit is contained in:
parent
21d241f9b1
commit
d471d7c396
5
Makefile
5
Makefile
@ -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
|
||||||
|
|
||||||
@ -9,3 +11,6 @@ test:
|
|||||||
|
|
||||||
version:
|
version:
|
||||||
_data/version.sh
|
_data/version.sh
|
||||||
|
|
||||||
|
update-mongo:
|
||||||
|
_data/update-mongo.sh
|
72
_data/update-mongo.sh
Executable file
72
_data/update-mongo.sh
Executable 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
16
mongo/.errcheck-excludes
Normal 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
13
mongo/.gitignore
vendored
Normal 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
3
mongo/.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "specifications"]
|
||||||
|
path = specifications
|
||||||
|
url = git@github.com:mongodb/specifications.git
|
123
mongo/.golangci.yml
Normal file
123
mongo/.golangci.yml
Normal 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
201
mongo/LICENSE
Normal 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
210
mongo/Makefile
Normal 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
251
mongo/README.md
Normal 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
1554
mongo/THIRD-PARTY-NOTICES
Normal file
File diff suppressed because it is too large
Load Diff
307
mongo/bson/benchmark_test.go
Normal file
307
mongo/bson/benchmark_test.go
Normal 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
50
mongo/bson/bson.go
Normal 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
|
530
mongo/bson/bson_corpus_spec_test.go
Normal file
530
mongo/bson/bson_corpus_spec_test.go
Normal 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
279
mongo/bson/bson_test.go
Normal 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)
|
||||||
|
}
|
50
mongo/bson/bsoncodec/array_codec.go
Normal file
50
mongo/bson/bsoncodec/array_codec.go
Normal 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
|
||||||
|
}
|
238
mongo/bson/bsoncodec/bsoncodec.go
Normal file
238
mongo/bson/bsoncodec/bsoncodec.go
Normal 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
|
||||||
|
}
|
143
mongo/bson/bsoncodec/bsoncodec_test.go
Normal file
143
mongo/bson/bsoncodec/bsoncodec_test.go
Normal 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
|
||||||
|
}
|
111
mongo/bson/bsoncodec/byte_slice_codec.go
Normal file
111
mongo/bson/bsoncodec/byte_slice_codec.go
Normal 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
|
||||||
|
}
|
63
mongo/bson/bsoncodec/cond_addr_codec.go
Normal file
63
mongo/bson/bsoncodec/cond_addr_codec.go
Normal 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()}
|
||||||
|
}
|
97
mongo/bson/bsoncodec/cond_addr_codec_test.go
Normal file
97
mongo/bson/bsoncodec/cond_addr_codec_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
1729
mongo/bson/bsoncodec/default_value_decoders.go
Normal file
1729
mongo/bson/bsoncodec/default_value_decoders.go
Normal file
File diff suppressed because it is too large
Load Diff
3794
mongo/bson/bsoncodec/default_value_decoders_test.go
Normal file
3794
mongo/bson/bsoncodec/default_value_decoders_test.go
Normal file
File diff suppressed because it is too large
Load Diff
766
mongo/bson/bsoncodec/default_value_encoders.go
Normal file
766
mongo/bson/bsoncodec/default_value_encoders.go
Normal 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()
|
||||||
|
}
|
1909
mongo/bson/bsoncodec/default_value_encoders_test.go
Normal file
1909
mongo/bson/bsoncodec/default_value_encoders_test.go
Normal file
File diff suppressed because it is too large
Load Diff
90
mongo/bson/bsoncodec/doc.go
Normal file
90
mongo/bson/bsoncodec/doc.go
Normal 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
|
147
mongo/bson/bsoncodec/empty_interface_codec.go
Normal file
147
mongo/bson/bsoncodec/empty_interface_codec.go
Normal 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
|
||||||
|
}
|
309
mongo/bson/bsoncodec/map_codec.go
Normal file
309
mongo/bson/bsoncodec/map_codec.go
Normal 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
|
||||||
|
}
|
65
mongo/bson/bsoncodec/mode.go
Normal file
65
mongo/bson/bsoncodec/mode.go
Normal 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)
|
||||||
|
}
|
109
mongo/bson/bsoncodec/pointer_codec.go
Normal file
109
mongo/bson/bsoncodec/pointer_codec.go
Normal 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())
|
||||||
|
}
|
14
mongo/bson/bsoncodec/proxy.go
Normal file
14
mongo/bson/bsoncodec/proxy.go
Normal 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)
|
||||||
|
}
|
469
mongo/bson/bsoncodec/registry.go
Normal file
469
mongo/bson/bsoncodec/registry.go
Normal 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
|
||||||
|
}
|
125
mongo/bson/bsoncodec/registry_examples_test.go
Normal file
125
mongo/bson/bsoncodec/registry_examples_test.go
Normal 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))
|
||||||
|
}
|
452
mongo/bson/bsoncodec/registry_test.go
Normal file
452
mongo/bson/bsoncodec/registry_test.go
Normal 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 }
|
199
mongo/bson/bsoncodec/slice_codec.go
Normal file
199
mongo/bson/bsoncodec/slice_codec.go
Normal 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
|
||||||
|
}
|
119
mongo/bson/bsoncodec/string_codec.go
Normal file
119
mongo/bson/bsoncodec/string_codec.go
Normal 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
|
||||||
|
}
|
48
mongo/bson/bsoncodec/string_codec_test.go
Normal file
48
mongo/bson/bsoncodec/string_codec_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
669
mongo/bson/bsoncodec/struct_codec.go
Normal file
669
mongo/bson/bsoncodec/struct_codec.go
Normal 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
|
||||||
|
}
|
47
mongo/bson/bsoncodec/struct_codec_test.go
Normal file
47
mongo/bson/bsoncodec/struct_codec_test.go
Normal 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))
|
||||||
|
}
|
139
mongo/bson/bsoncodec/struct_tag_parser.go
Normal file
139
mongo/bson/bsoncodec/struct_tag_parser.go
Normal 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)
|
||||||
|
}
|
160
mongo/bson/bsoncodec/struct_tag_parser_test.go
Normal file
160
mongo/bson/bsoncodec/struct_tag_parser_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
127
mongo/bson/bsoncodec/time_codec.go
Normal file
127
mongo/bson/bsoncodec/time_codec.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright (C) MongoDB, Inc. 2017-present.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
package 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))
|
||||||
|
}
|
79
mongo/bson/bsoncodec/time_codec_test.go
Normal file
79
mongo/bson/bsoncodec/time_codec_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
57
mongo/bson/bsoncodec/types.go
Normal file
57
mongo/bson/bsoncodec/types.go
Normal 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{})
|
173
mongo/bson/bsoncodec/uint_codec.go
Normal file
173
mongo/bson/bsoncodec/uint_codec.go
Normal 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
|
||||||
|
}
|
38
mongo/bson/bsonoptions/byte_slice_codec_options.go
Normal file
38
mongo/bson/bsonoptions/byte_slice_codec_options.go
Normal 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
|
||||||
|
}
|
8
mongo/bson/bsonoptions/doc.go
Normal file
8
mongo/bson/bsonoptions/doc.go
Normal 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
|
38
mongo/bson/bsonoptions/empty_interface_codec_options.go
Normal file
38
mongo/bson/bsonoptions/empty_interface_codec_options.go
Normal 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
|
||||||
|
}
|
67
mongo/bson/bsonoptions/map_codec_options.go
Normal file
67
mongo/bson/bsonoptions/map_codec_options.go
Normal 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
|
||||||
|
}
|
38
mongo/bson/bsonoptions/slice_codec_options.go
Normal file
38
mongo/bson/bsonoptions/slice_codec_options.go
Normal 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
|
||||||
|
}
|
41
mongo/bson/bsonoptions/string_codec_options.go
Normal file
41
mongo/bson/bsonoptions/string_codec_options.go
Normal 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
|
||||||
|
}
|
87
mongo/bson/bsonoptions/struct_codec_options.go
Normal file
87
mongo/bson/bsonoptions/struct_codec_options.go
Normal 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
|
||||||
|
}
|
38
mongo/bson/bsonoptions/time_codec_options.go
Normal file
38
mongo/bson/bsonoptions/time_codec_options.go
Normal 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
|
||||||
|
}
|
38
mongo/bson/bsonoptions/uint_codec_options.go
Normal file
38
mongo/bson/bsonoptions/uint_codec_options.go
Normal 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
|
||||||
|
}
|
33
mongo/bson/bsonrw/bsonrw_test.go
Normal file
33
mongo/bson/bsonrw/bsonrw_test.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
848
mongo/bson/bsonrw/bsonrwtest/bsonrwtest.go
Normal file
848
mongo/bson/bsonrw/bsonrwtest/bsonrwtest.go
Normal 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
445
mongo/bson/bsonrw/copier.go
Normal 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()
|
||||||
|
}
|
529
mongo/bson/bsonrw/copier_test.go
Normal file
529
mongo/bson/bsonrw/copier_test.go
Normal 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
9
mongo/bson/bsonrw/doc.go
Normal 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"
|
806
mongo/bson/bsonrw/extjson_parser.go
Normal file
806
mongo/bson/bsonrw/extjson_parser.go
Normal 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)
|
||||||
|
}
|
788
mongo/bson/bsonrw/extjson_parser_test.go
Normal file
788
mongo/bson/bsonrw/extjson_parser_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
644
mongo/bson/bsonrw/extjson_reader.go
Normal file
644
mongo/bson/bsonrw/extjson_reader.go
Normal 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
|
||||||
|
}
|
168
mongo/bson/bsonrw/extjson_reader_test.go
Normal file
168
mongo/bson/bsonrw/extjson_reader_test.go
Normal 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
|
||||||
|
}
|
223
mongo/bson/bsonrw/extjson_tables.go
Normal file
223
mongo/bson/bsonrw/extjson_tables.go
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
// Copyright (C) MongoDB, Inc. 2017-present.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Based on github.com/golang/go by The Go Authors
|
||||||
|
// See THIRD-PARTY-NOTICES for original license terms.
|
||||||
|
|
||||||
|
package 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,
|
||||||
|
}
|
492
mongo/bson/bsonrw/extjson_wrappers.go
Normal file
492
mongo/bson/bsonrw/extjson_wrappers.go
Normal 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
|
||||||
|
}
|
732
mongo/bson/bsonrw/extjson_writer.go
Normal file
732
mongo/bson/bsonrw/extjson_writer.go
Normal 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))
|
||||||
|
}
|
260
mongo/bson/bsonrw/extjson_writer_test.go
Normal file
260
mongo/bson/bsonrw/extjson_writer_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
528
mongo/bson/bsonrw/json_scanner.go
Normal file
528
mongo/bson/bsonrw/json_scanner.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
376
mongo/bson/bsonrw/json_scanner_test.go
Normal file
376
mongo/bson/bsonrw/json_scanner_test.go
Normal 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
108
mongo/bson/bsonrw/mode.go
Normal 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
|
||||||
|
}
|
63
mongo/bson/bsonrw/reader.go
Normal file
63
mongo/bson/bsonrw/reader.go
Normal 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)
|
||||||
|
}
|
874
mongo/bson/bsonrw/value_reader.go
Normal file
874
mongo/bson/bsonrw/value_reader.go
Normal 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
|
||||||
|
}
|
1538
mongo/bson/bsonrw/value_reader_test.go
Normal file
1538
mongo/bson/bsonrw/value_reader_test.go
Normal file
File diff suppressed because it is too large
Load Diff
608
mongo/bson/bsonrw/value_reader_writer_test.go
Normal file
608
mongo/bson/bsonrw/value_reader_writer_test.go
Normal 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
|
||||||
|
}
|
606
mongo/bson/bsonrw/value_writer.go
Normal file
606
mongo/bson/bsonrw/value_writer.go
Normal 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')
|
||||||
|
}
|
368
mongo/bson/bsonrw/value_writer_test.go
Normal file
368
mongo/bson/bsonrw/value_writer_test.go
Normal 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 }
|
78
mongo/bson/bsonrw/writer.go
Normal file
78
mongo/bson/bsonrw/writer.go
Normal 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
|
||||||
|
}
|
97
mongo/bson/bsontype/bsontype.go
Normal file
97
mongo/bson/bsontype/bsontype.go
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
49
mongo/bson/bsontype/bsontype_test.go
Normal file
49
mongo/bson/bsontype/bsontype_test.go
Normal 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
141
mongo/bson/decoder.go
Normal 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
435
mongo/bson/decoder_test.go
Normal 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
141
mongo/bson/doc.go
Normal 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
99
mongo/bson/encoder.go
Normal 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
135
mongo/bson/encoder_test.go
Normal 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
|
||||||
|
}
|
47
mongo/bson/extjson_prose_test.go
Normal file
47
mongo/bson/extjson_prose_test.go
Normal 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
40
mongo/bson/fuzz_test.go
Normal 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
248
mongo/bson/marshal.go
Normal 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
382
mongo/bson/marshal_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
168
mongo/bson/marshal_value_test.go
Normal file
168
mongo/bson/marshal_value_test.go
Normal 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)
|
||||||
|
}
|
29
mongo/bson/marshaling_cases_test.go
Normal file
29
mongo/bson/marshaling_cases_test.go
Normal 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}}),
|
||||||
|
},
|
||||||
|
}
|
1712
mongo/bson/mgocompat/bson_test.go
Normal file
1712
mongo/bson/mgocompat/bson_test.go
Normal file
File diff suppressed because it is too large
Load Diff
57
mongo/bson/mgocompat/doc.go
Normal file
57
mongo/bson/mgocompat/doc.go
Normal 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
|
114
mongo/bson/mgocompat/registry.go
Normal file
114
mongo/bson/mgocompat/registry.go
Normal 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
|
||||||
|
}
|
127
mongo/bson/mgocompat/setter_getter.go
Normal file
127
mongo/bson/mgocompat/setter_getter.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright (C) MongoDB, Inc. 2017-present.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
package 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()
|
||||||
|
}
|
437
mongo/bson/primitive/decimal.go
Normal file
437
mongo/bson/primitive/decimal.go
Normal 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)
|
||||||
|
}
|
236
mongo/bson/primitive/decimal_test.go
Normal file
236
mongo/bson/primitive/decimal_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
206
mongo/bson/primitive/objectid.go
Normal file
206
mongo/bson/primitive/objectid.go
Normal 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)
|
||||||
|
}
|
259
mongo/bson/primitive/objectid_test.go
Normal file
259
mongo/bson/primitive/objectid_test.go
Normal 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)
|
||||||
|
}
|
217
mongo/bson/primitive/primitive.go
Normal file
217
mongo/bson/primitive/primitive.go
Normal 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{}
|
122
mongo/bson/primitive/primitive_test.go
Normal file
122
mongo/bson/primitive/primitive_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
92
mongo/bson/primitive_codecs.go
Normal file
92
mongo/bson/primitive_codecs.go
Normal 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
Loading…
Reference in New Issue
Block a user