wangchuanjin 5 år sedan
förälder
incheckning
d6df53b390
100 ändrade filer med 29271 tillägg och 0 borttagningar
  1. 16 0
      src/go.mongodb.org/mongo-driver/.errcheck-excludes
  2. 911 0
      src/go.mongodb.org/mongo-driver/.evergreen/config.yml
  3. 8 0
      src/go.mongodb.org/mongo-driver/.evergreen/krb5.config
  4. 12 0
      src/go.mongodb.org/mongo-driver/.gitignore
  5. 3 0
      src/go.mongodb.org/mongo-driver/.gitmodules
  6. 64 0
      src/go.mongodb.org/mongo-driver/.lint-whitelist
  7. 37 0
      src/go.mongodb.org/mongo-driver/CONTRIBUTING.md
  8. 367 0
      src/go.mongodb.org/mongo-driver/Gopkg.lock
  9. 58 0
      src/go.mongodb.org/mongo-driver/Gopkg.toml
  10. 201 0
      src/go.mongodb.org/mongo-driver/LICENSE
  11. 147 0
      src/go.mongodb.org/mongo-driver/Makefile
  12. 193 0
      src/go.mongodb.org/mongo-driver/README.md
  13. 1336 0
      src/go.mongodb.org/mongo-driver/THIRD-PARTY-NOTICES
  14. 75 0
      src/go.mongodb.org/mongo-driver/benchmark/bson.go
  15. 123 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_document.go
  16. 88 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_map.go
  17. 103 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_struct.go
  18. 35 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_test.go
  19. 306 0
      src/go.mongodb.org/mongo-driver/benchmark/bson_types.go
  20. 29 0
      src/go.mongodb.org/mongo-driver/benchmark/canary.go
  21. 12 0
      src/go.mongodb.org/mongo-driver/benchmark/canary_test.go
  22. 226 0
      src/go.mongodb.org/mongo-driver/benchmark/harness.go
  23. 154 0
      src/go.mongodb.org/mongo-driver/benchmark/harness_case.go
  24. 69 0
      src/go.mongodb.org/mongo-driver/benchmark/harness_main.go
  25. 140 0
      src/go.mongodb.org/mongo-driver/benchmark/harness_results.go
  26. 142 0
      src/go.mongodb.org/mongo-driver/benchmark/multi.go
  27. 13 0
      src/go.mongodb.org/mongo-driver/benchmark/multi_test.go
  28. 174 0
      src/go.mongodb.org/mongo-driver/benchmark/single.go
  29. 14 0
      src/go.mongodb.org/mongo-driver/benchmark/single_test.go
  30. 134 0
      src/go.mongodb.org/mongo-driver/bson/benchmark_test.go
  31. 60 0
      src/go.mongodb.org/mongo-driver/bson/bson.go
  32. 91 0
      src/go.mongodb.org/mongo-driver/bson/bson_1_8.go
  33. 371 0
      src/go.mongodb.org/mongo-driver/bson/bson_corpus_spec_test.go
  34. 113 0
      src/go.mongodb.org/mongo-driver/bson/bson_test.go
  35. 163 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec.go
  36. 145 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec_test.go
  37. 1014 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders.go
  38. 2870 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders_test.go
  39. 648 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders.go
  40. 1436 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders_test.go
  41. 61 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/doc.go
  42. 65 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/mode.go
  43. 110 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/pointer_codec.go
  44. 14 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/proxy.go
  45. 384 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry.go
  46. 359 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry_test.go
  47. 367 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec.go
  48. 47 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec_test.go
  49. 119 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser.go
  50. 73 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser_test.go
  51. 80 0
      src/go.mongodb.org/mongo-driver/bson/bsoncodec/types.go
  52. 33 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrw_test.go
  53. 847 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest/bsonrwtest.go
  54. 389 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/copier.go
  55. 529 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/copier_test.go
  56. 9 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/doc.go
  57. 731 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_parser.go
  58. 747 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_parser_test.go
  59. 659 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_reader.go
  60. 168 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_reader_test.go
  61. 223 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_tables.go
  62. 495 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_wrappers.go
  63. 734 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go
  64. 260 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer_test.go
  65. 439 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/json_scanner.go
  66. 346 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/json_scanner_test.go
  67. 108 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/mode.go
  68. 63 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/reader.go
  69. 882 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/value_reader.go
  70. 1526 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/value_reader_test.go
  71. 608 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/value_reader_writer_test.go
  72. 589 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/value_writer.go
  73. 438 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/value_writer_test.go
  74. 96 0
      src/go.mongodb.org/mongo-driver/bson/bsonrw/writer.go
  75. 87 0
      src/go.mongodb.org/mongo-driver/bson/bsontype/bsontype.go
  76. 49 0
      src/go.mongodb.org/mongo-driver/bson/bsontype/bsontype_test.go
  77. 112 0
      src/go.mongodb.org/mongo-driver/bson/decoder.go
  78. 277 0
      src/go.mongodb.org/mongo-driver/bson/decoder_test.go
  79. 42 0
      src/go.mongodb.org/mongo-driver/bson/doc.go
  80. 99 0
      src/go.mongodb.org/mongo-driver/bson/encoder.go
  81. 149 0
      src/go.mongodb.org/mongo-driver/bson/encoder_test.go
  82. 156 0
      src/go.mongodb.org/mongo-driver/bson/marshal.go
  83. 209 0
      src/go.mongodb.org/mongo-driver/bson/marshal_test.go
  84. 29 0
      src/go.mongodb.org/mongo-driver/bson/marshaling_cases_test.go
  85. 376 0
      src/go.mongodb.org/mongo-driver/bson/primitive/decimal.go
  86. 146 0
      src/go.mongodb.org/mongo-driver/bson/primitive/decimal_test.go
  87. 165 0
      src/go.mongodb.org/mongo-driver/bson/primitive/objectid.go
  88. 150 0
      src/go.mongodb.org/mongo-driver/bson/primitive/objectid_test.go
  89. 206 0
      src/go.mongodb.org/mongo-driver/bson/primitive/primitive.go
  90. 60 0
      src/go.mongodb.org/mongo-driver/bson/primitive/primitive_test.go
  91. 111 0
      src/go.mongodb.org/mongo-driver/bson/primitive_codecs.go
  92. 1076 0
      src/go.mongodb.org/mongo-driver/bson/primitive_codecs_test.go
  93. 92 0
      src/go.mongodb.org/mongo-driver/bson/raw.go
  94. 51 0
      src/go.mongodb.org/mongo-driver/bson/raw_element.go
  95. 347 0
      src/go.mongodb.org/mongo-driver/bson/raw_test.go
  96. 287 0
      src/go.mongodb.org/mongo-driver/bson/raw_value.go
  97. 124 0
      src/go.mongodb.org/mongo-driver/bson/raw_value_test.go
  98. 24 0
      src/go.mongodb.org/mongo-driver/bson/registry.go
  99. 63 0
      src/go.mongodb.org/mongo-driver/bson/truncation_test.go
  100. 85 0
      src/go.mongodb.org/mongo-driver/bson/types.go

+ 16 - 0
src/go.mongodb.org/mongo-driver/.errcheck-excludes

@@ -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

+ 911 - 0
src/go.mongodb.org/mongo-driver/.evergreen/config.yml

@@ -0,0 +1,911 @@
+########################################
+# Evergreen Template for MongoDB Drivers
+########################################
+
+# When a task that used to pass starts to fail
+# Go through all versions that may have been skipped to detect
+# when the task started failing
+stepback: true
+
+# Mark a failure as a system/bootstrap failure (purple box) rather then a task
+# failure by default.
+# Actual testing tasks are marked with `type: test`
+command_type: setup
+
+# Protect ourself against rogue test case, or curl gone wild, that runs forever
+# 12 minutes is the longest we'll ever run
+exec_timeout_secs: 3600 # 12 minutes is the longest we'll ever run
+
+# What to do when evergreen hits the timeout (`post:` tasks are run automatically)
+timeout:
+  - command: shell.exec
+    params:
+      script: |
+        ls -la
+functions:
+
+  fetch-source:
+    # Executes git clone and applies the submitted patch, if any
+    - command: git.get_project
+      type: system
+      params:
+        directory: src/go.mongodb.org/mongo-driver
+    # Make an evergreen expansion file with dynamic values
+    - command: shell.exec
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+           if [ "Windows_NT" = "$OS" ]; then
+              export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+           else
+              export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+           fi;
+
+           # Get the current unique version of this checkout
+           if [ "${is_patch}" = "true" ]; then
+              CURRENT_VERSION=$(git describe)-patch-${version_id}
+           else
+              CURRENT_VERSION=latest
+           fi
+
+           export DRIVERS_TOOLS="$(pwd)/../drivers-tools"
+           export PROJECT_DIRECTORY="$(pwd)"
+           export GOCACHE="$(pwd)/.cache"
+
+           # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory
+           if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin
+              export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS)
+              export PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY)
+              export GOCACHE=$(cygpath -m $GOCACHE)
+           fi
+
+           export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
+           export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
+           export UPLOAD_BUCKET="${project}"
+           export PATH="${GO_DIST}/bin:${GCC_PATH}:$GOPATH/bin:$MONGODB_BINARIES:$PATH"
+           export PROJECT="${project}"
+
+           if [ "Windows_NT" = "$OS" ]; then
+              export USERPROFILE=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+              export HOME=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+           fi
+
+           go version
+           go env
+
+           if [ "Windows_NT" = "$OS" ]; then
+              mkdir -p c:/libmongocrypt/include
+              mkdir -p c:/libmongocrypt/bin
+              curl https://s3.amazonaws.com/mciuploads/libmongocrypt/windows-test/master/latest/libmongocrypt.tar.gz --output libmongocrypt.tar.gz
+              tar -xvzf libmongocrypt.tar.gz
+              cp ./bin/mongocrypt.dll c:/libmongocrypt/bin
+              cp ./include/mongocrypt/*.h c:/libmongocrypt/include
+              export PATH=$PATH:/cygdrive/c/libmongocrypt/bin
+           else
+              git clone https://github.com/mongodb/libmongocrypt
+              ./libmongocrypt/.evergreen/compile.sh
+           fi
+
+           cat <<EOT > expansion.yml
+           CURRENT_VERSION: "$CURRENT_VERSION"
+           DRIVERS_TOOLS: "$DRIVERS_TOOLS"
+           MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME"
+           MONGODB_BINARIES: "$MONGODB_BINARIES"
+           UPLOAD_BUCKET: "$UPLOAD_BUCKET"
+           PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
+           PREPARE_SHELL: |
+              set -o errexit
+              set -o xtrace
+              export GOPATH="$GOPATH"
+              export GOROOT="${GO_DIST}"
+              export GOCACHE="$GOCACHE"
+              export DRIVERS_TOOLS="$DRIVERS_TOOLS"
+              export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
+              export MONGODB_BINARIES="$MONGODB_BINARIES"
+              export UPLOAD_BUCKET="$UPLOAD_BUCKET"
+              export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
+              export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
+              export PATH="$PATH"
+              export PROJECT="$PROJECT"
+              export PKG_CONFIG_PATH=$(pwd)/install/libmongocrypt/lib/pkgconfig:$(pwd)/install/mongo-c-driver/lib/pkgconfig
+              export LD_LIBRARY_PATH=$(pwd)/install/libmongocrypt/lib
+           EOT
+           # See what we've done
+           cat expansion.yml
+    # Load the expansion file to make an evergreen variable with the current unique version
+    - command: expansions.update
+      params:
+        file: src/go.mongodb.org/mongo-driver/expansion.yml
+
+
+  prepare-resources:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          rm -rf $DRIVERS_TOOLS
+          if [ "${project}" = "drivers-tools" ]; then
+            # If this was a patch build, doing a fresh clone would not actually test the patch
+            cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS
+          else
+            git clone git://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS
+          fi
+          echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
+    - command: shell.exec
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+           ${PREPARE_SHELL}
+           # any go tools that we need
+           go get -u golang.org/x/lint/golint
+           go get -u github.com/kisielk/errcheck
+
+           # initialize submodules
+           git submodule init
+           git submodule update
+
+           # generate any source code
+           make generate
+
+
+  upload-mo-artifacts:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz
+    - command: s3.put
+      params:
+        aws_key: ${aws_key}
+        aws_secret: ${aws_secret}
+        local_file: mongodb-logs.tar.gz
+        remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
+        bucket: mciuploads
+        permissions: public-read
+        content_type: ${content_type|application/x-gzip}
+        display_name: "mongodb-logs.tar.gz"
+
+  bootstrap-mongo-orchestration:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+
+          cp ${PROJECT_DIRECTORY}/data/certificates/server.pem ${DRIVERS_TOOLS}/.evergreen/x509gen/server.pem
+          cp ${PROJECT_DIRECTORY}/data/certificates/ca.pem ${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem
+          cp ${PROJECT_DIRECTORY}/data/certificates/client.pem ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem
+          cp ${PROJECT_DIRECTORY}/data/certificates/client.pem ${MONGO_ORCHESTRATION_HOME}/lib/client.pem
+
+          MONGODB_VERSION=${VERSION} AUTH=${AUTH} SSL=${SSL} sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh
+    - command: expansions.update
+      params:
+        file: mo-expansion.yml
+
+
+  cleanup:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          cd "$MONGO_ORCHESTRATION_HOME"
+          # source the mongo-orchestration virtualenv if it exists
+          if [ -f venv/bin/activate ]; then
+            . venv/bin/activate
+          elif [ -f venv/Scripts/activate ]; then
+            . venv/Scripts/activate
+          fi
+          mongo-orchestration stop
+          cd -
+          rm -rf $DRIVERS_TOOLS || true
+
+
+  fix-absolute-paths:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          for filename in $(find ${DRIVERS_TOOLS} -name \*.json); do
+            perl -p -i -e "s|ABSOLUTE_PATH_REPLACEMENT_TOKEN|${DRIVERS_TOOLS}|g" $filename
+          done
+
+
+  windows-fix:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY} -name \*.sh); do
+            cat $i | tr -d '\r' > $i.new
+            mv $i.new $i
+          done
+
+
+  make-files-executable:
+    - command: shell.exec
+      params:
+        script: |
+          ${PREPARE_SHELL}
+          for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY} -name \*.sh); do
+            chmod +x $i
+          done
+
+
+  run-make:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          ${PREPARE_SHELL}
+          ${BUILD_ENV|} make ${targets} BUILD_TAGS="-tags gssapi"
+
+
+  run-tests:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          ${PREPARE_SHELL}
+
+          if [ ${SSL} = "ssl" ]; then
+              export MONGO_GO_DRIVER_CA_FILE="$PROJECT_DIRECTORY/data/certificates/ca.pem"
+
+              if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin
+                  export MONGO_GO_DRIVER_CA_FILE=$(cygpath -m $MONGO_GO_DRIVER_CA_FILE)
+              fi
+          fi
+
+          set +o xtrace
+          AUTH=${AUTH} \
+          SSL=${SSL} \
+          MONGODB_URI="${MONGODB_URI}" \
+          MONGO_GO_DRIVER_COMPRESSOR=${MONGO_GO_DRIVER_COMPRESSOR} \
+          BUILD_TAGS="-tags cse" \
+          AWS_ACCESS_KEY_ID="${cse_aws_access_key_id}" \
+          AWS_SECRET_ACCESS_KEY="${cse_aws_secret_access_key}" \
+          make evg-test \
+          PKG_CONFIG_PATH=$PKG_CONFIG_PATH \
+          LD_LIBRARY_PATH=$LD_LIBRARY_PATH
+
+  send-perf-data:
+   - command: json.send
+     params:
+       name: perf
+       file: src/go.mongodb.org/mongo-driver/perf.json
+
+
+  run-enterprise-auth-tests:
+    - command: shell.exec
+      type: test
+      params:
+        silent: true
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          MONGODB_URI="${MONGODB_URI}" MONGO_GO_DRIVER_COMPRESSOR="${MONGO_GO_DRIVER_COMPRESSOR}" make -s evg-test-auth
+
+  run-enterprise-gssapi-auth-tests:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+            export MONGODB_URI=${gssapi_auth_windows_mongodb_uri}
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+            echo "${gssapi_auth_linux_keytab_base64}" > /tmp/drivers.keytab.base64
+            base64 --decode /tmp/drivers.keytab.base64 > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab
+            mkdir -p ~/.krb5
+            cat .evergreen/krb5.config | tee -a ~/.krb5/config
+            kinit -k -t ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab -p "${gssapi_auth_username}"
+            export MONGODB_URI="${gssapi_auth_linux_mongodb_uri}"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          MONGO_GO_DRIVER_COMPRESSOR="${MONGO_GO_DRIVER_COMPRESSOR}" make -s evg-test-auth
+
+  run-enterprise-gssapi-service-host-auth-tests:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+            export MONGODB_URI="${gssapi_service_host_auth_windows_mongodb_uri}"
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+            echo "${gssapi_auth_linux_keytab_base64}" > /tmp/drivers.keytab.base64
+            base64 --decode /tmp/drivers.keytab.base64 > ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab
+            mkdir -p ~/.krb5
+            cat .evergreen/krb5.config | tee -a ~/.krb5/config
+            kinit -k -t ${PROJECT_DIRECTORY}/.evergreen/drivers.keytab -p "${gssapi_auth_username}"
+            export MONGODB_URI="${gssapi_service_host_auth_linux_mongodb_uri}"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          MONGO_GO_DRIVER_COMPRESSOR="${MONGO_GO_DRIVER_COMPRESSOR}" make -s evg-test-auth
+
+  run-atlas-test:
+    - command: shell.exec
+      type: test
+      params:
+        working_dir: src/go.mongodb.org/mongo-driver
+        script: |
+          # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does)
+          if [ "Windows_NT" = "$OS" ]; then
+            export GOPATH=$(cygpath -w $(dirname $(dirname $(dirname `pwd`))))
+            export GOCACHE=$(cygpath -w "$(pwd)/.cache")
+          else
+            export GOPATH=$(dirname $(dirname $(dirname `pwd`)))
+            export GOCACHE="$(pwd)/.cache"
+          fi;
+          export GOPATH="$GOPATH"
+          export GOROOT="${GO_DIST}"
+          export GOCACHE="$GOCACHE"
+          export PATH="${GCC_PATH}:${GO_DIST}/bin:$PATH"
+          export ATLAS_FREE="${atlas_free_tier_uri}"
+          export ATLAS_REPLSET="${atlas_replica_set_uri}"
+          export ATLAS_SHARD="${atlas_sharded_uri}"
+          export ATLAS_TLS11="${atlas_tls_v11_uri}"
+          export ATLAS_TLS12="${atlas_tls_v12_uri}"
+          export ATLAS_FREE_SRV="${atlas_free_tier_uri_srv}"
+          export ATLAS_REPLSET_SRV="${atlas_replica_set_uri_srv}"
+          export ATLAS_SHARD_SRV="${atlas_sharded_uri_srv}"
+          export ATLAS_TLS11_SRV="${atlas_tls_v11_uri_srv}"
+          export ATLAS_TLS12_SRV="${atlas_tls_v12_uri_srv}"
+          make -s evg-test-atlas
+
+
+pre:
+  - func: fetch-source
+  - func: prepare-resources
+  - func: windows-fix
+  - func: fix-absolute-paths
+  - func: make-files-executable
+
+post:
+  - command: gotest.parse_files
+    params:
+      files:
+        - "src/go.mongodb.org/mongo-driver/*.suite"
+  - func: upload-mo-artifacts
+  - func: cleanup
+
+tasks:
+    - name: sa-fmt
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: check-fmt
+
+    - name: sa-errcheck
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: errcheck
+
+
+    - name: sa-lint
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: lint
+
+    - name: sa-vet
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: vet
+
+    - name: perf
+      tags: ["performance"]
+      exec_timeout_secs: 7200
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-make
+          vars:
+            targets: driver-benchmark
+        - func: send-perf-data
+
+    - name: sa-build-examples
+      tags: ["static-analysis"]
+      commands:
+        - func: run-make
+          vars:
+            targets: build-examples
+
+    - name: test-standalone-noauth-nossl
+      tags: ["test", "standalone"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+
+    - name: test-standalone-noauth-nossl-snappy-compression
+      tags: ["test", "standalone", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-standalone-noauth-nossl-zlib-compression
+      tags: ["test", "standalone", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-standalone-noauth-nossl-zstd-compression
+      tags: ["test", "standalone", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-standalone-auth-ssl
+      tags: ["test", "standalone", "authssl"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+
+    - name: test-standalone-auth-ssl-snappy-compression
+      tags: ["test", "standalone", "authssl", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-standalone-auth-ssl-zlib-compression
+      tags: ["test", "standalone", "authssl", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-standalone-auth-ssl-zstd-compression
+      tags: ["test", "standalone", "authssl", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-replicaset-noauth-nossl
+      tags: ["test", "replicaset"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+
+    - name: test-replicaset-auth-ssl
+      tags: ["test", "replicaset", "authssl"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+
+    - name: test-sharded-noauth-nossl
+      tags: ["test", "sharded"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+
+    - name: test-sharded-noauth-nossl-snappy-compression
+      tags: ["test", "sharded", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-sharded-noauth-nossl-zlib-compression
+      tags: ["test", "sharded", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-sharded-noauth-nossl-zstd-compression
+      tags: ["test", "sharded", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+        - func: run-tests
+          vars:
+            AUTH: "noauth"
+            SSL: "nossl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-sharded-auth-ssl
+      tags: ["test", "sharded", "authssl"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+
+    - name: test-sharded-auth-ssl-snappy-compression
+      tags: ["test", "sharded", "authssl", "compression", "snappy"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: test-sharded-auth-ssl-zlib-compression
+      tags: ["test", "sharded", "authssl", "compression", "zlib"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zlib"
+
+    - name: test-sharded-auth-ssl-zstd-compression
+      tags: ["test", "sharded", "authssl", "compression", "zstd"]
+      commands:
+        - func: bootstrap-mongo-orchestration
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+        - func: run-tests
+          vars:
+            AUTH: "auth"
+            SSL: "ssl"
+            MONGO_GO_DRIVER_COMPRESSOR: "zstd"
+
+    - name: test-enterprise-auth-plain
+      tags: ["test", "enterprise-auth"]
+      commands:
+        - func: run-enterprise-auth-tests
+          vars:
+            MONGODB_URI: "${plain_auth_mongodb_uri}"
+
+    - name: test-enterprise-auth-gssapi
+      tags: ["test", "enterprise-auth"]
+      commands:
+        - func: run-enterprise-gssapi-auth-tests
+
+    - name: test-enterprise-auth-gssapi-service-host
+      tags: ["test", "enterprise-auth"]
+      commands:
+        - func: run-enterprise-gssapi-service-host-auth-tests
+          vars:
+            MONGO_GO_DRIVER_COMPRESSOR: "snappy"
+
+    - name: go1.9-build
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "PATH=/opt/golang/go1.9/bin:$PATH GOROOT=/opt/golang/go1.9"
+
+    - name: go1.9-build-nocgo
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "CGO_ENABLED=0 PATH=/opt/golang/go1.9/bin:$PATH GOROOT=/opt/golang/go1.9"
+
+    - name: go1.10-build
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "PATH=/opt/golang/go1.10/bin:$PATH GOROOT=/opt/golang/go1.10"
+
+    - name: go1.10-build-nocgo
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "CGO_ENABLED=0 PATH=/opt/golang/go1.10/bin:$PATH GOROOT=/opt/golang/go1.10"
+
+    - name: linux-32-bit
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "GOARCH=386"
+
+    - name: linux-arm64
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "GOARCH=arm64"
+
+    - name: linux-s390x
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build"
+            BUILD_ENV: "GOARCH=ppc64le"
+
+    - name: "atlas-test"
+      commands:
+        - func: "run-atlas-test"
+
+    - name: go1.10-build-cse
+      tags: ["compile-check"]
+      commands:
+        - func: run-make
+          vars:
+            targets: "build-cse"
+            BUILD_ENV: "PATH=/opt/golang/go1.10/bin:$PATH GOROOT=/opt/golang/go1.10"
+
+axes:
+  - id: version
+    display_name: MongoDB Version
+    values:
+      - id: "4.2"
+        display_name: "4.2"
+        variables:
+          VERSION: "4.2"
+      - id: "4.0"
+        display_name: "4.0"
+        variables:
+           VERSION: "4.0"
+      - id: "3.6"
+        display_name: "3.6"
+        variables:
+           VERSION: "3.6"
+      - id: "3.4"
+        display_name: "3.4"
+        variables:
+           VERSION: "3.4"
+      - id: "3.2"
+        display_name: "3.2"
+        variables:
+           VERSION: "3.2"
+      - id: "3.0"
+        display_name: "3.0"
+        variables:
+          VERSION: "3.0"
+      - id: "2.6"
+        display_name: "2.6"
+        variables:
+          VERSION: "2.6"
+      - id: "latest"
+        display_name: "latest"
+        variables:
+           VERSION: "latest"
+
+  # OSes that support versions of MongoDB >= 2.6 with SSL.
+  - id: os-ssl-legacy
+    display_name: OS
+    values:
+      - id: "ubuntu1404-go-1-12"
+        display_name: "Ubuntu 14.04"
+        run_on: ubuntu1404-test
+        variables:
+          GO_DIST: "/opt/golang/go1.12"
+
+  # OSes that require >= 3.2 for SSL
+  - id: os-ssl-32
+    display_name: OS
+    values:
+      - id: "windows-64-go-1-12"
+        display_name: "Windows 64-bit"
+        run_on:
+          - windows-64-vs2015-test
+        variables:
+          GCC_PATH: "/cygdrive/c/mingw-w64/x86_64-4.9.1-posix-seh-rt_v3-rev1/mingw64/bin"
+          GO_DIST: "C:\\golang\\go1.12"
+      - id: "ubuntu1604-64-go-1-12"
+        display_name: "Ubuntu 16.04"
+        run_on: ubuntu1604-build
+        variables:
+          GO_DIST: "/opt/golang/go1.12"
+      - id: "osx-go-1-12"
+        display_name: "MacOS 10.14"
+        run_on: macos-1014
+        variables:
+          GO_DIST: "/opt/golang/go1.12"
+
+
+buildvariants:
+- name: static-analysis
+  display_name: "Static Analysis"
+  run_on:
+    - ubuntu1604-build
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: ".static-analysis"
+
+- name: perf
+  display_name: "Performance"
+  run_on:
+    - ubuntu1604-build
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: ".performance"
+
+- name: build-check
+  display_name: "Compile Only Checks"
+  run_on:
+    - ubuntu1604-test
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: ".compile-check"
+
+- name: atlas-test
+  display_name: "Atlas test"
+  run_on:
+    - ubuntu1604-build
+  expansions:
+    GO_DIST: "/opt/golang/go1.12"
+  tasks:
+    - name: "atlas-test"
+
+- matrix_name: "tests-legacy-auth-ssl"
+  matrix_spec: { version: ["2.6", "3.0"], os-ssl-legacy: "*" }
+  display_name: "${version} ${os-ssl-legacy}"
+  tasks:
+    - name: ".test !.enterprise-auth !.compression"
+
+- matrix_name: "tests-legacy-noauth-nossl"
+  matrix_spec: { version: ["2.6", "3.0"], os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.authssl !.enterprise-auth !.compression"
+
+- matrix_name: "tests-nonlegacy-servers"
+  matrix_spec: { version: "3.2", os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.compression"
+
+- matrix_name: "tests-nonlegacy-servers-with-snappy-support"
+  matrix_spec: { version: "3.4", os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.zlib !.zstd"
+
+- matrix_name: "tests-36-40-with-zlib-support"
+  matrix_spec: { version: ["3.6", "4.0"], os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.snappy !.zstd"
+
+- matrix_name: "tests-42-lts-zlib-zstd-support"
+  matrix_spec: { version: ["4.2", "latest"], os-ssl-32: "*" }
+  display_name: "${version} ${os-ssl-32}"
+  tasks:
+    - name: ".test !.enterprise-auth !.snappy"
+
+- matrix_name: "enterprise-auth-tests"
+  matrix_spec: { os-ssl-32: "*" }
+  display_name: "Enterprise Auth - ${os-ssl-32}"
+  tasks:
+     - name: ".test .enterprise-auth"

+ 8 - 0
src/go.mongodb.org/mongo-driver/.evergreen/krb5.config

@@ -0,0 +1,8 @@
+[realms]
+  LDAPTEST.10GEN.CC = {
+    kdc = ldaptest.10gen.cc
+    admin_server = ldaptest.10gen.cc
+  }
+
+[libdefaults]
+  rdns = false

+ 12 - 0
src/go.mongodb.org/mongo-driver/.gitignore

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

+ 3 - 0
src/go.mongodb.org/mongo-driver/.gitmodules

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

+ 64 - 0
src/go.mongodb.org/mongo-driver/.lint-whitelist

@@ -0,0 +1,64 @@
+bson/bson.go:103:5: error var SetZero should have name of the form ErrFoo
+bson/bson.go:187:6: type ObjectId should be ObjectID
+bson/bson.go:192:6: func ObjectIdHex should be ObjectIDHex
+bson/bson.go:202:6: func IsObjectIdHex should be IsObjectIDHex
+bson/bson.go:249:6: func NewObjectId should be NewObjectID
+bson/bson.go:273:6: func NewObjectIdWithTime should be NewObjectIDWithTime
+bson/bson.go:470:2: struct field Id should be ID
+bson/bson.go:587:21: error strings should not be capitalized or end with punctuation or a newline
+bson/bson.go:589:21: error strings should not be capitalized or end with punctuation or a newline
+bson/bson.go:613:21: error strings should not be capitalized or end with punctuation or a newline
+bson/bson.go:615:21: error strings should not be capitalized or end with punctuation or a newline
+bson/encode.go:46:2: var typeObjectId should be typeObjectID
+bson/internal/json/stream_test.go:196:3: struct field Id should be ID
+bson/internal/json/stream_test.go:221:3: struct field Id should be ID
+bson/internal/json/stream_test.go:285:22: should omit type []tokenStreamCase from declaration of var tokenStreamCases; it will be inferred from the right-hand side
+bson/internal/testutil/close_helper.go:14:1: exported function CloseReadOnlyFile should have comment or be unexported
+bson/internal/testutil/close_helper.go:8:1: exported function CloseOrError should have comment or be unexported
+bson/json.go:246:6: func jdecObjectId should be jdecObjectID
+bson/json.go:263:6: func jencObjectId should be jencObjectID
+mongo/internal/testutil/helpers/helpers.go:10:1: exported function RequireNoErrorOnClose should have comment or be unexported
+mongo/internal/testutil/helpers/helpers.go:14:1: exported function FindJSONFilesInDir should have comment or be unexported
+mongo/internal/testutil/helpers/helpers.go:45:1: exported function VerifyConnStringOptions should have comment or be unexported
+mongo/options/find_and_modify.go:19:1: exported function CopyFindOneAndReplaceOptions should have comment or be unexported
+mongo/options/find_and_modify.go:29:1: exported function CopyFindOneAndUpdateOptions should have comment or be unexported
+mongo/options/find_and_modify.go:9:1: exported function CopyFindOneAndDeleteOptions should have comment or be unexported
+mongo/options.go:157:1: exported function OplogReplay should have comment or be unexported
+mongo/options.go:177:56: exported func ReadPreference returns unexported type *mongo.optReadPreference, which can be annoying to use
+bson/internal/jsonparser/bytes.go:9:10: should omit type bool from declaration of var neg; it will be inferred from the right-hand side
+bson/internal/jsonparser/bytes.go:25:9: if block ends with a return statement, so drop this else and outdent its block
+bson/internal/jsonparser/escape.go:113:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/escape.go:123:1: comment on exported function Unescape should be of the form "Unescape ..."
+bson/internal/jsonparser/parser.go:14:2: error var KeyPathNotFoundError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:15:2: error var UnknownValueTypeError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:16:2: error var MalformedJsonError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:16:2: var MalformedJsonError should be MalformedJSONError
+bson/internal/jsonparser/parser.go:17:2: error var MalformedStringError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:18:2: error var MalformedArrayError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:19:2: error var MalformedObjectError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:20:2: error var MalformedValueError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:21:2: error var MalformedStringEscapeError should have name of the form ErrFoo
+bson/internal/jsonparser/parser.go:147:11: if block ends with a return statement, so drop this else and outdent its block
+bson/internal/jsonparser/parser.go:285:6: should replace curIdx += 1 with curIdx++
+bson/internal/jsonparser/parser.go:292:12: if block ends with a return statement, so drop this else and outdent its block
+bson/internal/jsonparser/parser.go:303:12: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:329:6: don't use underscores in Go names; range var pi_1 should be pi1
+bson/internal/jsonparser/parser.go:329:12: don't use underscores in Go names; range var p_1 should be p1
+bson/internal/jsonparser/parser.go:338:1: exported function EachKey should have comment or be unexported
+bson/internal/jsonparser/parser.go:489:6: should replace curIdx += 1 with curIdx++
+bson/internal/jsonparser/parser.go:503:12: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:517:1: comment on exported type ValueType should be of the form "ValueType ..." (with optional leading article)
+bson/internal/jsonparser/parser.go:521:2: exported const NotExist should have comment (or a comment on this block) or be unexported
+bson/internal/jsonparser/parser.go:582:1: comment on exported function Delete should be of the form "Delete ..."
+bson/internal/jsonparser/parser.go:931:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:971:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:980:11: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1006:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1021:10: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1128:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1133:1: comment on exported function ParseFloat should be of the form "ParseFloat ..."
+bson/internal/jsonparser/parser.go:1137:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser.go:1146:9: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)
+bson/internal/jsonparser/parser_test.go:1361:5: var testJson should be testJSON
+bson/internal/jsonpretty/pretty.go:7:1: comment on exported type Options should be of the form "Options ..." (with optional leading article)
+examples/documentation_examples/examples.go:10:1: don't use an underscore in package name

+ 37 - 0
src/go.mongodb.org/mongo-driver/CONTRIBUTING.md

@@ -0,0 +1,37 @@
+# Contributing to the MongoDB Go Driver
+
+Thank you for your interest in contributing to the MongoDB Go driver.
+
+We are building this software together and strongly encourage contributions from the community that are within the guidelines set forth
+below.
+
+## Bug Fixes and New Features
+
+Before starting to write code, look for existing [tickets](https://jira.mongodb.org/browse/GODRIVER) or
+[create one](https://jira.mongodb.org/secure/CreateIssue!default.jspa) for your bug, issue, or feature request. This helps the community
+avoid working on something that might not be of interest or which has already been addressed.
+
+## Pull Requests & Patches
+
+The Go Driver team is experimenting with GerritHub for contributions. GerritHub uses GitHub for authentication and uses a patch based
+workflow. Since GerritHub supports importing of Pull Requests we will also accept Pull Requests, but Code Review will be done in
+GerritHub.
+
+Patches should generally be made against the master (default) branch and include relevant tests, if applicable.
+
+Code should compile and tests should pass under all go versions which the driver currently supports.  Currently the driver
+supports a minimum version of go 1.7. Please ensure the following tools have been run on the code: gofmt, golint, errcheck,
+go test (with coverage and with the race detector), and go vet. For convenience, you can run 'make' to run all these tools.
+**By default, running the tests requires that you have a mongod server running on localhost, listening on the default port.**
+At minimum, please test against the latest release version of the MongoDB server.
+
+If any tests do not pass, or relevant tests are not included, the patch will not be considered.
+
+If you are working on a bug or feature listed in Jira, please include the ticket number prefixed with GODRIVER in the commit,
+e.g. GODRIVER-123. For the patch commit message itself, please follow the [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) guide.
+
+## Talk To Us
+
+If you want to work on the driver, write documentation, or have questions/complaints, please reach out to use either via
+the [mongo-go-driver Google Group](https://groups.google.com/forum/#!forum/mongodb-go-driver) or by creating a Question
+issue at (https://jira.mongodb.org/secure/CreateIssue!default.jspa).

+ 367 - 0
src/go.mongodb.org/mongo-driver/Gopkg.lock

@@ -0,0 +1,367 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  digest = "1:6d8a3b164679872fa5a4c44559235f7fb109c7b5cd0f456a2159d579b76cc9ba"
+  name = "github.com/DataDog/zstd"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "809b919c325d7887bff7bd876162af73db53e878"
+  version = "v1.4.0"
+
+[[projects]]
+  digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
+  name = "github.com/davecgh/go-spew"
+  packages = ["spew"]
+  pruneopts = "UT"
+  revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
+  version = "v1.1.1"
+
+[[projects]]
+  digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d"
+  name = "github.com/go-stack/stack"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a"
+  version = "v1.8.0"
+
+[[projects]]
+  digest = "1:d29ee5ef14a7e0253facd0bcebe6a69a7a4e02a67eb24d2aacd8ccb4a7cea6fc"
+  name = "github.com/gobuffalo/envy"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "043cb4b8af871b49563291e32c66bb84378a60ac"
+  version = "v1.7.0"
+
+[[projects]]
+  digest = "1:149bf2c3b6b332f0cfefed9ae4b8c01c527927d369d7a0f41a211c8bf588b556"
+  name = "github.com/gobuffalo/genny"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "05dc5921e98daa5d406f90c2844ff163cb1e1c4e"
+  version = "v0.1.1"
+
+[[projects]]
+  digest = "1:3812ee37547a0d26633006c910672c42ddf6f932f79dea6a4446979a561f7d60"
+  name = "github.com/gobuffalo/gogen"
+  packages = [
+    ".",
+    "goimports",
+    "gomods",
+  ]
+  pruneopts = "UT"
+  revision = "5482e6ef5d999f8cfa9b038a53953c9b4ee090af"
+  version = "v0.1.1"
+
+[[projects]]
+  branch = "master"
+  digest = "1:f391538f4166f7c0aa50943372af196304488b039fe663c134d42f064e1ed92c"
+  name = "github.com/gobuffalo/logger"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "86e12af44bc273e0063fad5b6ad588890b8cfe6b"
+
+[[projects]]
+  digest = "1:a3259d2a6c245b62e40232800e3ebb4d7d5840940638de0e4754cb13cdd37790"
+  name = "github.com/gobuffalo/mapi"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "0bb5e840be332d4280e40f2e6c50777c615bbac5"
+  version = "v1.0.2"
+
+[[projects]]
+  digest = "1:1342bed0d7aded5e5c1c958675f0662c9dbaf2d61fa0a8c5918a144737cff3e4"
+  name = "github.com/gobuffalo/packd"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "9efbb667f2024a9370cadda45a57a0657c88a6fb"
+  version = "v0.1.0"
+
+[[projects]]
+  digest = "1:0fac441be66684180b031233313b8315644c0734fd73944c3e156e3cf5fbe203"
+  name = "github.com/gobuffalo/packr"
+  packages = [
+    "v2",
+    "v2/file",
+    "v2/file/resolver",
+    "v2/file/resolver/encoding/hex",
+    "v2/jam/parser",
+    "v2/plog",
+  ]
+  pruneopts = "UT"
+  revision = "cda4ac25577350dac682c8715dc6659327232596"
+  version = "v2.2.0"
+
+[[projects]]
+  branch = "master"
+  digest = "1:b90ac64448d67ef218124185a8524f3c665fb7f51f096d5dd51ce950a1775d89"
+  name = "github.com/gobuffalo/syncx"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "33c29581e754bd354236e977dfe426e55331c45d"
+
+[[projects]]
+  branch = "master"
+  digest = "1:e4f5819333ac698d294fe04dbf640f84719658d5c7ce195b10060cc37292ce79"
+  name = "github.com/golang/snappy"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "2a8bb927dd31d8daada140a5d09578521ce5c36a"
+
+[[projects]]
+  digest = "1:d2754cafcab0d22c13541618a8029a70a8959eb3525ff201fe971637e2274cd0"
+  name = "github.com/google/go-cmp"
+  packages = [
+    "cmp",
+    "cmp/cmpopts",
+    "cmp/internal/diff",
+    "cmp/internal/function",
+    "cmp/internal/value",
+  ]
+  pruneopts = "UT"
+  revision = "3af367b6b30c263d47e8895973edcca9a49cf029"
+  version = "v0.2.0"
+
+[[projects]]
+  digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b"
+  name = "github.com/joho/godotenv"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "23d116af351c84513e1946b527c88823e476be13"
+  version = "v1.3.0"
+
+[[projects]]
+  digest = "1:19cce3954c778b676622745e850f7d201eefffbb62d2c42ce87673617f96e95a"
+  name = "github.com/karrick/godirwalk"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "5e617d9cfec6f870e63b168e9a2fb7c490a1c108"
+  version = "v1.10.3"
+
+[[projects]]
+  digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de"
+  name = "github.com/konsorten/go-windows-terminal-sequences"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
+  version = "v1.0.2"
+
+[[projects]]
+  digest = "1:ca955a9cd5b50b0f43d2cc3aeb35c951473eeca41b34eb67507f1dbcc0542394"
+  name = "github.com/kr/pretty"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712"
+  version = "v0.1.0"
+
+[[projects]]
+  digest = "1:15b5cc79aad436d47019f814fde81a10221c740dc8ddf769221a65097fb6c2e9"
+  name = "github.com/kr/text"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f"
+  version = "v0.1.0"
+
+[[projects]]
+  branch = "master"
+  digest = "1:6e2ed1bdbf1d14b4d0be58bcd3f1c3000c1e226964354457b8e6ca69e83a1cbb"
+  name = "github.com/markbates/oncer"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "bf2de49a0be218916e69a11d22866e6cd0a560f2"
+
+[[projects]]
+  digest = "1:28687e854cec240942c103259668b132a8450b05a6cc677eea2282b26ae29310"
+  name = "github.com/markbates/safe"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "6fea05a5732486546a4836b7a1d596c5ec687b98"
+  version = "v1.0.1"
+
+[[projects]]
+  digest = "1:246ab598a22ea9d50f46e65f655f78161a19822f6597268b02f04af998684807"
+  name = "github.com/montanaflynn/stats"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "1bf9dbcd8cbe1fdb75add3785b1d4a9a646269ab"
+  version = "0.3.0"
+
+[[projects]]
+  digest = "1:93131d8002d7025da13582877c32d1fc302486775a1b06f62241741006428c5e"
+  name = "github.com/pelletier/go-toml"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "728039f679cbcd4f6a54e080d2219a4c4928c546"
+  version = "v1.4.0"
+
+[[projects]]
+  digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
+  name = "github.com/pkg/errors"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
+  version = "v0.8.1"
+
+[[projects]]
+  digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
+  name = "github.com/pmezard/go-difflib"
+  packages = ["difflib"]
+  pruneopts = "UT"
+  revision = "792786c7400a136282c1664665ae0a8db921c6c2"
+  version = "v1.0.0"
+
+[[projects]]
+  digest = "1:e09ada96a5a41deda4748b1659cc8953961799e798aea557257b56baee4ecaf3"
+  name = "github.com/rogpeppe/go-internal"
+  packages = [
+    "modfile",
+    "module",
+    "semver",
+  ]
+  pruneopts = "UT"
+  revision = "438578804ca6f31be148c27683afc419ce47c06e"
+  version = "v1.3.0"
+
+[[projects]]
+  digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976"
+  name = "github.com/sirupsen/logrus"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
+  version = "v1.4.2"
+
+[[projects]]
+  digest = "1:5da8ce674952566deae4dbc23d07c85caafc6cfa815b0b3e03e41979cedb8750"
+  name = "github.com/stretchr/testify"
+  packages = [
+    "assert",
+    "require",
+  ]
+  pruneopts = "UT"
+  revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
+  version = "v1.3.0"
+
+[[projects]]
+  branch = "master"
+  digest = "1:ddfe0a54e5f9b29536a6d7b2defa376f2cb2b6e4234d676d7ff214d5b097cb50"
+  name = "github.com/tidwall/pretty"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "1166b9ac2b65e46a43d8618d30d1554f4652d49b"
+
+[[projects]]
+  branch = "master"
+  digest = "1:40fdfd6ab85ca32b6935853bbba35935dcb1d796c8135efd85947566c76e662e"
+  name = "github.com/xdg/scram"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "7eeb5667e42c09cb51bf7b7c28aea8c56767da90"
+
+[[projects]]
+  branch = "master"
+  digest = "1:f5c1d04bc09c644c592b45b9f0bad4030521b1a7d11c7dadbb272d9439fa6e8e"
+  name = "github.com/xdg/stringprep"
+  packages = ["."]
+  pruneopts = "UT"
+  revision = "73f8eece6fdcd902c185bf651de50f3828bed5ed"
+
+[[projects]]
+  branch = "master"
+  digest = "1:35ce874cf16f78a2da908ab28b0277497e36603d289db00d39da5200bc8ace08"
+  name = "golang.org/x/crypto"
+  packages = [
+    "pbkdf2",
+    "ssh/terminal",
+  ]
+  pruneopts = "UT"
+  revision = "20be4c3c3ed52bfccdb2d59a412ee1a936d175a7"
+
+[[projects]]
+  branch = "master"
+  digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70"
+  name = "golang.org/x/net"
+  packages = ["context"]
+  pruneopts = "UT"
+  revision = "f3200d17e092c607f615320ecaad13d87ad9a2b3"
+
+[[projects]]
+  branch = "master"
+  digest = "1:382bb5a7fb4034db3b6a2d19e5a4a6bcf52f4750530603c01ca18a172fa3089b"
+  name = "golang.org/x/sync"
+  packages = ["semaphore"]
+  pruneopts = "UT"
+  revision = "112230192c580c3556b8cee6403af37a4fc5f28c"
+
+[[projects]]
+  branch = "master"
+  digest = "1:a02364950e8c7394b03f886e57a3f7acfc4fba32ce03c186dd9f36b501d0484b"
+  name = "golang.org/x/sys"
+  packages = [
+    "unix",
+    "windows",
+  ]
+  pruneopts = "UT"
+  revision = "4c3a928424d249a1013e789cf5e458d480bcba16"
+
+[[projects]]
+  digest = "1:1093f2eb4b344996604f7d8b29a16c5b22ab9e1b25652140d3fede39f640d5cd"
+  name = "golang.org/x/text"
+  packages = [
+    "internal/gen",
+    "internal/triegen",
+    "internal/ucd",
+    "transform",
+    "unicode/cldr",
+    "unicode/norm",
+  ]
+  pruneopts = "UT"
+  revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
+  version = "v0.3.2"
+
+[[projects]]
+  branch = "master"
+  digest = "1:7c7aa31cc6bc5c85038e46d8c6541a2fb2332b1687229a5a5a04d7b91cea0efe"
+  name = "golang.org/x/tools"
+  packages = [
+    "go/ast/astutil",
+    "go/gcexportdata",
+    "go/internal/gcimporter",
+    "go/internal/packagesdriver",
+    "go/packages",
+    "go/types/typeutil",
+    "imports",
+    "internal/fastwalk",
+    "internal/gopathwalk",
+    "internal/imports",
+    "internal/module",
+    "internal/semver",
+  ]
+  pruneopts = "UT"
+  revision = "b3315ee88b7da2b23cc6c62484a006958910e841"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  input-imports = [
+    "github.com/DataDog/zstd",
+    "github.com/go-stack/stack",
+    "github.com/gobuffalo/packr/v2",
+    "github.com/golang/snappy",
+    "github.com/google/go-cmp/cmp",
+    "github.com/google/go-cmp/cmp/cmpopts",
+    "github.com/kr/pretty",
+    "github.com/montanaflynn/stats",
+    "github.com/pelletier/go-toml",
+    "github.com/pkg/errors",
+    "github.com/stretchr/testify/assert",
+    "github.com/stretchr/testify/require",
+    "github.com/tidwall/pretty",
+    "github.com/xdg/scram",
+    "github.com/xdg/stringprep",
+    "golang.org/x/net/context",
+    "golang.org/x/sync/semaphore",
+    "golang.org/x/tools/go/packages",
+    "golang.org/x/tools/imports",
+  ]
+  solver-name = "gps-cdcl"
+  solver-version = 1

+ 58 - 0
src/go.mongodb.org/mongo-driver/Gopkg.toml

@@ -0,0 +1,58 @@
+# Gopkg.toml example
+#
+# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#   name = "github.com/x/y"
+#   version = "2.4.0"
+#
+# [prune]
+#   non-go = false
+#   go-tests = true
+#   unused-packages = true
+
+
+[[constraint]]
+  name = "github.com/go-stack/stack"
+  version = "1.7.0"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/golang/snappy"
+
+[[constraint]]
+  name = "github.com/google/go-cmp"
+  version = "0.2.0"
+
+[[constraint]]
+  name = "github.com/montanaflynn/stats"
+  version = "0.3.0"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/tidwall/pretty"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/xdg/stringprep"
+
+[prune]
+  go-tests = true
+  unused-packages = true
+
+[[constraint]]
+  name = "github.com/DataDog/zstd"
+  version = "~1.4.0"

+ 201 - 0
src/go.mongodb.org/mongo-driver/LICENSE

@@ -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.

+ 147 - 0
src/go.mongodb.org/mongo-driver/Makefile

@@ -0,0 +1,147 @@
+BSON_PKGS = $(shell etc/list_pkgs.sh ./bson)
+BSON_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./bson)
+MONGO_PKGS = $(shell etc/list_pkgs.sh ./mongo)
+MONGO_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./mongo)
+UNSTABLE_PKGS = $(shell etc/list_pkgs.sh ./x)
+UNSTABLE_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./x)
+TAG_PKG = $(shell etc/list_pkgs.sh ./tag)
+TAG_TEST_PKG = $(shell etc/list_test_pkgs.sh ./tag)
+EXAMPLES_PKGS = $(shell etc/list_pkgs.sh ./examples)
+EXAMPLES_TEST_PKGS = $(shell etc/list_test_pkgs.sh ./examples)
+PKGS = $(BSON_PKGS) $(MONGO_PKGS) $(UNSTABLE_PKGS) $(TAG_PKG) $(EXAMPLES_PKGS)
+TEST_PKGS = $(BSON_TEST_PKGS) $(MONGO_TEST_PKGS) $(UNSTABLE_TEST_PKGS) $(TAG_PKG) $(EXAMPLES_TEST_PKGS)
+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)"
+
+TEST_TIMEOUT = 600
+
+.PHONY: default
+default: check-env check-fmt vet build-examples lint errcheck test-cover test-race
+
+.PHONY: check-env
+check-env:
+	etc/check_env.sh
+
+.PHONY: doc
+doc:
+	godoc -http=:6060 -index
+
+.PHONY: build-examples
+build-examples:
+	go build $(BUILD_TAGS) ./examples/... ./x/mongo/driver/examples/...
+
+.PHONY: build
+build:
+	go build $(filter-out ./core/auth/internal/gssapi,$(PKGS))
+
+.PHONY: build-cse
+build-cse:
+	go build -tags cse $(filter-out ./core/auth/internal/gssapi,$(PKGS))
+
+.PHONY: check-fmt
+check-fmt:
+	etc/check_fmt.sh $(PKGS)
+
+.PHONY: fmt
+fmt:
+	gofmt -l -s -w $(PKGS)
+
+.PHONY: lint
+lint:
+	golint $(PKGS) | ./etc/lintscreen.pl .lint-whitelist
+
+.PHONY: lint-add-whitelist
+lint-add-whitelist:
+	golint $(PKGS) | ./etc/lintscreen.pl -u .lint-whitelist
+	sort .lint-whitelist -o .lint-whitelist
+
+.PHONY: errcheck
+errcheck:
+	errcheck -exclude .errcheck-excludes ./bson/... ./mongo/... ./x/...
+
+.PHONY: test
+test:
+	for TEST in $(TEST_PKGS) ; do \
+		go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s $$TEST ; \
+	done
+
+.PHONY: test-cover
+test-cover:
+	for TEST in $(TEST_PKGS) ; do \
+    	go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -cover $(COVER_ARGS) $$TEST ; \
+    done
+
+.PHONY: test-race
+test-race:
+	for TEST in $(TEST_PKGS) ; do \
+    	go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -race $(COVER_ARGS) $$TEST ; \
+    done
+
+.PHONY: test-short
+test-short:
+	for TEST in $(TEST_PKGS) ; do \
+    	go test $(BUILD_TAGS) -timeout $(TEST_TIMEOUT)s -short $(COVER_ARGS) $$TEST ; \
+    done
+
+.PHONY: update-bson-corpus-tests
+update-bson-corpus-tests:
+	etc/update-spec-tests.sh bson-corpus
+
+.PHONY: update-connection-string-tests
+update-connection-string-tests:
+	etc/update-spec-tests.sh connection-string
+
+.PHONY: update-crud-tests
+update-crud-tests:
+	etc/update-spec-tests.sh crud
+
+.PHONY: update-initial-dns-seedlist-discovery-tests
+update-initial-dns-seedlist-discovery-tests:
+	etc/update-spec-tests.sh initial-dns-seedlist-discovery
+
+.PHONY: update-max-staleness-tests
+update-max-staleness-tests:
+	etc/update-spec-tests.sh max-staleness
+
+.PHONY: update-server-discovery-and-monitoring-tests
+update-server-discovery-and-monitoring-tests:
+	etc/update-spec-tests.sh server-discovery-and-monitoring
+
+.PHONY: update-server-selection-tests
+update-server-selection-tests:
+	etc/update-spec-tests.sh server-selection
+
+.PHONY: update-notices
+update-notices:
+	etc/generate-notices.pl > THIRD-PARTY-NOTICES
+
+.PHONY: vet
+vet:
+	go vet -cgocall=false -composites=false -unusedstringmethods="Error" $(PKGS)
+
+
+# Evergreen specific targets
+.PHONY: evg-test
+evg-test:
+	for TEST in $(TEST_PKGS) ; do \
+		LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) go test $(BUILD_TAGS) -v -timeout $(TEST_TIMEOUT)s $$TEST >> test.suite ; \
+	done
+
+.PHONY: evg-test-auth
+evg-test-auth:
+	go run -tags gssapi ./x/mongo/driver/examples/count/main.go -uri $(MONGODB_URI)
+
+.PHONY: evg-test-atlas
+evg-test-atlas:
+	go run ./mongo/testatlas/main.go $(ATLAS_URIS)
+
+# benchmark specific targets and support
+perf:driver-test-data.tar.gz
+	tar -zxf $< $(if $(eq $(UNAME_S),Darwin),-s , --transform=s)/data/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
+benchmark:perf
+	go test $(BUILD_TAGS) -benchmem -bench=. ./benchmark
+driver-benchmark:perf
+	@go run cmd/godriver-benchmark/main.go | tee perf.suite
+.PHONY:benchmark driver-benchmark

+ 193 - 0
src/go.mongodb.org/mongo-driver/README.md

@@ -0,0 +1,193 @@
+<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://godoc.org/go.mongodb.org/mongo-driver/mongo"><img src="etc/assets/godoc-mongo-blue.svg" alt="GoDoc"></a>
+  <a href="https://godoc.org/go.mongodb.org/mongo-driver/bson"><img src="etc/assets/godoc-bson-blue.svg" alt="GoDoc"></a>
+  <a href="https://docs.mongodb.com/ecosystem/drivers/go/"><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)
+- [Bugs / Feature Reporting](#bugs--feature-reporting)
+- [Testing / Development](#testing--development)
+- [Continuous Integration](#continuous-integration)
+- [License](#license)
+
+-------------------------
+## Requirements
+
+- Go 1.10 or higher. We aim to support the latest supported versions of go.
+- MongoDB 2.6 and higher.
+
+-------------------------
+## Installation
+
+The recommended way to get started using the MongoDB Go driver is by using `dep` to install the dependency in your project.
+
+```bash
+dep ensure -add "go.mongodb.org/mongo-driver/mongo@~1.1.0"
+```
+
+-------------------------
+## Usage
+
+To get started with the driver, import the `mongo` package, create a `mongo.Client`:
+
+```go
+import (
+    "go.mongodb.org/mongo-driver/mongo"
+    "go.mongodb.org/mongo-driver/mongo/options"
+)
+
+client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
+```
+
+And connect it to your running MongoDB server:
+
+```go
+ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
+err = client.Connect(ctx)
+```
+
+To do this in a single step, you can use the `Connect` function:
+
+```go
+ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
+client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
+```
+
+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, _ = context.WithTimeout(context.Background(), 2*time.Second)
+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, _ = context.WithTimeout(context.Background(), 5*time.Second)
+res, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})
+id := res.InsertedID
+```
+
+Several query methods return a cursor, which can be used like this:
+
+```go
+ctx, _ = context.WithTimeout(context.Background(), 30*time.Second)
+cur, err := collection.Find(ctx, bson.D{})
+if err != nil { log.Fatal(err) }
+defer cur.Close(ctx)
+for cur.Next(ctx) {
+   var result bson.M
+   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.M{"name": "pi"}
+ctx, _ = context.WithTimeout(context.Background(), 5*time.Second)
+err = collection.FindOne(ctx, filter).Decode(&result)
+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://docs.mongodb.com/ecosystem/drivers/go/).
+
+-------------------------
+## Bugs / Feature Reporting
+
+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:27018/?replicaSet=rs1" make
+```
+
+### Testing Auth and SSL
+
+To test authentication and SSL, first set up a MongoDB cluster with auth and SSL configured. Testing authentication requires a user with the `root` role on the `admin` database. The Go Driver repository comes with example certificates in the `data/certificates` directory. These certs can be used for testing. Here is an example command that would run a mongod with SSL correctly configured for tests:
+
+```
+mongod \
+--auth \
+--sslMode requireSSL \
+--sslPEMKeyFile $(pwd)/data/certificates/server.pem \
+--sslCAFile $(pwd)/data/certificates/ca.pem \
+--sslWeakCertificateValidation
+```
+
+To run the tests with `make`, set `MONGO_GO_DRIVER_CA_FILE` to the location of the CA file used by the database, set `MONGODB_URI` to the connection string of the server, set `AUTH=auth`, and set `SSL=ssl`. For example:
+
+```
+AUTH=auth SSL=ssl MONGO_GO_DRIVER_CA_FILE=$(pwd)/data/certificates/ca.pem  MONGODB_URI="mongodb://user:password@localhost:27017/?authSource=admin" make
+```
+
+Notes:
+- The `--sslWeakCertificateValidation` 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://docs.mongodb.com/manual/reference/program/mongod/#cmdoption-mongod-networkmessagecompressors) on mongod or mongos includes `zlib` if testing zLib compression.
+
+-------------------------
+## Feedback
+
+The MongoDB Go Driver is not feature complete, so any help is appreciated. Check out the [project page](https://jira.mongodb.org/browse/GODRIVER)
+for tickets that need completing. See our [contribution guidelines](CONTRIBUTING.md) for details.
+
+-------------------------
+## Continuous Integration
+
+Commits to master are run automatically on [evergreen](https://evergreen.mongodb.com/waterfall/mongo-go-driver).
+
+-------------------------
+## 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).

+ 1336 - 0
src/go.mongodb.org/mongo-driver/THIRD-PARTY-NOTICES

@@ -0,0 +1,1336 @@
+---------------------------------------------------------------------
+License notice for gopkg.in/mgo.v2/bson
+---------------------------------------------------------------------
+
+BSON library for Go
+
+Copyright (c) 2010-2013 - Gustavo Niemeyer <gustavo@niemeyer.net>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met: 
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer. 
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---------------------------------------------------------------------
+License notice for JSON and CSV code from github.com/golang/go
+---------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/davecgh/go-spew
+----------------------------------------------------------------------
+
+ISC License
+
+Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/genny
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2019 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/genny/genny
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright © 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/gogen
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2019 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/gogen/goimports
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/logger
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/mapi
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/packd
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/packr/v2/packr2
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright © 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/gobuffalo/syncx
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/golang/snappy
+----------------------------------------------------------------------
+
+Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/google/go-cmp
+----------------------------------------------------------------------
+
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/karrick/godirwalk
+----------------------------------------------------------------------
+
+BSD 2-Clause License
+
+Copyright (c) 2017, Karrick McDermott
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/konsorten/go-windows-terminal-sequences
+----------------------------------------------------------------------
+
+(The MIT License)
+
+Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/kr/pretty
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright 2012 Keith Rarick
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/kr/text
+----------------------------------------------------------------------
+
+Copyright 2012 Keith Rarick
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/markbates/oncer
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/markbates/safe
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2018 Mark Bates
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/montanaflynn/stats
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2014-2015 Montana Flynn (https://anonfunction.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/pelletier/go-toml
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/pkg/errors
+----------------------------------------------------------------------
+
+Copyright (c) 2015, Dave Cheney <dave@cheney.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/pmezard/go-difflib
+----------------------------------------------------------------------
+
+Copyright (c) 2013, Patrick Mezard
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+    Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+    The names of its contributors may not be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/rogpeppe/go-internal
+----------------------------------------------------------------------
+
+Copyright (c) 2018 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for github.com/sirupsen/logrus
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Simon Eskildsen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/stretchr/testify
+----------------------------------------------------------------------
+
+MIT License
+
+Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/tidwall/pretty
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2017 Josh Baker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----------------------------------------------------------------------
+License notice for github.com/xdg/scram
+----------------------------------------------------------------------
+
+
+                                 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.
+
+----------------------------------------------------------------------
+License notice for github.com/xdg/stringprep
+----------------------------------------------------------------------
+
+
+                                 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.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/crypto
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/net
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/sync
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/sys
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/text
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/tools
+----------------------------------------------------------------------
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+License notice for golang.org/x/tools/cmd/getgo
+----------------------------------------------------------------------
+
+Copyright (c) 2017 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 75 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson.go

@@ -0,0 +1,75 @@
+// 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 benchmark
+
+import (
+	"errors"
+	"io/ioutil"
+	"path/filepath"
+
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+const (
+	perfDataDir  = "perf"
+	bsonDataDir  = "extended_bson"
+	flatBSONData = "flat_bson.json"
+	deepBSONData = "deep_bson.json"
+	fullBSONData = "full_bson.json"
+)
+
+// utility functions for the bson benchmarks
+
+func loadSourceDocument(pathParts ...string) (bsonx.Doc, error) {
+	data, err := ioutil.ReadFile(filepath.Join(pathParts...))
+	if err != nil {
+		return nil, err
+	}
+	doc := bsonx.Doc{}
+	err = bson.UnmarshalExtJSON(data, true, &doc)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(doc) == 0 {
+		return nil, errors.New("empty bson document")
+	}
+
+	return doc, nil
+}
+
+func loadSourceRaw(pathParts ...string) (bson.Raw, error) {
+	doc, err := loadSourceDocument(pathParts...)
+	if err != nil {
+		return nil, err
+	}
+	raw, err := doc.MarshalBSON()
+	if err != nil {
+		return nil, err
+	}
+
+	return bson.Raw(raw), nil
+}
+
+func loadSourceD(pathParts ...string) (bson.D, error) {
+	data, err := ioutil.ReadFile(filepath.Join(pathParts...))
+	if err != nil {
+		return nil, err
+	}
+	doc := bson.D{}
+	err = bson.UnmarshalExtJSON(data, true, &doc)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(doc) == 0 {
+		return nil, errors.New("empty bson document")
+	}
+
+	return doc, nil
+}

+ 123 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_document.go

@@ -0,0 +1,123 @@
+// 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 benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+func bsonDocumentEncoding(ctx context.Context, tm TimerManager, iters int, source string) error {
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, bsonDataDir, source)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out, err := doc.MarshalBSON()
+		if err != nil {
+			return err
+		}
+		if len(out) == 0 {
+			return errors.New("marshaling error")
+		}
+	}
+
+	return nil
+}
+
+func bsonDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int, source string) error {
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, bsonDataDir, source)
+	if err != nil {
+		return err
+	}
+
+	raw, err := doc.MarshalBSON()
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out, err := bsonx.ReadDoc(raw)
+		if err != nil {
+			return err
+		}
+		if len(out) == 0 {
+			return errors.New("marshaling error")
+		}
+	}
+	return nil
+}
+
+func bsonDocumentDecoding(ctx context.Context, tm TimerManager, iters, numKeys int, source string) error {
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, bsonDataDir, source)
+	if err != nil {
+		return err
+	}
+
+	raw, err := doc.MarshalBSON()
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out, err := bsonx.ReadDoc(raw)
+		if err != nil {
+			return err
+		}
+
+		if len(out) != numKeys {
+			return errors.New("document parsing error")
+		}
+	}
+	return nil
+
+}
+
+func BSONFlatDocumentEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentEncoding(ctx, tm, iters, flatBSONData)
+}
+
+func BSONFlatDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecodingLazy(ctx, tm, iters, flatBSONData)
+}
+
+func BSONFlatDocumentDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecoding(ctx, tm, iters, 145, flatBSONData)
+}
+
+func BSONDeepDocumentEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentEncoding(ctx, tm, iters, deepBSONData)
+}
+
+func BSONDeepDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecodingLazy(ctx, tm, iters, deepBSONData)
+}
+
+func BSONDeepDocumentDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecoding(ctx, tm, iters, 126, deepBSONData)
+}
+
+func BSONFullDocumentEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentEncoding(ctx, tm, iters, fullBSONData)
+}
+
+func BSONFullDocumentDecodingLazy(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecodingLazy(ctx, tm, iters, fullBSONData)
+}
+
+func BSONFullDocumentDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonDocumentDecoding(ctx, tm, iters, 145, fullBSONData)
+}

+ 88 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_map.go

@@ -0,0 +1,88 @@
+// 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 benchmark
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+func bsonMapDecoding(ctx context.Context, tm TimerManager, iters int, dataSet string) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, dataSet)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out := make(map[string]interface{})
+		err := bson.Unmarshal(r, &out)
+		if err != nil {
+			return nil
+		}
+		if len(out) == 0 {
+			return fmt.Errorf("decoding failed")
+		}
+	}
+	return nil
+}
+
+func bsonMapEncoding(ctx context.Context, tm TimerManager, iters int, dataSet string) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, dataSet)
+	if err != nil {
+		return err
+	}
+
+	doc := make(map[string]interface{})
+	err = bson.Unmarshal(r, &doc)
+	if err != nil {
+		return err
+	}
+
+	var buf []byte
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		buf, err = bson.MarshalAppend(buf[:0], doc)
+		if err != nil {
+			return nil
+		}
+
+		if len(buf) == 0 {
+			return errors.New("encoding failed")
+		}
+	}
+
+	return nil
+}
+
+func BSONFlatMapDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapDecoding(ctx, tm, iters, flatBSONData)
+}
+
+func BSONFlatMapEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapEncoding(ctx, tm, iters, flatBSONData)
+}
+
+func BSONDeepMapDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapDecoding(ctx, tm, iters, deepBSONData)
+}
+
+func BSONDeepMapEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapEncoding(ctx, tm, iters, deepBSONData)
+}
+
+func BSONFullMapDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapDecoding(ctx, tm, iters, fullBSONData)
+}
+
+func BSONFullMapEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	return bsonMapEncoding(ctx, tm, iters, fullBSONData)
+}

+ 103 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_struct.go

@@ -0,0 +1,103 @@
+// 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 benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+func BSONFlatStructDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		out := flatBSON{}
+		err := bson.Unmarshal(r, &out)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func BSONFlatStructEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	doc := flatBSON{}
+	err = bson.Unmarshal(r, &doc)
+	if err != nil {
+		return err
+	}
+
+	var buf []byte
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		buf, err = bson.Marshal(doc)
+		if err != nil {
+			return err
+		}
+		if len(buf) == 0 {
+			return errors.New("encoding failed")
+		}
+	}
+	return nil
+}
+
+func BSONFlatStructTagsEncoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	doc := flatBSONTags{}
+	err = bson.Unmarshal(r, &doc)
+	if err != nil {
+		return err
+	}
+
+	var buf []byte
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		buf, err = bson.MarshalAppend(buf[:0], doc)
+		if err != nil {
+			return err
+		}
+		if len(buf) == 0 {
+			return errors.New("encoding failed")
+		}
+	}
+	return nil
+}
+
+func BSONFlatStructTagsDecoding(ctx context.Context, tm TimerManager, iters int) error {
+	r, err := loadSourceRaw(getProjectRoot(), perfDataDir, bsonDataDir, flatBSONData)
+	if err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		out := flatBSONTags{}
+		err := bson.Unmarshal(r, &out)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 35 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_test.go

@@ -0,0 +1,35 @@
+// 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 benchmark
+
+import "testing"
+
+// func BenchmarkBSONFullReaderDecoding(b *testing.B)       { WrapCase(BSONFullReaderDecoding)(b) }
+
+func BenchmarkBSONFlatDocumentEncoding(b *testing.B)     { WrapCase(BSONFlatDocumentEncoding)(b) }
+func BenchmarkBSONFlatDocumentDecodingLazy(b *testing.B) { WrapCase(BSONFlatDocumentDecodingLazy)(b) }
+func BenchmarkBSONFlatDocumentDecoding(b *testing.B)     { WrapCase(BSONFlatDocumentDecoding)(b) }
+func BenchmarkBSONDeepDocumentEncoding(b *testing.B)     { WrapCase(BSONDeepDocumentEncoding)(b) }
+func BenchmarkBSONDeepDocumentDecodingLazy(b *testing.B) { WrapCase(BSONDeepDocumentDecodingLazy)(b) }
+func BenchmarkBSONDeepDocumentDecoding(b *testing.B)     { WrapCase(BSONDeepDocumentDecoding)(b) }
+
+// func BenchmarkBSONFullDocumentEncoding(b *testing.B)     { WrapCase(BSONFullDocumentEncoding)(b) }
+// func BenchmarkBSONFullDocumentDecodingLazy(b *testing.B) { WrapCase(BSONFullDocumentDecodingLazy)(b) }
+// func BenchmarkBSONFullDocumentDecoding(b *testing.B)     { WrapCase(BSONFullDocumentDecoding)(b) }
+
+func BenchmarkBSONFlatMapDecoding(b *testing.B) { WrapCase(BSONFlatMapDecoding)(b) }
+func BenchmarkBSONFlatMapEncoding(b *testing.B) { WrapCase(BSONFlatMapEncoding)(b) }
+func BenchmarkBSONDeepMapDecoding(b *testing.B) { WrapCase(BSONDeepMapDecoding)(b) }
+func BenchmarkBSONDeepMapEncoding(b *testing.B) { WrapCase(BSONDeepMapEncoding)(b) }
+
+// func BenchmarkBSONFullMapDecoding(b *testing.B)       { WrapCase(BSONFullMapDecoding)(b) }
+// func BenchmarkBSONFullMapEncoding(b *testing.B)       { WrapCase(BSONFullMapEncoding)(b) }
+
+func BenchmarkBSONFlatStructDecoding(b *testing.B)     { WrapCase(BSONFlatStructDecoding)(b) }
+func BenchmarkBSONFlatStructTagsDecoding(b *testing.B) { WrapCase(BSONFlatStructTagsDecoding)(b) }
+func BenchmarkBSONFlatStructEncoding(b *testing.B)     { WrapCase(BSONFlatStructEncoding)(b) }
+func BenchmarkBSONFlatStructTagsEncoding(b *testing.B) { WrapCase(BSONFlatStructTagsEncoding)(b) }

+ 306 - 0
src/go.mongodb.org/mongo-driver/benchmark/bson_types.go

@@ -0,0 +1,306 @@
+// 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 benchmark
+
+import "go.mongodb.org/mongo-driver/bson/primitive"
+
+type flatBSONTags struct {
+	ID primitive.ObjectID `bson:"_id"`
+
+	AA  int64  `bson:"AAgSNVyBb"`
+	AI  bool   `bson:"aicoMxZq"`
+	AM  int64  `bson:"AMQrGQmu"`
+	Ag  int    `bson:"AgYYbYPr"`
+	Ah  int64  `bson:"ahFCBmqT"`
+	At  int64  `bson:"AtWNZJXa"`
+	BB  string `bson:"BBqZInWV"`
+	BK  int64  `bson:"bkuaZWRT"`
+	Bw  int    `bson:"BwTXiovJ"`
+	CD  int    `bson:"CDIGOuIZ"`
+	CEA string `bson:"CEtYKsdd"`
+	CEB string `bson:"cepcgozk"`
+	CF  int    `bson:"CFujXoob"`
+	CV  int64  `bson:"cVjWCrlu"`
+	CX  string `bson:"cxOHMeDJ"`
+	CY  string `bson:"CYhSCkWB"`
+	Cq  string `bson:"CqCssWxW"`
+	DC  int    `bson:"dCLfYqqM"`
+	DDA int    `bson:"ddPdLgGg"`
+	DDB int    `bson:"ddVenEkK"`
+	DH  string `bson:"dHsYhRbV"`
+	DJ  int    `bson:"DJsnHZIC"`
+	DN  string `bson:"dNSuxlSU"`
+	DO  int64  `bson:"doshbrpF"`
+	DP  string `bson:"dpbwfSRb"`
+	DQ  int64  `bson:"DQBQcQFj"`
+	DT  string `bson:"dtywOLeD"`
+	DV  int    `bson:"dVkWIafN"`
+	EG  bool   `bson:"egxZaSsw"`
+	ER  string `bson:"eRTIdIJR"`
+	FD  int64  `bson:"FDYGeSiR"`
+	FE  string `bson:"fEheUtop"`
+	Fp  bool   `bson:"FpduyhQP"`
+	GE  string `bson:"gErhgZTh"`
+	GY  int    `bson:"gySFZeAE"`
+	Gi  uint   `bson:"GiAHzFII"`
+	HN  string `bson:"hnVgYIQi"`
+	HQA int    `bson:"HQeCoswW"`
+	HQB int    `bson:"HQiykral"`
+	HV  int64  `bson:"HVHyetUM"`
+	HW  int    `bson:"hwHOTmmW"`
+	Hi  bool   `bson:"HicJbMpj"`
+	Hr  int    `bson:"HrUPbFHD"`
+	IF  string `bson:"iFFGfTXc"`
+	IJ  int    `bson:"ijwXMKqI"`
+	IW  int    `bson:"iwfbMdcv"`
+	Ib  string `bson:"Ibrdrtgg"`
+	Is  bool   `bson:"IsorvnMR"`
+	JB  string `bson:"jbUymqiB"`
+	JM  string `bson:"jmglLvAS"`
+	JW  int    `bson:"jWaFvVAz"`
+	JX  int    `bson:"JXMyYkfb"`
+	Jh  bool   `bson:"JhImQOkw"`
+	Jr  string `bson:"JrJzKiIx"`
+	Jz  int    `bson:"JzgaUWVG"`
+	KF  bool   `bson:"kfvcFmKw"`
+	KM  int64  `bson:"KMKBtlov"`
+	Kn  string `bson:"KnhgtAOJ"`
+	Ky  int    `bson:"KyxOoCqS"`
+	LU  string `bson:"LUPqMOHS"`
+	LV  bool   `bson:"LVNIFCYm"`
+	Ln  int    `bson:"LngvlnTV"`
+	ML  int    `bson:"mlfZVfVT"`
+	MN  bool   `bson:"MNuWZMLP"`
+	MX  int    `bson:"MXMxLVBk"`
+	Mc  string `bson:"McpOBmaR"`
+	Me  string `bson:"MeUYSkPS"`
+	Mq  int    `bson:"MqfkBZJF"`
+	NB  int    `bson:"nBKWWUWk"`
+	NK  int    `bson:"nKhiSITP"`
+	OB  int    `bson:"obcwwqWZ"`
+	OC  string `bson:"OCsIhHxq"`
+	OM  int    `bson:"omnwvBbA"`
+	OR  string `bson:"oRWMNJTE"`
+	Of  string `bson:"OfTmCvDx"`
+	PA  int    `bson:"pacTBmxE"`
+	PF  int    `bson:"PFZSRHNN"`
+	PK  bool   `bson:"pKjOghFa"`
+	PO  int    `bson:"pOMEwSod"`
+	PP  string `bson:"pPtPsgRl"`
+	PQ  int    `bson:"pQyCJaEd"`
+	Pj  int    `bson:"PjKiuWnQ"`
+	Pv  int    `bson:"PvfnpsMV"`
+	QH  int    `bson:"qHzOMXeT"`
+	QR  bool   `bson:"qrJASGzU"`
+	Qo  string `bson:"QobifTeZ"`
+	RE  int64  `bson:"reiKnuza"`
+	RM  string `bson:"rmzUAgmk"`
+	RP  string `bson:"RPsQhgRD"`
+	Rb  uint   `bson:"Rbxpznea"`
+	ReA bool   `bson:"RemSsnnR"`
+	ReB int    `bson:"ReOZakjB"`
+	Rw  string `bson:"RwAVVKHM"`
+	SG  bool   `bson:"sGWJTAcT"`
+	SU  uint8  `bson:"SUWXijHT"`
+	SYA int64  `bson:"sYtnozSc"`
+	SYB string `bson:"SYtZkQbC"`
+	Sq  int64  `bson:"SqNvlUZF"`
+	TA  int    `bson:"taoNnQYY"`
+	TD  string `bson:"TDUzNJiH"`
+	TI  string `bson:"tIJEYSYM"`
+	TR  bool   `bson:"TRpgnInA"`
+	Tg  int    `bson:"TgSwBbgp"`
+	Tk  int64  `bson:"TkXMwZlU"`
+	Tm  int64  `bson:"TmUnYUrv"`
+	UK  int    `bson:"UKwbAKGw"`
+	UM  string `bson:"uMDWqLMf"`
+	Up  bool   `bson:"UpdMADoN"`
+	Ut  int64  `bson:"UtbwOKLt"`
+	VC  int64  `bson:"VCSKFCoE"`
+	VK  string `bson:"vkEDWgmN"`
+	VL  string `bson:"vlSZaxCV"`
+	VS  string `bson:"vSLTtfDF"`
+	VVA bool   `bson:"vvUeXASH"`
+	VVB int    `bson:"VVvwKVRG"`
+	Vc  bool   `bson:"VcCSqSmp"`
+	Vp  int16  `bson:"VplFgewF"`
+	Vt  string `bson:"VtzeOlCT"`
+	WH  bool   `bson:"WHSQVLKG"`
+	WJA bool   `bson:"wjfyueDC"`
+	WJB string `bson:"wjAWaOog"`
+	WM  int64  `bson:"wmDLUkXt"`
+	WY  string `bson:"WYJdGJLu"`
+	Wm  bool   `bson:"WmMOvgFc"`
+	Wo  string `bson:"WoFGfdvb"`
+	XE  int    `bson:"XEBqaXkB"`
+	XG  bool   `bson:"XGxlHrXf"`
+	XR  string `bson:"xrzGnsEK"`
+	XWA int64  `bson:"xWpeGNjl"`
+	XWB string `bson:"xWUlYggc"`
+	XX  int64  `bson:"XXKbyIXG"`
+	XZ  int64  `bson:"xZOksssj"`
+	Xe  uint   `bson:"XeRkAyCq"`
+	Xx  int    `bson:"XxvXmHiQ"`
+	YD  string `bson:"YDHWnEXV"`
+	YE  bool   `bson:"yeTUgNrU"`
+	YK  int    `bson:"yKfZnGKG"`
+	YX  string `bson:"yXSBbPeT"`
+	ZD  bool   `bson:"zDzSGNnW"`
+	ZE  bool   `bson:"zEgGhhZf"`
+	ZM  string `bson:"zMCFzcWY"`
+	ZSA int64  `bson:"zSYvADVf"`
+	ZSB int64  `bson:"zswQbWEI"`
+	Zm  string `bson:"ZmtEJFSO"`
+}
+
+type flatBSON struct {
+	AMQrGQmu  int64
+	AAgSNVyBb int64
+	AgYYbYPr  int
+	AtWNZJXa  int64
+	BBqZInWV  string
+	BwTXiovJ  int
+	CDIGOuIZ  int
+	CEtYKsdd  string
+	CFujXoob  int
+	CYhSCkWB  string
+	CqCssWxW  string
+	DJsnHZIC  int
+	DQBQcQFj  int64
+	FDYGeSiR  int64
+	FpduyhQP  bool
+	GiAHzFII  uint
+	HQeCoswW  int
+	HQiykral  int
+	HVHyetUM  int64
+	HicJbMpj  bool
+	HrUPbFHD  int
+	Ibrdrtgg  string
+	IsorvnMR  bool
+	JXMyYkfb  int
+	JhImQOkw  bool
+	JrJzKiIx  string
+	JzgaUWVG  int
+	KMKBtlov  int64
+	KnhgtAOJ  string
+	KyxOoCqS  int
+	LUPqMOHS  string
+	LVNIFCYm  bool
+	LngvlnTV  int
+	MNuWZMLP  bool
+	MXMxLVBk  int
+	McpOBmaR  string
+	MeUYSkPS  string
+	MqfkBZJF  int
+	OCsIhHxq  string
+	OfTmCvDx  string
+	PjKiuWnQ  int
+	PvfnpsMV  int
+	QobifTeZ  string
+	RPsQhgRD  string
+	Rbxpznea  uint
+	ReOZakjB  int
+	RemSsnnR  bool
+	RwAVVKHM  string
+	SUWXijHT  uint8
+	SYtZkQbC  string
+	SqNvlUZF  int64
+	TDUzNJiH  string
+	TRpgnInA  bool
+	TgSwBbgp  int
+	TkXMwZlU  int64
+	TmUnYUrv  int64
+	UKwbAKGw  int
+	UpdMADoN  bool
+	UtbwOKLt  int64
+	VCSKFCoE  int64
+	VVvwKVRG  int
+	VcCSqSmp  bool
+	VplFgewF  int16
+	VtzeOlCT  string
+	WHSQVLKG  bool
+	WYJdGJLu  string
+	WmMOvgFc  bool
+	WoFGfdvb  string
+	XEBqaXkB  int
+	XGxlHrXf  bool
+	XXKbyIXG  int64
+	XeRkAyCq  uint
+	XxvXmHiQ  int
+	YDHWnEXV  string
+	ZmtEJFSO  string
+	ID        primitive.ObjectID `bson:"_id"`
+	AhFCBmqT  int64
+	AicoMxZq  bool
+	BkuaZWRT  int64
+	CVjWCrlu  int64
+	Cepcgozk  string
+	CxOHMeDJ  string
+	DCLfYqqM  int
+	DHsYhRbV  string
+	DNSuxlSU  string
+	DVkWIafN  int
+	DdPdLgGg  int
+	DdVenEkK  int
+	DoshbrpF  int64
+	DpbwfSRb  string
+	DtywOLeD  string
+	ERTIdIJR  string
+	EgxZaSsw  bool
+	FEheUtop  string
+	GErhgZTh  string
+	GySFZeAE  int
+	HnVgYIQi  string
+	HwHOTmmW  int
+	IFFGfTXc  string
+	IjwXMKqI  int
+	IwfbMdcv  int
+	JWaFvVAz  int
+	JbUymqiB  string
+	JmglLvAS  string
+	KfvcFmKw  bool
+	MlfZVfVT  int
+	NBKWWUWk  int
+	NKhiSITP  int
+	ORWMNJTE  string
+	ObcwwqWZ  int
+	OmnwvBbA  int
+	PKjOghFa  bool
+	POMEwSod  int
+	PPtPsgRl  string
+	PQyCJaEd  int
+	PacTBmxE  int
+	QHzOMXeT  int
+	QrJASGzU  bool
+	ReiKnuza  int64
+	RmzUAgmk  string
+	SGWJTAcT  bool
+	SYtnozSc  int64
+	TIJEYSYM  string
+	TaoNnQYY  int
+	UMDWqLMf  string
+	VSLTtfDF  string
+	VkEDWgmN  string
+	VlSZaxCV  string
+	VvUeXASH  bool
+	WjAWaOog  string
+	WjfyueDC  bool
+	WmDLUkXt  int64
+	XWUlYggc  string
+	XWpeGNjl  int64
+	XZOksssj  int64
+	XrzGnsEK  string
+	YKfZnGKG  int
+	YXSBbPeT  string
+	YeTUgNrU  bool
+	ZDzSGNnW  bool
+	ZEgGhhZf  bool
+	ZMCFzcWY  string
+	ZSYvADVf  int64
+	ZswQbWEI  int64
+	PfZSRHnn  int
+}

+ 29 - 0
src/go.mongodb.org/mongo-driver/benchmark/canary.go

@@ -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 benchmark
+
+import (
+	"context"
+)
+
+func CanaryIncCase(ctx context.Context, tm TimerManager, iters int) error {
+	var canaryCount int
+	for i := 0; i < iters; i++ {
+		canaryCount++
+	}
+	return nil
+}
+
+var globalCanaryCount int
+
+func GlobalCanaryIncCase(ctx context.Context, tm TimerManager, iters int) error {
+	for i := 0; i < iters; i++ {
+		globalCanaryCount++
+	}
+
+	return nil
+}

+ 12 - 0
src/go.mongodb.org/mongo-driver/benchmark/canary_test.go

@@ -0,0 +1,12 @@
+// 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 benchmark
+
+import "testing"
+
+func BenchmarkCanaryInc(b *testing.B)       { WrapCase(CanaryIncCase)(b) }
+func BenchmarkGlobalCanaryInc(b *testing.B) { WrapCase(GlobalCanaryIncCase)(b) }

+ 226 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness.go

@@ -0,0 +1,226 @@
+// 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 benchmark // import "go.mongodb.org/mongo-driver/benchmark"
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+const (
+	five            = 5
+	ten             = 2 * five
+	hundred         = ten * ten
+	thousand        = ten * hundred
+	tenThousand     = ten * thousand
+	hundredThousand = hundred * thousand
+	million         = hundred * hundredThousand
+	halfMillion     = five * hundredThousand
+
+	ExecutionTimeout = five * time.Minute
+	StandardRuntime  = time.Minute
+	MinimumRuntime   = five * time.Second
+	MinIterations    = hundred
+)
+
+type BenchCase func(context.Context, TimerManager, int) error
+type BenchFunction func(*testing.B)
+
+func WrapCase(bench BenchCase) BenchFunction {
+	name := getName(bench)
+	return func(b *testing.B) {
+		ctx := context.Background()
+		b.ResetTimer()
+		err := bench(ctx, b, b.N)
+		require.NoError(b, err, "case='%s'", name)
+	}
+}
+
+func getAllCases() []*CaseDefinition {
+	return []*CaseDefinition{
+		{
+			Bench:              CanaryIncCase,
+			Count:              million,
+			Size:               -1,
+			Runtime:            MinimumRuntime,
+			RequiredIterations: ten,
+		},
+		{
+			Bench:              GlobalCanaryIncCase,
+			Count:              million,
+			Size:               -1,
+			Runtime:            MinimumRuntime,
+			RequiredIterations: ten,
+		},
+		{
+			Bench:   BSONFlatDocumentEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatDocumentDecodingLazy,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatDocumentDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepDocumentEncoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepDocumentDecodingLazy,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepDocumentDecoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		// {
+		//	Bench:   BSONFullDocumentEncoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullDocumentDecodingLazy,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullDocumentDecoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullReaderDecoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		{
+			Bench:   BSONFlatMapDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatMapEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepMapDecoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONDeepMapEncoding,
+			Count:   tenThousand,
+			Size:    19640000,
+			Runtime: StandardRuntime,
+		},
+		// {
+		//	Bench:   BSONFullMapDecoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		// {
+		//	Bench:   BSONFullMapEncoding,
+		//	Count:   tenThousand,
+		//	Size:    57340000,
+		//	Runtime: StandardRuntime,
+		// },
+		{
+			Bench:   BSONFlatStructDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatStructTagsDecoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatStructEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   BSONFlatStructTagsEncoding,
+			Count:   tenThousand,
+			Size:    75310000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleRunCommand,
+			Count:   tenThousand,
+			Size:    160000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleFindOneByID,
+			Count:   tenThousand,
+			Size:    16220000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleInsertSmallDocument,
+			Count:   tenThousand,
+			Size:    2750000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   SingleInsertLargeDocument,
+			Count:   ten,
+			Size:    27310890,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   MultiFindMany,
+			Count:   tenThousand,
+			Size:    16220000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:   MultiInsertSmallDocument,
+			Count:   tenThousand,
+			Size:    2750000,
+			Runtime: StandardRuntime,
+		},
+		{
+			Bench:              MultiInsertLargeDocument,
+			Count:              ten,
+			Size:               27310890,
+			Runtime:            StandardRuntime,
+			RequiredIterations: tenThousand,
+		},
+	}
+}

+ 154 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness_case.go

@@ -0,0 +1,154 @@
+// 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 benchmark
+
+import (
+	"context"
+	"fmt"
+	"path/filepath"
+	"reflect"
+	"runtime"
+	"strings"
+	"time"
+)
+
+type CaseDefinition struct {
+	Bench              BenchCase
+	Count              int
+	Size               int
+	RequiredIterations int
+	Runtime            time.Duration
+
+	cumulativeRuntime time.Duration
+	elapsed           time.Duration
+	startAt           time.Time
+	isRunning         bool
+}
+
+// TimerManager is a subset of the testing.B tool, used to manage
+// setup code.
+type TimerManager interface {
+	ResetTimer()
+	StartTimer()
+	StopTimer()
+}
+
+func (c *CaseDefinition) ResetTimer() {
+	c.startAt = time.Now()
+	c.elapsed = 0
+	c.isRunning = true
+}
+
+func (c *CaseDefinition) StartTimer() {
+	c.startAt = time.Now()
+	c.isRunning = true
+}
+
+func (c *CaseDefinition) StopTimer() {
+	if !c.isRunning {
+		return
+	}
+	c.elapsed += time.Since(c.startAt)
+	c.isRunning = false
+}
+
+func (c *CaseDefinition) roundedRuntime() time.Duration {
+	return roundDurationMS(c.Runtime)
+}
+
+func (c *CaseDefinition) Run(ctx context.Context) *BenchResult {
+	out := &BenchResult{
+		Trials:     1,
+		DataSize:   c.Size,
+		Name:       c.Name(),
+		Operations: c.Count,
+	}
+	var cancel context.CancelFunc
+	ctx, cancel = context.WithTimeout(ctx, 2*ExecutionTimeout)
+	defer cancel()
+
+	fmt.Println("=== RUN", out.Name)
+	if c.RequiredIterations == 0 {
+		c.RequiredIterations = MinIterations
+	}
+
+benchRepeat:
+	for {
+		if ctx.Err() != nil {
+			break
+		}
+		if c.cumulativeRuntime >= c.Runtime {
+			if out.Trials >= c.RequiredIterations {
+				break
+			} else if c.cumulativeRuntime >= ExecutionTimeout {
+				break
+			}
+		}
+
+		res := Result{
+			Iterations: c.Count,
+		}
+
+		c.StartTimer()
+		res.Error = c.Bench(ctx, c, c.Count)
+		c.StopTimer()
+		res.Duration = c.elapsed
+		c.cumulativeRuntime += res.Duration
+
+		switch res.Error {
+		case context.DeadlineExceeded:
+			break benchRepeat
+		case context.Canceled:
+			break benchRepeat
+		case nil:
+			out.Trials++
+			c.elapsed = 0
+			out.Raw = append(out.Raw, res)
+		default:
+			continue
+		}
+
+	}
+
+	out.Duration = out.totalDuration()
+	fmt.Printf("    --- REPORT: count=%d trials=%d requiredTrials=%d runtime=%s\n",
+		c.Count, out.Trials, c.RequiredIterations, c.Runtime)
+	if out.HasErrors() {
+		fmt.Printf("    --- ERRORS: %s\n", strings.Join(out.errReport(), "\n       "))
+		fmt.Printf("--- FAIL: %s (%s)\n", out.Name, out.roundedRuntime())
+	} else {
+		fmt.Printf("--- PASS: %s (%s)\n", out.Name, out.roundedRuntime())
+	}
+
+	return out
+
+}
+
+func (c *CaseDefinition) String() string {
+	return fmt.Sprintf("name=%s, count=%d, runtime=%s timeout=%s",
+		c.Name(), c.Count, c.Runtime, ExecutionTimeout)
+}
+
+func (c *CaseDefinition) Name() string { return getName(c.Bench) }
+func getName(i interface{}) string {
+	n := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
+	parts := strings.Split(n, ".")
+	if len(parts) > 1 {
+		return parts[len(parts)-1]
+	}
+
+	return n
+
+}
+
+func getProjectRoot() string { return filepath.Dir(getDirectoryOfFile()) }
+
+func getDirectoryOfFile() string {
+	_, file, _, _ := runtime.Caller(1)
+
+	return filepath.Dir(file)
+}

+ 69 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness_main.go

@@ -0,0 +1,69 @@
+// 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 benchmark
+
+import (
+	"context"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+)
+
+func DriverBenchmarkMain() int {
+	var hasErrors bool
+	var outputFileName string
+	flag.StringVar(&outputFileName, "output", "perf.json", "path to write the 'perf.json' file")
+	flag.Parse()
+
+	ctx := context.Background()
+	output := []interface{}{}
+	for _, res := range runDriverCases(ctx) {
+		if res.HasErrors() {
+			hasErrors = true
+		}
+
+		evg, err := res.EvergreenPerfFormat()
+		if err != nil {
+			hasErrors = true
+			continue
+		}
+
+		output = append(output, evg...)
+	}
+
+	evgOutput, err := json.MarshalIndent(map[string]interface{}{"results": output}, "", "   ")
+	if err != nil {
+		return 1
+	}
+	evgOutput = append(evgOutput, []byte("\n")...)
+
+	if outputFileName == "" {
+		fmt.Println(string(evgOutput))
+	} else if err := ioutil.WriteFile(outputFileName, evgOutput, 0644); err != nil {
+		fmt.Fprintf(os.Stderr, "problem writing file '%s': %s", outputFileName, err.Error())
+		return 1
+	}
+
+	if hasErrors {
+		return 1
+	}
+
+	return 0
+}
+
+func runDriverCases(ctx context.Context) []*BenchResult {
+	cases := getAllCases()
+
+	results := []*BenchResult{}
+	for _, bc := range cases {
+		results = append(results, bc.Run(ctx))
+	}
+
+	return results
+}

+ 140 - 0
src/go.mongodb.org/mongo-driver/benchmark/harness_results.go

@@ -0,0 +1,140 @@
+// 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 benchmark
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/montanaflynn/stats"
+)
+
+type BenchResult struct {
+	Name       string
+	Trials     int
+	Duration   time.Duration
+	Raw        []Result
+	DataSize   int
+	Operations int
+	hasErrors  *bool
+}
+
+func (r *BenchResult) EvergreenPerfFormat() ([]interface{}, error) {
+	timings := r.timings()
+
+	median, err := stats.Median(timings)
+	if err != nil {
+		return nil, err
+	}
+
+	min, err := stats.Min(timings)
+	if err != nil {
+		return nil, err
+	}
+
+	max, err := stats.Max(timings)
+	if err != nil {
+		return nil, err
+	}
+
+	out := []interface{}{
+		map[string]interface{}{
+			"name": r.Name + "-throughput",
+			"results": map[string]interface{}{
+				"1": map[string]interface{}{
+					"seconds":        r.Duration.Round(time.Millisecond).Seconds(),
+					"ops_per_second": r.getThroughput(median),
+					"ops_per_second_values": []float64{
+						r.getThroughput(min),
+						r.getThroughput(max),
+					},
+				},
+			},
+		},
+	}
+
+	if r.DataSize > 0 {
+		out = append(out, interface{}(map[string]interface{}{
+			"name": r.Name + "-MB-adjusted",
+			"results": map[string]interface{}{
+				"1": map[string]interface{}{
+					"seconds":        r.Duration.Round(time.Millisecond).Seconds(),
+					"ops_per_second": r.adjustResults(median),
+					"ops_per_second_values": []float64{
+						r.adjustResults(min),
+						r.adjustResults(max),
+					},
+				},
+			},
+		}))
+	}
+
+	return out, nil
+}
+
+func (r *BenchResult) timings() []float64 {
+	out := []float64{}
+	for _, r := range r.Raw {
+		out = append(out, r.Duration.Seconds())
+	}
+	return out
+}
+
+func (r *BenchResult) totalDuration() time.Duration {
+	var out time.Duration
+	for _, trial := range r.Raw {
+		out += trial.Duration
+	}
+	return out
+}
+
+func (r *BenchResult) adjustResults(data float64) float64 { return float64(r.DataSize) / data }
+func (r *BenchResult) getThroughput(data float64) float64 { return float64(r.Operations) / data }
+func (r *BenchResult) roundedRuntime() time.Duration      { return roundDurationMS(r.Duration) }
+
+func (r *BenchResult) String() string {
+	return fmt.Sprintf("name=%s, trials=%d, secs=%s", r.Name, r.Trials, r.Duration)
+}
+
+func (r *BenchResult) HasErrors() bool {
+	if r.hasErrors == nil {
+		var val bool
+		for _, res := range r.Raw {
+			if res.Error != nil {
+				val = true
+				break
+			}
+		}
+		r.hasErrors = &val
+	}
+
+	return *r.hasErrors
+}
+
+func (r *BenchResult) errReport() []string {
+	errs := []string{}
+	for _, res := range r.Raw {
+		if res.Error != nil {
+			errs = append(errs, res.Error.Error())
+		}
+	}
+	return errs
+}
+
+type Result struct {
+	Duration   time.Duration
+	Iterations int
+	Error      error
+}
+
+func roundDurationMS(d time.Duration) time.Duration {
+	rounded := d.Round(time.Millisecond)
+	if rounded == 1<<63-1 {
+		return 0
+	}
+	return rounded
+}

+ 142 - 0
src/go.mongodb.org/mongo-driver/benchmark/multi.go

@@ -0,0 +1,142 @@
+// 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 benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+func MultiFindMany(ctx context.Context, tm TimerManager, iters int) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, tweetData)
+	if err != nil {
+		return err
+	}
+
+	coll := db.Collection("corpus")
+
+	payload := make([]interface{}, iters)
+	for idx := range payload {
+		payload[idx] = doc
+	}
+
+	if _, err = coll.InsertMany(ctx, payload); err != nil {
+		return err
+	}
+
+	tm.ResetTimer()
+
+	cursor, err := coll.Find(ctx, bsonx.Doc{})
+	if err != nil {
+		return err
+	}
+	defer cursor.Close(ctx)
+
+	counter := 0
+	for cursor.Next(ctx) {
+		err = cursor.Err()
+		if err != nil {
+			return err
+		}
+		if len(cursor.Current) == 0 {
+			return errors.New("error retrieving document")
+		}
+
+		counter++
+	}
+
+	if counter != iters {
+		return errors.New("problem iterating cursors")
+
+	}
+
+	tm.StopTimer()
+
+	if err = cursor.Close(ctx); err != nil {
+		return err
+	}
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func multiInsertCase(ctx context.Context, tm TimerManager, iters int, data string) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, data)
+	if err != nil {
+		return err
+	}
+
+	err = db.RunCommand(ctx, bsonx.Doc{{"create", bsonx.String("corpus")}}).Err()
+	if err != nil {
+		return err
+	}
+
+	payload := make([]interface{}, iters)
+	for idx := range payload {
+		payload[idx] = doc
+	}
+
+	coll := db.Collection("corpus")
+
+	tm.ResetTimer()
+	res, err := coll.InsertMany(ctx, payload)
+	if err != nil {
+		return err
+	}
+	tm.StopTimer()
+
+	if len(res.InsertedIDs) != iters {
+		return errors.New("bulk operation did not complete")
+	}
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func MultiInsertSmallDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return multiInsertCase(ctx, tm, iters, smallData)
+}
+
+func MultiInsertLargeDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return multiInsertCase(ctx, tm, iters, largeData)
+}

+ 13 - 0
src/go.mongodb.org/mongo-driver/benchmark/multi_test.go

@@ -0,0 +1,13 @@
+// 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 benchmark
+
+import "testing"
+
+func BenchmarkMultiFindMany(b *testing.B)            { WrapCase(MultiFindMany)(b) }
+func BenchmarkMultiInsertSmallDocument(b *testing.B) { WrapCase(MultiInsertSmallDocument)(b) }
+func BenchmarkMultiInsertLargeDocument(b *testing.B) { WrapCase(MultiInsertLargeDocument)(b) }

+ 174 - 0
src/go.mongodb.org/mongo-driver/benchmark/single.go

@@ -0,0 +1,174 @@
+// 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 benchmark
+
+import (
+	"context"
+	"errors"
+
+	"go.mongodb.org/mongo-driver/internal/testutil"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
+	"go.mongodb.org/mongo-driver/x/bsonx"
+)
+
+const (
+	singleAndMultiDataDir = "single_and_multi_document"
+	tweetData             = "tweet.json"
+	smallData             = "small_doc.json"
+	largeData             = "large_doc.json"
+)
+
+func getClientDB(ctx context.Context) (*mongo.Database, error) {
+	cs, err := testutil.GetConnString()
+	if err != nil {
+		return nil, err
+	}
+	client, err := mongo.NewClient(options.Client().ApplyURI(cs.String()))
+	if err != nil {
+		return nil, err
+	}
+	if err = client.Connect(ctx); err != nil {
+		return nil, err
+	}
+
+	db := client.Database(testutil.GetDBName(cs))
+	return db, nil
+}
+
+func SingleRunCommand(ctx context.Context, tm TimerManager, iters int) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	cmd := bsonx.Doc{{"ismaster", bsonx.Boolean(true)}}
+
+	tm.ResetTimer()
+	for i := 0; i < iters; i++ {
+		var doc bsonx.Doc
+		err := db.RunCommand(ctx, cmd).Decode(&doc)
+		if err != nil {
+			return err
+		}
+		// read the document and then throw it away to prevent
+		out, err := doc.MarshalBSON()
+		if len(out) == 0 {
+			return errors.New("output of ismaster is empty")
+		}
+	}
+	tm.StopTimer()
+
+	return nil
+}
+
+func SingleFindOneByID(ctx context.Context, tm TimerManager, iters int) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, tweetData)
+	if err != nil {
+		return err
+	}
+	coll := db.Collection("corpus")
+	for i := 0; i < iters; i++ {
+		id := int32(i)
+		res, err := coll.InsertOne(ctx, doc.Set("_id", bsonx.Int32(id)))
+		if err != nil {
+			return err
+		}
+		if res.InsertedID == nil {
+			return errors.New("insert failed")
+		}
+	}
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		res := coll.FindOne(ctx, bsonx.Doc{{"_id", bsonx.Int32(int32(i))}})
+		if res == nil {
+			return errors.New("find one query produced nil result")
+		}
+	}
+
+	tm.StopTimer()
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func singleInsertCase(ctx context.Context, tm TimerManager, iters int, data string) error {
+	ctx, cancel := context.WithCancel(ctx)
+	defer cancel()
+
+	db, err := getClientDB(ctx)
+	if err != nil {
+		return err
+	}
+	defer db.Client().Disconnect(ctx)
+
+	db = db.Client().Database("perftest")
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	doc, err := loadSourceDocument(getProjectRoot(), perfDataDir, singleAndMultiDataDir, data)
+	if err != nil {
+		return err
+	}
+
+	err = db.RunCommand(ctx, bsonx.Doc{{"create", bsonx.String("corpus")}}).Err()
+	if err != nil {
+		return err
+	}
+
+	coll := db.Collection("corpus")
+
+	tm.ResetTimer()
+
+	for i := 0; i < iters; i++ {
+		if _, err = coll.InsertOne(ctx, doc); err != nil {
+			return err
+		}
+
+		// TODO: should be remove after resolving GODRIVER-468
+		_ = doc.Delete("_id")
+	}
+
+	tm.StopTimer()
+
+	if err = db.Drop(ctx); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func SingleInsertSmallDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return singleInsertCase(ctx, tm, iters, smallData)
+}
+
+func SingleInsertLargeDocument(ctx context.Context, tm TimerManager, iters int) error {
+	return singleInsertCase(ctx, tm, iters, largeData)
+}

+ 14 - 0
src/go.mongodb.org/mongo-driver/benchmark/single_test.go

@@ -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 benchmark
+
+import "testing"
+
+func BenchmarkSingleRunCommand(b *testing.B)          { WrapCase(SingleRunCommand)(b) }
+func BenchmarkSingleFindOneByID(b *testing.B)         { WrapCase(SingleFindOneByID)(b) }
+func BenchmarkSingleInsertSmallDocument(b *testing.B) { WrapCase(SingleInsertSmallDocument)(b) }
+func BenchmarkSingleInsertLargeDocument(b *testing.B) { WrapCase(SingleInsertLargeDocument)(b) }

+ 134 - 0
src/go.mongodb.org/mongo-driver/bson/benchmark_test.go

@@ -0,0 +1,134 @@
+// 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 (
+	"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,
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	},
+}
+
+func BenchmarkEncoding(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _ = Marshal(encodetestInstance)
+	}
+}
+
+func BenchmarkEncodingNested(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_, _ = Marshal(nestedInstance)
+	}
+}

+ 60 - 0
src/go.mongodb.org/mongo-driver/bson/bson.go

@@ -0,0 +1,60 @@
+// 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.
+
+// +build go1.9
+
+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 represents a BSON Document. This type can be used to represent BSON in a concise and readable
+// manner. It should generally be used when serializing to BSON. For deserializing, the Raw or
+// Document types should be used.
+//
+// Example usage:
+//
+// 		bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+//
+// This type should be used in situations where order matters, such as MongoDB commands. If the
+// order is not important, a map is more comfortable and concise.
+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, concise representation of a BSON Document. It should generally be used to
+// serialize BSON when the order of the elements of a BSON document do not matter. If the element
+// order matters, use a D instead.
+//
+// Example usage:
+//
+// 		bson.M{"foo": "bar", "hello": "world", "pi": 3.14159}
+//
+// This type is handled in the encoders as a regular map[string]interface{}. The elements will be
+// serialized in an undefined, random order, and the order will be different each time.
+type M = primitive.M
+
+// An A represents a BSON array. This type can be used to represent a BSON array in a concise and
+// readable manner. It should generally be used when serializing to BSON. For deserializing, the
+// RawArray or Array types should be used.
+//
+// Example usage:
+//
+// 		bson.A{"bar", "world", 3.14159, bson.D{{"qux", 12345}}}
+//
+type A = primitive.A

+ 91 - 0
src/go.mongodb.org/mongo-driver/bson/bson_1_8.go

@@ -0,0 +1,91 @@
+// 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
+
+// +build !go1.9
+
+package bson // import "go.mongodb.org/mongo-driver/bson"
+
+import (
+	"math"
+	"strconv"
+	"strings"
+)
+
+// 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 represents a BSON Document. This type can be used to represent BSON in a concise and readable
+// manner. It should generally be used when serializing to BSON. For deserializing, the Raw or
+// Document types should be used.
+//
+// Example usage:
+//
+// 		primitive.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+//
+// This type should be used in situations where order matters, such as MongoDB commands. If the
+// order is not important, a map is more comfortable and concise.
+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, concise representation of a BSON Document. It should generally be used to
+// serialize BSON when the order of the elements of a BSON document do not matter. If the element
+// order matters, use a D instead.
+//
+// Example usage:
+//
+// 		primitive.M{"foo": "bar", "hello": "world", "pi": 3.14159}
+//
+// This type is handled in the encoders as a regular map[string]interface{}. The elements will be
+// serialized in an undefined, random order, and the order will be different each time.
+type M map[string]interface{}
+
+// An A represents a BSON array. This type can be used to represent a BSON array in a concise and
+// readable manner. It should generally be used when serializing to BSON. For deserializing, the
+// RawArray or Array types should be used.
+//
+// Example usage:
+//
+// 		primitive.A{"bar", "world", 3.14159, primitive.D{{"qux", 12345}}}
+//
+type A []interface{}
+
+func formatDouble(f float64) string {
+	var s string
+	if math.IsInf(f, 1) {
+		s = "Infinity"
+	} else if math.IsInf(f, -1) {
+		s = "-Infinity"
+	} else if math.IsNaN(f) {
+		s = "NaN"
+	} else {
+		// Print exactly one decimalType place for integers; otherwise, print as many are necessary to
+		// perfectly represent it.
+		s = strconv.FormatFloat(f, 'G', -1, 64)
+		if !strings.ContainsRune(s, '.') {
+			s += ".0"
+		}
+	}
+
+	return s
+}

+ 371 - 0
src/go.mongodb.org/mongo-driver/bson/bson_corpus_spec_test.go

@@ -0,0 +1,371 @@
+// 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"
+	"io/ioutil"
+	"math"
+	"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/bsoncodec"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+)
+
+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 = "../data"
+
+var dvd bsoncodec.DefaultValueDecoders
+var dve bsoncodec.DefaultValueEncoders
+
+var dc = bsoncodec.DecodeContext{Registry: NewRegistryBuilder().Build()}
+var ec = bsoncodec.EncodeContext{Registry: NewRegistryBuilder().Build()}
+
+func findJSONFilesInDir(t *testing.T, dir string) []string {
+	files := make([]string, 0)
+
+	entries, err := ioutil.ReadDir(dir)
+	require.NoError(t, err)
+
+	for _, entry := range entries {
+		if entry.IsDir() || path.Ext(entry.Name()) != ".json" {
+			continue
+		}
+
+		files = append(files, entry.Name())
+	}
+
+	return files
+}
+
+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)
+
+	// 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(t *testing.T, ej, ejType, testDesc string) D {
+	var doc D
+	err := UnmarshalExtJSON([]byte(ej), ejType != "relaxed", &doc)
+	expectNoError(t, err, fmt.Sprintf("%s: decoding %s extended JSON", testDesc, ejType))
+	return doc
+}
+
+// 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 := ioutil.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))
+
+		for _, v := range test.Valid {
+			// 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 = jsonToNative(t, rEJ, "relaxed", v.Description)
+
+				// 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 = jsonToNative(t, cEJ, "canonical", v.Description)
+
+			// 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 = jsonToNative(t, dEJ, "degenerate canonical", v.Description)
+
+				// 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)")
+				}
+			}
+		}
+
+		for _, d := range test.DecodeErrors {
+			b, err := hex.DecodeString(d.Bson)
+			expectNoError(t, err, d.Description)
+
+			var doc D
+			err = Unmarshal(b, &doc)
+			expectError(t, err, fmt.Sprintf("%s: expected decode error", d.Description))
+		}
+
+		for _, p := range test.ParseErrors {
+			// skip DBRef tests
+			if strings.Contains(p.Description, "Bad DBRef") {
+				continue
+			}
+
+			s := unescapeUnicode(p.String, test.BsonType)
+			if test.BsonType == "0x13" {
+				s = fmt.Sprintf(`{"$numberDecimal": "%s"}`, s)
+			}
+
+			switch test.BsonType {
+			case "0x00":
+				var doc D
+				err := UnmarshalExtJSON([]byte(s), true, &doc)
+				expectError(t, err, fmt.Sprintf("%s: expected parse error", p.Description))
+			case "0x13":
+				ejvr, err := bsonrw.NewExtJSONValueReader(strings.NewReader(s), true)
+				expectNoError(t, err, fmt.Sprintf("error creating value reader: %s", err))
+				_, err = ejvr.ReadDecimal128()
+				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) {
+	for _, file := range findJSONFilesInDir(t, dataDir) {
+		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()
+	}
+}

+ 113 - 0
src/go.mongodb.org/mongo-driver/bson/bson_test.go

@@ -0,0 +1,113 @@
+// 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"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
+	"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 requireErrEqual(t *testing.T, err1 error, err2 error) { require.True(t, compareErrors(err1, err2)) }
+
+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)
+		}
+	})
+}

+ 163 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec.go

@@ -0,0 +1,163 @@
+// 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"
+)
+
+// 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 representaiton 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.
+	Ancestor reflect.Type
+}
+
+// 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)
+}
+
+// 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
+}

+ 145 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/bsoncodec_test.go

@@ -0,0 +1,145 @@
+// 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
+}
+
+func compareStrings(s1, s2 string) bool { return s1 == s2 }
+
+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
+}

+ 1014 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders.go

@@ -0,0 +1,1014 @@
+// 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"
+	"strconv"
+	"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 defaultValueDecoders DefaultValueDecoders
+
+// DefaultValueDecoders is a namespace type for the default ValueDecoders used
+// when creating a registry.
+type DefaultValueDecoders struct{}
+
+// RegisterDefaultDecoders will register the decoder methods attached to DefaultValueDecoders with
+// the provided RegistryBuilder.
+//
+// There is no support for decoding map[string]interface{} becuase there is no decoder for
+// interface{}, so users must either register this decoder themselves or use the
+// EmptyInterfaceDecoder avaialble in the bson package.
+func (dvd DefaultValueDecoders) RegisterDefaultDecoders(rb *RegistryBuilder) {
+	if rb == nil {
+		panic(errors.New("argument to RegisterDefaultDecoders must not be nil"))
+	}
+
+	rb.
+		RegisterDecoder(tBinary, ValueDecoderFunc(dvd.BinaryDecodeValue)).
+		RegisterDecoder(tUndefined, ValueDecoderFunc(dvd.UndefinedDecodeValue)).
+		RegisterDecoder(tDateTime, ValueDecoderFunc(dvd.DateTimeDecodeValue)).
+		RegisterDecoder(tNull, ValueDecoderFunc(dvd.NullDecodeValue)).
+		RegisterDecoder(tRegex, ValueDecoderFunc(dvd.RegexDecodeValue)).
+		RegisterDecoder(tDBPointer, ValueDecoderFunc(dvd.DBPointerDecodeValue)).
+		RegisterDecoder(tTimestamp, ValueDecoderFunc(dvd.TimestampDecodeValue)).
+		RegisterDecoder(tMinKey, ValueDecoderFunc(dvd.MinKeyDecodeValue)).
+		RegisterDecoder(tMaxKey, ValueDecoderFunc(dvd.MaxKeyDecodeValue)).
+		RegisterDecoder(tJavaScript, ValueDecoderFunc(dvd.JavaScriptDecodeValue)).
+		RegisterDecoder(tSymbol, ValueDecoderFunc(dvd.SymbolDecodeValue)).
+		RegisterDecoder(tByteSlice, ValueDecoderFunc(dvd.ByteSliceDecodeValue)).
+		RegisterDecoder(tTime, ValueDecoderFunc(dvd.TimeDecodeValue)).
+		RegisterDecoder(tEmpty, ValueDecoderFunc(dvd.EmptyInterfaceDecodeValue)).
+		RegisterDecoder(tOID, ValueDecoderFunc(dvd.ObjectIDDecodeValue)).
+		RegisterDecoder(tDecimal, ValueDecoderFunc(dvd.Decimal128DecodeValue)).
+		RegisterDecoder(tJSONNumber, ValueDecoderFunc(dvd.JSONNumberDecodeValue)).
+		RegisterDecoder(tURL, ValueDecoderFunc(dvd.URLDecodeValue)).
+		RegisterDecoder(tValueUnmarshaler, ValueDecoderFunc(dvd.ValueUnmarshalerDecodeValue)).
+		RegisterDecoder(tUnmarshaler, ValueDecoderFunc(dvd.UnmarshalerDecodeValue)).
+		RegisterDecoder(tCoreDocument, ValueDecoderFunc(dvd.CoreDocumentDecodeValue)).
+		RegisterDecoder(tCodeWithScope, ValueDecoderFunc(dvd.CodeWithScopeDecodeValue)).
+		RegisterDefaultDecoder(reflect.Bool, ValueDecoderFunc(dvd.BooleanDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int8, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int16, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int32, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Int64, ValueDecoderFunc(dvd.IntDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint8, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint16, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint32, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Uint64, ValueDecoderFunc(dvd.UintDecodeValue)).
+		RegisterDefaultDecoder(reflect.Float32, ValueDecoderFunc(dvd.FloatDecodeValue)).
+		RegisterDefaultDecoder(reflect.Float64, ValueDecoderFunc(dvd.FloatDecodeValue)).
+		RegisterDefaultDecoder(reflect.Array, ValueDecoderFunc(dvd.ArrayDecodeValue)).
+		RegisterDefaultDecoder(reflect.Map, ValueDecoderFunc(dvd.MapDecodeValue)).
+		RegisterDefaultDecoder(reflect.Slice, ValueDecoderFunc(dvd.SliceDecodeValue)).
+		RegisterDefaultDecoder(reflect.String, ValueDecoderFunc(dvd.StringDecodeValue)).
+		RegisterDefaultDecoder(reflect.Struct, &StructCodec{cache: make(map[reflect.Type]*structDescription), parser: DefaultStructTagParser}).
+		RegisterDefaultDecoder(reflect.Ptr, NewPointerCodec()).
+		RegisterTypeMapEntry(bsontype.Double, tFloat64).
+		RegisterTypeMapEntry(bsontype.String, tString).
+		RegisterTypeMapEntry(bsontype.Array, tA).
+		RegisterTypeMapEntry(bsontype.Binary, tBinary).
+		RegisterTypeMapEntry(bsontype.Undefined, tUndefined).
+		RegisterTypeMapEntry(bsontype.ObjectID, tOID).
+		RegisterTypeMapEntry(bsontype.Boolean, tBool).
+		RegisterTypeMapEntry(bsontype.DateTime, tDateTime).
+		RegisterTypeMapEntry(bsontype.Regex, tRegex).
+		RegisterTypeMapEntry(bsontype.DBPointer, tDBPointer).
+		RegisterTypeMapEntry(bsontype.JavaScript, tJavaScript).
+		RegisterTypeMapEntry(bsontype.Symbol, tSymbol).
+		RegisterTypeMapEntry(bsontype.CodeWithScope, tCodeWithScope).
+		RegisterTypeMapEntry(bsontype.Int32, tInt32).
+		RegisterTypeMapEntry(bsontype.Int64, tInt64).
+		RegisterTypeMapEntry(bsontype.Timestamp, tTimestamp).
+		RegisterTypeMapEntry(bsontype.Decimal128, tDecimal).
+		RegisterTypeMapEntry(bsontype.MinKey, tMinKey).
+		RegisterTypeMapEntry(bsontype.MaxKey, tMaxKey).
+		RegisterTypeMapEntry(bsontype.Type(0), tD)
+}
+
+// BooleanDecodeValue is the ValueDecoderFunc for bool types.
+func (dvd DefaultValueDecoders) BooleanDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.Boolean {
+		return fmt.Errorf("cannot decode %v into a boolean", vr.Type())
+	}
+	if !val.IsValid() || !val.CanSet() || val.Kind() != reflect.Bool {
+		return ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: val}
+	}
+
+	b, err := vr.ReadBoolean()
+	val.SetBool(b)
+	return err
+}
+
+// IntDecodeValue is the ValueDecoderFunc for bool types.
+func (dvd DefaultValueDecoders) IntDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var i64 int64
+	var err error
+	switch vr.Type() {
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		i64 = int64(i32)
+	case bsontype.Int64:
+		i64, err = vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+	case bsontype.Double:
+		f64, err := vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+		if !dc.Truncate && math.Floor(f64) != f64 {
+			return errors.New("IntDecodeValue can only truncate float64 to an integer type when truncation is enabled")
+		}
+		if f64 > float64(math.MaxInt64) {
+			return fmt.Errorf("%g overflows int64", f64)
+		}
+		i64 = int64(f64)
+	default:
+		return fmt.Errorf("cannot decode %v into an integer type", vr.Type())
+	}
+
+	if !val.CanSet() {
+		return ValueDecoderError{
+			Name:     "IntDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+			Received: val,
+		}
+	}
+
+	switch val.Kind() {
+	case reflect.Int8:
+		if i64 < math.MinInt8 || i64 > math.MaxInt8 {
+			return fmt.Errorf("%d overflows int8", i64)
+		}
+	case reflect.Int16:
+		if i64 < math.MinInt16 || i64 > math.MaxInt16 {
+			return fmt.Errorf("%d overflows int16", i64)
+		}
+	case reflect.Int32:
+		if i64 < math.MinInt32 || i64 > math.MaxInt32 {
+			return fmt.Errorf("%d overflows int32", i64)
+		}
+	case reflect.Int64:
+	case reflect.Int:
+		if int64(int(i64)) != i64 { // Can we fit this inside of an int
+			return fmt.Errorf("%d overflows int", i64)
+		}
+	default:
+		return ValueDecoderError{
+			Name:     "IntDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+			Received: val,
+		}
+	}
+
+	val.SetInt(i64)
+	return nil
+}
+
+// UintDecodeValue is the ValueDecoderFunc for uint types.
+func (dvd DefaultValueDecoders) UintDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var i64 int64
+	var err error
+	switch vr.Type() {
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		i64 = int64(i32)
+	case bsontype.Int64:
+		i64, err = vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+	case bsontype.Double:
+		f64, err := vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+		if !dc.Truncate && math.Floor(f64) != f64 {
+			return errors.New("UintDecodeValue can only truncate float64 to an integer type when truncation is enabled")
+		}
+		if f64 > float64(math.MaxInt64) {
+			return fmt.Errorf("%g overflows int64", f64)
+		}
+		i64 = int64(f64)
+	default:
+		return fmt.Errorf("cannot decode %v into an integer type", vr.Type())
+	}
+
+	if !val.CanSet() {
+		return ValueDecoderError{
+			Name:     "UintDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+			Received: val,
+		}
+	}
+
+	switch val.Kind() {
+	case reflect.Uint8:
+		if i64 < 0 || i64 > math.MaxUint8 {
+			return fmt.Errorf("%d overflows uint8", i64)
+		}
+	case reflect.Uint16:
+		if i64 < 0 || i64 > math.MaxUint16 {
+			return fmt.Errorf("%d overflows uint16", i64)
+		}
+	case reflect.Uint32:
+		if i64 < 0 || i64 > math.MaxUint32 {
+			return fmt.Errorf("%d overflows uint32", i64)
+		}
+	case reflect.Uint64:
+		if i64 < 0 {
+			return fmt.Errorf("%d overflows uint64", i64)
+		}
+	case reflect.Uint:
+		if i64 < 0 || int64(uint(i64)) != i64 { // Can we fit this inside of an uint
+			return fmt.Errorf("%d overflows uint", i64)
+		}
+	default:
+		return ValueDecoderError{
+			Name:     "UintDecodeValue",
+			Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+			Received: val,
+		}
+	}
+
+	val.SetUint(uint64(i64))
+	return nil
+}
+
+// FloatDecodeValue is the ValueDecoderFunc for float types.
+func (dvd DefaultValueDecoders) FloatDecodeValue(ec DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var f float64
+	var err error
+	switch vr.Type() {
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		f = float64(i32)
+	case bsontype.Int64:
+		i64, err := vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+		f = float64(i64)
+	case bsontype.Double:
+		f, err = vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into a float32 or float64 type", vr.Type())
+	}
+
+	if !val.CanSet() {
+		return ValueDecoderError{Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: val}
+	}
+
+	switch val.Kind() {
+	case reflect.Float32:
+		if !ec.Truncate && float64(float32(f)) != f {
+			return errors.New("FloatDecodeValue can only convert float64 to float32 when truncation is allowed")
+		}
+	case reflect.Float64:
+	default:
+		return ValueDecoderError{Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: val}
+	}
+
+	val.SetFloat(f)
+	return nil
+}
+
+// StringDecodeValue is the ValueDecoderFunc for string types.
+func (dvd DefaultValueDecoders) StringDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	var str string
+	var err error
+	switch vr.Type() {
+	// TODO(GODRIVER-577): Handle JavaScript and Symbol BSON types when allowed.
+	case bsontype.String:
+		str, err = vr.ReadString()
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into a string type", vr.Type())
+	}
+	if !val.CanSet() || val.Kind() != reflect.String {
+		return ValueDecoderError{Name: "StringDecodeValue", Kinds: []reflect.Kind{reflect.String}, Received: val}
+	}
+
+	val.SetString(str)
+	return nil
+}
+
+// JavaScriptDecodeValue is the ValueDecoderFunc for the primitive.JavaScript type.
+func (DefaultValueDecoders) JavaScriptDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tJavaScript {
+		return ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tJavaScript}, Received: val}
+	}
+
+	if vr.Type() != bsontype.JavaScript {
+		return fmt.Errorf("cannot decode %v into a primitive.JavaScript", vr.Type())
+	}
+
+	js, err := vr.ReadJavascript()
+	if err != nil {
+		return err
+	}
+
+	val.SetString(js)
+	return nil
+}
+
+// SymbolDecodeValue is the ValueDecoderFunc for the primitive.Symbol type.
+func (DefaultValueDecoders) SymbolDecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tSymbol {
+		return ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tSymbol}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Symbol {
+		return fmt.Errorf("cannot decode %v into a primitive.Symbol", vr.Type())
+	}
+
+	symbol, err := vr.ReadSymbol()
+	if err != nil {
+		return err
+	}
+
+	val.SetString(symbol)
+	return nil
+}
+
+// BinaryDecodeValue is the ValueDecoderFunc for Binary.
+func (DefaultValueDecoders) BinaryDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tBinary {
+		return ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tBinary}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Binary {
+		return fmt.Errorf("cannot decode %v into a Binary", vr.Type())
+	}
+
+	data, subtype, err := vr.ReadBinary()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.Binary{Subtype: subtype, Data: data}))
+	return nil
+}
+
+// UndefinedDecodeValue is the ValueDecoderFunc for Undefined.
+func (DefaultValueDecoders) UndefinedDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tUndefined {
+		return ValueDecoderError{Name: "UndefinedDecodeValue", Types: []reflect.Type{tUndefined}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Undefined {
+		return fmt.Errorf("cannot decode %v into an Undefined", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.Undefined{}))
+	return vr.ReadUndefined()
+}
+
+// ObjectIDDecodeValue is the ValueDecoderFunc for primitive.ObjectID.
+func (dvd DefaultValueDecoders) ObjectIDDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tOID {
+		return ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}, Received: val}
+	}
+
+	if vr.Type() != bsontype.ObjectID {
+		return fmt.Errorf("cannot decode %v into an ObjectID", vr.Type())
+	}
+	oid, err := vr.ReadObjectID()
+	val.Set(reflect.ValueOf(oid))
+	return err
+}
+
+// DateTimeDecodeValue is the ValueDecoderFunc for DateTime.
+func (DefaultValueDecoders) DateTimeDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tDateTime {
+		return ValueDecoderError{Name: "DateTimeDecodeValue", Types: []reflect.Type{tDateTime}, Received: val}
+	}
+
+	if vr.Type() != bsontype.DateTime {
+		return fmt.Errorf("cannot decode %v into a DateTime", vr.Type())
+	}
+
+	dt, err := vr.ReadDateTime()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.DateTime(dt)))
+	return nil
+}
+
+// NullDecodeValue is the ValueDecoderFunc for Null.
+func (DefaultValueDecoders) NullDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tNull {
+		return ValueDecoderError{Name: "NullDecodeValue", Types: []reflect.Type{tNull}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Null {
+		return fmt.Errorf("cannot decode %v into a Null", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.Null{}))
+	return vr.ReadNull()
+}
+
+// RegexDecodeValue is the ValueDecoderFunc for Regex.
+func (DefaultValueDecoders) RegexDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tRegex {
+		return ValueDecoderError{Name: "RegexDecodeValue", Types: []reflect.Type{tRegex}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Regex {
+		return fmt.Errorf("cannot decode %v into a Regex", vr.Type())
+	}
+
+	pattern, options, err := vr.ReadRegex()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.Regex{Pattern: pattern, Options: options}))
+	return nil
+}
+
+// DBPointerDecodeValue is the ValueDecoderFunc for DBPointer.
+func (DefaultValueDecoders) DBPointerDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tDBPointer {
+		return ValueDecoderError{Name: "DBPointerDecodeValue", Types: []reflect.Type{tDBPointer}, Received: val}
+	}
+
+	if vr.Type() != bsontype.DBPointer {
+		return fmt.Errorf("cannot decode %v into a DBPointer", vr.Type())
+	}
+
+	ns, pointer, err := vr.ReadDBPointer()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.DBPointer{DB: ns, Pointer: pointer}))
+	return nil
+}
+
+// TimestampDecodeValue is the ValueDecoderFunc for Timestamp.
+func (DefaultValueDecoders) TimestampDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tTimestamp {
+		return ValueDecoderError{Name: "TimestampDecodeValue", Types: []reflect.Type{tTimestamp}, Received: val}
+	}
+
+	if vr.Type() != bsontype.Timestamp {
+		return fmt.Errorf("cannot decode %v into a Timestamp", vr.Type())
+	}
+
+	t, incr, err := vr.ReadTimestamp()
+	if err != nil {
+		return err
+	}
+
+	val.Set(reflect.ValueOf(primitive.Timestamp{T: t, I: incr}))
+	return nil
+}
+
+// MinKeyDecodeValue is the ValueDecoderFunc for MinKey.
+func (DefaultValueDecoders) MinKeyDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tMinKey {
+		return ValueDecoderError{Name: "MinKeyDecodeValue", Types: []reflect.Type{tMinKey}, Received: val}
+	}
+
+	if vr.Type() != bsontype.MinKey {
+		return fmt.Errorf("cannot decode %v into a MinKey", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.MinKey{}))
+	return vr.ReadMinKey()
+}
+
+// MaxKeyDecodeValue is the ValueDecoderFunc for MaxKey.
+func (DefaultValueDecoders) MaxKeyDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tMaxKey {
+		return ValueDecoderError{Name: "MaxKeyDecodeValue", Types: []reflect.Type{tMaxKey}, Received: val}
+	}
+
+	if vr.Type() != bsontype.MaxKey {
+		return fmt.Errorf("cannot decode %v into a MaxKey", vr.Type())
+	}
+
+	val.Set(reflect.ValueOf(primitive.MaxKey{}))
+	return vr.ReadMaxKey()
+}
+
+// Decimal128DecodeValue is the ValueDecoderFunc for primitive.Decimal128.
+func (dvd DefaultValueDecoders) Decimal128DecodeValue(dctx DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.Decimal128 {
+		return fmt.Errorf("cannot decode %v into a primitive.Decimal128", vr.Type())
+	}
+
+	if !val.CanSet() || val.Type() != tDecimal {
+		return ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}, Received: val}
+	}
+	d128, err := vr.ReadDecimal128()
+	val.Set(reflect.ValueOf(d128))
+	return err
+}
+
+// JSONNumberDecodeValue is the ValueDecoderFunc for json.Number.
+func (dvd DefaultValueDecoders) JSONNumberDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tJSONNumber {
+		return ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Double:
+		f64, err := vr.ReadDouble()
+		if err != nil {
+			return err
+		}
+		val.Set(reflect.ValueOf(json.Number(strconv.FormatFloat(f64, 'g', -1, 64))))
+	case bsontype.Int32:
+		i32, err := vr.ReadInt32()
+		if err != nil {
+			return err
+		}
+		val.Set(reflect.ValueOf(json.Number(strconv.FormatInt(int64(i32), 10))))
+	case bsontype.Int64:
+		i64, err := vr.ReadInt64()
+		if err != nil {
+			return err
+		}
+		val.Set(reflect.ValueOf(json.Number(strconv.FormatInt(i64, 10))))
+	default:
+		return fmt.Errorf("cannot decode %v into a json.Number", vr.Type())
+	}
+
+	return nil
+}
+
+// URLDecodeValue is the ValueDecoderFunc for url.URL.
+func (dvd DefaultValueDecoders) URLDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.String {
+		return fmt.Errorf("cannot decode %v into a *url.URL", vr.Type())
+	}
+
+	str, err := vr.ReadString()
+	if err != nil {
+		return err
+	}
+
+	u, err := url.Parse(str)
+	if err != nil {
+		return err
+	}
+
+	if !val.CanSet() || val.Type() != tURL {
+		return ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}, Received: val}
+	}
+
+	val.Set(reflect.ValueOf(u).Elem())
+	return nil
+}
+
+// TimeDecodeValue is the ValueDecoderFunc for time.Time.
+func (dvd DefaultValueDecoders) TimeDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.DateTime {
+		return fmt.Errorf("cannot decode %v into a time.Time", vr.Type())
+	}
+
+	dt, err := vr.ReadDateTime()
+	if err != nil {
+		return err
+	}
+
+	if !val.CanSet() || val.Type() != tTime {
+		return ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}, Received: val}
+	}
+
+	val.Set(reflect.ValueOf(time.Unix(dt/1000, dt%1000*1000000).UTC()))
+	return nil
+}
+
+// ByteSliceDecodeValue is the ValueDecoderFunc for []byte.
+func (dvd DefaultValueDecoders) ByteSliceDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if vr.Type() != bsontype.Binary && vr.Type() != bsontype.Null {
+		return fmt.Errorf("cannot decode %v into a []byte", vr.Type())
+	}
+
+	if !val.CanSet() || val.Type() != tByteSlice {
+		return ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: val}
+	}
+
+	if vr.Type() == bsontype.Null {
+		val.Set(reflect.Zero(val.Type()))
+		return vr.ReadNull()
+	}
+
+	data, subtype, err := vr.ReadBinary()
+	if err != nil {
+		return err
+	}
+	if subtype != 0x00 {
+		return fmt.Errorf("ByteSliceDecodeValue can only be used to decode subtype 0x00 for %s, got %v", bsontype.Binary, subtype)
+	}
+
+	val.Set(reflect.ValueOf(data))
+	return nil
+}
+
+// MapDecodeValue is the ValueDecoderFunc for map[string]* types.
+func (dvd DefaultValueDecoders) MapDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Kind() != reflect.Map || val.Type().Key().Kind() != reflect.String {
+		return ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+	case bsontype.Null:
+		val.Set(reflect.Zero(val.Type()))
+		return vr.ReadNull()
+	default:
+		return fmt.Errorf("cannot decode %v into a %s", vr.Type(), val.Type())
+	}
+
+	dr, err := vr.ReadDocument()
+	if err != nil {
+		return err
+	}
+
+	if val.IsNil() {
+		val.Set(reflect.MakeMap(val.Type()))
+	}
+
+	eType := val.Type().Elem()
+	decoder, err := dc.LookupDecoder(eType)
+	if err != nil {
+		return err
+	}
+
+	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
+		}
+
+		elem := reflect.New(eType).Elem()
+
+		err = decoder.DecodeValue(dc, vr, elem)
+		if err != nil {
+			return err
+		}
+
+		val.SetMapIndex(reflect.ValueOf(key).Convert(keyType), elem)
+	}
+	return nil
+}
+
+// ArrayDecodeValue is the ValueDecoderFunc for array types.
+func (dvd DefaultValueDecoders) ArrayDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.IsValid() || val.Kind() != reflect.Array {
+		return ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: val}
+	}
+
+	switch vr.Type() {
+	case bsontype.Array:
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+		if val.Type().Elem() != tE {
+			return fmt.Errorf("cannot decode document into %s", val.Type())
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into an array", vr.Type())
+	}
+
+	var elemsFunc func(DecodeContext, bsonrw.ValueReader, reflect.Value) ([]reflect.Value, error)
+	switch val.Type().Elem() {
+	case tE:
+		elemsFunc = dvd.decodeD
+	default:
+		elemsFunc = dvd.decodeDefault
+	}
+
+	elems, err := elemsFunc(dc, vr, val)
+	if err != nil {
+		return err
+	}
+
+	if len(elems) > val.Len() {
+		return fmt.Errorf("more elements returned in array than can fit inside %s", val.Type())
+	}
+
+	for idx, elem := range elems {
+		val.Index(idx).Set(elem)
+	}
+
+	return nil
+}
+
+// SliceDecodeValue is the ValueDecoderFunc for slice types.
+func (dvd DefaultValueDecoders) SliceDecodeValue(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 vr.Type() {
+	case bsontype.Array:
+	case bsontype.Null:
+		val.Set(reflect.Zero(val.Type()))
+		return vr.ReadNull()
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+		if val.Type().Elem() != tE {
+			return fmt.Errorf("cannot decode document into %s", val.Type())
+		}
+	default:
+		return fmt.Errorf("cannot decode %v into a slice", vr.Type())
+	}
+
+	var elemsFunc func(DecodeContext, bsonrw.ValueReader, reflect.Value) ([]reflect.Value, error)
+	switch val.Type().Elem() {
+	case tE:
+		dc.Ancestor = val.Type()
+		elemsFunc = dvd.decodeD
+	default:
+		elemsFunc = dvd.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
+}
+
+// ValueUnmarshalerDecodeValue is the ValueDecoderFunc for ValueUnmarshaler implementations.
+func (dvd DefaultValueDecoders) ValueUnmarshalerDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.IsValid() || (!val.Type().Implements(tValueUnmarshaler) && !reflect.PtrTo(val.Type()).Implements(tValueUnmarshaler)) {
+		return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+	}
+
+	if val.Kind() == reflect.Ptr && val.IsNil() {
+		if !val.CanSet() {
+			return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+		}
+		val.Set(reflect.New(val.Type().Elem()))
+	}
+
+	if !val.Type().Implements(tValueUnmarshaler) {
+		if !val.CanAddr() {
+			return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+		}
+		val = val.Addr() // If they 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(tValueUnmarshaler).MethodByName("UnmarshalBSONValue")
+	errVal := fn.Call([]reflect.Value{reflect.ValueOf(t), reflect.ValueOf(src)})[0]
+	if !errVal.IsNil() {
+		return errVal.Interface().(error)
+	}
+	return nil
+}
+
+// UnmarshalerDecodeValue is the ValueDecoderFunc for Unmarshaler implementations.
+func (dvd DefaultValueDecoders) UnmarshalerDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.IsValid() || (!val.Type().Implements(tUnmarshaler) && !reflect.PtrTo(val.Type()).Implements(tUnmarshaler)) {
+		return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
+	}
+
+	if val.Kind() == reflect.Ptr && val.IsNil() {
+		if !val.CanSet() {
+			return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
+		}
+		val.Set(reflect.New(val.Type().Elem()))
+	}
+
+	if !val.Type().Implements(tUnmarshaler) {
+		if !val.CanAddr() {
+			return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
+		}
+		val = val.Addr() // If they type doesn't implement the interface, a pointer to it must.
+	}
+
+	_, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
+	if err != nil {
+		return err
+	}
+
+	fn := val.Convert(tUnmarshaler).MethodByName("UnmarshalBSON")
+	errVal := fn.Call([]reflect.Value{reflect.ValueOf(src)})[0]
+	if !errVal.IsNil() {
+		return errVal.Interface().(error)
+	}
+	return nil
+}
+
+// EmptyInterfaceDecodeValue is the ValueDecoderFunc for interface{}.
+func (dvd DefaultValueDecoders) EmptyInterfaceDecodeValue(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}
+	}
+
+	rtype, err := dc.LookupTypeMapEntry(vr.Type())
+	if err != nil {
+		switch vr.Type() {
+		case bsontype.EmbeddedDocument:
+			if dc.Ancestor != nil {
+				rtype = dc.Ancestor
+				break
+			}
+			rtype = tD
+		case bsontype.Null:
+			val.Set(reflect.Zero(val.Type()))
+			return vr.ReadNull()
+		default:
+			return err
+		}
+	}
+
+	decoder, err := dc.LookupDecoder(rtype)
+	if err != nil {
+		return err
+	}
+
+	elem := reflect.New(rtype).Elem()
+	err = decoder.DecodeValue(dc, vr, elem)
+	if err != nil {
+		return err
+	}
+
+	val.Set(elem)
+	return nil
+}
+
+// CoreDocumentDecodeValue is the ValueDecoderFunc for bsoncore.Document.
+func (DefaultValueDecoders) CoreDocumentDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tCoreDocument {
+		return ValueDecoderError{Name: "CoreDocumentDecodeValue", Types: []reflect.Type{tCoreDocument}, Received: val}
+	}
+
+	if val.IsNil() {
+		val.Set(reflect.MakeSlice(val.Type(), 0, 0))
+	}
+
+	val.SetLen(0)
+
+	cdoc, err := bsonrw.Copier{}.AppendDocumentBytes(val.Interface().(bsoncore.Document), vr)
+	val.Set(reflect.ValueOf(cdoc))
+	return err
+}
+
+func (dvd DefaultValueDecoders) decodeDefault(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) ([]reflect.Value, error) {
+	elems := make([]reflect.Value, 0)
+
+	ar, err := vr.ReadArray()
+	if err != nil {
+		return nil, err
+	}
+
+	eType := val.Type().Elem()
+
+	decoder, err := dc.LookupDecoder(eType)
+	if err != nil {
+		return nil, err
+	}
+
+	for {
+		vr, err := ar.ReadValue()
+		if err == bsonrw.ErrEOA {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+
+		elem := reflect.New(eType).Elem()
+
+		err = decoder.DecodeValue(dc, vr, elem)
+		if err != nil {
+			return nil, err
+		}
+		elems = append(elems, elem)
+	}
+
+	return elems, nil
+}
+
+// CodeWithScopeDecodeValue is the ValueDecoderFunc for CodeWithScope.
+func (dvd DefaultValueDecoders) CodeWithScopeDecodeValue(dc DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
+	if !val.CanSet() || val.Type() != tCodeWithScope {
+		return ValueDecoderError{Name: "CodeWithScopeDecodeValue", Types: []reflect.Type{tCodeWithScope}, Received: val}
+	}
+
+	if vr.Type() != bsontype.CodeWithScope {
+		return fmt.Errorf("cannot decode %v into a primitive.CodeWithScope", vr.Type())
+	}
+
+	code, dr, err := vr.ReadCodeWithScope()
+	if err != nil {
+		return err
+	}
+
+	scope := reflect.New(tD).Elem()
+
+	elems, err := dvd.decodeElemsFromDocumentReader(dc, dr)
+	if err != nil {
+		return err
+	}
+
+	scope.Set(reflect.MakeSlice(tD, 0, len(elems)))
+	scope.Set(reflect.Append(scope, elems...))
+
+	val.Set(reflect.ValueOf(primitive.CodeWithScope{Code: primitive.JavaScript(code), Scope: scope.Interface().(primitive.D)}))
+	return nil
+}
+
+func (dvd DefaultValueDecoders) decodeD(dc DecodeContext, vr bsonrw.ValueReader, _ reflect.Value) ([]reflect.Value, error) {
+	switch vr.Type() {
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+	default:
+		return nil, fmt.Errorf("cannot decode %v into a D", vr.Type())
+	}
+
+	dr, err := vr.ReadDocument()
+	if err != nil {
+		return nil, err
+	}
+
+	return dvd.decodeElemsFromDocumentReader(dc, dr)
+}
+
+func (DefaultValueDecoders) decodeElemsFromDocumentReader(dc DecodeContext, dr bsonrw.DocumentReader) ([]reflect.Value, error) {
+	decoder, err := dc.LookupDecoder(tEmpty)
+	if err != nil {
+		return nil, err
+	}
+
+	elems := make([]reflect.Value, 0)
+	for {
+		key, vr, err := dr.ReadElement()
+		if err == bsonrw.ErrEOD {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+
+		val := reflect.New(tEmpty).Elem()
+		err = decoder.DecodeValue(dc, vr, val)
+		if err != nil {
+			return nil, err
+		}
+
+		elems = append(elems, reflect.ValueOf(primitive.E{Key: key, Value: val.Interface()}))
+	}
+
+	return elems, nil
+}

+ 2870 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_decoders_test.go

@@ -0,0 +1,2870 @@
+// 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 (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math"
+	"net/url"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+func TestDefaultValueDecoders(t *testing.T) {
+	var dvd DefaultValueDecoders
+	var wrong = func(string, string) string { return "wrong" }
+
+	type mybool bool
+	type myint8 int8
+	type myint16 int16
+	type myint32 int32
+	type myint64 int64
+	type myint int
+	type myuint8 uint8
+	type myuint16 uint16
+	type myuint32 uint32
+	type myuint64 uint64
+	type myuint uint
+	type myfloat32 float32
+	type myfloat64 float64
+	type mystring string
+
+	const cansetreflectiontest = "cansetreflectiontest"
+	const cansettest = "cansettest"
+
+	now := time.Now().Truncate(time.Millisecond)
+	d128 := primitive.NewDecimal128(12345, 67890)
+	var pbool = func(b bool) *bool { return &b }
+	var pi32 = func(i32 int32) *int32 { return &i32 }
+	var pi64 = func(i64 int64) *int64 { return &i64 }
+
+	type subtest struct {
+		name   string
+		val    interface{}
+		dctx   *DecodeContext
+		llvrw  *bsonrwtest.ValueReaderWriter
+		invoke bsonrwtest.Invoked
+		err    error
+	}
+
+	testCases := []struct {
+		name     string
+		vd       ValueDecoder
+		subtests []subtest
+	}{
+		{
+			"BooleanDecodeValue",
+			ValueDecoderFunc(dvd.BooleanDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not boolean",
+					bool(false),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a boolean", bsontype.String),
+				},
+				{
+					"fast path",
+					bool(true),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean, Return: bool(true)},
+					bsonrwtest.ReadBoolean,
+					nil,
+				},
+				{
+					"reflection path",
+					mybool(true),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean, Return: bool(true)},
+					bsonrwtest.ReadBoolean,
+					nil,
+				},
+				{
+					"reflection path error",
+					mybool(true),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean, Return: bool(true), Err: errors.New("ReadBoolean Error"), ErrAfter: bsonrwtest.ReadBoolean},
+					bsonrwtest.ReadBoolean, errors.New("ReadBoolean Error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Boolean},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}},
+				},
+			},
+		},
+		{
+			"IntDecodeValue",
+			ValueDecoderFunc(dvd.IntDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not int32/int64",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an integer type", bsontype.String),
+				},
+				{
+					"ReadInt32 error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ReadInt32 error"),
+				},
+				{
+					"ReadInt64 error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ReadInt64 error"),
+				},
+				{
+					"ReadDouble error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("ReadDouble error"),
+				},
+				{
+					"ReadDouble", int64(3), &DecodeContext{},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.00)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (truncate)", int64(3), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (no truncate)", int64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("IntDecodeValue can only truncate float64 to an integer type when truncation is enabled"),
+				},
+				{
+					"ReadDouble overflows int64", int64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: math.MaxFloat64}, bsonrwtest.ReadDouble,
+					fmt.Errorf("%g overflows int64", math.MaxFloat64),
+				},
+				{"int8/fast path", int8(127), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32, nil},
+				{"int16/fast path", int16(32676), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32676)}, bsonrwtest.ReadInt32, nil},
+				{"int32/fast path", int32(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1234)}, bsonrwtest.ReadInt32, nil},
+				{"int64/fast path", int64(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{"int/fast path", int(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{
+					"int8/fast path - nil", (*int8)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int8)(nil)),
+					},
+				},
+				{
+					"int16/fast path - nil", (*int16)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int16)(nil)),
+					},
+				},
+				{
+					"int32/fast path - nil", (*int32)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int32)(nil)),
+					},
+				},
+				{
+					"int64/fast path - nil", (*int64)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int64)(nil)),
+					},
+				},
+				{
+					"int/fast path - nil", (*int)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "IntDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf((*int)(nil)),
+					},
+				},
+				{
+					"int8/fast path - overflow", int8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", 129),
+				},
+				{
+					"int16/fast path - overflow", int16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32768)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", 32768),
+				},
+				{
+					"int32/fast path - overflow", int32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(2147483648)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", 2147483648),
+				},
+				{
+					"int8/fast path - overflow (negative)", int8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", -129),
+				},
+				{
+					"int16/fast path - overflow (negative)", int16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-32769)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", -32769),
+				},
+				{
+					"int32/fast path - overflow (negative)", int32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-2147483649)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", -2147483649),
+				},
+				{
+					"int8/reflection path", myint8(127), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int16/reflection path", myint16(255), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(255)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int32/reflection path", myint32(511), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(511)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int64/reflection path", myint64(1023), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1023)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int/reflection path", myint(2047), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(2047)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"int8/reflection path - overflow", myint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", 129),
+				},
+				{
+					"int16/reflection path - overflow", myint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32768)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", 32768),
+				},
+				{
+					"int32/reflection path - overflow", myint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(2147483648)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", 2147483648),
+				},
+				{
+					"int8/reflection path - overflow (negative)", myint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-129)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int8", -129),
+				},
+				{
+					"int16/reflection path - overflow (negative)", myint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-32769)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows int16", -32769),
+				},
+				{
+					"int32/reflection path - overflow (negative)", myint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-2147483649)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows int32", -2147483649),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:  "IntDecodeValue",
+						Kinds: []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+					},
+				},
+			},
+		},
+		{
+			"UintDecodeValue",
+			ValueDecoderFunc(dvd.UintDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not int32/int64",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an integer type", bsontype.String),
+				},
+				{
+					"ReadInt32 error",
+					uint(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ReadInt32 error"),
+				},
+				{
+					"ReadInt64 error",
+					uint(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ReadInt64 error"),
+				},
+				{
+					"ReadDouble error",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("ReadDouble error"),
+				},
+				{
+					"ReadDouble", uint64(3), &DecodeContext{},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.00)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (truncate)", uint64(3), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"ReadDouble (no truncate)", uint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("UintDecodeValue can only truncate float64 to an integer type when truncation is enabled"),
+				},
+				{
+					"ReadDouble overflows int64", uint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: math.MaxFloat64}, bsonrwtest.ReadDouble,
+					fmt.Errorf("%g overflows int64", math.MaxFloat64),
+				},
+				{"uint8/fast path", uint8(127), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32, nil},
+				{"uint16/fast path", uint16(255), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(255)}, bsonrwtest.ReadInt32, nil},
+				{"uint32/fast path", uint32(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1234)}, bsonrwtest.ReadInt32, nil},
+				{"uint64/fast path", uint64(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{"uint/fast path", uint(1234), nil, &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234)}, bsonrwtest.ReadInt64, nil},
+				{
+					"uint8/fast path - nil", (*uint8)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint8)(nil)),
+					},
+				},
+				{
+					"uint16/fast path - nil", (*uint16)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint16)(nil)),
+					},
+				},
+				{
+					"uint32/fast path - nil", (*uint32)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint32)(nil)),
+					},
+				},
+				{
+					"uint64/fast path - nil", (*uint64)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint64)(nil)),
+					},
+				},
+				{
+					"uint/fast path - nil", (*uint)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)}, bsonrwtest.ReadInt32,
+					ValueDecoderError{
+						Name:     "UintDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf((*uint)(nil)),
+					},
+				},
+				{
+					"uint8/fast path - overflow", uint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 8)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", 1<<8),
+				},
+				{
+					"uint16/fast path - overflow", uint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 16)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", 1<<16),
+				},
+				{
+					"uint32/fast path - overflow", uint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1 << 32)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", 1<<32),
+				},
+				{
+					"uint8/fast path - overflow (negative)", uint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", -1),
+				},
+				{
+					"uint16/fast path - overflow (negative)", uint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", -1),
+				},
+				{
+					"uint32/fast path - overflow (negative)", uint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", -1),
+				},
+				{
+					"uint64/fast path - overflow (negative)", uint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint64", -1),
+				},
+				{
+					"uint/fast path - overflow (negative)", uint(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint", -1),
+				},
+				{
+					"uint8/reflection path", myuint8(127), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(127)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint16/reflection path", myuint16(255), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(255)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint32/reflection path", myuint32(511), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(511)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint64/reflection path", myuint64(1023), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1023)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint/reflection path", myuint(2047), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(2047)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"uint8/reflection path - overflow", myuint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 8)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", 1<<8),
+				},
+				{
+					"uint16/reflection path - overflow", myuint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(1 << 16)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", 1<<16),
+				},
+				{
+					"uint32/reflection path - overflow", myuint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1 << 32)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", 1<<32),
+				},
+				{
+					"uint8/reflection path - overflow (negative)", myuint8(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint8", -1),
+				},
+				{
+					"uint16/reflection path - overflow (negative)", myuint16(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(-1)}, bsonrwtest.ReadInt32,
+					fmt.Errorf("%d overflows uint16", -1),
+				},
+				{
+					"uint32/reflection path - overflow (negative)", myuint32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint32", -1),
+				},
+				{
+					"uint64/reflection path - overflow (negative)", myuint64(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint64", -1),
+				},
+				{
+					"uint/reflection path - overflow (negative)", myuint(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(-1)}, bsonrwtest.ReadInt64,
+					fmt.Errorf("%d overflows uint", -1),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:  "UintDecodeValue",
+						Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+					},
+				},
+			},
+		},
+		{
+			"FloatDecodeValue",
+			ValueDecoderFunc(dvd.FloatDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)},
+					bsonrwtest.ReadDouble,
+					ValueDecoderError{
+						Name:     "FloatDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not double",
+					0,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a float32 or float64 type", bsontype.String),
+				},
+				{
+					"ReadDouble error",
+					float64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("ReadDouble error"),
+				},
+				{
+					"ReadInt32 error",
+					float64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ReadInt32 error"),
+				},
+				{
+					"ReadInt64 error",
+					float64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ReadInt64 error"),
+				},
+				{
+					"float64/int32", float32(32.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(32)}, bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"float64/int64", float32(64.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(64)}, bsonrwtest.ReadInt64,
+					nil,
+				},
+				{
+					"float32/fast path (equal)", float32(3.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.0)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float64/fast path", float64(3.14159), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/fast path (truncate)", float32(3.14), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/fast path (no truncate)", float32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("FloatDecodeValue can only convert float64 to float32 when truncation is allowed"),
+				},
+				{
+					"float32/fast path - nil", (*float32)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)}, bsonrwtest.ReadDouble,
+					ValueDecoderError{
+						Name:     "FloatDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf((*float32)(nil)),
+					},
+				},
+				{
+					"float64/fast path - nil", (*float64)(nil), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)}, bsonrwtest.ReadDouble,
+					ValueDecoderError{
+						Name:     "FloatDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf((*float64)(nil)),
+					},
+				},
+				{
+					"float32/reflection path (equal)", myfloat32(3.0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.0)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float64/reflection path", myfloat64(3.14159), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/reflection path (truncate)", myfloat32(3.14), &DecodeContext{Truncate: true},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"float32/reflection path (no truncate)", myfloat32(0), nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14)}, bsonrwtest.ReadDouble,
+					errors.New("FloatDecodeValue can only convert float64 to float32 when truncation is allowed"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:  "FloatDecodeValue",
+						Kinds: []reflect.Kind{reflect.Float32, reflect.Float64},
+					},
+				},
+			},
+		},
+		{
+			"TimeDecodeValue",
+			ValueDecoderFunc(dvd.TimeDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(0)},
+					bsonrwtest.ReadDateTime,
+					ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"ReadDateTime error",
+					time.Time{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(0), Err: errors.New("ReadDateTime error"), ErrAfter: bsonrwtest.ReadDateTime},
+					bsonrwtest.ReadDateTime,
+					errors.New("ReadDateTime error"),
+				},
+				{
+					"time.Time",
+					now,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(now.UnixNano() / int64(time.Millisecond))},
+					bsonrwtest.ReadDateTime,
+					nil,
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(0)},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}},
+				},
+			},
+		},
+		{
+			"MapDecodeValue",
+			ValueDecoderFunc(dvd.MapDecodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"wrong kind (non-string key)",
+					map[int]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "MapDecodeValue",
+						Kinds:    []reflect.Kind{reflect.Map},
+						Received: reflect.ValueOf(map[int]interface{}{}),
+					},
+				},
+				{
+					"ReadDocument Error",
+					make(map[string]interface{}),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("rd error"), ErrAfter: bsonrwtest.ReadDocument},
+					bsonrwtest.ReadDocument,
+					errors.New("rd error"),
+				},
+				{
+					"Lookup Error",
+					map[string]string{},
+					&DecodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.ReadDocument,
+					ErrNoDecoder{Type: reflect.TypeOf(string(""))},
+				},
+				{
+					"ReadElement Error",
+					make(map[string]interface{}),
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("re error"), ErrAfter: bsonrwtest.ReadElement},
+					bsonrwtest.ReadElement,
+					errors.New("re error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}},
+				},
+				{
+					"wrong BSON type",
+					map[string]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into a map[string]interface {}"),
+				},
+			},
+		},
+		{
+			"ArrayDecodeValue",
+			ValueDecoderFunc(dvd.ArrayDecodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}},
+				},
+				{
+					"Not Type Array",
+					[1]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into an array"),
+				},
+				{
+					"ReadArray Error",
+					[1]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ra error"), ErrAfter: bsonrwtest.ReadArray, BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					errors.New("ra error"),
+				},
+				{
+					"Lookup Error",
+					[1]string{},
+					&DecodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					ErrNoDecoder{Type: reflect.TypeOf(string(""))},
+				},
+				{
+					"ReadValue Error",
+					[1]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("rv error"), ErrAfter: bsonrwtest.ReadValue, BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("rv error"),
+				},
+				{
+					"DecodeValue Error",
+					[1]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("cannot decode array into a string type"),
+				},
+				{
+					"Document but not D",
+					[1]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Type(0)},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into [1]string"),
+				},
+				{
+					"EmbeddedDocument but not D",
+					[1]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.EmbeddedDocument},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into [1]string"),
+				},
+			},
+		},
+		{
+			"SliceDecodeValue",
+			ValueDecoderFunc(dvd.SliceDecodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}},
+				},
+				{
+					"Not Type Array",
+					[]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into a slice"),
+				},
+				{
+					"ReadArray Error",
+					[]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ra error"), ErrAfter: bsonrwtest.ReadArray, BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					errors.New("ra error"),
+				},
+				{
+					"Lookup Error",
+					[]string{},
+					&DecodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadArray,
+					ErrNoDecoder{Type: reflect.TypeOf(string(""))},
+				},
+				{
+					"ReadValue Error",
+					[]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("rv error"), ErrAfter: bsonrwtest.ReadValue, BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("rv error"),
+				},
+				{
+					"DecodeValue Error",
+					[]string{},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Array},
+					bsonrwtest.ReadValue,
+					errors.New("cannot decode array into a string type"),
+				},
+				{
+					"Document but not D",
+					[]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Type(0)},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into []string"),
+				},
+				{
+					"EmbeddedDocument but not D",
+					[]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.EmbeddedDocument},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode document into []string"),
+				},
+			},
+		},
+		{
+			"ObjectIDDecodeValue",
+			ValueDecoderFunc(dvd.ObjectIDDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not objectID",
+					primitive.ObjectID{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an ObjectID", bsontype.String),
+				},
+				{
+					"ReadObjectID Error",
+					primitive.ObjectID{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID, Err: errors.New("roid error"), ErrAfter: bsonrwtest.ReadObjectID},
+					bsonrwtest.ReadObjectID,
+					errors.New("roid error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID, Return: primitive.ObjectID{}},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}},
+				},
+				{
+					"success",
+					primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.ObjectID,
+						Return:   primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					bsonrwtest.ReadObjectID,
+					nil,
+				},
+			},
+		},
+		{
+			"Decimal128DecodeValue",
+			ValueDecoderFunc(dvd.Decimal128DecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not decimal128",
+					primitive.Decimal128{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.Decimal128", bsontype.String),
+				},
+				{
+					"ReadDecimal128 Error",
+					primitive.Decimal128{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128, Err: errors.New("rd128 error"), ErrAfter: bsonrwtest.ReadDecimal128},
+					bsonrwtest.ReadDecimal128,
+					errors.New("rd128 error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128, Return: d128},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}},
+				},
+				{
+					"success",
+					d128,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Decimal128, Return: d128},
+					bsonrwtest.ReadDecimal128,
+					nil,
+				},
+			},
+		},
+		{
+			"JSONNumberDecodeValue",
+			ValueDecoderFunc(dvd.JSONNumberDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not double/int32/int64",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a json.Number", bsontype.String),
+				},
+				{
+					"ReadDouble Error",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Err: errors.New("rd error"), ErrAfter: bsonrwtest.ReadDouble},
+					bsonrwtest.ReadDouble,
+					errors.New("rd error"),
+				},
+				{
+					"ReadInt32 Error",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Err: errors.New("ri32 error"), ErrAfter: bsonrwtest.ReadInt32},
+					bsonrwtest.ReadInt32,
+					errors.New("ri32 error"),
+				},
+				{
+					"ReadInt64 Error",
+					json.Number(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Err: errors.New("ri64 error"), ErrAfter: bsonrwtest.ReadInt64},
+					bsonrwtest.ReadInt64,
+					errors.New("ri64 error"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.ObjectID, Return: primitive.ObjectID{}},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}},
+				},
+				{
+					"success/double",
+					json.Number("3.14159"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)},
+					bsonrwtest.ReadDouble,
+					nil,
+				},
+				{
+					"success/int32",
+					json.Number("12345"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32, Return: int32(12345)},
+					bsonrwtest.ReadInt32,
+					nil,
+				},
+				{
+					"success/int64",
+					json.Number("1234567890"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int64, Return: int64(1234567890)},
+					bsonrwtest.ReadInt64,
+					nil,
+				},
+			},
+		},
+		{
+			"URLDecodeValue",
+			ValueDecoderFunc(dvd.URLDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a *url.URL", bsontype.Int32),
+				},
+				{
+					"type not *url.URL",
+					int64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("http://example.com")},
+					bsonrwtest.ReadString,
+					ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}, Received: reflect.ValueOf(int64(0))},
+				},
+				{
+					"ReadString error",
+					url.URL{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Err: errors.New("rs error"), ErrAfter: bsonrwtest.ReadString},
+					bsonrwtest.ReadString,
+					errors.New("rs error"),
+				},
+				{
+					"url.Parse error",
+					url.URL{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("not-valid-%%%%://")},
+					bsonrwtest.ReadString,
+					errors.New("parse not-valid-%%%%://: first path segment in URL cannot contain colon"),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("http://example.com")},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}},
+				},
+				{
+					"url.URL",
+					url.URL{Scheme: "http", Host: "example.com"},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("http://example.com")},
+					bsonrwtest.ReadString,
+					nil,
+				},
+			},
+		},
+		{
+			"ByteSliceDecodeValue",
+			ValueDecoderFunc(dvd.ByteSliceDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Int32},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a []byte", bsontype.Int32),
+				},
+				{
+					"type not []byte",
+					int64(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Return: bsoncore.Value{Type: bsontype.Binary}},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.ValueOf(int64(0))},
+				},
+				{
+					"ReadBinary error",
+					[]byte{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Err: errors.New("rb error"), ErrAfter: bsonrwtest.ReadBinary},
+					bsonrwtest.ReadBinary,
+					errors.New("rb error"),
+				},
+				{
+					"incorrect subtype",
+					[]byte{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Binary,
+						Return: bsoncore.Value{
+							Type: bsontype.Binary,
+							Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
+						},
+					},
+					bsonrwtest.ReadBinary,
+					fmt.Errorf("ByteSliceDecodeValue can only be used to decode subtype 0x00 for %s, got %v", bsontype.Binary, byte(0xFF)),
+				},
+				{
+					"can set false",
+					cansettest,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Return: bsoncore.AppendBinary(nil, 0x00, []byte{0x01, 0x02, 0x03})},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}},
+				},
+			},
+		},
+		{
+			"ValueUnmarshalerDecodeValue",
+			ValueDecoderFunc(dvd.ValueUnmarshalerDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "ValueUnmarshalerDecodeValue",
+						Types:    []reflect.Type{tValueUnmarshaler},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"copy error",
+					&testValueUnmarshaler{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadString},
+					bsonrwtest.ReadString,
+					errors.New("copy error"),
+				},
+				{
+					"ValueUnmarshaler",
+					&testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world")},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("hello, world")},
+					bsonrwtest.ReadString,
+					nil,
+				},
+			},
+		},
+		{
+			"UnmarshalerDecodeValue",
+			ValueDecoderFunc(dvd.UnmarshalerDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"copy error",
+					&testUnmarshaler{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadString},
+					bsonrwtest.ReadString,
+					errors.New("copy error"),
+				},
+				{
+					"Unmarshaler",
+					testUnmarshaler{Val: bsoncore.AppendDouble(nil, 3.14159)},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double, Return: float64(3.14159)},
+					bsonrwtest.ReadDouble,
+					nil,
+				},
+			},
+		},
+		{
+			"PointerCodec.DecodeValue",
+			NewPointerCodec(),
+			[]subtest{
+				{
+					"not valid", nil, nil, nil, bsonrwtest.Nothing,
+					ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: reflect.Value{}},
+				},
+				{
+					"can set", cansettest, nil, nil, bsonrwtest.Nothing,
+					ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}},
+				},
+				{
+					"No Decoder", &wrong, &DecodeContext{Registry: buildDefaultRegistry()}, nil, bsonrwtest.Nothing,
+					ErrNoDecoder{Type: reflect.TypeOf(wrong)},
+				},
+			},
+		},
+		{
+			"BinaryDecodeValue",
+			ValueDecoderFunc(dvd.BinaryDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tBinary}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not binary",
+					primitive.Binary{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Binary", bsontype.String),
+				},
+				{
+					"ReadBinary Error",
+					primitive.Binary{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Binary, Err: errors.New("rb error"), ErrAfter: bsonrwtest.ReadBinary},
+					bsonrwtest.ReadBinary,
+					errors.New("rb error"),
+				},
+				{
+					"Binary/success",
+					primitive.Binary{Data: []byte{0x01, 0x02, 0x03}, Subtype: 0xFF},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Binary,
+						Return: bsoncore.Value{
+							Type: bsontype.Binary,
+							Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
+						},
+					},
+					bsonrwtest.ReadBinary,
+					nil,
+				},
+			},
+		},
+		{
+			"UndefinedDecodeValue",
+			ValueDecoderFunc(dvd.UndefinedDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Undefined},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "UndefinedDecodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not undefined",
+					primitive.Undefined{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into an Undefined", bsontype.String),
+				},
+				{
+					"ReadUndefined Error",
+					primitive.Undefined{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Undefined, Err: errors.New("ru error"), ErrAfter: bsonrwtest.ReadUndefined},
+					bsonrwtest.ReadUndefined,
+					errors.New("ru error"),
+				},
+				{
+					"ReadUndefined/success",
+					primitive.Undefined{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Undefined},
+					bsonrwtest.ReadUndefined,
+					nil,
+				},
+			},
+		},
+		{
+			"DateTimeDecodeValue",
+			ValueDecoderFunc(dvd.DateTimeDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "DateTimeDecodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not datetime",
+					primitive.DateTime(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a DateTime", bsontype.String),
+				},
+				{
+					"ReadDateTime Error",
+					primitive.DateTime(0),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Err: errors.New("rdt error"), ErrAfter: bsonrwtest.ReadDateTime},
+					bsonrwtest.ReadDateTime,
+					errors.New("rdt error"),
+				},
+				{
+					"success",
+					primitive.DateTime(1234567890),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DateTime, Return: int64(1234567890)},
+					bsonrwtest.ReadDateTime,
+					nil,
+				},
+			},
+		},
+		{
+			"NullDecodeValue",
+			ValueDecoderFunc(dvd.NullDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Null},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "NullDecodeValue", Types: []reflect.Type{tNull}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not null",
+					primitive.Null{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Null", bsontype.String),
+				},
+				{
+					"ReadNull Error",
+					primitive.Null{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Null, Err: errors.New("rn error"), ErrAfter: bsonrwtest.ReadNull},
+					bsonrwtest.ReadNull,
+					errors.New("rn error"),
+				},
+				{
+					"success",
+					primitive.Null{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Null},
+					bsonrwtest.ReadNull,
+					nil,
+				},
+			},
+		},
+		{
+			"RegexDecodeValue",
+			ValueDecoderFunc(dvd.RegexDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Regex},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "RegexDecodeValue", Types: []reflect.Type{tRegex}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not regex",
+					primitive.Regex{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Regex", bsontype.String),
+				},
+				{
+					"ReadRegex Error",
+					primitive.Regex{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Regex, Err: errors.New("rr error"), ErrAfter: bsonrwtest.ReadRegex},
+					bsonrwtest.ReadRegex,
+					errors.New("rr error"),
+				},
+				{
+					"success",
+					primitive.Regex{Pattern: "foo", Options: "bar"},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Regex,
+						Return: bsoncore.Value{
+							Type: bsontype.Regex,
+							Data: bsoncore.AppendRegex(nil, "foo", "bar"),
+						},
+					},
+					bsonrwtest.ReadRegex,
+					nil,
+				},
+			},
+		},
+		{
+			"DBPointerDecodeValue",
+			ValueDecoderFunc(dvd.DBPointerDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DBPointer},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "DBPointerDecodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not dbpointer",
+					primitive.DBPointer{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a DBPointer", bsontype.String),
+				},
+				{
+					"ReadDBPointer Error",
+					primitive.DBPointer{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.DBPointer, Err: errors.New("rdbp error"), ErrAfter: bsonrwtest.ReadDBPointer},
+					bsonrwtest.ReadDBPointer,
+					errors.New("rdbp error"),
+				},
+				{
+					"success",
+					primitive.DBPointer{
+						DB:      "foobar",
+						Pointer: primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.DBPointer,
+						Return: bsoncore.Value{
+							Type: bsontype.DBPointer,
+							Data: bsoncore.AppendDBPointer(
+								nil, "foobar", primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+							),
+						},
+					},
+					bsonrwtest.ReadDBPointer,
+					nil,
+				},
+			},
+		},
+		{
+			"TimestampDecodeValue",
+			ValueDecoderFunc(dvd.TimestampDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Timestamp},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "TimestampDecodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not timestamp",
+					primitive.Timestamp{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a Timestamp", bsontype.String),
+				},
+				{
+					"ReadTimestamp Error",
+					primitive.Timestamp{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Timestamp, Err: errors.New("rt error"), ErrAfter: bsonrwtest.ReadTimestamp},
+					bsonrwtest.ReadTimestamp,
+					errors.New("rt error"),
+				},
+				{
+					"success",
+					primitive.Timestamp{T: 12345, I: 67890},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Timestamp,
+						Return: bsoncore.Value{
+							Type: bsontype.Timestamp,
+							Data: bsoncore.AppendTimestamp(nil, 12345, 67890),
+						},
+					},
+					bsonrwtest.ReadTimestamp,
+					nil,
+				},
+			},
+		},
+		{
+			"MinKeyDecodeValue",
+			ValueDecoderFunc(dvd.MinKeyDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MinKey},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MinKeyDecodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not null",
+					primitive.MinKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a MinKey", bsontype.String),
+				},
+				{
+					"ReadMinKey Error",
+					primitive.MinKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MinKey, Err: errors.New("rn error"), ErrAfter: bsonrwtest.ReadMinKey},
+					bsonrwtest.ReadMinKey,
+					errors.New("rn error"),
+				},
+				{
+					"success",
+					primitive.MinKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MinKey},
+					bsonrwtest.ReadMinKey,
+					nil,
+				},
+			},
+		},
+		{
+			"MaxKeyDecodeValue",
+			ValueDecoderFunc(dvd.MaxKeyDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MaxKey},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "MaxKeyDecodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not null",
+					primitive.MaxKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a MaxKey", bsontype.String),
+				},
+				{
+					"ReadMaxKey Error",
+					primitive.MaxKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MaxKey, Err: errors.New("rn error"), ErrAfter: bsonrwtest.ReadMaxKey},
+					bsonrwtest.ReadMaxKey,
+					errors.New("rn error"),
+				},
+				{
+					"success",
+					primitive.MaxKey{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.MaxKey},
+					bsonrwtest.ReadMaxKey,
+					nil,
+				},
+			},
+		},
+		{
+			"JavaScriptDecodeValue",
+			ValueDecoderFunc(dvd.JavaScriptDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.JavaScript, Return: ""},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not Javascript",
+					primitive.JavaScript(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.JavaScript", bsontype.String),
+				},
+				{
+					"ReadJavascript Error",
+					primitive.JavaScript(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.JavaScript, Err: errors.New("rjs error"), ErrAfter: bsonrwtest.ReadJavascript},
+					bsonrwtest.ReadJavascript,
+					errors.New("rjs error"),
+				},
+				{
+					"JavaScript/success",
+					primitive.JavaScript("var hello = 'world';"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.JavaScript, Return: "var hello = 'world';"},
+					bsonrwtest.ReadJavascript,
+					nil,
+				},
+			},
+		},
+		{
+			"SymbolDecodeValue",
+			ValueDecoderFunc(dvd.SymbolDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Symbol, Return: ""},
+					bsonrwtest.Nothing,
+					ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"type not Symbol",
+					primitive.Symbol(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.Symbol", bsontype.String),
+				},
+				{
+					"ReadSymbol Error",
+					primitive.Symbol(""),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Symbol, Err: errors.New("rjs error"), ErrAfter: bsonrwtest.ReadSymbol},
+					bsonrwtest.ReadSymbol,
+					errors.New("rjs error"),
+				},
+				{
+					"Symbol/success",
+					primitive.Symbol("var hello = 'world';"),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.Symbol, Return: "var hello = 'world';"},
+					bsonrwtest.ReadSymbol,
+					nil,
+				},
+			},
+		},
+		{
+			"CoreDocumentDecodeValue",
+			ValueDecoderFunc(dvd.CoreDocumentDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "CoreDocumentDecodeValue",
+						Types:    []reflect.Type{tCoreDocument},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"*bsoncore.Document is nil",
+					(*bsoncore.Document)(nil),
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "CoreDocumentDecodeValue",
+						Types:    []reflect.Type{tCoreDocument},
+						Received: reflect.ValueOf((*bsoncore.Document)(nil)),
+					},
+				},
+				{
+					"Copy error",
+					bsoncore.Document{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadDocument},
+					bsonrwtest.ReadDocument,
+					errors.New("copy error"),
+				},
+			},
+		},
+		{
+			"StructCodec.DecodeValue",
+			defaultStructCodec,
+			[]subtest{
+				{
+					"Not struct",
+					reflect.New(reflect.TypeOf(struct{ Foo string }{})).Elem().Interface(),
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					errors.New("cannot decode string into a struct { Foo string }"),
+				},
+			},
+		},
+		{
+			"CodeWithScopeDecodeValue",
+			ValueDecoderFunc(dvd.CodeWithScopeDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.CodeWithScope},
+					bsonrwtest.Nothing,
+					ValueDecoderError{
+						Name:     "CodeWithScopeDecodeValue",
+						Types:    []reflect.Type{tCodeWithScope},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"type not codewithscope",
+					primitive.CodeWithScope{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.String},
+					bsonrwtest.Nothing,
+					fmt.Errorf("cannot decode %v into a primitive.CodeWithScope", bsontype.String),
+				},
+				{
+					"ReadCodeWithScope Error",
+					primitive.CodeWithScope{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.CodeWithScope, Err: errors.New("rcws error"), ErrAfter: bsonrwtest.ReadCodeWithScope},
+					bsonrwtest.ReadCodeWithScope,
+					errors.New("rcws error"),
+				},
+				{
+					"decodeDocument Error",
+					primitive.CodeWithScope{
+						Code:  "var hello = 'world';",
+						Scope: primitive.D{{"foo", nil}},
+					},
+					&DecodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{BSONType: bsontype.CodeWithScope, Err: errors.New("dd error"), ErrAfter: bsonrwtest.ReadElement},
+					bsonrwtest.ReadElement,
+					errors.New("dd error"),
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, rc := range tc.subtests {
+				t.Run(rc.name, func(t *testing.T) {
+					var dc DecodeContext
+					if rc.dctx != nil {
+						dc = *rc.dctx
+					}
+					llvrw := new(bsonrwtest.ValueReaderWriter)
+					if rc.llvrw != nil {
+						llvrw = rc.llvrw
+					}
+					llvrw.T = t
+					// var got interface{}
+					if rc.val == cansetreflectiontest { // We're doing a CanSet reflection test
+						err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
+						if !compareErrors(err, rc.err) {
+							t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+						}
+
+						val := reflect.New(reflect.TypeOf(rc.val)).Elem()
+						err = tc.vd.DecodeValue(dc, llvrw, val)
+						if !compareErrors(err, rc.err) {
+							t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+						}
+						return
+					}
+					if rc.val == cansettest { // We're doing an IsValid and CanSet test
+						wanterr, ok := rc.err.(ValueDecoderError)
+						if !ok {
+							t.Fatalf("Error must be a DecodeValueError, but got a %T", rc.err)
+						}
+
+						err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
+						wanterr.Received = reflect.ValueOf(nil)
+						if !compareErrors(err, wanterr) {
+							t.Errorf("Errors do not match. got %v; want %v", err, wanterr)
+						}
+
+						err = tc.vd.DecodeValue(dc, llvrw, reflect.ValueOf(int(12345)))
+						wanterr.Received = reflect.ValueOf(int(12345))
+						if !compareErrors(err, wanterr) {
+							t.Errorf("Errors do not match. got %v; want %v", err, wanterr)
+						}
+						return
+					}
+					var val reflect.Value
+					if rtype := reflect.TypeOf(rc.val); rtype != nil {
+						val = reflect.New(rtype).Elem()
+					}
+					want := rc.val
+					defer func() {
+						if err := recover(); err != nil {
+							fmt.Println(t.Name())
+							panic(err)
+						}
+					}()
+					err := tc.vd.DecodeValue(dc, llvrw, val)
+					if !compareErrors(err, rc.err) {
+						t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+					}
+					invoked := llvrw.Invoked
+					if !cmp.Equal(invoked, rc.invoke) {
+						t.Errorf("Incorrect method invoked. got %v; want %v", invoked, rc.invoke)
+					}
+					var got interface{}
+					if val.IsValid() && val.CanInterface() {
+						got = val.Interface()
+					}
+					if rc.err == nil && !cmp.Equal(got, want, cmp.Comparer(compareDecimal128)) {
+						t.Errorf("Values do not match. got (%T)%v; want (%T)%v", got, got, want, want)
+					}
+				})
+			}
+		})
+	}
+
+	t.Run("CodeWithScopeCodec/DecodeValue/success", func(t *testing.T) {
+		dc := DecodeContext{Registry: buildDefaultRegistry()}
+		b := bsoncore.BuildDocument(nil,
+			bsoncore.AppendCodeWithScopeElement(
+				nil, "foo", "var hello = 'world';",
+				buildDocument(bsoncore.AppendNullElement(nil, "bar")),
+			),
+		)
+		dvr := bsonrw.NewBSONDocumentReader(b)
+		dr, err := dvr.ReadDocument()
+		noerr(t, err)
+		_, vr, err := dr.ReadElement()
+		noerr(t, err)
+
+		want := primitive.CodeWithScope{
+			Code:  "var hello = 'world';",
+			Scope: primitive.D{{"bar", nil}},
+		}
+		val := reflect.New(tCodeWithScope).Elem()
+		err = dvd.CodeWithScopeDecodeValue(dc, vr, val)
+		noerr(t, err)
+
+		got := val.Interface().(primitive.CodeWithScope)
+		if got.Code != want.Code && !cmp.Equal(got.Scope, want.Scope) {
+			t.Errorf("CodeWithScopes do not match. got %v; want %v", got, want)
+		}
+	})
+	t.Run("ValueUnmarshalerDecodeValue/UnmarshalBSONValue error", func(t *testing.T) {
+		var dc DecodeContext
+		llvrw := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("hello, world!")}
+		llvrw.T = t
+
+		want := errors.New("ubsonv error")
+		valUnmarshaler := &testValueUnmarshaler{err: want}
+		got := dvd.ValueUnmarshalerDecodeValue(dc, llvrw, reflect.ValueOf(valUnmarshaler))
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+	t.Run("ValueUnmarshalerDecodeValue/Unaddressable value", func(t *testing.T) {
+		var dc DecodeContext
+		llvrw := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.String, Return: string("hello, world!")}
+		llvrw.T = t
+
+		val := reflect.ValueOf(testValueUnmarshaler{})
+		want := ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
+		got := dvd.ValueUnmarshalerDecodeValue(dc, llvrw, val)
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+
+	t.Run("SliceCodec/DecodeValue/can't set slice", func(t *testing.T) {
+		var val []string
+		want := ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(val)}
+		got := dvd.SliceDecodeValue(DecodeContext{}, nil, reflect.ValueOf(val))
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+	t.Run("SliceCodec/DecodeValue/too many elements", func(t *testing.T) {
+		idx, doc := bsoncore.AppendDocumentStart(nil)
+		aidx, doc := bsoncore.AppendArrayElementStart(doc, "foo")
+		doc = bsoncore.AppendStringElement(doc, "0", "foo")
+		doc = bsoncore.AppendStringElement(doc, "1", "bar")
+		doc, err := bsoncore.AppendArrayEnd(doc, aidx)
+		noerr(t, err)
+		doc, err = bsoncore.AppendDocumentEnd(doc, idx)
+		noerr(t, err)
+		dvr := bsonrw.NewBSONDocumentReader(doc)
+		noerr(t, err)
+		dr, err := dvr.ReadDocument()
+		noerr(t, err)
+		_, vr, err := dr.ReadElement()
+		noerr(t, err)
+		var val [1]string
+		want := fmt.Errorf("more elements returned in array than can fit inside %T", val)
+
+		dc := DecodeContext{Registry: buildDefaultRegistry()}
+		got := dvd.ArrayDecodeValue(dc, vr, reflect.ValueOf(val))
+		if !compareErrors(got, want) {
+			t.Errorf("Errors do not match. got %v; want %v", got, want)
+		}
+	})
+
+	t.Run("success path", func(t *testing.T) {
+		oid := primitive.NewObjectID()
+		oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
+		var str = new(string)
+		*str = "bar"
+		now := time.Now().Truncate(time.Millisecond).UTC()
+		murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
+		if err != nil {
+			t.Errorf("Error parsing URL: %v", err)
+			t.FailNow()
+		}
+		decimal128, err := primitive.ParseDecimal128("1.5e10")
+		if err != nil {
+			t.Errorf("Error parsing decimal128: %v", err)
+			t.FailNow()
+		}
+
+		testCases := []struct {
+			name  string
+			value interface{}
+			b     []byte
+			err   error
+		}{
+			{
+				"map[string]int",
+				map[string]int32{"foo": 1},
+				[]byte{
+					0x0E, 0x00, 0x00, 0x00,
+					0x10, 'f', 'o', 'o', 0x00,
+					0x01, 0x00, 0x00, 0x00,
+					0x00,
+				},
+				nil,
+			},
+			{
+				"map[string]primitive.ObjectID",
+				map[string]primitive.ObjectID{"foo": oid},
+				func() []byte {
+					idx, doc := bsoncore.AppendDocumentStart(nil)
+					doc = bsoncore.AppendObjectIDElement(doc, "foo", oid)
+					doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+					return doc
+				}(),
+				nil,
+			},
+			{
+				"map[string][]int32",
+				map[string][]int32{"Z": {1, 2, 3}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt32Element(doc, "0", 1)
+					doc = bsoncore.AppendInt32Element(doc, "1", 2)
+					return bsoncore.AppendInt32Element(doc, "2", 3)
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.ObjectID",
+				map[string][]primitive.ObjectID{"Z": oids},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendObjectIDElement(doc, "0", oids[0])
+					doc = bsoncore.AppendObjectIDElement(doc, "1", oids[1])
+					return bsoncore.AppendObjectIDElement(doc, "2", oids[2])
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(int64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendInt64Element(doc, "1", 10)
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(float64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendDoubleElement(doc, "1", 10.1)
+				}),
+				nil,
+			},
+			{
+				"map[string][]*url.URL",
+				map[string][]*url.URL{"Z": {murl}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendStringElement(doc, "0", murl.String())
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.Decimal128",
+				map[string][]primitive.Decimal128{"Z": {decimal128}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendDecimal128Element(doc, "0", decimal128)
+				}),
+				nil,
+			},
+			{
+				"map[mystring]interface{}",
+				map[mystring]interface{}{"pi": 3.14159},
+				buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+				nil,
+			},
+			{
+				"-",
+				struct {
+					A string `bson:"-"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty",
+				struct {
+					A string `bson:",omitempty"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty, empty time",
+				struct {
+					A time.Time `bson:",omitempty"`
+				}{
+					A: time.Time{},
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"no private fields",
+				noPrivateFields{a: "should be empty"},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"minsize",
+				struct {
+					A int64 `bson:",minsize"`
+				}{
+					A: 12345,
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline",
+				struct {
+					Foo struct {
+						A int64 `bson:",minsize"`
+					} `bson:",inline"`
+				}{
+					Foo: struct {
+						A int64 `bson:",minsize"`
+					}{
+						A: 12345,
+					},
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline map",
+				struct {
+					Foo map[string]string `bson:",inline"`
+				}{
+					Foo: map[string]string{"foo": "bar"},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name bson:name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"inline, omitempty",
+				struct {
+					A   string
+					Foo zeroTest `bson:"omitempty,inline"`
+				}{
+					A:   "bar",
+					Foo: zeroTest{true},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "a", "bar")),
+				nil,
+			},
+			{
+				"struct{}",
+				struct {
+					A bool
+					B int32
+					C int64
+					D uint16
+					E uint64
+					F float64
+					G string
+					H map[string]string
+					I []byte
+					K [2]string
+					L struct {
+						M string
+					}
+					Q  primitive.ObjectID
+					T  []struct{}
+					Y  json.Number
+					Z  time.Time
+					AA json.Number
+					AB *url.URL
+					AC primitive.Decimal128
+					AD *time.Time
+					AE *testValueUnmarshaler
+					AF *bool
+					AG *bool
+					AH *int32
+					AI *int64
+					AJ *primitive.ObjectID
+					AK *primitive.ObjectID
+					AL testValueUnmarshaler
+					AM interface{}
+					AN interface{}
+					AO interface{}
+					AP primitive.D
+					AQ primitive.A
+					AR [2]primitive.E
+					AS []byte
+					AT map[string]interface{}
+					AU primitive.CodeWithScope
+					AV primitive.M
+					AW primitive.D
+					AX map[string]interface{}
+					AY []primitive.E
+					AZ interface{}
+				}{
+					A: true,
+					B: 123,
+					C: 456,
+					D: 789,
+					E: 101112,
+					F: 3.14159,
+					G: "Hello, world",
+					H: map[string]string{"foo": "bar"},
+					I: []byte{0x01, 0x02, 0x03},
+					K: [2]string{"baz", "qux"},
+					L: struct {
+						M string
+					}{
+						M: "foobar",
+					},
+					Q:  oid,
+					T:  nil,
+					Y:  json.Number("5"),
+					Z:  now,
+					AA: json.Number("10.1"),
+					AB: murl,
+					AC: decimal128,
+					AD: &now,
+					AE: &testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world!")},
+					AF: func(b bool) *bool { return &b }(true),
+					AG: nil,
+					AH: func(i32 int32) *int32 { return &i32 }(12345),
+					AI: func(i64 int64) *int64 { return &i64 }(1234567890),
+					AJ: &oid,
+					AK: nil,
+					AL: testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world!")},
+					AM: "hello, world",
+					AN: int32(12345),
+					AO: oid,
+					AP: primitive.D{{"foo", "bar"}},
+					AQ: primitive.A{"foo", "bar"},
+					AR: [2]primitive.E{{"hello", "world"}, {"pi", 3.14159}},
+					AS: nil,
+					AT: nil,
+					AU: primitive.CodeWithScope{Code: "var hello = 'world';", Scope: primitive.D{{"pi", 3.14159}}},
+					AV: primitive.M{"foo": primitive.M{"bar": "baz"}},
+					AW: primitive.D{{"foo", primitive.D{{"bar", "baz"}}}},
+					AX: map[string]interface{}{"foo": map[string]interface{}{"bar": "baz"}},
+					AY: []primitive.E{{"foo", []primitive.E{{"bar", "baz"}}}},
+					AZ: primitive.D{{"foo", primitive.D{{"bar", "baz"}}}},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = bsoncore.AppendBooleanElement(doc, "a", true)
+					doc = bsoncore.AppendInt32Element(doc, "b", 123)
+					doc = bsoncore.AppendInt64Element(doc, "c", 456)
+					doc = bsoncore.AppendInt32Element(doc, "d", 789)
+					doc = bsoncore.AppendInt64Element(doc, "e", 101112)
+					doc = bsoncore.AppendDoubleElement(doc, "f", 3.14159)
+					doc = bsoncore.AppendStringElement(doc, "g", "Hello, world")
+					doc = bsoncore.AppendDocumentElement(doc, "h", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = bsoncore.AppendBinaryElement(doc, "i", 0x00, []byte{0x01, 0x02, 0x03})
+					doc = bsoncore.AppendArrayElement(doc, "k",
+						buildArray(bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = bsoncore.AppendDocumentElement(doc, "l", buildDocument(bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = bsoncore.AppendObjectIDElement(doc, "q", oid)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendInt64Element(doc, "y", 5)
+					doc = bsoncore.AppendDateTimeElement(doc, "z", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendDoubleElement(doc, "aa", 10.1)
+					doc = bsoncore.AppendStringElement(doc, "ab", murl.String())
+					doc = bsoncore.AppendDecimal128Element(doc, "ac", decimal128)
+					doc = bsoncore.AppendDateTimeElement(doc, "ad", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendStringElement(doc, "ae", "hello, world!")
+					doc = bsoncore.AppendBooleanElement(doc, "af", true)
+					doc = bsoncore.AppendNullElement(doc, "ag")
+					doc = bsoncore.AppendInt32Element(doc, "ah", 12345)
+					doc = bsoncore.AppendInt32Element(doc, "ai", 1234567890)
+					doc = bsoncore.AppendObjectIDElement(doc, "aj", oid)
+					doc = bsoncore.AppendNullElement(doc, "ak")
+					doc = bsoncore.AppendStringElement(doc, "al", "hello, world!")
+					doc = bsoncore.AppendStringElement(doc, "am", "hello, world")
+					doc = bsoncore.AppendInt32Element(doc, "an", 12345)
+					doc = bsoncore.AppendObjectIDElement(doc, "ao", oid)
+					doc = bsoncore.AppendDocumentElement(doc, "ap", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = bsoncore.AppendArrayElement(doc, "aq",
+						buildArray(bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar")),
+					)
+					doc = bsoncore.AppendDocumentElement(doc, "ar",
+						buildDocument(bsoncore.AppendDoubleElement(bsoncore.AppendStringElement(nil, "hello", "world"), "pi", 3.14159)),
+					)
+					doc = bsoncore.AppendNullElement(doc, "as")
+					doc = bsoncore.AppendNullElement(doc, "at")
+					doc = bsoncore.AppendCodeWithScopeElement(doc, "au",
+						"var hello = 'world';", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+					)
+					for _, name := range [5]string{"av", "aw", "ax", "ay", "az"} {
+						doc = bsoncore.AppendDocumentElement(doc, name, buildDocument(
+							bsoncore.AppendDocumentElement(nil, "foo", buildDocument(
+								bsoncore.AppendStringElement(nil, "bar", "baz"),
+							)),
+						))
+					}
+					return doc
+				}(nil)),
+				nil,
+			},
+			{
+				"struct{[]interface{}}",
+				struct {
+					A []bool
+					B []int32
+					C []int64
+					D []uint16
+					E []uint64
+					F []float64
+					G []string
+					H []map[string]string
+					I [][]byte
+					K [1][2]string
+					L []struct {
+						M string
+					}
+					N  [][]string
+					R  []primitive.ObjectID
+					T  []struct{}
+					W  []map[string]struct{}
+					X  []map[string]struct{}
+					Y  []map[string]struct{}
+					Z  []time.Time
+					AA []json.Number
+					AB []*url.URL
+					AC []primitive.Decimal128
+					AD []*time.Time
+					AE []*testValueUnmarshaler
+					AF []*bool
+					AG []*int32
+					AH []*int64
+					AI []*primitive.ObjectID
+					AJ []primitive.D
+					AK []primitive.A
+					AL [][2]primitive.E
+				}{
+					A: []bool{true},
+					B: []int32{123},
+					C: []int64{456},
+					D: []uint16{789},
+					E: []uint64{101112},
+					F: []float64{3.14159},
+					G: []string{"Hello, world"},
+					H: []map[string]string{{"foo": "bar"}},
+					I: [][]byte{{0x01, 0x02, 0x03}},
+					K: [1][2]string{{"baz", "qux"}},
+					L: []struct {
+						M string
+					}{
+						{
+							M: "foobar",
+						},
+					},
+					N:  [][]string{{"foo", "bar"}},
+					R:  oids,
+					T:  nil,
+					W:  nil,
+					X:  []map[string]struct{}{},   // Should be empty BSON Array
+					Y:  []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
+					Z:  []time.Time{now, now},
+					AA: []json.Number{json.Number("5"), json.Number("10.1")},
+					AB: []*url.URL{murl},
+					AC: []primitive.Decimal128{decimal128},
+					AD: []*time.Time{&now, &now},
+					AE: []*testValueUnmarshaler{
+						{t: bsontype.String, val: bsoncore.AppendString(nil, "hello")},
+						{t: bsontype.String, val: bsoncore.AppendString(nil, "world")},
+					},
+					AF: []*bool{pbool(true), nil},
+					AG: []*int32{pi32(12345), nil},
+					AH: []*int64{pi64(1234567890), nil, pi64(9012345678)},
+					AI: []*primitive.ObjectID{&oid, nil},
+					AJ: []primitive.D{{{"foo", "bar"}}, nil},
+					AK: []primitive.A{{"foo", "bar"}, nil},
+					AL: [][2]primitive.E{{{"hello", "world"}, {"pi", 3.14159}}},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = appendArrayElement(doc, "a", bsoncore.AppendBooleanElement(nil, "0", true))
+					doc = appendArrayElement(doc, "b", bsoncore.AppendInt32Element(nil, "0", 123))
+					doc = appendArrayElement(doc, "c", bsoncore.AppendInt64Element(nil, "0", 456))
+					doc = appendArrayElement(doc, "d", bsoncore.AppendInt32Element(nil, "0", 789))
+					doc = appendArrayElement(doc, "e", bsoncore.AppendInt64Element(nil, "0", 101112))
+					doc = appendArrayElement(doc, "f", bsoncore.AppendDoubleElement(nil, "0", 3.14159))
+					doc = appendArrayElement(doc, "g", bsoncore.AppendStringElement(nil, "0", "Hello, world"))
+					doc = appendArrayElement(doc, "h", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = appendArrayElement(doc, "i", bsoncore.AppendBinaryElement(nil, "0", 0x00, []byte{0x01, 0x02, 0x03}))
+					doc = appendArrayElement(doc, "k",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = appendArrayElement(doc, "l", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = appendArrayElement(doc, "n",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar")),
+					)
+					doc = appendArrayElement(doc, "r",
+						bsoncore.AppendObjectIDElement(
+							bsoncore.AppendObjectIDElement(
+								bsoncore.AppendObjectIDElement(nil,
+									"0", oids[0]),
+								"1", oids[1]),
+							"2", oids[2]),
+					)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendNullElement(doc, "w")
+					doc = appendArrayElement(doc, "x", nil)
+					doc = appendArrayElement(doc, "y", buildDocumentElement("0", nil))
+					doc = appendArrayElement(doc, "z",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(
+								nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "aa", bsoncore.AppendDoubleElement(bsoncore.AppendInt64Element(nil, "0", 5), "1", 10.10))
+					doc = appendArrayElement(doc, "ab", bsoncore.AppendStringElement(nil, "0", murl.String()))
+					doc = appendArrayElement(doc, "ac", bsoncore.AppendDecimal128Element(nil, "0", decimal128))
+					doc = appendArrayElement(doc, "ad",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "ae",
+						bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "hello"), "1", "world"),
+					)
+					doc = appendArrayElement(doc, "af",
+						bsoncore.AppendNullElement(bsoncore.AppendBooleanElement(nil, "0", true), "1"),
+					)
+					doc = appendArrayElement(doc, "ag",
+						bsoncore.AppendNullElement(bsoncore.AppendInt32Element(nil, "0", 12345), "1"),
+					)
+					doc = appendArrayElement(doc, "ah",
+						bsoncore.AppendInt64Element(
+							bsoncore.AppendNullElement(bsoncore.AppendInt64Element(nil, "0", 1234567890), "1"),
+							"2", 9012345678,
+						),
+					)
+					doc = appendArrayElement(doc, "ai",
+						bsoncore.AppendNullElement(bsoncore.AppendObjectIDElement(nil, "0", oid), "1"),
+					)
+					doc = appendArrayElement(doc, "aj",
+						bsoncore.AppendNullElement(
+							bsoncore.AppendDocumentElement(nil, "0", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar"))),
+							"1",
+						),
+					)
+					doc = appendArrayElement(doc, "ak",
+						bsoncore.AppendNullElement(
+							buildArrayElement("0",
+								bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar"),
+							),
+							"1",
+						),
+					)
+					doc = appendArrayElement(doc, "al",
+						buildDocumentElement(
+							"0",
+							bsoncore.AppendDoubleElement(bsoncore.AppendStringElement(nil, "hello", "world"), "pi", 3.14159),
+						),
+					)
+					return doc
+				}(nil)),
+				nil,
+			},
+		}
+
+		t.Run("Decode", func(t *testing.T) {
+			for _, tc := range testCases {
+				t.Run(tc.name, func(t *testing.T) {
+					vr := bsonrw.NewBSONDocumentReader(tc.b)
+					reg := buildDefaultRegistry()
+					vtype := reflect.TypeOf(tc.value)
+					dec, err := reg.LookupDecoder(vtype)
+					noerr(t, err)
+
+					gotVal := reflect.New(reflect.TypeOf(tc.value)).Elem()
+					err = dec.DecodeValue(DecodeContext{Registry: reg}, vr, gotVal)
+					noerr(t, err)
+
+					got := gotVal.Interface()
+					want := tc.value
+					if diff := cmp.Diff(
+						got, want,
+						cmp.Comparer(compareDecimal128),
+						cmp.Comparer(compareNoPrivateFields),
+						cmp.Comparer(compareZeroTest),
+						cmp.Comparer(compareTime),
+					); diff != "" {
+						t.Errorf("difference:\n%s", diff)
+						t.Errorf("Values are not equal.\ngot: %#v\nwant:%#v", got, want)
+					}
+				})
+			}
+		})
+	})
+
+	t.Run("EmptyInterfaceDecodeValue", func(t *testing.T) {
+		t.Run("DecodeValue", func(t *testing.T) {
+			testCases := []struct {
+				name     string
+				val      interface{}
+				bsontype bsontype.Type
+			}{
+				{
+					"Double - float64",
+					float64(3.14159),
+					bsontype.Double,
+				},
+				{
+					"String - string",
+					string("foo bar baz"),
+					bsontype.String,
+				},
+				{
+					"Array - primitive.A",
+					primitive.A{3.14159},
+					bsontype.Array,
+				},
+				{
+					"Binary - Binary",
+					primitive.Binary{Subtype: 0xFF, Data: []byte{0x01, 0x02, 0x03}},
+					bsontype.Binary,
+				},
+				{
+					"Undefined - Undefined",
+					primitive.Undefined{},
+					bsontype.Undefined,
+				},
+				{
+					"ObjectID - primitive.ObjectID",
+					primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					bsontype.ObjectID,
+				},
+				{
+					"Boolean - bool",
+					bool(true),
+					bsontype.Boolean,
+				},
+				{
+					"DateTime - DateTime",
+					primitive.DateTime(1234567890),
+					bsontype.DateTime,
+				},
+				{
+					"Null - Null",
+					nil,
+					bsontype.Null,
+				},
+				{
+					"Regex - Regex",
+					primitive.Regex{Pattern: "foo", Options: "bar"},
+					bsontype.Regex,
+				},
+				{
+					"DBPointer - DBPointer",
+					primitive.DBPointer{
+						DB:      "foobar",
+						Pointer: primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					bsontype.DBPointer,
+				},
+				{
+					"JavaScript - JavaScript",
+					primitive.JavaScript("var foo = 'bar';"),
+					bsontype.JavaScript,
+				},
+				{
+					"Symbol - Symbol",
+					primitive.Symbol("foobarbazlolz"),
+					bsontype.Symbol,
+				},
+				{
+					"Int32 - int32",
+					int32(123456),
+					bsontype.Int32,
+				},
+				{
+					"Int64 - int64",
+					int64(1234567890),
+					bsontype.Int64,
+				},
+				{
+					"Timestamp - Timestamp",
+					primitive.Timestamp{T: 12345, I: 67890},
+					bsontype.Timestamp,
+				},
+				{
+					"Decimal128 - decimal.Decimal128",
+					primitive.NewDecimal128(12345, 67890),
+					bsontype.Decimal128,
+				},
+				{
+					"MinKey - MinKey",
+					primitive.MinKey{},
+					bsontype.MinKey,
+				},
+				{
+					"MaxKey - MaxKey",
+					primitive.MaxKey{},
+					bsontype.MaxKey,
+				},
+			}
+			for _, tc := range testCases {
+				t.Run(tc.name, func(t *testing.T) {
+					llvr := &bsonrwtest.ValueReaderWriter{BSONType: tc.bsontype}
+
+					t.Run("Type Map failure", func(t *testing.T) {
+						if tc.bsontype == bsontype.Null {
+							t.Skip()
+						}
+						val := reflect.New(tEmpty).Elem()
+						dc := DecodeContext{Registry: NewRegistryBuilder().Build()}
+						want := ErrNoTypeMapEntry{Type: tc.bsontype}
+						got := dvd.EmptyInterfaceDecodeValue(dc, llvr, val)
+						if !compareErrors(got, want) {
+							t.Errorf("Errors are not equal. got %v; want %v", got, want)
+						}
+					})
+
+					t.Run("Lookup failure", func(t *testing.T) {
+						if tc.bsontype == bsontype.Null {
+							t.Skip()
+						}
+						val := reflect.New(tEmpty).Elem()
+						dc := DecodeContext{
+							Registry: NewRegistryBuilder().
+								RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)).
+								Build(),
+						}
+						want := ErrNoDecoder{Type: reflect.TypeOf(tc.val)}
+						got := dvd.EmptyInterfaceDecodeValue(dc, llvr, val)
+						if !compareErrors(got, want) {
+							t.Errorf("Errors are not equal. got %v; want %v", got, want)
+						}
+					})
+
+					t.Run("DecodeValue failure", func(t *testing.T) {
+						if tc.bsontype == bsontype.Null {
+							t.Skip()
+						}
+						want := errors.New("DecodeValue failure error")
+						llc := &llCodec{t: t, err: want}
+						dc := DecodeContext{
+							Registry: NewRegistryBuilder().
+								RegisterDecoder(reflect.TypeOf(tc.val), llc).
+								RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)).
+								Build(),
+						}
+						got := dvd.EmptyInterfaceDecodeValue(dc, llvr, reflect.New(tEmpty).Elem())
+						if !compareErrors(got, want) {
+							t.Errorf("Errors are not equal. got %v; want %v", got, want)
+						}
+					})
+
+					t.Run("Success", func(t *testing.T) {
+						want := tc.val
+						llc := &llCodec{t: t, decodeval: tc.val}
+						dc := DecodeContext{
+							Registry: NewRegistryBuilder().
+								RegisterDecoder(reflect.TypeOf(tc.val), llc).
+								RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)).
+								Build(),
+						}
+						got := reflect.New(tEmpty).Elem()
+						err := dvd.EmptyInterfaceDecodeValue(dc, llvr, got)
+						noerr(t, err)
+						if !cmp.Equal(got.Interface(), want, cmp.Comparer(compareDecimal128)) {
+							t.Errorf("Did not receive expected value. got %v; want %v", got.Interface(), want)
+						}
+					})
+				})
+			}
+		})
+
+		t.Run("non-interface{}", func(t *testing.T) {
+			val := uint64(1234567890)
+			want := ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(val)}
+			got := dvd.EmptyInterfaceDecodeValue(DecodeContext{}, nil, reflect.ValueOf(val))
+			if !compareErrors(got, want) {
+				t.Errorf("Errors are not equal. got %v; want %v", got, want)
+			}
+		})
+
+		t.Run("nil *interface{}", func(t *testing.T) {
+			var val interface{}
+			want := ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(val)}
+			got := dvd.EmptyInterfaceDecodeValue(DecodeContext{}, nil, reflect.ValueOf(val))
+			if !compareErrors(got, want) {
+				t.Errorf("Errors are not equal. got %v; want %v", got, want)
+			}
+		})
+
+		t.Run("no type registered", func(t *testing.T) {
+			llvr := &bsonrwtest.ValueReaderWriter{BSONType: bsontype.Double}
+			want := ErrNoTypeMapEntry{Type: bsontype.Double}
+			val := reflect.New(tEmpty).Elem()
+			got := dvd.EmptyInterfaceDecodeValue(DecodeContext{Registry: NewRegistryBuilder().Build()}, llvr, val)
+			if !compareErrors(got, want) {
+				t.Errorf("Errors are not equal. got %v; want %v", got, want)
+			}
+		})
+		t.Run("top level document", func(t *testing.T) {
+			data := bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))
+			vr := bsonrw.NewBSONDocumentReader(data)
+			want := primitive.D{{"pi", 3.14159}}
+			var got interface{}
+			val := reflect.ValueOf(&got).Elem()
+			err := dvd.EmptyInterfaceDecodeValue(DecodeContext{Registry: buildDefaultRegistry()}, vr, val)
+			noerr(t, err)
+			if !cmp.Equal(got, want) {
+				t.Errorf("Did not get correct result. got %v; want %v", got, want)
+			}
+		})
+	})
+}
+
+type testValueUnmarshaler struct {
+	t   bsontype.Type
+	val []byte
+	err error
+}
+
+func (tvu *testValueUnmarshaler) UnmarshalBSONValue(t bsontype.Type, val []byte) error {
+	tvu.t, tvu.val = t, val
+	return tvu.err
+}
+
+type testUnmarshaler struct {
+	Val []byte
+	Err error
+}
+
+func (tvu *testUnmarshaler) UnmarshalBSON(val []byte) error {
+	tvu.Val = val
+	return tvu.Err
+}
+
+func (tvu testValueUnmarshaler) Equal(tvu2 testValueUnmarshaler) bool {
+	return tvu.t == tvu2.t && bytes.Equal(tvu.val, tvu2.val)
+}
+
+// buildDocumentArray inserts vals inside of an array inside of a document.
+func buildDocumentArray(fn func([]byte) []byte) []byte {
+	aix, doc := bsoncore.AppendArrayElementStart(nil, "Z")
+	doc = fn(doc)
+	doc, _ = bsoncore.AppendArrayEnd(doc, aix)
+	return buildDocument(doc)
+}
+
+func buildArray(vals []byte) []byte {
+	aix, doc := bsoncore.AppendArrayStart(nil)
+	doc = append(doc, vals...)
+	doc, _ = bsoncore.AppendArrayEnd(doc, aix)
+	return doc
+}
+
+func buildArrayElement(key string, vals []byte) []byte {
+	return appendArrayElement(nil, key, vals)
+}
+
+func appendArrayElement(dst []byte, key string, vals []byte) []byte {
+	aix, doc := bsoncore.AppendArrayElementStart(dst, key)
+	doc = append(doc, vals...)
+	doc, _ = bsoncore.AppendArrayEnd(doc, aix)
+	return doc
+}
+
+// buildDocument inserts elems inside of a document.
+func buildDocument(elems []byte) []byte {
+	idx, doc := bsoncore.AppendDocumentStart(nil)
+	doc = append(doc, elems...)
+	doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+	return doc
+}
+
+func buildDocumentElement(key string, elems []byte) []byte {
+	idx, doc := bsoncore.AppendDocumentElementStart(nil, key)
+	doc = append(doc, elems...)
+	doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
+	return doc
+}

+ 648 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders.go

@@ -0,0 +1,648 @@
+// 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 sliceWriterPool = sync.Pool{
+	New: func() interface{} {
+		sw := make(bsonrw.SliceWriter, 0, 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.
+		RegisterEncoder(tByteSlice, ValueEncoderFunc(dve.ByteSliceEncodeValue)).
+		RegisterEncoder(tTime, ValueEncoderFunc(dve.TimeEncodeValue)).
+		RegisterEncoder(tEmpty, ValueEncoderFunc(dve.EmptyInterfaceEncodeValue)).
+		RegisterEncoder(tOID, ValueEncoderFunc(dve.ObjectIDEncodeValue)).
+		RegisterEncoder(tDecimal, ValueEncoderFunc(dve.Decimal128EncodeValue)).
+		RegisterEncoder(tJSONNumber, ValueEncoderFunc(dve.JSONNumberEncodeValue)).
+		RegisterEncoder(tURL, ValueEncoderFunc(dve.URLEncodeValue)).
+		RegisterEncoder(tValueMarshaler, ValueEncoderFunc(dve.ValueMarshalerEncodeValue)).
+		RegisterEncoder(tMarshaler, ValueEncoderFunc(dve.MarshalerEncodeValue)).
+		RegisterEncoder(tProxy, ValueEncoderFunc(dve.ProxyEncodeValue)).
+		RegisterEncoder(tJavaScript, ValueEncoderFunc(dve.JavaScriptEncodeValue)).
+		RegisterEncoder(tSymbol, ValueEncoderFunc(dve.SymbolEncodeValue)).
+		RegisterEncoder(tBinary, ValueEncoderFunc(dve.BinaryEncodeValue)).
+		RegisterEncoder(tUndefined, ValueEncoderFunc(dve.UndefinedEncodeValue)).
+		RegisterEncoder(tDateTime, ValueEncoderFunc(dve.DateTimeEncodeValue)).
+		RegisterEncoder(tNull, ValueEncoderFunc(dve.NullEncodeValue)).
+		RegisterEncoder(tRegex, ValueEncoderFunc(dve.RegexEncodeValue)).
+		RegisterEncoder(tDBPointer, ValueEncoderFunc(dve.DBPointerEncodeValue)).
+		RegisterEncoder(tTimestamp, ValueEncoderFunc(dve.TimestampEncodeValue)).
+		RegisterEncoder(tMinKey, ValueEncoderFunc(dve.MinKeyEncodeValue)).
+		RegisterEncoder(tMaxKey, ValueEncoderFunc(dve.MaxKeyEncodeValue)).
+		RegisterEncoder(tCoreDocument, ValueEncoderFunc(dve.CoreDocumentEncodeValue)).
+		RegisterEncoder(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, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint8, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint16, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint32, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Uint64, ValueEncoderFunc(dve.UintEncodeValue)).
+		RegisterDefaultEncoder(reflect.Float32, ValueEncoderFunc(dve.FloatEncodeValue)).
+		RegisterDefaultEncoder(reflect.Float64, ValueEncoderFunc(dve.FloatEncodeValue)).
+		RegisterDefaultEncoder(reflect.Array, ValueEncoderFunc(dve.ArrayEncodeValue)).
+		RegisterDefaultEncoder(reflect.Map, ValueEncoderFunc(dve.MapEncodeValue)).
+		RegisterDefaultEncoder(reflect.Slice, ValueEncoderFunc(dve.SliceEncodeValue)).
+		RegisterDefaultEncoder(reflect.String, ValueEncoderFunc(dve.StringEncodeValue)).
+		RegisterDefaultEncoder(reflect.Struct, &StructCodec{cache: make(map[reflect.Type]*structDescription), parser: DefaultStructTagParser}).
+		RegisterDefaultEncoder(reflect.Ptr, NewPointerCodec())
+}
+
+// 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.
+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.
+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.
+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)
+	return vw.WriteDateTime(tt.Unix()*1000 + int64(tt.Nanosecond()/1e6))
+}
+
+// ByteSliceEncodeValue is the ValueEncoderFunc for []byte.
+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.
+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 {
+
+	encoder, err := ec.LookupEncoder(val.Type().Elem())
+	if err != nil {
+		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)
+		}
+		vw, err := dw.WriteDocumentElement(key.String())
+		if err != nil {
+			return err
+		}
+
+		if enc, ok := encoder.(ValueEncoder); ok {
+			err = enc.EncodeValue(ec, vw, val.MapIndex(key))
+			if err != nil {
+				return err
+			}
+			continue
+		}
+		err = encoder.EncodeValue(ec, vw, val.MapIndex(key))
+		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()
+	}
+
+	aw, err := vw.WriteArray()
+	if err != nil {
+		return err
+	}
+
+	encoder, err := ec.LookupEncoder(val.Type().Elem())
+	if err != nil {
+		return err
+	}
+
+	for idx := 0; idx < val.Len(); idx++ {
+		vw, err := aw.WriteArrayElement()
+		if err != nil {
+			return err
+		}
+
+		err = encoder.EncodeValue(ec, vw, val.Index(idx))
+		if err != nil {
+			return err
+		}
+	}
+	return aw.WriteArrayEnd()
+}
+
+// SliceEncodeValue is the ValueEncoderFunc for slice types.
+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
+	}
+
+	encoder, err := ec.LookupEncoder(val.Type().Elem())
+	if err != nil {
+		return err
+	}
+
+	for idx := 0; idx < val.Len(); idx++ {
+		vw, err := aw.WriteArrayElement()
+		if err != nil {
+			return err
+		}
+
+		err = encoder.EncodeValue(ec, vw, val.Index(idx))
+		if err != nil {
+			return err
+		}
+	}
+	return aw.WriteArrayEnd()
+}
+
+// EmptyInterfaceEncodeValue is the ValueEncoderFunc for interface{}.
+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 {
+	if !val.IsValid() || !val.Type().Implements(tValueMarshaler) {
+		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 {
+	if !val.IsValid() || !val.Type().Implements(tMarshaler) {
+		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 {
+	if !val.IsValid() || !val.Type().Implements(tProxy) {
+		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()
+}

+ 1436 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/default_value_encoders_test.go

@@ -0,0 +1,1436 @@
+// 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"
+	"net/url"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+	"math"
+)
+
+func TestDefaultValueEncoders(t *testing.T) {
+	var dve DefaultValueEncoders
+	var wrong = func(string, string) string { return "wrong" }
+
+	type mybool bool
+	type myint8 int8
+	type myint16 int16
+	type myint32 int32
+	type myint64 int64
+	type myint int
+	type myuint8 uint8
+	type myuint16 uint16
+	type myuint32 uint32
+	type myuint64 uint64
+	type myuint uint
+	type myfloat32 float32
+	type myfloat64 float64
+	type mystring string
+
+	now := time.Now().Truncate(time.Millisecond)
+	pjsnum := new(json.Number)
+	*pjsnum = json.Number("3.14159")
+	d128 := primitive.NewDecimal128(12345, 67890)
+
+	type subtest struct {
+		name   string
+		val    interface{}
+		ectx   *EncodeContext
+		llvrw  *bsonrwtest.ValueReaderWriter
+		invoke bsonrwtest.Invoked
+		err    error
+	}
+
+	testCases := []struct {
+		name     string
+		ve       ValueEncoder
+		subtests []subtest
+	}{
+		{
+			"BooleanEncodeValue",
+			ValueEncoderFunc(dve.BooleanEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "BooleanEncodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.ValueOf(wrong)},
+				},
+				{"fast path", bool(true), nil, nil, bsonrwtest.WriteBoolean, nil},
+				{"reflection path", mybool(true), nil, nil, bsonrwtest.WriteBoolean, nil},
+			},
+		},
+		{
+			"IntEncodeValue",
+			ValueEncoderFunc(dve.IntEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "IntEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{"int8/fast path", int8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int16/fast path", int16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int32/fast path", int32(2147483647), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/fast path", int64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/fast path - minsize", int64(math.MaxInt32), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/fast path - minsize too large", int64(math.MaxInt32 + 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/fast path - minsize too small", int64(math.MinInt32 - 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int/fast path - positive int32", int(math.MaxInt32 - 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - negative int32", int(math.MinInt32 + 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - MaxInt32", int(math.MaxInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - MinInt32", int(math.MinInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/fast path - larger than MaxInt32", int(math.MaxInt32 + 1), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int/fast path - smaller than MinInt32", int(math.MinInt32 - 1), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int8/reflection path", myint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int16/reflection path", myint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int32/reflection path", myint32(2147483647), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/reflection path", myint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/reflection path - minsize", myint64(math.MaxInt32), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"int64/reflection path - minsize too large", myint64(math.MaxInt32 + 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int64/reflection path - minsize too small", myint64(math.MinInt32 - 1), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"int/reflection path - positive int32", myint(math.MaxInt32 - 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - negative int32", myint(math.MinInt32 + 1), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - MaxInt32", myint(math.MaxInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - MinInt32", myint(math.MinInt32), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"int/reflection path - larger than MaxInt32", myint(math.MaxInt32 + 1), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"int/reflection path - smaller than MinInt32", myint(math.MinInt32 - 1), nil, nil, bsonrwtest.WriteInt64, nil},
+			},
+		},
+		{
+			"UintEncodeValue",
+			ValueEncoderFunc(dve.UintEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "UintEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{"uint8/fast path", uint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint16/fast path", uint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/fast path", uint32(2147483647), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/fast path", uint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/fast path", uint(1234567), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint32/fast path - minsize", uint32(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint64/fast path - minsize", uint64(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint/fast path - minsize", uint(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/fast path - minsize too large", uint32(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/fast path - minsize too large", uint64(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/fast path - minsize too large", uint(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/fast path - overflow", uint64(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+				{"uint/fast path - overflow", uint(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+				{"uint8/reflection path", myuint8(127), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint16/reflection path", myuint16(32767), nil, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/reflection path", myuint32(2147483647), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/reflection path", myuint64(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/reflection path", myuint(1234567890987), nil, nil, bsonrwtest.WriteInt64, nil},
+				{"uint32/reflection path - minsize", myuint32(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint64/reflection path - minsize", myuint64(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint/reflection path - minsize", myuint(2147483647), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt32, nil},
+				{"uint32/reflection path - minsize too large", myuint(1 << 31), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/reflection path - minsize too large", myuint64(1 << 31), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint/reflection path - minsize too large", myuint(2147483648), &EncodeContext{MinSize: true}, nil, bsonrwtest.WriteInt64, nil},
+				{"uint64/reflection path - overflow", myuint64(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+				{"uint/reflection path - overflow", myuint(1 << 63), nil, nil, bsonrwtest.Nothing, fmt.Errorf("%d overflows int64", uint(1<<63))},
+			},
+		},
+		{
+			"FloatEncodeValue",
+			ValueEncoderFunc(dve.FloatEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "FloatEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Float32, reflect.Float64},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{"float32/fast path", float32(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+				{"float64/fast path", float64(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+				{"float32/reflection path", myfloat32(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+				{"float64/reflection path", myfloat64(3.14159), nil, nil, bsonrwtest.WriteDouble, nil},
+			},
+		},
+		{
+			"TimeEncodeValue",
+			ValueEncoderFunc(dve.TimeEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "TimeEncodeValue", Types: []reflect.Type{tTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{"time.Time", now, nil, nil, bsonrwtest.WriteDateTime, nil},
+			},
+		},
+		{
+			"MapEncodeValue",
+			ValueEncoderFunc(dve.MapEncodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MapEncodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"wrong kind (non-string key)",
+					map[int]interface{}{},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "MapEncodeValue",
+						Kinds:    []reflect.Kind{reflect.Map},
+						Received: reflect.ValueOf(map[int]interface{}{}),
+					},
+				},
+				{
+					"WriteDocument Error",
+					map[string]interface{}{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
+					bsonrwtest.WriteDocument,
+					errors.New("wd error"),
+				},
+				{
+					"Lookup Error",
+					map[string]interface{}{},
+					&EncodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocument,
+					ErrNoEncoder{Type: reflect.TypeOf((*interface{})(nil)).Elem()},
+				},
+				{
+					"WriteDocumentElement Error",
+					map[string]interface{}{"foo": "bar"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("wde error"),
+				},
+				{
+					"EncodeValue Error",
+					map[string]interface{}{"foo": "bar"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
+					bsonrwtest.WriteString,
+					errors.New("ev error"),
+				},
+			},
+		},
+		{
+			"ArrayEncodeValue",
+			ValueEncoderFunc(dve.ArrayEncodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ArrayEncodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"WriteArray Error",
+					[1]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wa error"), ErrAfter: bsonrwtest.WriteArray},
+					bsonrwtest.WriteArray,
+					errors.New("wa error"),
+				},
+				{
+					"Lookup Error",
+					[1]interface{}{},
+					&EncodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteArray,
+					ErrNoEncoder{Type: reflect.TypeOf((*interface{})(nil)).Elem()},
+				},
+				{
+					"WriteArrayElement Error",
+					[1]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wae error"), ErrAfter: bsonrwtest.WriteArrayElement},
+					bsonrwtest.WriteArrayElement,
+					errors.New("wae error"),
+				},
+				{
+					"EncodeValue Error",
+					[1]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
+					bsonrwtest.WriteString,
+					errors.New("ev error"),
+				},
+				{
+					"[1]primitive.E/success",
+					[1]primitive.E{{"hello", "world"}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+				{
+					"[1]primitive.E/success",
+					[1]primitive.E{{"hello", nil}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+			},
+		},
+		{
+			"SliceEncodeValue",
+			ValueEncoderFunc(dve.SliceEncodeValue),
+			[]subtest{
+				{
+					"wrong kind",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "SliceEncodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"WriteArray Error",
+					[]string{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wa error"), ErrAfter: bsonrwtest.WriteArray},
+					bsonrwtest.WriteArray,
+					errors.New("wa error"),
+				},
+				{
+					"Lookup Error",
+					[]interface{}{},
+					&EncodeContext{Registry: NewRegistryBuilder().Build()},
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteArray,
+					ErrNoEncoder{Type: reflect.TypeOf((*interface{})(nil)).Elem()},
+				},
+				{
+					"WriteArrayElement Error",
+					[]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wae error"), ErrAfter: bsonrwtest.WriteArrayElement},
+					bsonrwtest.WriteArrayElement,
+					errors.New("wae error"),
+				},
+				{
+					"EncodeValue Error",
+					[]string{"foo"},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteString},
+					bsonrwtest.WriteString,
+					errors.New("ev error"),
+				},
+				{
+					"D/success",
+					primitive.D{{"hello", "world"}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+				{
+					"D/success",
+					primitive.D{{"hello", nil}},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+			},
+		},
+		{
+			"ObjectIDEncodeValue",
+			ValueEncoderFunc(dve.ObjectIDEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ObjectIDEncodeValue", Types: []reflect.Type{tOID}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"primitive.ObjectID/success",
+					primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					nil, nil, bsonrwtest.WriteObjectID, nil,
+				},
+			},
+		},
+		{
+			"Decimal128EncodeValue",
+			ValueEncoderFunc(dve.Decimal128EncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "Decimal128EncodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Decimal128/success", d128, nil, nil, bsonrwtest.WriteDecimal128, nil},
+			},
+		},
+		{
+			"JSONNumberEncodeValue",
+			ValueEncoderFunc(dve.JSONNumberEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "JSONNumberEncodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"json.Number/invalid",
+					json.Number("hello world"),
+					nil, nil, bsonrwtest.Nothing, errors.New(`strconv.ParseFloat: parsing "hello world": invalid syntax`),
+				},
+				{
+					"json.Number/int64/success",
+					json.Number("1234567890"),
+					nil, nil, bsonrwtest.WriteInt64, nil,
+				},
+				{
+					"json.Number/float64/success",
+					json.Number("3.14159"),
+					nil, nil, bsonrwtest.WriteDouble, nil,
+				},
+			},
+		},
+		{
+			"URLEncodeValue",
+			ValueEncoderFunc(dve.URLEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "URLEncodeValue", Types: []reflect.Type{tURL}, Received: reflect.ValueOf(wrong)},
+				},
+				{"url.URL", url.URL{Scheme: "http", Host: "example.com"}, nil, nil, bsonrwtest.WriteString, nil},
+			},
+		},
+		{
+			"ByteSliceEncodeValue",
+			ValueEncoderFunc(dve.ByteSliceEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.ValueOf(wrong)},
+				},
+				{"[]byte", []byte{0x01, 0x02, 0x03}, nil, nil, bsonrwtest.WriteBinary, nil},
+				{"[]byte/nil", []byte(nil), nil, nil, bsonrwtest.WriteNull, nil},
+			},
+		},
+		{
+			"EmptyInterfaceEncodeValue",
+			ValueEncoderFunc(dve.EmptyInterfaceEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(wrong)},
+				},
+			},
+		},
+		{
+			"ValueMarshalerEncodeValue",
+			ValueEncoderFunc(dve.ValueMarshalerEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "ValueMarshalerEncodeValue",
+						Types:    []reflect.Type{tValueMarshaler},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"MarshalBSONValue error",
+					testValueMarshaler{err: errors.New("mbsonv error")},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					errors.New("mbsonv error"),
+				},
+				{
+					"Copy error",
+					testValueMarshaler{},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					fmt.Errorf("Cannot copy unknown BSON type %s", bsontype.Type(0)),
+				},
+				{
+					"success",
+					testValueMarshaler{t: bsontype.String, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}},
+					nil,
+					nil,
+					bsonrwtest.WriteString,
+					nil,
+				},
+			},
+		},
+		{
+			"MarshalerEncodeValue",
+			ValueEncoderFunc(dve.MarshalerEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"MarshalBSON error",
+					testMarshaler{err: errors.New("mbson error")},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					errors.New("mbson error"),
+				},
+				{
+					"success",
+					testMarshaler{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))},
+					nil,
+					nil,
+					bsonrwtest.WriteDocumentEnd,
+					nil,
+				},
+			},
+		},
+		{
+			"ProxyEncodeValue",
+			ValueEncoderFunc(dve.ProxyEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "ProxyEncodeValue", Types: []reflect.Type{tProxy}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"Proxy error",
+					testProxy{err: errors.New("proxy error")},
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					errors.New("proxy error"),
+				},
+				{
+					"Lookup error",
+					testProxy{ret: nil},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.Nothing,
+					ErrNoEncoder{Type: nil},
+				},
+				{
+					"success",
+					testProxy{ret: int64(1234567890)},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.WriteInt64,
+					nil,
+				},
+			},
+		},
+		{
+			"PointerCodec.EncodeValue",
+			NewPointerCodec(),
+			[]subtest{
+				{
+					"nil",
+					nil,
+					nil,
+					nil,
+					bsonrwtest.WriteNull,
+					nil,
+				},
+				{
+					"not pointer",
+					int32(123456),
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "PointerCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: reflect.ValueOf(int32(123456))},
+				},
+				{
+					"typed nil",
+					(*int32)(nil),
+					nil,
+					nil,
+					bsonrwtest.WriteNull,
+					nil,
+				},
+				{
+					"no encoder",
+					&wrong,
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil,
+					bsonrwtest.Nothing,
+					ErrNoEncoder{Type: reflect.TypeOf(wrong)},
+				},
+			},
+		},
+		{
+			"JavaScriptEncodeValue",
+			ValueEncoderFunc(dve.JavaScriptEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "JavaScriptEncodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.ValueOf(wrong)},
+				},
+				{"JavaScript", primitive.JavaScript("foobar"), nil, nil, bsonrwtest.WriteJavascript, nil},
+			},
+		},
+		{
+			"SymbolEncodeValue",
+			ValueEncoderFunc(dve.SymbolEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "SymbolEncodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Symbol", primitive.Symbol("foobar"), nil, nil, bsonrwtest.WriteSymbol, nil},
+			},
+		},
+		{
+			"BinaryEncodeValue",
+			ValueEncoderFunc(dve.BinaryEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "BinaryEncodeValue", Types: []reflect.Type{tBinary}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Binary/success", primitive.Binary{Data: []byte{0x01, 0x02}, Subtype: 0xFF}, nil, nil, bsonrwtest.WriteBinaryWithSubtype, nil},
+			},
+		},
+		{
+			"UndefinedEncodeValue",
+			ValueEncoderFunc(dve.UndefinedEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "UndefinedEncodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Undefined/success", primitive.Undefined{}, nil, nil, bsonrwtest.WriteUndefined, nil},
+			},
+		},
+		{
+			"DateTimeEncodeValue",
+			ValueEncoderFunc(dve.DateTimeEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.ValueOf(wrong)},
+				},
+				{"DateTime/success", primitive.DateTime(1234567890), nil, nil, bsonrwtest.WriteDateTime, nil},
+			},
+		},
+		{
+			"NullEncodeValue",
+			ValueEncoderFunc(dve.NullEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "NullEncodeValue", Types: []reflect.Type{tNull}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Null/success", primitive.Null{}, nil, nil, bsonrwtest.WriteNull, nil},
+			},
+		},
+		{
+			"RegexEncodeValue",
+			ValueEncoderFunc(dve.RegexEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "RegexEncodeValue", Types: []reflect.Type{tRegex}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Regex/success", primitive.Regex{Pattern: "foo", Options: "bar"}, nil, nil, bsonrwtest.WriteRegex, nil},
+			},
+		},
+		{
+			"DBPointerEncodeValue",
+			ValueEncoderFunc(dve.DBPointerEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "DBPointerEncodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"DBPointer/success",
+					primitive.DBPointer{
+						DB:      "foobar",
+						Pointer: primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+					},
+					nil, nil, bsonrwtest.WriteDBPointer, nil,
+				},
+			},
+		},
+		{
+			"TimestampEncodeValue",
+			ValueEncoderFunc(dve.TimestampEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "TimestampEncodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.ValueOf(wrong)},
+				},
+				{"Timestamp/success", primitive.Timestamp{T: 12345, I: 67890}, nil, nil, bsonrwtest.WriteTimestamp, nil},
+			},
+		},
+		{
+			"MinKeyEncodeValue",
+			ValueEncoderFunc(dve.MinKeyEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MinKeyEncodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{"MinKey/success", primitive.MinKey{}, nil, nil, bsonrwtest.WriteMinKey, nil},
+			},
+		},
+		{
+			"MaxKeyEncodeValue",
+			ValueEncoderFunc(dve.MaxKeyEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{Name: "MaxKeyEncodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.ValueOf(wrong)},
+				},
+				{"MaxKey/success", primitive.MaxKey{}, nil, nil, bsonrwtest.WriteMaxKey, nil},
+			},
+		},
+		{
+			"CoreDocumentEncodeValue",
+			ValueEncoderFunc(dve.CoreDocumentEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "CoreDocumentEncodeValue",
+						Types:    []reflect.Type{tCoreDocument},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"WriteDocument Error",
+					bsoncore.Document{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
+					bsonrwtest.WriteDocument,
+					errors.New("wd error"),
+				},
+				{
+					"bsoncore.Document.Elements Error",
+					bsoncore.Document{0xFF, 0x00, 0x00, 0x00, 0x00},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocument,
+					errors.New("length read exceeds number of bytes available. length=5 bytes=255"),
+				},
+				{
+					"WriteDocumentElement Error",
+					bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("wde error"),
+				},
+				{
+					"encodeValue error",
+					bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteNull},
+					bsonrwtest.WriteNull,
+					errors.New("ev error"),
+				},
+				{
+					"iterator error",
+					bsoncore.Document{0x0C, 0x00, 0x00, 0x00, 0x01, 'f', 'o', 'o', 0x00, 0x01, 0x02, 0x03},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("not enough bytes available to read type. bytes=3 type=double"),
+				},
+			},
+		},
+		{
+			"CodeWithScopeEncodeValue",
+			ValueEncoderFunc(dve.CodeWithScopeEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					ValueEncoderError{
+						Name:     "CodeWithScopeEncodeValue",
+						Types:    []reflect.Type{tCodeWithScope},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"WriteCodeWithScope error",
+					primitive.CodeWithScope{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wcws error"), ErrAfter: bsonrwtest.WriteCodeWithScope},
+					bsonrwtest.WriteCodeWithScope,
+					errors.New("wcws error"),
+				},
+				{
+					"CodeWithScope/success",
+					primitive.CodeWithScope{
+						Code:  "var hello = 'world';",
+						Scope: primitive.D{},
+					},
+					&EncodeContext{Registry: buildDefaultRegistry()},
+					nil, bsonrwtest.WriteDocumentEnd, nil,
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, subtest := range tc.subtests {
+				t.Run(subtest.name, func(t *testing.T) {
+					var ec EncodeContext
+					if subtest.ectx != nil {
+						ec = *subtest.ectx
+					}
+					llvrw := new(bsonrwtest.ValueReaderWriter)
+					if subtest.llvrw != nil {
+						llvrw = subtest.llvrw
+					}
+					llvrw.T = t
+					err := tc.ve.EncodeValue(ec, llvrw, reflect.ValueOf(subtest.val))
+					if !compareErrors(err, subtest.err) {
+						t.Errorf("Errors do not match. got %v; want %v", err, subtest.err)
+					}
+					invoked := llvrw.Invoked
+					if !cmp.Equal(invoked, subtest.invoke) {
+						t.Errorf("Incorrect method invoked. got %v; want %v", invoked, subtest.invoke)
+					}
+				})
+			}
+		})
+	}
+
+	t.Run("success path", func(t *testing.T) {
+		oid := primitive.NewObjectID()
+		oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
+		var str = new(string)
+		*str = "bar"
+		now := time.Now().Truncate(time.Millisecond)
+		murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
+		if err != nil {
+			t.Errorf("Error parsing URL: %v", err)
+			t.FailNow()
+		}
+		decimal128, err := primitive.ParseDecimal128("1.5e10")
+		if err != nil {
+			t.Errorf("Error parsing decimal128: %v", err)
+			t.FailNow()
+		}
+
+		testCases := []struct {
+			name  string
+			value interface{}
+			b     []byte
+			err   error
+		}{
+			{
+				"map[string]int",
+				map[string]int32{"foo": 1},
+				[]byte{
+					0x0E, 0x00, 0x00, 0x00,
+					0x10, 'f', 'o', 'o', 0x00,
+					0x01, 0x00, 0x00, 0x00,
+					0x00,
+				},
+				nil,
+			},
+			{
+				"map[string]primitive.ObjectID",
+				map[string]primitive.ObjectID{"foo": oid},
+				buildDocument(bsoncore.AppendObjectIDElement(nil, "foo", oid)),
+				nil,
+			},
+			{
+				"map[string][]int32",
+				map[string][]int32{"Z": {1, 2, 3}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt32Element(doc, "0", 1)
+					doc = bsoncore.AppendInt32Element(doc, "1", 2)
+					return bsoncore.AppendInt32Element(doc, "2", 3)
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.ObjectID",
+				map[string][]primitive.ObjectID{"Z": oids},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendObjectIDElement(doc, "0", oids[0])
+					doc = bsoncore.AppendObjectIDElement(doc, "1", oids[1])
+					return bsoncore.AppendObjectIDElement(doc, "2", oids[2])
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(int64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendInt64Element(doc, "1", 10)
+				}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(float64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
+				buildDocumentArray(func(doc []byte) []byte {
+					doc = bsoncore.AppendInt64Element(doc, "0", 5)
+					return bsoncore.AppendDoubleElement(doc, "1", 10.1)
+				}),
+				nil,
+			},
+			{
+				"map[string][]*url.URL",
+				map[string][]*url.URL{"Z": {murl}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendStringElement(doc, "0", murl.String())
+				}),
+				nil,
+			},
+			{
+				"map[string][]primitive.Decimal128",
+				map[string][]primitive.Decimal128{"Z": {decimal128}},
+				buildDocumentArray(func(doc []byte) []byte {
+					return bsoncore.AppendDecimal128Element(doc, "0", decimal128)
+				}),
+				nil,
+			},
+			{
+				"-",
+				struct {
+					A string `bson:"-"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty",
+				struct {
+					A string `bson:",omitempty"`
+				}{
+					A: "",
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"omitempty, empty time",
+				struct {
+					A time.Time `bson:",omitempty"`
+				}{
+					A: time.Time{},
+				},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"no private fields",
+				noPrivateFields{a: "should be empty"},
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				nil,
+			},
+			{
+				"minsize",
+				struct {
+					A int64 `bson:",minsize"`
+				}{
+					A: 12345,
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline",
+				struct {
+					Foo struct {
+						A int64 `bson:",minsize"`
+					} `bson:",inline"`
+				}{
+					Foo: struct {
+						A int64 `bson:",minsize"`
+					}{
+						A: 12345,
+					},
+				},
+				buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)),
+				nil,
+			},
+			{
+				"inline map",
+				struct {
+					Foo map[string]string `bson:",inline"`
+				}{
+					Foo: map[string]string{"foo": "bar"},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name bson:name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"alternate name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")),
+				nil,
+			},
+			{
+				"inline, omitempty",
+				struct {
+					A   string
+					Foo zeroTest `bson:"omitempty,inline"`
+				}{
+					A:   "bar",
+					Foo: zeroTest{true},
+				},
+				buildDocument(bsoncore.AppendStringElement(nil, "a", "bar")),
+				nil,
+			},
+			{
+				"struct{}",
+				struct {
+					A bool
+					B int32
+					C int64
+					D uint16
+					E uint64
+					F float64
+					G string
+					H map[string]string
+					I []byte
+					K [2]string
+					L struct {
+						M string
+					}
+					Q  primitive.ObjectID
+					T  []struct{}
+					Y  json.Number
+					Z  time.Time
+					AA json.Number
+					AB *url.URL
+					AC primitive.Decimal128
+					AD *time.Time
+					AE testValueMarshaler
+					AF Proxy
+					AG testProxy
+					AH map[string]interface{}
+					AI primitive.CodeWithScope
+				}{
+					A: true,
+					B: 123,
+					C: 456,
+					D: 789,
+					E: 101112,
+					F: 3.14159,
+					G: "Hello, world",
+					H: map[string]string{"foo": "bar"},
+					I: []byte{0x01, 0x02, 0x03},
+					K: [2]string{"baz", "qux"},
+					L: struct {
+						M string
+					}{
+						M: "foobar",
+					},
+					Q:  oid,
+					T:  nil,
+					Y:  json.Number("5"),
+					Z:  now,
+					AA: json.Number("10.1"),
+					AB: murl,
+					AC: decimal128,
+					AD: &now,
+					AE: testValueMarshaler{t: bsontype.String, buf: bsoncore.AppendString(nil, "hello, world")},
+					AF: testProxy{ret: struct{ Hello string }{Hello: "world!"}},
+					AG: testProxy{ret: struct{ Pi float64 }{Pi: 3.14159}},
+					AH: nil,
+					AI: primitive.CodeWithScope{Code: "var hello = 'world';", Scope: primitive.D{{"pi", 3.14159}}},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = bsoncore.AppendBooleanElement(doc, "a", true)
+					doc = bsoncore.AppendInt32Element(doc, "b", 123)
+					doc = bsoncore.AppendInt64Element(doc, "c", 456)
+					doc = bsoncore.AppendInt32Element(doc, "d", 789)
+					doc = bsoncore.AppendInt64Element(doc, "e", 101112)
+					doc = bsoncore.AppendDoubleElement(doc, "f", 3.14159)
+					doc = bsoncore.AppendStringElement(doc, "g", "Hello, world")
+					doc = bsoncore.AppendDocumentElement(doc, "h", buildDocument(bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = bsoncore.AppendBinaryElement(doc, "i", 0x00, []byte{0x01, 0x02, 0x03})
+					doc = bsoncore.AppendArrayElement(doc, "k",
+						buildArray(bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = bsoncore.AppendDocumentElement(doc, "l", buildDocument(bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = bsoncore.AppendObjectIDElement(doc, "q", oid)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendInt64Element(doc, "y", 5)
+					doc = bsoncore.AppendDateTimeElement(doc, "z", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendDoubleElement(doc, "aa", 10.1)
+					doc = bsoncore.AppendStringElement(doc, "ab", murl.String())
+					doc = bsoncore.AppendDecimal128Element(doc, "ac", decimal128)
+					doc = bsoncore.AppendDateTimeElement(doc, "ad", now.UnixNano()/int64(time.Millisecond))
+					doc = bsoncore.AppendStringElement(doc, "ae", "hello, world")
+					doc = bsoncore.AppendDocumentElement(doc, "af", buildDocument(bsoncore.AppendStringElement(nil, "hello", "world!")))
+					doc = bsoncore.AppendDocumentElement(doc, "ag", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)))
+					doc = bsoncore.AppendNullElement(doc, "ah")
+					doc = bsoncore.AppendCodeWithScopeElement(doc, "ai",
+						"var hello = 'world';", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+					)
+					return doc
+				}(nil)),
+				nil,
+			},
+			{
+				"struct{[]interface{}}",
+				struct {
+					A []bool
+					B []int32
+					C []int64
+					D []uint16
+					E []uint64
+					F []float64
+					G []string
+					H []map[string]string
+					I [][]byte
+					K [1][2]string
+					L []struct {
+						M string
+					}
+					N  [][]string
+					R  []primitive.ObjectID
+					T  []struct{}
+					W  []map[string]struct{}
+					X  []map[string]struct{}
+					Y  []map[string]struct{}
+					Z  []time.Time
+					AA []json.Number
+					AB []*url.URL
+					AC []primitive.Decimal128
+					AD []*time.Time
+					AE []testValueMarshaler
+					AF []Proxy
+					AG []testProxy
+				}{
+					A: []bool{true},
+					B: []int32{123},
+					C: []int64{456},
+					D: []uint16{789},
+					E: []uint64{101112},
+					F: []float64{3.14159},
+					G: []string{"Hello, world"},
+					H: []map[string]string{{"foo": "bar"}},
+					I: [][]byte{{0x01, 0x02, 0x03}},
+					K: [1][2]string{{"baz", "qux"}},
+					L: []struct {
+						M string
+					}{
+						{
+							M: "foobar",
+						},
+					},
+					N:  [][]string{{"foo", "bar"}},
+					R:  oids,
+					T:  nil,
+					W:  nil,
+					X:  []map[string]struct{}{},   // Should be empty BSON Array
+					Y:  []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
+					Z:  []time.Time{now, now},
+					AA: []json.Number{json.Number("5"), json.Number("10.1")},
+					AB: []*url.URL{murl},
+					AC: []primitive.Decimal128{decimal128},
+					AD: []*time.Time{&now, &now},
+					AE: []testValueMarshaler{
+						{t: bsontype.String, buf: bsoncore.AppendString(nil, "hello")},
+						{t: bsontype.String, buf: bsoncore.AppendString(nil, "world")},
+					},
+					AF: []Proxy{
+						testProxy{ret: struct{ Hello string }{Hello: "world!"}},
+						testProxy{ret: struct{ Foo string }{Foo: "bar"}},
+					},
+					AG: []testProxy{
+						{ret: struct{ One int64 }{One: 1234567890}},
+						{ret: struct{ Pi float64 }{Pi: 3.14159}},
+					},
+				},
+				buildDocument(func(doc []byte) []byte {
+					doc = appendArrayElement(doc, "a", bsoncore.AppendBooleanElement(nil, "0", true))
+					doc = appendArrayElement(doc, "b", bsoncore.AppendInt32Element(nil, "0", 123))
+					doc = appendArrayElement(doc, "c", bsoncore.AppendInt64Element(nil, "0", 456))
+					doc = appendArrayElement(doc, "d", bsoncore.AppendInt32Element(nil, "0", 789))
+					doc = appendArrayElement(doc, "e", bsoncore.AppendInt64Element(nil, "0", 101112))
+					doc = appendArrayElement(doc, "f", bsoncore.AppendDoubleElement(nil, "0", 3.14159))
+					doc = appendArrayElement(doc, "g", bsoncore.AppendStringElement(nil, "0", "Hello, world"))
+					doc = appendArrayElement(doc, "h", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "foo", "bar")))
+					doc = appendArrayElement(doc, "i", bsoncore.AppendBinaryElement(nil, "0", 0x00, []byte{0x01, 0x02, 0x03}))
+					doc = appendArrayElement(doc, "k",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")),
+					)
+					doc = appendArrayElement(doc, "l", buildDocumentElement("0", bsoncore.AppendStringElement(nil, "m", "foobar")))
+					doc = appendArrayElement(doc, "n",
+						buildArrayElement("0",
+							bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar")),
+					)
+					doc = appendArrayElement(doc, "r",
+						bsoncore.AppendObjectIDElement(
+							bsoncore.AppendObjectIDElement(
+								bsoncore.AppendObjectIDElement(nil,
+									"0", oids[0]),
+								"1", oids[1]),
+							"2", oids[2]),
+					)
+					doc = bsoncore.AppendNullElement(doc, "t")
+					doc = bsoncore.AppendNullElement(doc, "w")
+					doc = appendArrayElement(doc, "x", nil)
+					doc = appendArrayElement(doc, "y", buildDocumentElement("0", nil))
+					doc = appendArrayElement(doc, "z",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(
+								nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "aa", bsoncore.AppendDoubleElement(bsoncore.AppendInt64Element(nil, "0", 5), "1", 10.10))
+					doc = appendArrayElement(doc, "ab", bsoncore.AppendStringElement(nil, "0", murl.String()))
+					doc = appendArrayElement(doc, "ac", bsoncore.AppendDecimal128Element(nil, "0", decimal128))
+					doc = appendArrayElement(doc, "ad",
+						bsoncore.AppendDateTimeElement(
+							bsoncore.AppendDateTimeElement(nil, "0", now.UnixNano()/int64(time.Millisecond)),
+							"1", now.UnixNano()/int64(time.Millisecond)),
+					)
+					doc = appendArrayElement(doc, "ae",
+						bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "hello"), "1", "world"),
+					)
+					doc = appendArrayElement(doc, "af",
+						bsoncore.AppendDocumentElement(
+							bsoncore.AppendDocumentElement(nil, "0",
+								bsoncore.BuildDocument(nil, bsoncore.AppendStringElement(nil, "hello", "world!")),
+							), "1",
+							bsoncore.BuildDocument(nil, bsoncore.AppendStringElement(nil, "foo", "bar")),
+						),
+					)
+					doc = appendArrayElement(doc, "ag",
+						bsoncore.AppendDocumentElement(
+							bsoncore.AppendDocumentElement(nil, "0",
+								bsoncore.BuildDocument(nil, bsoncore.AppendInt64Element(nil, "one", 1234567890)),
+							), "1",
+							bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+						),
+					)
+					return doc
+				}(nil)),
+				nil,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				b := make(bsonrw.SliceWriter, 0, 512)
+				vw, err := bsonrw.NewBSONValueWriter(&b)
+				noerr(t, err)
+				reg := buildDefaultRegistry()
+				enc, err := reg.LookupEncoder(reflect.TypeOf(tc.value))
+				noerr(t, err)
+				err = enc.EncodeValue(EncodeContext{Registry: reg}, vw, reflect.ValueOf(tc.value))
+				if err != tc.err {
+					t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err)
+				}
+				if diff := cmp.Diff([]byte(b), tc.b); diff != "" {
+					t.Errorf("Bytes written differ: (-got +want)\n%s", diff)
+					t.Errorf("Bytes\ngot: %v\nwant:%v\n", b, tc.b)
+					t.Errorf("Readers\ngot: %v\nwant:%v\n", bsoncore.Document(b), bsoncore.Document(tc.b))
+				}
+			})
+		}
+	})
+
+	t.Run("EmptyInterfaceEncodeValue/nil", func(t *testing.T) {
+		val := reflect.New(tEmpty).Elem()
+		llvrw := new(bsonrwtest.ValueReaderWriter)
+		err := dve.EmptyInterfaceEncodeValue(EncodeContext{Registry: NewRegistryBuilder().Build()}, llvrw, val)
+		noerr(t, err)
+		if llvrw.Invoked != bsonrwtest.WriteNull {
+			t.Errorf("Incorrect method called. got %v; want %v", llvrw.Invoked, bsonrwtest.WriteNull)
+		}
+	})
+
+	t.Run("EmptyInterfaceEncodeValue/LookupEncoder error", func(t *testing.T) {
+		val := reflect.New(tEmpty).Elem()
+		val.Set(reflect.ValueOf(int64(1234567890)))
+		llvrw := new(bsonrwtest.ValueReaderWriter)
+		got := dve.EmptyInterfaceEncodeValue(EncodeContext{Registry: NewRegistryBuilder().Build()}, llvrw, val)
+		want := ErrNoEncoder{Type: tInt64}
+		if !compareErrors(got, want) {
+			t.Errorf("Did not recieve expected error. got %v; want %v", got, want)
+		}
+	})
+}
+
+type testValueMarshaler struct {
+	t   bsontype.Type
+	buf []byte
+	err error
+}
+
+func (tvm testValueMarshaler) MarshalBSONValue() (bsontype.Type, []byte, error) {
+	return tvm.t, tvm.buf, tvm.err
+}
+
+type testMarshaler struct {
+	buf []byte
+	err error
+}
+
+func (tvm testMarshaler) MarshalBSON() ([]byte, error) {
+	return tvm.buf, tvm.err
+}
+
+type testProxy struct {
+	ret interface{}
+	err error
+}
+
+func (tp testProxy) ProxyBSON() (interface{}, error) { return tp.ret, tp.err }

+ 61 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/doc.go

@@ -0,0 +1,61 @@
+// 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. For looking up
+// ValueEncoders and Decoders the Registry first attempts to find a ValueEncoder or ValueDecoder for
+// the type provided; if one cannot be found it then checks to see if a registered ValueEncoder or
+// ValueDecoder exists for an interface the type implements. Finally, the reflect.Kind of the type
+// is used to lookup a default ValueEncoder or ValueDecoder for that kind. If no ValueEncoder or
+// ValueDecoder can be found, an error is returned.
+//
+// The Registry also holds a type map. This allows users to retrieve the Go type that should be used
+// when decoding a BSON value into an empty interface. This is primarily only used for the empty
+// interface ValueDecoder.
+//
+// A RegistryBuilder is used to construct a Registry. The Register methods are used to associate
+// either a reflect.Type or a reflect.Kind with a ValueEncoder or ValueDecoder. A RegistryBuilder
+// returned from NewRegistryBuilder contains no registered ValueEncoders nor ValueDecoders and
+// contains an empty type map.
+//
+// The RegisterTypeMapEntry method handles associating a BSON type with a Go type. For example, if
+// you want to decode BSON int64 and int32 values into Go int instances, you would do the following:
+//
+//  var regbuilder *RegistryBuilder = ... intType := reflect.TypeOf(int(0))
+//  regbuilder.RegisterTypeMapEntry(bsontype.Int64, intType).RegisterTypeMapEntry(bsontype.Int32,
+//  intType)
+//
+// 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

+ 65 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/mode.go

@@ -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)
+}

+ 110 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/pointer_codec.go

@@ -0,0 +1,110 @@
+// 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 defaultPointerCodec = &PointerCodec{
+	ecache: make(map[reflect.Type]ValueEncoder),
+	dcache: make(map[reflect.Type]ValueDecoder),
+}
+
+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 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 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/proxy.go

@@ -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)
+}

+ 384 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry.go

@@ -0,0 +1,384 @@
+// 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"
+	"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")
+
+var defaultRegistry *Registry
+
+func init() {
+	defaultRegistry = buildDefaultRegistry()
+}
+
+// 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.RegisterEncoder(t, codec)
+	rb.RegisterDecoder(t, codec)
+	return rb
+}
+
+// RegisterEncoder will register the provided ValueEncoder to the provided type.
+//
+// The type registered 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.
+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 will register the provided ValueDecoder to the provided type.
+//
+// The type registered 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.
+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.
+//
+// NOTE: It is unlikely that registering a type for BSON Embedded Document is actually desired. By
+// registering a type map entry for BSON Embedded Document the type registered will be used in any
+// case where a BSON Embedded Document will be decoded into an empty interface. For example, if you
+// register primitive.M, the EmptyInterface decoder will always use primitive.M, even if an ancestor
+// was a primitive.D.
+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 will inspect the registry for an encoder that satisfies the
+// type provided. An encoder registered for a specific type will take
+// precedence over an encoder registered for an interface the type satisfies,
+// which takes precedence over an encoder for the reflect.Kind of the value. If
+// no encoder can be found, an error 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)
+	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) (ValueEncoder, bool) {
+	if t == nil {
+		return nil, false
+	}
+	for _, ienc := range r.interfaceEncoders {
+		if !t.Implements(ienc.i) {
+			continue
+		}
+
+		return ienc.ve, true
+	}
+	return nil, false
+}
+
+// LookupDecoder will inspect the registry for a decoder that satisfies the
+// type provided. A decoder registered for a specific type will take
+// precedence over a decoder registered for an interface the type satisfies,
+// which takes precedence over a decoder for the reflect.Kind of the value. If
+// no decoder can be found, an error 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)
+	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) (ValueDecoder, bool) {
+	for _, idec := range r.interfaceDecoders {
+		if !t.Implements(idec.i) && !reflect.PtrTo(t).Implements(idec.i) {
+			continue
+		}
+
+		return idec.vd, 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
+}

+ 359 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/registry_test.go

@@ -0,0 +1,359 @@
+// 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"
+)
+
+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.RegisterEncoder(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().
+				RegisterEncoder(reflect.TypeOf(ft1), fc1).
+				RegisterEncoder(reflect.TypeOf(ft2), fc2).
+				RegisterEncoder(reflect.TypeOf(ft1), fc3).
+				RegisterEncoder(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" }))
+			ti2 := reflect.TypeOf((*testInterface2)(nil)).Elem()
+			fc1, fc2, fc4 := fakeCodec{num: 1}, fakeCodec{num: 2}, fakeCodec{num: 4}
+			fsc, fslcc, fmc := new(fakeStructCodec), new(fakeSliceCodec), new(fakeMapCodec)
+			pc := NewPointerCodec()
+			reg := NewRegistryBuilder().
+				RegisterEncoder(ft1, fc1).
+				RegisterEncoder(ft2, fc2).
+				RegisterEncoder(ti2, fc4).
+				RegisterDefaultEncoder(reflect.Struct, fsc).
+				RegisterDefaultEncoder(reflect.Slice, fslcc).
+				RegisterDefaultEncoder(reflect.Array, fslcc).
+				RegisterDefaultEncoder(reflect.Map, fmc).
+				RegisterDefaultEncoder(reflect.Ptr, pc).
+				RegisterDecoder(ft1, fc1).
+				RegisterDecoder(ft2, fc2).
+				RegisterDecoder(ti2, fc4).
+				RegisterDefaultDecoder(reflect.Struct, fsc).
+				RegisterDefaultDecoder(reflect.Slice, fslcc).
+				RegisterDefaultDecoder(reflect.Array, fslcc).
+				RegisterDefaultDecoder(reflect.Map, fmc).
+				RegisterDefaultDecoder(reflect.Ptr, pc).
+				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,
+				},
+				{
+					"interface registry",
+					ti2,
+					fc4,
+					nil,
+					true,
+				},
+				{
+					"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{Type: ene.Type}
+						} 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)
+						}
+					})
+				})
+			}
+		})
+	})
+	t.Run("Type Map", func(t *testing.T) {
+		reg := NewRegistryBuilder().
+			RegisterTypeMapEntry(bsontype.String, reflect.TypeOf(string(""))).
+			RegisterTypeMapEntry(bsontype.Int32, reflect.TypeOf(int(0))).
+			Build()
+
+		var got, want reflect.Type
+
+		want = reflect.TypeOf(string(""))
+		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{ b bool }
+type fakeType2 struct{ b bool }
+type fakeType3 struct{ b bool }
+type fakeType4 struct{ b bool }
+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() }
+
+func typeComparer(i1, i2 reflect.Type) bool { return i1 == i2 }

+ 367 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec.go

@@ -0,0 +1,367 @@
+// 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"
+	"strings"
+	"sync"
+
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+)
+
+var defaultStructCodec = &StructCodec{
+	cache:  make(map[reflect.Type]*structDescription),
+	parser: DefaultStructTagParser,
+}
+
+// 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
+}
+
+var _ ValueEncoder = &StructCodec{}
+var _ ValueDecoder = &StructCodec{}
+
+// NewStructCodec returns a StructCodec that uses p for struct tag parsing.
+func NewStructCodec(p StructTagParser) (*StructCodec, error) {
+	if p == nil {
+		return nil, errors.New("a StructTagParser must be provided to NewStructCodec")
+	}
+
+	return &StructCodec{
+		cache:  make(map[reflect.Type]*structDescription),
+		parser: p,
+	}, 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 = val.FieldByIndex(desc.inline)
+		}
+
+		if desc.encoder == nil {
+			return ErrNoEncoder{Type: rv.Type()}
+		}
+
+		encoder := desc.encoder
+
+		iszero := sc.isZero
+		if iz, ok := encoder.(CodecZeroer); ok {
+			iszero = iz.IsTypeZero
+		}
+
+		if desc.omitEmpty && iszero(rv.Interface()) {
+			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 defaultValueEncoders.mapEncodeValue(r, dw, rv, collisionFn)
+	}
+
+	return dw.WriteDocumentEnd()
+}
+
+// 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 vr.Type() {
+	case bsontype.Type(0), bsontype.EmbeddedDocument:
+	default:
+		return fmt.Errorf("cannot decode %v into a %s", vr.Type(), val.Type())
+	}
+
+	sd, err := sc.describeStruct(r.Registry, val.Type())
+	if err != nil {
+		return err
+	}
+
+	var decoder ValueDecoder
+	var inlineMap reflect.Value
+	if sd.inlineMap >= 0 {
+		inlineMap = val.Field(sd.inlineMap)
+		if inlineMap.IsNil() {
+			inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
+		}
+		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
+			}
+
+			elem := reflect.New(inlineMap.Type().Elem()).Elem()
+			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 = val.FieldByIndex(fd.inline)
+		}
+
+		if !field.CanSet() { // Being settable is a super set of being addressable.
+			return fmt.Errorf("cannot decode element '%s' into field %v; it is not settable", name, field)
+		}
+		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}
+		if fd.decoder == nil {
+			return ErrNoDecoder{Type: field.Elem().Type()}
+		}
+
+		if decoder, ok := fd.decoder.(ValueDecoder); ok {
+			err = decoder.DecodeValue(dctx, vr, field.Elem())
+			if err != nil {
+				return err
+			}
+			continue
+		}
+		err = fd.decoder.DecodeValue(dctx, vr, field)
+		if err != nil {
+			return 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()
+	}
+
+	return false
+}
+
+type structDescription struct {
+	fm        map[string]fieldDescription
+	fl        []fieldDescription
+	inlineMap int
+}
+
+type fieldDescription struct {
+	name      string
+	idx       int
+	omitEmpty bool
+	minSize   bool
+	truncate  bool
+	inline    []int
+	encoder   ValueEncoder
+	decoder   ValueDecoder
+}
+
+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,
+	}
+
+	for i := 0; i < numFields; i++ {
+		sf := t.Field(i)
+		if sf.PkgPath != "" {
+			// unexported, ignore
+			continue
+		}
+
+		encoder, err := r.LookupEncoder(sf.Type)
+		if err != nil {
+			encoder = nil
+		}
+		decoder, err := r.LookupDecoder(sf.Type)
+		if err != nil {
+			decoder = nil
+		}
+
+		description := fieldDescription{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 {
+			switch sf.Type.Kind() {
+			case reflect.Map:
+				if sd.inlineMap >= 0 {
+					return nil, errors.New("(struct " + t.String() + ") multiple inline maps")
+				}
+				if sf.Type.Key() != tString {
+					return nil, errors.New("(struct " + t.String() + ") inline map must have a string keys")
+				}
+				sd.inlineMap = description.idx
+			case reflect.Struct:
+				inlinesf, err := sc.describeStruct(r, sf.Type)
+				if err != nil {
+					return nil, err
+				}
+				for _, fd := range inlinesf.fl {
+					if _, exists := sd.fm[fd.name]; exists {
+						return nil, fmt.Errorf("(struct %s) duplicated key %s", t.String(), fd.name)
+					}
+					if fd.inline == nil {
+						fd.inline = []int{i, fd.idx}
+					} else {
+						fd.inline = append([]int{i}, fd.inline...)
+					}
+					sd.fm[fd.name] = fd
+					sd.fl = append(sd.fl, fd)
+				}
+			default:
+				return nil, fmt.Errorf("(struct %s) inline fields must be either a struct or a map", t.String())
+			}
+			continue
+		}
+
+		if _, exists := sd.fm[description.name]; exists {
+			return nil, fmt.Errorf("struct %s) duplicated key %s", t.String(), description.name)
+		}
+
+		sd.fm[description.name] = description
+		sd.fl = append(sd.fl, description)
+	}
+
+	sc.l.Lock()
+	sc.cache[t] = sd
+	sc.l.Unlock()
+
+	return sd, nil
+}

+ 47 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_codec_test.go

@@ -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))
+}

+ 119 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser.go

@@ -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 (
+	"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)
+	}
+	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
+}

+ 73 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/struct_tag_parser_test.go

@@ -0,0 +1,73 @@
+// 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 TestDefaultStructTagParser(t *testing.T) {
+	testCases := []struct {
+		name string
+		sf   reflect.StructField
+		want StructTags
+	}{
+		{
+			"no bson tag",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag("bar")},
+			StructTags{Name: "bar"},
+		},
+		{
+			"empty",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag("")},
+			StructTags{Name: "foo"},
+		},
+		{
+			"tag only dash",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag("-")},
+			StructTags{Skip: true},
+		},
+		{
+			"bson tag only dash",
+			reflect.StructField{Name: "foo", Tag: reflect.StructTag(`bson:"-"`)},
+			StructTags{Skip: true},
+		},
+		{
+			"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},
+		},
+		{
+			"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},
+		},
+		{
+			"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},
+		},
+		{
+			"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},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := DefaultStructTagParser(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)
+			}
+		})
+	}
+}

+ 80 - 0
src/go.mongodb.org/mongo-driver/bson/bsoncodec/types.go

@@ -0,0 +1,80 @@
+// 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 ptBool = reflect.TypeOf((*bool)(nil))
+var ptInt8 = reflect.TypeOf((*int8)(nil))
+var ptInt16 = reflect.TypeOf((*int16)(nil))
+var ptInt32 = reflect.TypeOf((*int32)(nil))
+var ptInt64 = reflect.TypeOf((*int64)(nil))
+var ptInt = reflect.TypeOf((*int)(nil))
+var ptUint8 = reflect.TypeOf((*uint8)(nil))
+var ptUint16 = reflect.TypeOf((*uint16)(nil))
+var ptUint32 = reflect.TypeOf((*uint32)(nil))
+var ptUint64 = reflect.TypeOf((*uint64)(nil))
+var ptUint = reflect.TypeOf((*uint)(nil))
+var ptFloat32 = reflect.TypeOf((*float32)(nil))
+var ptFloat64 = reflect.TypeOf((*float64)(nil))
+var ptString = reflect.TypeOf((*string)(nil))
+
+var tBool = reflect.TypeOf(false)
+var tFloat32 = reflect.TypeOf(float32(0))
+var tFloat64 = reflect.TypeOf(float64(0))
+var tInt = reflect.TypeOf(int(0))
+var tInt8 = reflect.TypeOf(int8(0))
+var tInt16 = reflect.TypeOf(int16(0))
+var tInt32 = reflect.TypeOf(int32(0))
+var tInt64 = reflect.TypeOf(int64(0))
+var tString = reflect.TypeOf("")
+var tTime = reflect.TypeOf(time.Time{})
+var tUint = reflect.TypeOf(uint(0))
+var tUint8 = reflect.TypeOf(uint8(0))
+var tUint16 = reflect.TypeOf(uint16(0))
+var tUint32 = reflect.TypeOf(uint32(0))
+var tUint64 = reflect.TypeOf(uint64(0))
+
+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{})

+ 33 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrw_test.go

@@ -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()
+	}
+}

+ 847 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/bsonrwtest/bsonrwtest.go

@@ -0,0 +1,847 @@
+// 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 // 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
+}

+ 389 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/copier.go

@@ -0,0 +1,389 @@
+// 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)
+}
+
+// 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()
+}
+
+// CopyBytesToDocumentWriter copies the values from a BSON document represented as a []byte to a
+// DocumentWriter.
+func (c Copier) CopyBytesToDocumentWriter(dst DocumentWriter, src []byte) 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)
+		}
+		dvw, err := dst.WriteDocumentElement(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(dvw, 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
+}
+
+// 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 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/copier_test.go

@@ -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: string("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: string("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: string("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: string("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 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/doc.go

@@ -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"

+ 731 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_parser.go

@@ -0,0 +1,731 @@
+// 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"
+
+	"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
+}
+
+// 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
+
+	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:
+			t = wrapperKeyBSONType(ejp.k)
+
+			if t == 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
+				}
+			}
+		}
+	}
+
+	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 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 decribed 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)
+}

+ 747 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_parser_test.go

@@ -0,0 +1,747 @@
+// 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},
+		}
+	}
+
+	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},
+		},
+	}
+
+	for _, tc := range cases {
+		ejp := newExtJSONParser(strings.NewReader(tc.input), true)
+
+		for i, eTyp := range tc.typs {
+			errF := tc.errFs[i]
+
+			typ, err := ejp.peekType()
+			typDiff(t, eTyp, typ, tc.desc)
+			errF(t, err, 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)}),
+	}
+
+	for _, tc := range cases {
+		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)
+		}
+	})
+}

+ 659 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_reader.go

@@ -0,0 +1,659 @@
+// 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) skipDocument() error {
+	// read entire document until ErrEOD (using readKey and readValue)
+	_, typ, err := ejvr.p.readKey()
+	for err == nil {
+		_, err = ejvr.p.readValue(typ)
+		if err != nil {
+			break
+		}
+
+		_, typ, err = ejvr.p.readKey()
+	}
+
+	return err
+}
+
+func (ejvr *extJSONValueReader) skipArray() error {
+	// read entire array until ErrEOA (using peekType)
+	_, err := ejvr.p.peekType()
+	for err == nil {
+		_, err = ejvr.p.peekType()
+	}
+
+	return err
+}
+
+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:
+		// read entire array until ErrEOA
+		err := ejvr.skipArray()
+		if err != ErrEOA {
+			return err
+		}
+	case bsontype.EmbeddedDocument:
+		// read entire doc until ErrEOD
+		err := ejvr.skipDocument()
+		if err != ErrEOD {
+			return err
+		}
+	case bsontype.CodeWithScope:
+		// read the code portion and set up parser in document mode
+		_, err := ejvr.p.readValue(t)
+		if err != nil {
+			return err
+		}
+
+		// read until ErrEOD
+		err = ejvr.skipDocument()
+		if err != ErrEOD {
+			return err
+		}
+	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 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_reader_test.go

@@ -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 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_tables.go

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

+ 495 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_wrappers.go

@@ -0,0 +1,495 @@
+// 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 string(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 "$ref":
+		fallthrough
+	case "$id":
+		fallthrough
+	case "$db":
+		return bsontype.EmbeddedDocument // dbrefs aren't bson types
+	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 t.Unix()*1e3 + int64(t.Nanosecond())/1e6, 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 string(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 string(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:
+			if val.v.(int32) < 0 {
+				return 0, fmt.Errorf("$timestamp %s number should be uint32: %s", key, string(val.v.(int32)))
+			}
+
+			return uint32(val.v.(int32)), nil
+		case bsontype.Int64:
+			if val.v.(int64) < 0 || uint32(val.v.(int64)) > math.MaxUint32 {
+				return 0, fmt.Errorf("$timestamp %s number should be uint32: %s", key, string(val.v.(int32)))
+			}
+
+			return uint32(val.v.(int64)), 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
+}

+ 734 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer.go

@@ -0,0 +1,734 @@
+// 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"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"io"
+	"math"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+	"unicode/utf8"
+)
+
+var ejvwPool = sync.Pool{
+	New: func() interface{} {
+		return new(extJSONValueWriter)
+	},
+}
+
+// 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:
+		ejvw.buf = append(ejvw.buf, []byte(fmt.Sprintf(`"%s":`, key))...)
+		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 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/extjson_writer_test.go

@@ -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)
+		}
+	})
+}

+ 439 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/json_scanner.go

@@ -0,0 +1,439 @@
+// 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"
+	"strings"
+	"unicode"
+)
+
+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)
+}
+
+// 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
+		}
+
+		switch c {
+		case '\\':
+			c, err = js.readNextByte()
+			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)
+				}
+
+				s := fmt.Sprintf(`\u%s`, us)
+				s, err = strconv.Unquote(strings.Replace(strconv.Quote(s), `\\u`, `\u`, 1))
+				if err != nil {
+					return nil, err
+				}
+
+				b.WriteString(s)
+			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
+		}
+	}
+}

+ 346 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/json_scanner_test.go

@@ -0,0 +1,346 @@
+// 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 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 {
+		js := &jsonScanner{r: strings.NewReader(tc.input)}
+
+		for _, token := range tc.tokens {
+			c, err := js.nextToken()
+			jttDiff(t, token.t, c.t, tc.desc)
+			jtvDiff(t, token.v, c.v, tc.desc)
+			expectNoError(t, err, tc.desc)
+		}
+
+		c, err := js.nextToken()
+		jttDiff(t, jttEOF, c.t, tc.desc)
+		noerr(t, err)
+
+		// testing early EOF reading
+		js = &jsonScanner{r: iotest.DataErrReader(strings.NewReader(tc.input))}
+
+		for _, token := range tc.tokens {
+			c, err := js.nextToken()
+			jttDiff(t, token.t, c.t, tc.desc)
+			jtvDiff(t, token.v, c.v, tc.desc)
+			expectNoError(t, err, tc.desc)
+		}
+
+		c, err = js.nextToken()
+		jttDiff(t, jttEOF, c.t, tc.desc)
+		noerr(t, err)
+	}
+}
+
+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 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 {
+		js := &jsonScanner{r: strings.NewReader(tc.input)}
+
+		c, err := js.nextToken()
+		expectNilToken(t, c, tc.desc)
+		expectError(t, err, tc.desc)
+	}
+}

+ 108 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/mode.go

@@ -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 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/reader.go

@@ -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)
+}

+ 882 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/value_reader.go

@@ -0,0 +1,882 @@
+// 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"
+	"unicode"
+
+	"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.
+//
+// TODO(skriptble): There's a lack of symmetry between the reader and writer, since the reader takes
+// a []byte while the writer takes an io.Writer. We should have two versions of each, one that takes
+// a []byte and one that takes an io.Reader or io.Writer. The []byte version will need to return a
+// thing that can return the finished []byte since it might be reallocated when appended to.
+func NewBSONDocumentReader(b []byte) ValueReader {
+	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
+	}
+
+	if btype == 0x02 {
+		length, err = vr.readLength()
+		if err != nil {
+			return nil, 0, err
+		}
+	}
+
+	b, err = vr.readBytes(length)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	vr.pop()
+	return b, 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
+	}
+	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
+}
+
+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) skipCString() error {
+	idx := bytes.IndexByte(vr.d[vr.offset:], 0x00)
+	if idx < 0 {
+		return io.EOF
+	}
+	// idx does not include the null byte
+	vr.offset += int64(idx) + 1
+	return 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)
+
+	if length == 2 {
+		asciiByte := vr.d[start]
+		if asciiByte > unicode.MaxASCII {
+			return "", fmt.Errorf("invalid ascii byte")
+		}
+	}
+
+	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
+}

+ 1526 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/value_reader_test.go

@@ -0,0 +1,1526 @@
+// 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"
+	"fmt"
+	"io"
+	"math"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+func TestValueReader(t *testing.T) {
+	t.Run("ReadBinary", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			btype  byte
+			b      []byte
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				0,
+				nil,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Binary),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				0,
+				nil,
+				io.EOF,
+				bsontype.Binary,
+			},
+			{
+				"no byte available",
+				[]byte{0x00, 0x00, 0x00, 0x00},
+				0,
+				0,
+				nil,
+				io.EOF,
+				bsontype.Binary,
+			},
+			{
+				"not enough bytes for binary",
+				[]byte{0x05, 0x00, 0x00, 0x00, 0x00},
+				0,
+				0,
+				nil,
+				io.EOF,
+				bsontype.Binary,
+			},
+			{
+				"success",
+				[]byte{0x03, 0x00, 0x00, 0x00, 0xEA, 0x01, 0x02, 0x03},
+				0,
+				0xEA,
+				[]byte{0x01, 0x02, 0x03},
+				nil,
+				bsontype.Binary,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				b, btype, err := vr.ReadBinary()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if btype != tc.btype {
+					t.Errorf("Incorrect binary type returned. got %v; want %v", btype, tc.btype)
+				}
+				if !bytes.Equal(b, tc.b) {
+					t.Errorf("Binary data does not match. got %v; want %v", b, tc.b)
+				}
+			})
+		}
+	})
+	t.Run("ReadBoolean", func(t *testing.T) {
+		testCases := []struct {
+			name    string
+			data    []byte
+			offset  int64
+			boolean bool
+			err     error
+			vType   bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				false,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Boolean),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"no byte available",
+				[]byte{},
+				0,
+				false,
+				io.EOF,
+				bsontype.Boolean,
+			},
+			{
+				"invalid byte for boolean",
+				[]byte{0x03},
+				0,
+				false,
+				fmt.Errorf("invalid byte for boolean, %b", 0x03),
+				bsontype.Boolean,
+			},
+			{
+				"success",
+				[]byte{0x01},
+				0,
+				true,
+				nil,
+				bsontype.Boolean,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				boolean, err := vr.ReadBoolean()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if boolean != tc.boolean {
+					t.Errorf("Incorrect boolean returned. got %v; want %v", boolean, tc.boolean)
+				}
+			})
+		}
+	})
+	t.Run("ReadDocument", func(t *testing.T) {
+		t.Run("TopLevel", func(t *testing.T) {
+			doc := []byte{0x05, 0x00, 0x00, 0x00, 0x00}
+			vr := &valueReader{
+				offset: 0,
+				stack:  []vrState{{mode: mTopLevel}},
+				frame:  0,
+			}
+
+			// invalid length
+			vr.d = []byte{0x00, 0x00}
+			_, err := vr.ReadDocument()
+			if err != io.EOF {
+				t.Errorf("Expected io.EOF with document length too small. got %v; want %v", err, io.EOF)
+			}
+
+			vr.d = doc
+			_, err = vr.ReadDocument()
+			noerr(t, err)
+			if vr.stack[vr.frame].end != 5 {
+				t.Errorf("Incorrect end for document. got %d; want %d", vr.stack[vr.frame].end, 5)
+			}
+		})
+		t.Run("EmbeddedDocument", func(t *testing.T) {
+			vr := &valueReader{
+				offset: 0,
+				stack: []vrState{
+					{mode: mTopLevel},
+					{mode: mElement, vType: bsontype.Boolean},
+				},
+				frame: 1,
+			}
+
+			var wanterr = (&valueReader{stack: []vrState{{mode: mElement, vType: bsontype.Boolean}}}).typeError(bsontype.EmbeddedDocument)
+			_, err := vr.ReadDocument()
+			if err == nil || err.Error() != wanterr.Error() {
+				t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr)
+			}
+
+			vr.stack[1].mode = mArray
+			wanterr = vr.invalidTransitionErr(mDocument, "ReadDocument", []mode{mTopLevel, mElement, mValue})
+			_, err = vr.ReadDocument()
+			if err == nil || err.Error() != wanterr.Error() {
+				t.Errorf("Incorrect returned error. got %v; want %v", err, wanterr)
+			}
+
+			vr.stack[1].mode, vr.stack[1].vType = mElement, bsontype.EmbeddedDocument
+			vr.d = []byte{0x0A, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}
+			vr.offset = 4
+			_, err = vr.ReadDocument()
+			noerr(t, err)
+			if len(vr.stack) != 3 {
+				t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3)
+			}
+			if vr.stack[2].mode != mDocument {
+				t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument)
+			}
+			if vr.stack[2].end != 9 {
+				t.Errorf("End of embedded document is not correct. got %d; want %d", vr.stack[2].end, 9)
+			}
+			if vr.offset != 8 {
+				t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 8)
+			}
+
+			vr.frame--
+			_, err = vr.ReadDocument()
+			if err != io.EOF {
+				t.Errorf("Should return error when attempting to read length with not enough bytes. got %v; want %v", err, io.EOF)
+			}
+		})
+	})
+	t.Run("ReadBinary", func(t *testing.T) {
+		codeWithScope := []byte{
+			0x11, 0x00, 0x00, 0x00, // total length
+			0x4, 0x00, 0x00, 0x00, // string length
+			'f', 'o', 'o', 0x00, // string
+			0x05, 0x00, 0x00, 0x00, 0x00, // document
+		}
+		mismatchCodeWithScope := []byte{
+			0x11, 0x00, 0x00, 0x00, // total length
+			0x4, 0x00, 0x00, 0x00, // string length
+			'f', 'o', 'o', 0x00, // string
+			0x07, 0x00, 0x00, 0x00, // document
+			0x0A, 0x00, // null element, empty key
+			0x00, // document end
+		}
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.CodeWithScope),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"total length not enough bytes",
+				[]byte{},
+				0,
+				io.EOF,
+				bsontype.CodeWithScope,
+			},
+			{
+				"string length not enough bytes",
+				codeWithScope[:4],
+				0,
+				io.EOF,
+				bsontype.CodeWithScope,
+			},
+			{
+				"not enough string bytes",
+				codeWithScope[:8],
+				0,
+				io.EOF,
+				bsontype.CodeWithScope,
+			},
+			{
+				"document length not enough bytes",
+				codeWithScope[:12],
+				0,
+				io.EOF,
+				bsontype.CodeWithScope,
+			},
+			{
+				"length mismatch",
+				mismatchCodeWithScope,
+				0,
+				fmt.Errorf("length of CodeWithScope does not match lengths of components; total: %d; components: %d", 17, 19),
+				bsontype.CodeWithScope,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				_, _, err := vr.ReadCodeWithScope()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+			})
+		}
+
+		t.Run("success", func(t *testing.T) {
+			doc := []byte{0x00, 0x00, 0x00, 0x00}
+			doc = append(doc, codeWithScope...)
+			doc = append(doc, 0x00)
+			vr := &valueReader{
+				offset: 4,
+				d:      doc,
+				stack: []vrState{
+					{mode: mTopLevel},
+					{mode: mElement, vType: bsontype.CodeWithScope},
+				},
+				frame: 1,
+			}
+
+			code, _, err := vr.ReadCodeWithScope()
+			noerr(t, err)
+			if code != "foo" {
+				t.Errorf("Code does not match. got %s; want %s", code, "foo")
+			}
+			if len(vr.stack) != 3 {
+				t.Errorf("Incorrect number of stack frames. got %d; want %d", len(vr.stack), 3)
+			}
+			if vr.stack[2].mode != mCodeWithScope {
+				t.Errorf("Incorrect mode set. got %v; want %v", vr.stack[2].mode, mDocument)
+			}
+			if vr.stack[2].end != 21 {
+				t.Errorf("End of scope is not correct. got %d; want %d", vr.stack[2].end, 21)
+			}
+			if vr.offset != 20 {
+				t.Errorf("Offset not incremented correctly. got %d; want %d", vr.offset, 20)
+			}
+		})
+	})
+	t.Run("ReadDBPointer", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			ns     string
+			oid    primitive.ObjectID
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				"",
+				primitive.ObjectID{},
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.DBPointer),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				"",
+				primitive.ObjectID{},
+				io.EOF,
+				bsontype.DBPointer,
+			},
+			{
+				"not enough bytes for namespace",
+				[]byte{0x04, 0x00, 0x00, 0x00},
+				0,
+				"",
+				primitive.ObjectID{},
+				io.EOF,
+				bsontype.DBPointer,
+			},
+			{
+				"not enough bytes for objectID",
+				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
+				0,
+				"",
+				primitive.ObjectID{},
+				io.EOF,
+				bsontype.DBPointer,
+			},
+			{
+				"success",
+				[]byte{
+					0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00,
+					0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
+				},
+				0,
+				"foo",
+				primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+				nil,
+				bsontype.DBPointer,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				ns, oid, err := vr.ReadDBPointer()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if ns != tc.ns {
+					t.Errorf("Incorrect namespace returned. got %v; want %v", ns, tc.ns)
+				}
+				if !bytes.Equal(oid[:], tc.oid[:]) {
+					t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid)
+				}
+			})
+		}
+	})
+	t.Run("ReadDateTime", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			dt     int64
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				0,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.DateTime),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				0,
+				io.EOF,
+				bsontype.DateTime,
+			},
+			{
+				"success",
+				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+				0,
+				255,
+				nil,
+				bsontype.DateTime,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				dt, err := vr.ReadDateTime()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if dt != tc.dt {
+					t.Errorf("Incorrect datetime returned. got %d; want %d", dt, tc.dt)
+				}
+			})
+		}
+	})
+	t.Run("ReadDecimal128", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			dc128  primitive.Decimal128
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				primitive.Decimal128{},
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Decimal128),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				primitive.Decimal128{},
+				io.EOF,
+				bsontype.Decimal128,
+			},
+			{
+				"success",
+				[]byte{
+					0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Low
+					0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // High
+				},
+				0,
+				primitive.NewDecimal128(65280, 255),
+				nil,
+				bsontype.Decimal128,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				dc128, err := vr.ReadDecimal128()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				gotHigh, gotLow := dc128.GetBytes()
+				wantHigh, wantLow := tc.dc128.GetBytes()
+				if gotHigh != wantHigh {
+					t.Errorf("Retuired high byte does not match. got %d; want %d", gotHigh, wantHigh)
+				}
+				if gotLow != wantLow {
+					t.Errorf("Returned low byte does not match. got %d; want %d", gotLow, wantLow)
+				}
+			})
+		}
+	})
+	t.Run("ReadDouble", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			double float64
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				0,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Double),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				0,
+				io.EOF,
+				bsontype.Double,
+			},
+			{
+				"success",
+				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+				0,
+				math.Float64frombits(255),
+				nil,
+				bsontype.Double,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				double, err := vr.ReadDouble()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if double != tc.double {
+					t.Errorf("Incorrect double returned. got %f; want %f", double, tc.double)
+				}
+			})
+		}
+	})
+	t.Run("ReadInt32", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			i32    int32
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				0,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Int32),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				0,
+				io.EOF,
+				bsontype.Int32,
+			},
+			{
+				"success",
+				[]byte{0xFF, 0x00, 0x00, 0x00},
+				0,
+				255,
+				nil,
+				bsontype.Int32,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				i32, err := vr.ReadInt32()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if i32 != tc.i32 {
+					t.Errorf("Incorrect int32 returned. got %d; want %d", i32, tc.i32)
+				}
+			})
+		}
+	})
+	t.Run("ReadInt32", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			i64    int64
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				0,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Int64),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				0,
+				io.EOF,
+				bsontype.Int64,
+			},
+			{
+				"success",
+				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+				0,
+				255,
+				nil,
+				bsontype.Int64,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				i64, err := vr.ReadInt64()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if i64 != tc.i64 {
+					t.Errorf("Incorrect int64 returned. got %d; want %d", i64, tc.i64)
+				}
+			})
+		}
+	})
+	t.Run("ReadJavascript/ReadString/ReadSymbol", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			fn     func(*valueReader) (string, error)
+			css    string // code, string, symbol :P
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"ReadJavascript/incorrect type",
+				[]byte{},
+				0,
+				(*valueReader).ReadJavascript,
+				"",
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.JavaScript),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"ReadString/incorrect type",
+				[]byte{},
+				0,
+				(*valueReader).ReadString,
+				"",
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.String),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"ReadSymbol/incorrect type",
+				[]byte{},
+				0,
+				(*valueReader).ReadSymbol,
+				"",
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Symbol),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"ReadJavascript/length too short",
+				[]byte{},
+				0,
+				(*valueReader).ReadJavascript,
+				"",
+				io.EOF,
+				bsontype.JavaScript,
+			},
+			{
+				"ReadString/length too short",
+				[]byte{},
+				0,
+				(*valueReader).ReadString,
+				"",
+				io.EOF,
+				bsontype.String,
+			},
+			{
+				"ReadSymbol/length too short",
+				[]byte{},
+				0,
+				(*valueReader).ReadSymbol,
+				"",
+				io.EOF,
+				bsontype.Symbol,
+			},
+			{
+				"ReadJavascript/incorrect end byte",
+				[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
+				0,
+				(*valueReader).ReadJavascript,
+				"",
+				fmt.Errorf("string does not end with null byte, but with %v", 0x05),
+				bsontype.JavaScript,
+			},
+			{
+				"ReadString/incorrect end byte",
+				[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
+				0,
+				(*valueReader).ReadString,
+				"",
+				fmt.Errorf("string does not end with null byte, but with %v", 0x05),
+				bsontype.String,
+			},
+			{
+				"ReadSymbol/incorrect end byte",
+				[]byte{0x01, 0x00, 0x00, 0x00, 0x05},
+				0,
+				(*valueReader).ReadSymbol,
+				"",
+				fmt.Errorf("string does not end with null byte, but with %v", 0x05),
+				bsontype.Symbol,
+			},
+			{
+				"ReadJavascript/success",
+				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
+				0,
+				(*valueReader).ReadJavascript,
+				"foo",
+				nil,
+				bsontype.JavaScript,
+			},
+			{
+				"ReadString/success",
+				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
+				0,
+				(*valueReader).ReadString,
+				"foo",
+				nil,
+				bsontype.String,
+			},
+			{
+				"ReadSymbol/success",
+				[]byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00},
+				0,
+				(*valueReader).ReadSymbol,
+				"foo",
+				nil,
+				bsontype.Symbol,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				css, err := tc.fn(vr)
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if css != tc.css {
+					t.Errorf("Incorrect (JavaScript,String,Symbol) returned. got %s; want %s", css, tc.css)
+				}
+			})
+		}
+	})
+	t.Run("ReadMaxKey/ReadMinKey/ReadNull/ReadUndefined", func(t *testing.T) {
+		testCases := []struct {
+			name  string
+			fn    func(*valueReader) error
+			err   error
+			vType bsontype.Type
+		}{
+			{
+				"ReadMaxKey/incorrect type",
+				(*valueReader).ReadMaxKey,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.MaxKey),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"ReadMaxKey/success",
+				(*valueReader).ReadMaxKey,
+				nil,
+				bsontype.MaxKey,
+			},
+			{
+				"ReadMinKey/incorrect type",
+				(*valueReader).ReadMinKey,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.MinKey),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"ReadMinKey/success",
+				(*valueReader).ReadMinKey,
+				nil,
+				bsontype.MinKey,
+			},
+			{
+				"ReadNull/incorrect type",
+				(*valueReader).ReadNull,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Null),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"ReadNull/success",
+				(*valueReader).ReadNull,
+				nil,
+				bsontype.Null,
+			},
+			{
+				"ReadUndefined/incorrect type",
+				(*valueReader).ReadUndefined,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Undefined),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"ReadUndefined/success",
+				(*valueReader).ReadUndefined,
+				nil,
+				bsontype.Undefined,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				err := tc.fn(vr)
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+			})
+		}
+	})
+	t.Run("ReadObjectID", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			oid    primitive.ObjectID
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				primitive.ObjectID{},
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.ObjectID),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"not enough bytes for objectID",
+				[]byte{},
+				0,
+				primitive.ObjectID{},
+				io.EOF,
+				bsontype.ObjectID,
+			},
+			{
+				"success",
+				[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+				0,
+				primitive.ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+				nil,
+				bsontype.ObjectID,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				oid, err := vr.ReadObjectID()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if !bytes.Equal(oid[:], tc.oid[:]) {
+					t.Errorf("ObjectIDs did not match. got %v; want %v", oid, tc.oid)
+				}
+			})
+		}
+	})
+	t.Run("ReadRegex", func(t *testing.T) {
+		testCases := []struct {
+			name    string
+			data    []byte
+			offset  int64
+			pattern string
+			options string
+			err     error
+			vType   bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				"",
+				"",
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Regex),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"length too short",
+				[]byte{},
+				0,
+				"",
+				"",
+				io.EOF,
+				bsontype.Regex,
+			},
+			{
+				"not enough bytes for options",
+				[]byte{'f', 'o', 'o', 0x00},
+				0,
+				"",
+				"",
+				io.EOF,
+				bsontype.Regex,
+			},
+			{
+				"success",
+				[]byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r', 0x00},
+				0,
+				"foo",
+				"bar",
+				nil,
+				bsontype.Regex,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				pattern, options, err := vr.ReadRegex()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if pattern != tc.pattern {
+					t.Errorf("Incorrect pattern returned. got %s; want %s", pattern, tc.pattern)
+				}
+				if options != tc.options {
+					t.Errorf("Incorrect options returned. got %s; want %s", options, tc.options)
+				}
+			})
+		}
+	})
+	t.Run("ReadTimestamp", func(t *testing.T) {
+		testCases := []struct {
+			name   string
+			data   []byte
+			offset int64
+			ts     uint32
+			incr   uint32
+			err    error
+			vType  bsontype.Type
+		}{
+			{
+				"incorrect type",
+				[]byte{},
+				0,
+				0,
+				0,
+				(&valueReader{stack: []vrState{{vType: bsontype.EmbeddedDocument}}, frame: 0}).typeError(bsontype.Timestamp),
+				bsontype.EmbeddedDocument,
+			},
+			{
+				"not enough bytes for increment",
+				[]byte{},
+				0,
+				0,
+				0,
+				io.EOF,
+				bsontype.Timestamp,
+			},
+			{
+				"not enough bytes for timestamp",
+				[]byte{0x01, 0x02, 0x03, 0x04},
+				0,
+				0,
+				0,
+				io.EOF,
+				bsontype.Timestamp,
+			},
+			{
+				"success",
+				[]byte{0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00},
+				0,
+				256,
+				255,
+				nil,
+				bsontype.Timestamp,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				vr := &valueReader{
+					offset: tc.offset,
+					d:      tc.data,
+					stack: []vrState{
+						{mode: mTopLevel},
+						{
+							mode:  mElement,
+							vType: tc.vType,
+						},
+					},
+					frame: 1,
+				}
+
+				ts, incr, err := vr.ReadTimestamp()
+				if !errequal(t, err, tc.err) {
+					t.Errorf("Returned errors do not match. got %v; want %v", err, tc.err)
+				}
+				if ts != tc.ts {
+					t.Errorf("Incorrect timestamp returned. got %d; want %d", ts, tc.ts)
+				}
+				if incr != tc.incr {
+					t.Errorf("Incorrect increment returned. got %d; want %d", incr, tc.incr)
+				}
+			})
+		}
+	})
+
+	t.Run("ReadBytes & Skip", func(t *testing.T) {
+		index, docb := bsoncore.ReserveLength(nil)
+		docb = bsoncore.AppendNullElement(docb, "foobar")
+		docb = append(docb, 0x00)
+		docb = bsoncore.UpdateLength(docb, index, int32(len(docb)))
+		cwsbytes := bsoncore.AppendCodeWithScope(nil, "var hellow = world;", docb)
+		strbytes := []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}
+		testCases := []struct {
+			name           string
+			t              bsontype.Type
+			data           []byte
+			err            error
+			offset         int64
+			startingOffset int64
+		}{
+			{
+				"Array/invalid length",
+				bsontype.Array,
+				[]byte{0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"Array/not enough bytes",
+				bsontype.Array,
+				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"Array/success",
+				bsontype.Array,
+				[]byte{0x08, 0x00, 0x00, 0x00, 0x0A, '1', 0x00, 0x00},
+				nil, 8, 0,
+			},
+			{
+				"EmbeddedDocument/invalid length",
+				bsontype.EmbeddedDocument,
+				[]byte{0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"EmbeddedDocument/not enough bytes",
+				bsontype.EmbeddedDocument,
+				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"EmbeddedDocument/success",
+				bsontype.EmbeddedDocument,
+				[]byte{0x08, 0x00, 0x00, 0x00, 0x0A, 'A', 0x00, 0x00},
+				nil, 8, 0,
+			},
+			{
+				"CodeWithScope/invalid length",
+				bsontype.CodeWithScope,
+				[]byte{0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"CodeWithScope/not enough bytes",
+				bsontype.CodeWithScope,
+				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"CodeWithScope/success",
+				bsontype.CodeWithScope,
+				cwsbytes,
+				nil, 41, 0,
+			},
+			{
+				"Binary/invalid length",
+				bsontype.Binary,
+				[]byte{0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"Binary/not enough bytes",
+				bsontype.Binary,
+				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"Binary/success",
+				bsontype.Binary,
+				[]byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
+				nil, 8, 0,
+			},
+			{
+				"Boolean/invalid length",
+				bsontype.Boolean,
+				[]byte{},
+				io.EOF, 0, 0,
+			},
+			{
+				"Boolean/success",
+				bsontype.Boolean,
+				[]byte{0x01},
+				nil, 1, 0,
+			},
+			{
+				"DBPointer/invalid length",
+				bsontype.DBPointer,
+				[]byte{0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"DBPointer/not enough bytes",
+				bsontype.DBPointer,
+				[]byte{0x0F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03},
+				io.EOF, 0, 0,
+			},
+			{
+				"DBPointer/success",
+				bsontype.DBPointer,
+				[]byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+				nil, 17, 0,
+			},
+			{"DBPointer/not enough bytes", bsontype.DateTime, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
+			{"DBPointer/success", bsontype.DateTime, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
+			{"Double/not enough bytes", bsontype.Double, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
+			{"Double/success", bsontype.Double, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
+			{"Int64/not enough bytes", bsontype.Int64, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
+			{"Int64/success", bsontype.Int64, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
+			{"Timestamp/not enough bytes", bsontype.Timestamp, []byte{0x01, 0x02, 0x03, 0x04}, io.EOF, 0, 0},
+			{"Timestamp/success", bsontype.Timestamp, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, nil, 8, 0},
+			{
+				"Decimal128/not enough bytes",
+				bsontype.Decimal128,
+				[]byte{0x01, 0x02, 0x03, 0x04},
+				io.EOF, 0, 0,
+			},
+			{
+				"Decimal128/success",
+				bsontype.Decimal128,
+				[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10},
+				nil, 16, 0,
+			},
+			{"Int32/not enough bytes", bsontype.Int32, []byte{0x01, 0x02}, io.EOF, 0, 0},
+			{"Int32/success", bsontype.Int32, []byte{0x01, 0x02, 0x03, 0x04}, nil, 4, 0},
+			{"Javascript/invalid length", bsontype.JavaScript, strbytes[:2], io.EOF, 0, 0},
+			{"Javascript/not enough bytes", bsontype.JavaScript, strbytes[:5], io.EOF, 0, 0},
+			{"Javascript/success", bsontype.JavaScript, strbytes, nil, 8, 0},
+			{"String/invalid length", bsontype.String, strbytes[:2], io.EOF, 0, 0},
+			{"String/not enough bytes", bsontype.String, strbytes[:5], io.EOF, 0, 0},
+			{"String/success", bsontype.String, strbytes, nil, 8, 0},
+			{"Symbol/invalid length", bsontype.Symbol, strbytes[:2], io.EOF, 0, 0},
+			{"Symbol/not enough bytes", bsontype.Symbol, strbytes[:5], io.EOF, 0, 0},
+			{"Symbol/success", bsontype.Symbol, strbytes, nil, 8, 0},
+			{"MaxKey/success", bsontype.MaxKey, []byte{}, nil, 0, 0},
+			{"MinKey/success", bsontype.MinKey, []byte{}, nil, 0, 0},
+			{"Null/success", bsontype.Null, []byte{}, nil, 0, 0},
+			{"Undefined/success", bsontype.Undefined, []byte{}, nil, 0, 0},
+			{
+				"ObjectID/not enough bytes",
+				bsontype.ObjectID,
+				[]byte{0x01, 0x02, 0x03, 0x04},
+				io.EOF, 0, 0,
+			},
+			{
+				"ObjectID/success",
+				bsontype.ObjectID,
+				[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C},
+				nil, 12, 0,
+			},
+			{
+				"Regex/not enough bytes (first string)",
+				bsontype.Regex,
+				[]byte{'f', 'o', 'o'},
+				io.EOF, 0, 0,
+			},
+			{
+				"Regex/not enough bytes (second string)",
+				bsontype.Regex,
+				[]byte{'f', 'o', 'o', 0x00, 'b', 'a', 'r'},
+				io.EOF, 0, 0,
+			},
+			{
+				"Regex/success",
+				bsontype.Regex,
+				[]byte{0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00, 'i', 0x00},
+				nil, 9, 3,
+			},
+			{
+				"Unknown Type",
+				bsontype.Type(0),
+				nil,
+				fmt.Errorf("attempted to read bytes of unknown BSON type %v", bsontype.Type(0)), 0, 0,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				t.Run("Skip", func(t *testing.T) {
+					vr := &valueReader{
+						d: tc.data,
+						stack: []vrState{
+							{mode: mTopLevel},
+							{mode: mElement, vType: tc.t},
+						},
+						frame:  1,
+						offset: tc.startingOffset,
+					}
+
+					err := vr.Skip()
+					if !errequal(t, err, tc.err) {
+						t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err)
+					}
+					if tc.err == nil && vr.offset != tc.offset {
+						t.Errorf("Offset not set at correct position; got %d; want %d", vr.offset, tc.offset)
+					}
+				})
+				t.Run("ReadBytes", func(t *testing.T) {
+					vr := &valueReader{
+						d: tc.data,
+						stack: []vrState{
+							{mode: mTopLevel},
+							{mode: mElement, vType: tc.t},
+						},
+						frame:  1,
+						offset: tc.startingOffset,
+					}
+
+					_, got, err := vr.ReadValueBytes(nil)
+					if !errequal(t, err, tc.err) {
+						t.Errorf("Did not receive expected error; got %v; want %v", err, tc.err)
+					}
+					if tc.err == nil && vr.offset != tc.offset {
+						t.Errorf("Offset not set at correct position; got %d; want %d", vr.offset, tc.offset)
+					}
+					if tc.err == nil && !bytes.Equal(got, tc.data[tc.startingOffset:]) {
+						t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.data[tc.startingOffset:])
+					}
+				})
+			})
+		}
+		t.Run("ReadValueBytes/Top Level Doc", func(t *testing.T) {
+			testCases := []struct {
+				name     string
+				want     []byte
+				wantType bsontype.Type
+				wantErr  error
+			}{
+				{
+					"success",
+					bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)),
+					bsontype.Type(0),
+					nil,
+				},
+				{
+					"wrong length",
+					[]byte{0x01, 0x02, 0x03},
+					bsontype.Type(0),
+					io.EOF,
+				},
+				{
+					"append bytes",
+					[]byte{0x01, 0x02, 0x03, 0x04},
+					bsontype.Type(0),
+					io.EOF,
+				},
+			}
+
+			for _, tc := range testCases {
+				tc := tc
+				t.Run(tc.name, func(t *testing.T) {
+					t.Parallel()
+					vr := &valueReader{
+						d: tc.want,
+						stack: []vrState{
+							{mode: mTopLevel},
+						},
+						frame: 0,
+					}
+					gotType, got, gotErr := vr.ReadValueBytes(nil)
+					if gotErr != tc.wantErr {
+						t.Errorf("Did not receive expected error. got %v; want %v", gotErr, tc.wantErr)
+					}
+					if tc.wantErr == nil && gotType != tc.wantType {
+						t.Errorf("Did not receive expected type. got %v; want %v", gotType, tc.wantType)
+					}
+					if tc.wantErr == nil && !bytes.Equal(got, tc.want) {
+						t.Errorf("Did not receive expected bytes. got %v; want %v", got, tc.want)
+					}
+				})
+			}
+		})
+	})
+
+	t.Run("invalid transition", func(t *testing.T) {
+		t.Run("Skip", func(t *testing.T) {
+			vr := &valueReader{stack: []vrState{{mode: mTopLevel}}}
+			wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}}}).invalidTransitionErr(0, "Skip", []mode{mElement, mValue})
+			goterr := vr.Skip()
+			if !cmp.Equal(goterr, wanterr, cmp.Comparer(compareErrors)) {
+				t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)
+			}
+		})
+	})
+	t.Run("ReadBytes", func(t *testing.T) {
+		vr := &valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1}
+		wanterr := (&valueReader{stack: []vrState{{mode: mTopLevel}, {mode: mDocument}}, frame: 1}).
+			invalidTransitionErr(0, "ReadValueBytes", []mode{mElement, mValue})
+		_, _, goterr := vr.ReadValueBytes(nil)
+		if !cmp.Equal(goterr, wanterr, cmp.Comparer(compareErrors)) {
+			t.Errorf("Expected correct invalid transition error. got %v; want %v", goterr, wanterr)
+		}
+	})
+}
+
+func errequal(t *testing.T, err1, err2 error) bool {
+	t.Helper()
+	if err1 == nil && err2 == nil { // If they are both nil, they are equal
+		return true
+	}
+	if err1 == nil || err2 == nil { // If only one is nil, they are not equal
+		return false
+	}
+
+	if err1 == err2 { // They are the same error, they are equal
+		return true
+	}
+
+	if err1.Error() == err2.Error() { // They string formats match, they are equal
+		return true
+	}
+
+	return false
+}

+ 608 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/value_reader_writer_test.go

@@ -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 (
+	llvrwNothing VRWInvoked = iota
+	llvrwReadArray
+	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
+}

+ 589 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/value_writer.go

@@ -0,0 +1,589 @@
+// 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"
+	"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)
+	if writer, ok := w.(*SliceWriter); ok {
+		vw.reset(*writer)
+		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 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
+	}
+
+	if _, ok := bvw.w.(*SliceWriter); ok {
+		bvw.buf = nil
+	}
+	bvw.w = nil
+
+	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:
+		vw.buf = bsoncore.AppendHeader(vw.buf, t, vw.stack[vw.frame].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 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 vw.w != nil {
+			if sw, ok := vw.w.(*SliceWriter); ok {
+				*sw = vw.buf
+			} else {
+				_, err = vw.w.Write(vw.buf)
+				if err != nil {
+					return err
+				}
+				// reset buffer
+				vw.buf = vw.buf[:0]
+			}
+		}
+	}
+
+	vw.pop()
+
+	if vw.stack[vw.frame].mode == mCodeWithScope {
+		// We ignore the error here because of the gaurantee of writeLength.
+		// See the docs for writeLength for more info.
+		_ = vw.writeLength()
+		vw.pop()
+	}
+	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
+}

+ 438 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/value_writer_test.go

@@ -0,0 +1,438 @@
+// 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 }
+
+func vwBasicDoc(t *testing.T, vw *valueWriter) {
+	dw, err := vw.WriteDocument()
+	noerr(t, err)
+	vw2, err := dw.WriteDocumentElement("foo")
+	noerr(t, err)
+	err = vw2.WriteBoolean(true)
+	noerr(t, err)
+	err = dw.WriteDocumentEnd()
+	noerr(t, err)
+
+	return
+}
+
+func vwBasicArray(t *testing.T, vw *valueWriter) {
+	dw, err := vw.WriteDocument()
+	noerr(t, err)
+	vw2, err := dw.WriteDocumentElement("foo")
+	noerr(t, err)
+	aw, err := vw2.WriteArray()
+	noerr(t, err)
+	vw2, err = aw.WriteArrayElement()
+	noerr(t, err)
+	err = vw2.WriteBoolean(true)
+	noerr(t, err)
+	err = aw.WriteArrayEnd()
+	noerr(t, err)
+	err = dw.WriteDocumentEnd()
+	noerr(t, err)
+
+	return
+}
+
+func vwNestedDoc(t *testing.T, vw *valueWriter) {
+	dw, err := vw.WriteDocument()
+	noerr(t, err)
+	vw2, err := dw.WriteDocumentElement("foo")
+	noerr(t, err)
+	dw2, err := vw2.WriteDocument()
+	noerr(t, err)
+	vw3, err := dw2.WriteDocumentElement("bar")
+	noerr(t, err)
+	err = vw3.WriteBoolean(true)
+	noerr(t, err)
+	err = dw2.WriteDocumentEnd()
+	noerr(t, err)
+	err = dw.WriteDocumentEnd()
+	noerr(t, err)
+
+	return
+}
+
+func vwCodeWithScopeNoNested(t *testing.T, vw *valueWriter) {
+	dw, err := vw.WriteDocument()
+	noerr(t, err)
+	vw2, err := dw.WriteDocumentElement("foo")
+	noerr(t, err)
+	dw2, err := vw2.WriteCodeWithScope("var hello = world;")
+	noerr(t, err)
+	vw2, err = dw2.WriteDocumentElement("bar")
+	noerr(t, err)
+	err = vw2.WriteBoolean(false)
+	noerr(t, err)
+	err = dw2.WriteDocumentEnd()
+	noerr(t, err)
+	err = dw.WriteDocumentEnd()
+	noerr(t, err)
+
+	return
+}

+ 96 - 0
src/go.mongodb.org/mongo-driver/bson/bsonrw/writer.go

@@ -0,0 +1,96 @@
+// 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
+}
+
+// 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
+}
+
+type writer []byte
+
+func (w *writer) Write(p []byte) (int, error) {
+	index := len(*w)
+	return w.WriteAt(p, int64(index))
+}
+
+func (w *writer) WriteAt(p []byte, off int64) (int, error) {
+	newend := off + int64(len(p))
+	if newend < int64(len(*w)) {
+		newend = int64(len(*w))
+	}
+
+	if newend > int64(cap(*w)) {
+		buf := make([]byte, int64(2*cap(*w))+newend)
+		copy(buf, *w)
+		*w = buf
+	}
+
+	*w = []byte(*w)[:newend]
+	copy([]byte(*w)[off:], p)
+	return len(p), nil
+}

+ 87 - 0
src/go.mongodb.org/mongo-driver/bson/bsontype/bsontype.go

@@ -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 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
+)
+
+// 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 - 0
src/go.mongodb.org/mongo-driver/bson/bsontype/bsontype_test.go

@@ -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)
+			}
+		})
+	}
+}

+ 112 - 0
src/go.mongodb.org/mongo-driver/bson/decoder.go

@@ -0,0 +1,112 @@
+// 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
+}
+
+// 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)
+	if rval.Kind() != reflect.Ptr {
+		return fmt.Errorf("argument to Decode must be a pointer to a type, but got %v", rval)
+	}
+	if rval.IsNil() {
+		return ErrDecodeToNil
+	}
+	rval = rval.Elem()
+	decoder, err := d.dc.LookupDecoder(rval.Type())
+	if err != nil {
+		return err
+	}
+	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
+}

+ 277 - 0
src/go.mongodb.org/mongo-driver/bson/decoder_test.go

@@ -0,0 +1,277 @@
+// 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"
+	"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/x/bsonx/bsoncore"
+)
+
+func TestBasicDecode(t *testing.T) {
+	for _, tc := range unmarshalingTestCases {
+		t.Run(tc.name, func(t *testing.T) {
+			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)
+
+			if !reflect.DeepEqual(got.Addr().Interface(), tc.want) {
+				t.Errorf("Results do not match. got %+v; want %+v", got, tc.want)
+			}
+		})
+	}
+}
+
+func TestDecoderv2(t *testing.T) {
+	t.Run("Decode", func(t *testing.T) {
+		for _, tc := range unmarshalingTestCases {
+			t.Run(tc.name, func(t *testing.T) {
+				got := reflect.New(tc.sType).Interface()
+				vr := bsonrw.NewBSONDocumentReader(tc.data)
+				var reg *bsoncodec.Registry
+				if tc.reg != nil {
+					reg = tc.reg
+				} else {
+					reg = DefaultRegistry
+				}
+				dec, err := NewDecoderWithContext(bsoncodec.DecodeContext{Registry: reg}, vr)
+				noerr(t, err)
+				err = dec.Decode(got)
+				noerr(t, err)
+
+				if !reflect.DeepEqual(got, tc.want) {
+					t.Errorf("Results do not match. got %+v; want %+v", got, tc.want)
+				}
+			})
+		}
+		t.Run("lookup error", func(t *testing.T) {
+			type certainlydoesntexistelsewhereihope func(string, string) string
+			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)
+			if !cmp.Equal(got, want, cmp.Comparer(compareErrors)) {
+				t.Errorf("Received unexpected error. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Unmarshaler", func(t *testing.T) {
+			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 {
+				t.Run(tc.name, func(t *testing.T) {
+					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) {
+				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.Run("error", func(t *testing.T) {
+			_, 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) {
+			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.Run("errors", func(t *testing.T) {
+			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) {
+			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) {
+		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}
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("Results do not match. got %+v; want %+v", got, want)
+		}
+	})
+	t.Run("Reset", func(t *testing.T) {
+		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) {
+		dc1 := bsoncodec.DecodeContext{Registry: DefaultRegistry}
+		dc2 := bsoncodec.DecodeContext{Registry: NewRegistryBuilder().Build()}
+		dec, err := NewDecoderWithContext(dc1, bsonrw.NewBSONDocumentReader([]byte{}))
+		noerr(t, err)
+		if 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 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) {
+		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 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 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) {
+		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)
+		}
+	})
+}
+
+type testDecoderCodec struct {
+	EncodeValueCalled bool
+	DecodeValueCalled bool
+}
+
+func (tdc *testDecoderCodec) EncodeValue(bsoncodec.EncodeContext, bsonrw.ValueWriter, interface{}) error {
+	tdc.EncodeValueCalled = true
+	return nil
+}
+
+func (tdc *testDecoderCodec) DecodeValue(bsoncodec.DecodeContext, bsonrw.ValueReader, interface{}) error {
+	tdc.DecodeValueCalled = true
+	return nil
+}
+
+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
+}

+ 42 - 0
src/go.mongodb.org/mongo-driver/bson/doc.go

@@ -0,0 +1,42 @@
+// 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. The
+// library has two families of types for representing 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...
+//
+// The D family of types is used to build concise representations of BSON using native Go types.
+// These types do not support automatic lookup.
+//
+// Example:
+// 		bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+//
+//
+// Marshaling and Unmarshaling are handled with the Marshal and Unmarshal family of functions. If
+// you need to write or read BSON from a non-slice source, an Encoder or Decoder can be used with a
+// bsonrw.ValueWriter or bsonrw.ValueReader.
+//
+// Example:
+// 		b, err := bson.Marshal(bson.D{{"foo", "bar"}})
+// 		if err != nil { return err }
+// 		var fooer struct {
+// 			Foo string
+// 		}
+// 		err = bson.Unmarshal(b, &fooer)
+// 		if err != nil { return err }
+// 		// do something with fooer...
+package bson

+ 99 - 0
src/go.mongodb.org/mongo-driver/bson/encoder.go

@@ -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
+}

+ 149 - 0
src/go.mongodb.org/mongo-driver/bson/encoder_test.go

@@ -0,0 +1,149 @@
+// 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
+}
+
+type byteMarshaler []byte
+
+func (bm byteMarshaler) MarshalBSON() ([]byte, error) { return bm, nil }
+
+type _Interface interface {
+	method()
+}
+
+type _impl struct {
+	Foo string
+}
+
+func (_impl) method() {}

+ 156 - 0
src/go.mongodb.org/mongo-driver/bson/marshal.go

@@ -0,0 +1,156 @@
+// 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"
+	"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.
+//
+// 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 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 MarshalAppend(dst []byte, val interface{}) ([]byte, error) {
+	return MarshalAppendWithRegistry(DefaultRegistry, dst, val)
+}
+
+// MarshalWithRegistry returns the BSON encoding of val using Registry r.
+func MarshalWithRegistry(r *bsoncodec.Registry, val interface{}) ([]byte, error) {
+	dst := make([]byte, 0, 256) // TODO: make the default cap a constant
+	return MarshalAppendWithRegistry(r, dst, val)
+}
+
+// MarshalWithContext returns the BSON encoding of val using EncodeContext ec.
+func MarshalWithContext(ec bsoncodec.EncodeContext, val interface{}) ([]byte, error) {
+	dst := make([]byte, 0, 256) // TODO: make the default cap a constant
+	return MarshalAppendWithContext(ec, dst, val)
+}
+
+// MarshalAppendWithRegistry 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 MarshalAppendWithRegistry(r *bsoncodec.Registry, dst []byte, val interface{}) ([]byte, error) {
+	return MarshalAppendWithContext(bsoncodec.EncodeContext{Registry: r}, dst, val)
+}
+
+// MarshalAppendWithContext 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 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
+}
+
+// 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
+}

+ 209 - 0
src/go.mongodb.org/mongo-driver/bson/marshal_test.go

@@ -0,0 +1,209 @@
+// 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"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
+	"go.mongodb.org/mongo-driver/bson/bsoncodec"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+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)
+	}
+}

+ 29 - 0
src/go.mongodb.org/mongo-driver/bson/marshaling_cases_test.go

@@ -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}}),
+	},
+}

+ 376 - 0
src/go.mongodb.org/mongo-driver/bson/primitive/decimal.go

@@ -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
+//
+// Based on gopkg.in/mgo.v2/bson by Gustavo Niemeyer
+// See THIRD-PARTY-NOTICES for original license terms.
+
+package primitive
+
+import (
+	"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() (bi *big.Int, exp int, err error) {
+	high, low := d.GetBytes()
+	var posSign bool // positive sign
+
+	posSign = high>>63&1 == 0
+
+	switch high >> 58 & (1<<5 - 1) {
+	case 0x1F:
+		return nil, 0, ErrParseNaN
+	case 0x1E:
+		if posSign {
+			return nil, 0, ErrParseInf
+		}
+		return nil, 0, ErrParseNegInf
+	}
+
+	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
+}
+
+// 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
+}
+
+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)
+	}
+
+	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)
+
+	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)
+}

+ 146 - 0
src/go.mongodb.org/mongo-driver/bson/primitive/decimal_test.go

@@ -0,0 +1,146 @@
+package primitive
+
+import (
+	"github.com/stretchr/testify/require"
+	"math/big"
+	"testing"
+)
+
+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 {
+		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 := append(bigIntTestCases,
+		[]bigIntTestCase{
+			{s: "-0001231.453454000000565600000000E-21", h: 0xafe6000003faa269, l: 0x81cfeceaabdb1800},
+			{s: "12345E+21", h: 0x306a000000000000, l: 12345},
+			{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"},
+			{s: ".125e1", h: 0x303c000000000000, l: 125},
+			{s: ".125", h: 0x303a000000000000, l: 125},
+			{s: "", remark: "parse fail"},
+		}...)
+	for _, c := range cases {
+		switch c.remark {
+		case "overflow", "parse fail":
+			_, err := ParseDecimal128(c.s)
+			require.Error(t, err)
+		case "", "rounding", "subnormal", "clamped", "NaN", "Infinity", "-Infinity":
+			d128, err := ParseDecimal128(c.s)
+			require.NoError(t, err)
+
+			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)
+		}
+	}
+}

+ 165 - 0
src/go.mongodb.org/mongo-driver/bson/primitive/objectid.go

@@ -0,0 +1,165 @@
+// 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 (
+	"bytes"
+	"crypto/rand"
+	"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()
+
+// 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 {
+	return hex.EncodeToString(id[:])
+}
+
+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 bytes.Equal(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) {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		return NilObjectID, err
+	}
+
+	if len(b) != 12 {
+		return NilObjectID, ErrInvalidHex
+	}
+
+	var oid [12]byte
+	copy(oid[:], b[:])
+
+	return oid, 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 64 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. Otherwise, it will
+// return an error.
+func (id *ObjectID) UnmarshalJSON(b []byte) error {
+	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")
+			}
+		}
+
+		if len(str) != 24 {
+			return fmt.Errorf("cannot unmarshal into an ObjectID, the length must be 12 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)
+}

+ 150 - 0
src/go.mongodb.org/mongo-driver/bson/primitive/objectid_test.go

@@ -0,0 +1,150 @@
+// 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 (
+	"testing"
+
+	"encoding/binary"
+	"encoding/hex"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+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 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 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)
+}

+ 206 - 0
src/go.mongodb.org/mongo-driver/bson/primitive/primitive.go

@@ -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
+
+// Package primitive contains types similar to Go primitives for BSON types can 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 is the 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
+
+// MarshalJSON marshal to time type
+func (d DateTime) MarshalJSON() ([]byte, error) {
+	return json.Marshal(d.Time())
+}
+
+// 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.UnixNano() / 1000000)
+}
+
+// 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 is the are equal.
+func (rp Regex) Equal(rp2 Regex) bool {
+	return rp.Pattern == rp2.Pattern && rp.Options == rp.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 is the are equal.
+func (d DBPointer) Equal(d2 DBPointer) bool {
+	return d.DB == d2.DB && bytes.Equal(d.Pointer[:], d2.Pointer[:])
+}
+
+// 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 is the 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 represents a BSON Document. This type can be used to represent BSON in a concise and readable
+// manner. It should generally be used when serializing to BSON. For deserializing, the Raw or
+// Document types should be used.
+//
+// Example usage:
+//
+// 		primitive.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}}
+//
+// This type should be used in situations where order matters, such as MongoDB commands. If the
+// order is not important, a map is more comfortable and concise.
+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, concise representation of a BSON Document. It should generally be used to
+// serialize BSON when the order of the elements of a BSON document do not matter. If the element
+// order matters, use a D instead.
+//
+// Example usage:
+//
+// 		primitive.M{"foo": "bar", "hello": "world", "pi": 3.14159}
+//
+// This type is handled in the encoders as a regular map[string]interface{}. The elements will be
+// serialized in an undefined, random order, and the order will be different each time.
+type M map[string]interface{}
+
+// An A represents a BSON array. This type can be used to represent a BSON array in a concise and
+// readable manner. It should generally be used when serializing to BSON. For deserializing, the
+// RawArray or Array types should be used.
+//
+// Example usage:
+//
+// 		primitive.A{"bar", "world", 3.14159, primitive.D{{"qux", 12345}}}
+//
+type A []interface{}

+ 60 - 0
src/go.mongodb.org/mongo-driver/bson/primitive/primitive_test.go

@@ -0,0 +1,60 @@
+// 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 (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+// 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}},
+		{"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())
+		})
+	}
+}

+ 111 - 0
src/go.mongodb.org/mongo-driver/bson/primitive_codecs.go

@@ -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 bson
+
+import (
+	"errors"
+	"reflect"
+
+	"go.mongodb.org/mongo-driver/bson/bsoncodec"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+)
+
+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.
+		RegisterEncoder(tRawValue, bsoncodec.ValueEncoderFunc(pc.RawValueEncodeValue)).
+		RegisterEncoder(tRaw, bsoncodec.ValueEncoderFunc(pc.RawEncodeValue)).
+		RegisterDecoder(tRawValue, bsoncodec.ValueDecoderFunc(pc.RawValueDecodeValue)).
+		RegisterDecoder(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
+}
+
+func (pc PrimitiveCodecs) encodeRaw(ec bsoncodec.EncodeContext, dw bsonrw.DocumentWriter, raw Raw) error {
+	var copier bsonrw.Copier
+	elems, err := raw.Elements()
+	if err != nil {
+		return err
+	}
+	for _, elem := range elems {
+		dvw, err := dw.WriteDocumentElement(elem.Key())
+		if err != nil {
+			return err
+		}
+
+		val := elem.Value()
+		err = copier.CopyValueFromBytes(dvw, val.Type, val.Value)
+		if err != nil {
+			return err
+		}
+	}
+
+	return dw.WriteDocumentEnd()
+}

+ 1076 - 0
src/go.mongodb.org/mongo-driver/bson/primitive_codecs_test.go

@@ -0,0 +1,1076 @@
+// 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"
+	"errors"
+	"fmt"
+	"net/url"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"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 bytesFromDoc(doc interface{}) []byte {
+	b, err := Marshal(doc)
+	if err != nil {
+		panic(fmt.Errorf("Couldn't marshal BSON document: %v", err))
+	}
+	return b
+}
+
+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
+}
+
+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 TestDefaultValueEncoders(t *testing.T) {
+	var pc PrimitiveCodecs
+
+	var wrong = func(string, string) string { return "wrong" }
+
+	type subtest struct {
+		name   string
+		val    interface{}
+		ectx   *bsoncodec.EncodeContext
+		llvrw  *bsonrwtest.ValueReaderWriter
+		invoke bsonrwtest.Invoked
+		err    error
+	}
+
+	testCases := []struct {
+		name     string
+		ve       bsoncodec.ValueEncoder
+		subtests []subtest
+	}{
+		{
+			"RawValueEncodeValue",
+			bsoncodec.ValueEncoderFunc(pc.RawValueEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					bsoncodec.ValueEncoderError{
+						Name:     "RawValueEncodeValue",
+						Types:    []reflect.Type{tRawValue},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"RawValue/success",
+					RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
+					nil,
+					nil,
+					bsonrwtest.WriteDouble,
+					nil,
+				},
+			},
+		},
+		{
+			"RawEncodeValue",
+			bsoncodec.ValueEncoderFunc(pc.RawEncodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					bsoncodec.ValueEncoderError{Name: "RawEncodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"WriteDocument Error",
+					Raw{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wd error"), ErrAfter: bsonrwtest.WriteDocument},
+					bsonrwtest.WriteDocument,
+					errors.New("wd error"),
+				},
+				{
+					"Raw.Elements Error",
+					Raw{0xFF, 0x00, 0x00, 0x00, 0x00},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocument,
+					errors.New("length read exceeds number of bytes available. length=5 bytes=255"),
+				},
+				{
+					"WriteDocumentElement Error",
+					Raw(bytesFromDoc(D{{"foo", nil}})),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("wde error"), ErrAfter: bsonrwtest.WriteDocumentElement},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("wde error"),
+				},
+				{
+					"encodeValue error",
+					Raw(bytesFromDoc(D{{"foo", nil}})),
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("ev error"), ErrAfter: bsonrwtest.WriteNull},
+					bsonrwtest.WriteNull,
+					errors.New("ev error"),
+				},
+				{
+					"iterator error",
+					Raw{0x0C, 0x00, 0x00, 0x00, 0x01, 'f', 'o', 'o', 0x00, 0x01, 0x02, 0x03},
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.WriteDocumentElement,
+					errors.New("not enough bytes available to read type. bytes=3 type=double"),
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, subtest := range tc.subtests {
+				t.Run(subtest.name, func(t *testing.T) {
+					var ec bsoncodec.EncodeContext
+					if subtest.ectx != nil {
+						ec = *subtest.ectx
+					}
+					llvrw := new(bsonrwtest.ValueReaderWriter)
+					if subtest.llvrw != nil {
+						llvrw = subtest.llvrw
+					}
+					llvrw.T = t
+					err := tc.ve.EncodeValue(ec, llvrw, reflect.ValueOf(subtest.val))
+					if !compareErrors(err, subtest.err) {
+						t.Errorf("Errors do not match. got %v; want %v", err, subtest.err)
+					}
+					invoked := llvrw.Invoked
+					if !cmp.Equal(invoked, subtest.invoke) {
+						t.Errorf("Incorrect method invoked. got %v; want %v", invoked, subtest.invoke)
+					}
+				})
+			}
+		})
+	}
+
+	t.Run("success path", func(t *testing.T) {
+		oid := primitive.NewObjectID()
+		oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
+		var str = new(string)
+		*str = "bar"
+		now := time.Now().Truncate(time.Millisecond)
+		murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
+		if err != nil {
+			t.Errorf("Error parsing URL: %v", err)
+			t.FailNow()
+		}
+		decimal128, err := primitive.ParseDecimal128("1.5e10")
+		if err != nil {
+			t.Errorf("Error parsing decimal128: %v", err)
+			t.FailNow()
+		}
+
+		testCases := []struct {
+			name  string
+			value interface{}
+			b     []byte
+			err   error
+		}{
+			{
+				"D to JavaScript",
+				D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}},
+				docToBytes(D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}}),
+				nil,
+			},
+			{
+				"D to Symbol",
+				D{{"a", primitive.Symbol("foobarbaz")}},
+				docToBytes(D{{"a", primitive.Symbol("foobarbaz")}}),
+				nil,
+			},
+			{
+				"struct{}",
+				struct {
+					A bool
+					B int32
+					C int64
+					D uint16
+					E uint64
+					F float64
+					G string
+					H map[string]string
+					I []byte
+					K [2]string
+					L struct {
+						M string
+					}
+					P  Raw
+					Q  primitive.ObjectID
+					T  []struct{}
+					Y  json.Number
+					Z  time.Time
+					AA json.Number
+					AB *url.URL
+					AC primitive.Decimal128
+					AD *time.Time
+					AE testValueMarshaler
+					AF RawValue
+					AG *RawValue
+					AH D
+					AI *D
+					AJ *D
+				}{
+					A: true,
+					B: 123,
+					C: 456,
+					D: 789,
+					E: 101112,
+					F: 3.14159,
+					G: "Hello, world",
+					H: map[string]string{"foo": "bar"},
+					I: []byte{0x01, 0x02, 0x03},
+					K: [2]string{"baz", "qux"},
+					L: struct {
+						M string
+					}{
+						M: "foobar",
+					},
+					P:  Raw{0x05, 0x00, 0x00, 0x00, 0x00},
+					Q:  oid,
+					T:  nil,
+					Y:  json.Number("5"),
+					Z:  now,
+					AA: json.Number("10.1"),
+					AB: murl,
+					AC: decimal128,
+					AD: &now,
+					AE: testValueMarshaler{t: TypeString, buf: bsoncore.AppendString(nil, "hello, world")},
+					AF: RawValue{Type: bsontype.String, Value: bsoncore.AppendString(nil, "hello, raw value")},
+					AG: &RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
+					AH: D{{"foo", "bar"}},
+					AI: &D{{"pi", 3.14159}},
+					AJ: nil,
+				},
+				docToBytes(D{
+					{"a", true},
+					{"b", int32(123)},
+					{"c", int64(456)},
+					{"d", int32(789)},
+					{"e", int64(101112)},
+					{"f", float64(3.14159)},
+					{"g", "Hello, world"},
+					{"h", D{{"foo", "bar"}}},
+					{"i", primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}},
+					{"k", A{"baz", "qux"}},
+					{"l", D{{"m", "foobar"}}},
+					{"p", D{}},
+					{"q", oid},
+					{"t", nil},
+					{"y", int64(5)},
+					{"z", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
+					{"aa", float64(10.1)},
+					{"ab", murl.String()},
+					{"ac", decimal128},
+					{"ad", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
+					{"ae", "hello, world"},
+					{"af", "hello, raw value"},
+					{"ag", 3.14159},
+					{"ah", D{{"foo", "bar"}}},
+					{"ai", D{{"pi", float64(3.14159)}}},
+					{"aj", nil},
+				}),
+				nil,
+			},
+			{
+				"struct{[]interface{}}",
+				struct {
+					A []bool
+					B []int32
+					C []int64
+					D []uint16
+					E []uint64
+					F []float64
+					G []string
+					H []map[string]string
+					I [][]byte
+					K [1][2]string
+					L []struct {
+						M string
+					}
+					N  [][]string
+					Q  []Raw
+					R  []primitive.ObjectID
+					T  []struct{}
+					W  []map[string]struct{}
+					X  []map[string]struct{}
+					Y  []map[string]struct{}
+					Z  []time.Time
+					AA []json.Number
+					AB []*url.URL
+					AC []primitive.Decimal128
+					AD []*time.Time
+					AE []testValueMarshaler
+					AF []D
+					AG []*D
+				}{
+					A: []bool{true},
+					B: []int32{123},
+					C: []int64{456},
+					D: []uint16{789},
+					E: []uint64{101112},
+					F: []float64{3.14159},
+					G: []string{"Hello, world"},
+					H: []map[string]string{{"foo": "bar"}},
+					I: [][]byte{{0x01, 0x02, 0x03}},
+					K: [1][2]string{{"baz", "qux"}},
+					L: []struct {
+						M string
+					}{
+						{
+							M: "foobar",
+						},
+					},
+					N:  [][]string{{"foo", "bar"}},
+					Q:  []Raw{{0x05, 0x00, 0x00, 0x00, 0x00}},
+					R:  oids,
+					T:  nil,
+					W:  nil,
+					X:  []map[string]struct{}{},   // Should be empty BSON Array
+					Y:  []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
+					Z:  []time.Time{now, now},
+					AA: []json.Number{json.Number("5"), json.Number("10.1")},
+					AB: []*url.URL{murl},
+					AC: []primitive.Decimal128{decimal128},
+					AD: []*time.Time{&now, &now},
+					AE: []testValueMarshaler{
+						{t: TypeString, buf: bsoncore.AppendString(nil, "hello")},
+						{t: TypeString, buf: bsoncore.AppendString(nil, "world")},
+					},
+					AF: []D{{{"foo", "bar"}}, {{"hello", "world"}, {"number", 12345}}},
+					AG: []*D{{{"pi", 3.14159}}, nil},
+				},
+				docToBytes(D{
+					{"a", A{true}},
+					{"b", A{int32(123)}},
+					{"c", A{int64(456)}},
+					{"d", A{int32(789)}},
+					{"e", A{int64(101112)}},
+					{"f", A{float64(3.14159)}},
+					{"g", A{"Hello, world"}},
+					{"h", A{D{{"foo", "bar"}}}},
+					{"i", A{primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}}},
+					{"k", A{A{"baz", "qux"}}},
+					{"l", A{D{{"m", "foobar"}}}},
+					{"n", A{A{"foo", "bar"}}},
+					{"q", A{D{}}},
+					{"r", A{oids[0], oids[1], oids[2]}},
+					{"t", nil},
+					{"w", nil},
+					{"x", A{}},
+					{"y", A{D{}}},
+					{"z", A{
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+					}},
+					{"aa", A{int64(5), float64(10.10)}},
+					{"ab", A{murl.String()}},
+					{"ac", A{decimal128}},
+					{"ad", A{
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+					}},
+					{"ae", A{"hello", "world"}},
+					{"af", A{D{{"foo", "bar"}}, D{{"hello", "world"}, {"number", int32(12345)}}}},
+					{"ag", A{D{{"pi", float64(3.14159)}}, nil}},
+				}),
+				nil,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				b := make(bsonrw.SliceWriter, 0, 512)
+				vw, err := bsonrw.NewBSONValueWriter(&b)
+				noerr(t, err)
+				enc, err := NewEncoder(vw)
+				noerr(t, err)
+				err = enc.Encode(tc.value)
+				if err != tc.err {
+					t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err)
+				}
+				if diff := cmp.Diff([]byte(b), tc.b); diff != "" {
+					t.Errorf("Bytes written differ: (-got +want)\n%s", diff)
+					t.Errorf("Bytes\ngot: %v\nwant:%v\n", b, tc.b)
+					t.Errorf("Readers\ngot: %v\nwant:%v\n", Raw(b), Raw(tc.b))
+				}
+			})
+		}
+	})
+}
+
+func TestDefaultValueDecoders(t *testing.T) {
+	var pc PrimitiveCodecs
+
+	var wrong = func(string, string) string { return "wrong" }
+
+	const cansetreflectiontest = "cansetreflectiontest"
+
+	type subtest struct {
+		name   string
+		val    interface{}
+		dctx   *bsoncodec.DecodeContext
+		llvrw  *bsonrwtest.ValueReaderWriter
+		invoke bsonrwtest.Invoked
+		err    error
+	}
+
+	testCases := []struct {
+		name     string
+		vd       bsoncodec.ValueDecoder
+		subtests []subtest
+	}{
+		{
+			"RawValueDecodeValue",
+			bsoncodec.ValueDecoderFunc(pc.RawValueDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					bsoncodec.ValueDecoderError{
+						Name:     "RawValueDecodeValue",
+						Types:    []reflect.Type{tRawValue},
+						Received: reflect.ValueOf(wrong),
+					},
+				},
+				{
+					"ReadValue Error",
+					RawValue{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Binary,
+						Err:      errors.New("rb error"),
+						ErrAfter: bsonrwtest.ReadBinary,
+					},
+					bsonrwtest.ReadBinary,
+					errors.New("rb error"),
+				},
+				{
+					"RawValue/success",
+					RawValue{Type: bsontype.Binary, Value: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
+					nil,
+					&bsonrwtest.ValueReaderWriter{
+						BSONType: bsontype.Binary,
+						Return: bsoncore.Value{
+							Type: bsontype.Binary,
+							Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}),
+						},
+					},
+					bsonrwtest.ReadBinary,
+					nil,
+				},
+			},
+		},
+		{
+			"RawDecodeValue",
+			bsoncodec.ValueDecoderFunc(pc.RawDecodeValue),
+			[]subtest{
+				{
+					"wrong type",
+					wrong,
+					nil,
+					&bsonrwtest.ValueReaderWriter{},
+					bsonrwtest.Nothing,
+					bsoncodec.ValueDecoderError{Name: "RawDecodeValue", Types: []reflect.Type{tRaw}, Received: reflect.ValueOf(wrong)},
+				},
+				{
+					"*Raw is nil",
+					(*Raw)(nil),
+					nil,
+					nil,
+					bsonrwtest.Nothing,
+					bsoncodec.ValueDecoderError{
+						Name:     "RawDecodeValue",
+						Types:    []reflect.Type{tRaw},
+						Received: reflect.ValueOf((*Raw)(nil)),
+					},
+				},
+				{
+					"Copy error",
+					Raw{},
+					nil,
+					&bsonrwtest.ValueReaderWriter{Err: errors.New("copy error"), ErrAfter: bsonrwtest.ReadDocument},
+					bsonrwtest.ReadDocument,
+					errors.New("copy error"),
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			for _, rc := range tc.subtests {
+				t.Run(rc.name, func(t *testing.T) {
+					var dc bsoncodec.DecodeContext
+					if rc.dctx != nil {
+						dc = *rc.dctx
+					}
+					llvrw := new(bsonrwtest.ValueReaderWriter)
+					if rc.llvrw != nil {
+						llvrw = rc.llvrw
+					}
+					llvrw.T = t
+					// var got interface{}
+					if rc.val == cansetreflectiontest { // We're doing a CanSet reflection test
+						err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{})
+						if !compareErrors(err, rc.err) {
+							t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+						}
+
+						val := reflect.New(reflect.TypeOf(rc.val)).Elem()
+						err = tc.vd.DecodeValue(dc, llvrw, val)
+						if !compareErrors(err, rc.err) {
+							t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+						}
+						return
+					}
+					var val reflect.Value
+					if rtype := reflect.TypeOf(rc.val); rtype != nil {
+						val = reflect.New(rtype).Elem()
+					}
+					want := rc.val
+					defer func() {
+						if err := recover(); err != nil {
+							fmt.Println(t.Name())
+							panic(err)
+						}
+					}()
+					err := tc.vd.DecodeValue(dc, llvrw, val)
+					if !compareErrors(err, rc.err) {
+						t.Errorf("Errors do not match. got %v; want %v", err, rc.err)
+					}
+					invoked := llvrw.Invoked
+					if !cmp.Equal(invoked, rc.invoke) {
+						t.Errorf("Incorrect method invoked. got %v; want %v", invoked, rc.invoke)
+					}
+					var got interface{}
+					if val.IsValid() && val.CanInterface() {
+						got = val.Interface()
+					}
+					if rc.err == nil && !cmp.Equal(got, want) {
+						t.Errorf("Values do not match. got (%T)%v; want (%T)%v", got, got, want, want)
+					}
+				})
+			}
+		})
+	}
+
+	t.Run("success path", func(t *testing.T) {
+		oid := primitive.NewObjectID()
+		oids := []primitive.ObjectID{primitive.NewObjectID(), primitive.NewObjectID(), primitive.NewObjectID()}
+		var str = new(string)
+		*str = "bar"
+		now := time.Now().Truncate(time.Millisecond)
+		murl, err := url.Parse("https://mongodb.com/random-url?hello=world")
+		if err != nil {
+			t.Errorf("Error parsing URL: %v", err)
+			t.FailNow()
+		}
+		decimal128, err := primitive.ParseDecimal128("1.5e10")
+		if err != nil {
+			t.Errorf("Error parsing decimal128: %v", err)
+			t.FailNow()
+		}
+
+		testCases := []struct {
+			name  string
+			value interface{}
+			b     []byte
+			err   error
+		}{
+			{
+				"map[string]int",
+				map[string]int32{"foo": 1},
+				[]byte{
+					0x0E, 0x00, 0x00, 0x00,
+					0x10, 'f', 'o', 'o', 0x00,
+					0x01, 0x00, 0x00, 0x00,
+					0x00,
+				},
+				nil,
+			},
+			{
+				"map[string]primitive.ObjectID",
+				map[string]primitive.ObjectID{"foo": oid},
+				docToBytes(D{{"foo", oid}}),
+				nil,
+			},
+			{
+				"map[string]Reader",
+				map[string]Raw{"Z": {0x05, 0x00, 0x00, 0x00, 0x00}},
+				docToBytes(D{{"Z", Raw{0x05, 0x00, 0x00, 0x00, 0x00}}}),
+				nil,
+			},
+			{
+				"map[string][]int32",
+				map[string][]int32{"Z": {1, 2, 3}},
+				docToBytes(D{{"Z", A{int32(1), int32(2), int32(3)}}}),
+				nil,
+			},
+			{
+				"map[string][]primitive.ObjectID",
+				map[string][]primitive.ObjectID{"Z": oids},
+				docToBytes(D{{"Z", A{oids[0], oids[1], oids[2]}}}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(int64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10")}},
+				docToBytes(D{{"Z", A{int64(5), int64(10)}}}),
+				nil,
+			},
+			{
+				"map[string][]json.Number(float64)",
+				map[string][]json.Number{"Z": {json.Number("5"), json.Number("10.1")}},
+				docToBytes(D{{"Z", A{int64(5), float64(10.1)}}}),
+				nil,
+			},
+			{
+				"map[string][]*url.URL",
+				map[string][]*url.URL{"Z": {murl}},
+				docToBytes(D{{"Z", A{murl.String()}}}),
+				nil,
+			},
+			{
+				"map[string][]primitive.Decimal128",
+				map[string][]primitive.Decimal128{"Z": {decimal128}},
+				docToBytes(D{{"Z", A{decimal128}}}),
+				nil,
+			},
+			{
+				"-",
+				struct {
+					A string `bson:"-"`
+				}{
+					A: "",
+				},
+				docToBytes(D{}),
+				nil,
+			},
+			{
+				"omitempty",
+				struct {
+					A string `bson:",omitempty"`
+				}{
+					A: "",
+				},
+				docToBytes(D{}),
+				nil,
+			},
+			{
+				"omitempty, empty time",
+				struct {
+					A time.Time `bson:",omitempty"`
+				}{
+					A: time.Time{},
+				},
+				docToBytes(D{}),
+				nil,
+			},
+			{
+				"no private fields",
+				noPrivateFields{a: "should be empty"},
+				docToBytes(D{}),
+				nil,
+			},
+			{
+				"minsize",
+				struct {
+					A int64 `bson:",minsize"`
+				}{
+					A: 12345,
+				},
+				docToBytes(D{{"a", int32(12345)}}),
+				nil,
+			},
+			{
+				"inline",
+				struct {
+					Foo struct {
+						A int64 `bson:",minsize"`
+					} `bson:",inline"`
+				}{
+					Foo: struct {
+						A int64 `bson:",minsize"`
+					}{
+						A: 12345,
+					},
+				},
+				docToBytes(D{{"a", int32(12345)}}),
+				nil,
+			},
+			{
+				"inline map",
+				struct {
+					Foo map[string]string `bson:",inline"`
+				}{
+					Foo: map[string]string{"foo": "bar"},
+				},
+				docToBytes(D{{"foo", "bar"}}),
+				nil,
+			},
+			{
+				"alternate name bson:name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				docToBytes(D{{"foo", "bar"}}),
+				nil,
+			},
+			{
+				"alternate name",
+				struct {
+					A string `bson:"foo"`
+				}{
+					A: "bar",
+				},
+				docToBytes(D{{"foo", "bar"}}),
+				nil,
+			},
+			{
+				"inline, omitempty",
+				struct {
+					A   string
+					Foo zeroTest `bson:"omitempty,inline"`
+				}{
+					A:   "bar",
+					Foo: zeroTest{true},
+				},
+				docToBytes(D{{"a", "bar"}}),
+				nil,
+			},
+			{
+				"JavaScript to D",
+				D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}},
+				docToBytes(D{{"a", primitive.JavaScript(`function() { var hello = "world"; }`)}}),
+				nil,
+			},
+			{
+				"Symbol to D",
+				D{{"a", primitive.Symbol("foobarbaz")}},
+				docToBytes(D{{"a", primitive.Symbol("foobarbaz")}}),
+				nil,
+			},
+			{
+				"struct{}",
+				struct {
+					A bool
+					B int32
+					C int64
+					D uint16
+					E uint64
+					F float64
+					G string
+					H map[string]string
+					I []byte
+					K [2]string
+					L struct {
+						M string
+					}
+					P  Raw
+					Q  primitive.ObjectID
+					T  []struct{}
+					Y  json.Number
+					Z  time.Time
+					AA json.Number
+					AB *url.URL
+					AC primitive.Decimal128
+					AD *time.Time
+					AE *testValueUnmarshaler
+					AF RawValue
+					AG *RawValue
+					AH D
+					AI *D
+					AJ *D
+				}{
+					A: true,
+					B: 123,
+					C: 456,
+					D: 789,
+					E: 101112,
+					F: 3.14159,
+					G: "Hello, world",
+					H: map[string]string{"foo": "bar"},
+					I: []byte{0x01, 0x02, 0x03},
+					K: [2]string{"baz", "qux"},
+					L: struct {
+						M string
+					}{
+						M: "foobar",
+					},
+					P:  Raw{0x05, 0x00, 0x00, 0x00, 0x00},
+					Q:  oid,
+					T:  nil,
+					Y:  json.Number("5"),
+					Z:  now,
+					AA: json.Number("10.1"),
+					AB: murl,
+					AC: decimal128,
+					AD: &now,
+					AE: &testValueUnmarshaler{t: bsontype.String, val: bsoncore.AppendString(nil, "hello, world!")},
+					AF: RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)},
+					AG: &RawValue{Type: bsontype.Binary, Value: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03})},
+					AH: D{{"foo", "bar"}},
+					AI: &D{{"pi", 3.14159}},
+					AJ: nil,
+				},
+				docToBytes(D{
+					{"a", true},
+					{"b", int32(123)},
+					{"c", int64(456)},
+					{"d", int32(789)},
+					{"e", int64(101112)},
+					{"f", float64(3.14159)},
+					{"g", "Hello, world"},
+					{"h", D{{"foo", "bar"}}},
+					{"i", primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}},
+					{"k", A{"baz", "qux"}},
+					{"l", D{{"m", "foobar"}}},
+					{"p", D{}},
+					{"q", oid},
+					{"t", nil},
+					{"y", int64(5)},
+					{"z", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
+					{"aa", float64(10.1)},
+					{"ab", murl.String()},
+					{"ac", decimal128},
+					{"ad", primitive.DateTime(now.UnixNano() / int64(time.Millisecond))},
+					{"ae", "hello, world!"},
+					{"af", float64(3.14159)},
+					{"ag", primitive.Binary{Subtype: 0xFF, Data: []byte{0x01, 0x02, 0x03}}},
+					{"ah", D{{"foo", "bar"}}},
+					{"ai", D{{"pi", float64(3.14159)}}},
+					{"aj", nil},
+				}),
+				nil,
+			},
+			{
+				"struct{[]interface{}}",
+				struct {
+					A []bool
+					B []int32
+					C []int64
+					D []uint16
+					E []uint64
+					F []float64
+					G []string
+					H []map[string]string
+					I [][]byte
+					K [1][2]string
+					L []struct {
+						M string
+					}
+					N  [][]string
+					Q  []Raw
+					R  []primitive.ObjectID
+					T  []struct{}
+					W  []map[string]struct{}
+					X  []map[string]struct{}
+					Y  []map[string]struct{}
+					Z  []time.Time
+					AA []json.Number
+					AB []*url.URL
+					AC []primitive.Decimal128
+					AD []*time.Time
+					AE []*testValueUnmarshaler
+					AF []D
+					AG []*D
+				}{
+					A: []bool{true},
+					B: []int32{123},
+					C: []int64{456},
+					D: []uint16{789},
+					E: []uint64{101112},
+					F: []float64{3.14159},
+					G: []string{"Hello, world"},
+					H: []map[string]string{{"foo": "bar"}},
+					I: [][]byte{{0x01, 0x02, 0x03}},
+					K: [1][2]string{{"baz", "qux"}},
+					L: []struct {
+						M string
+					}{
+						{
+							M: "foobar",
+						},
+					},
+					N:  [][]string{{"foo", "bar"}},
+					Q:  []Raw{{0x05, 0x00, 0x00, 0x00, 0x00}},
+					R:  oids,
+					T:  nil,
+					W:  nil,
+					X:  []map[string]struct{}{},   // Should be empty BSON Array
+					Y:  []map[string]struct{}{{}}, // Should be BSON array with one element, an empty BSON SubDocument
+					Z:  []time.Time{now, now},
+					AA: []json.Number{json.Number("5"), json.Number("10.1")},
+					AB: []*url.URL{murl},
+					AC: []primitive.Decimal128{decimal128},
+					AD: []*time.Time{&now, &now},
+					AE: []*testValueUnmarshaler{
+						{t: bsontype.String, val: bsoncore.AppendString(nil, "hello")},
+						{t: bsontype.String, val: bsoncore.AppendString(nil, "world")},
+					},
+					AF: []D{{{"foo", "bar"}}, {{"hello", "world"}, {"number", int64(12345)}}},
+					AG: []*D{{{"pi", 3.14159}}, nil},
+				},
+				docToBytes(D{
+					{"a", A{true}},
+					{"b", A{int32(123)}},
+					{"c", A{int64(456)}},
+					{"d", A{int32(789)}},
+					{"e", A{int64(101112)}},
+					{"f", A{float64(3.14159)}},
+					{"g", A{"Hello, world"}},
+					{"h", A{D{{"foo", "bar"}}}},
+					{"i", A{primitive.Binary{Subtype: 0x00, Data: []byte{0x01, 0x02, 0x03}}}},
+					{"k", A{A{"baz", "qux"}}},
+					{"l", A{D{{"m", "foobar"}}}},
+					{"n", A{A{"foo", "bar"}}},
+					{"q", A{D{}}},
+					{"r", A{oids[0], oids[1], oids[2]}},
+					{"t", nil},
+					{"w", nil},
+					{"x", A{}},
+					{"y", A{D{}}},
+					{"z", A{
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+					}},
+					{"aa", A{int64(5), float64(10.10)}},
+					{"ab", A{murl.String()}},
+					{"ac", A{decimal128}},
+					{"ad", A{
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+						primitive.DateTime(now.UnixNano() / int64(time.Millisecond)),
+					}},
+					{"ae", A{"hello", "world"}},
+					{"af", A{
+						D{{"foo", "bar"}},
+						D{{"hello", "world"}, {"number", int64(12345)}},
+					}},
+					{"ag", A{D{{"pi", float64(3.14159)}}, nil}},
+				}),
+				nil,
+			},
+		}
+
+		t.Run("Decode", func(t *testing.T) {
+			for _, tc := range testCases {
+				t.Run(tc.name, func(t *testing.T) {
+					vr := bsonrw.NewBSONDocumentReader(tc.b)
+					dec, err := NewDecoder(vr)
+					noerr(t, err)
+					gotVal := reflect.New(reflect.TypeOf(tc.value))
+					err = dec.Decode(gotVal.Interface())
+					noerr(t, err)
+					got := gotVal.Elem().Interface()
+					want := tc.value
+					if diff := cmp.Diff(
+						got, want,
+						cmp.Comparer(compareDecimal128),
+						cmp.Comparer(compareNoPrivateFields),
+						cmp.Comparer(compareZeroTest),
+					); diff != "" {
+						t.Errorf("difference:\n%s", diff)
+						t.Errorf("Values are not equal.\ngot: %#v\nwant:%#v", got, want)
+					}
+				})
+			}
+		})
+	})
+}
+
+type testValueMarshaler struct {
+	t   bsontype.Type
+	buf []byte
+	err error
+}
+
+func (tvm testValueMarshaler) MarshalBSONValue() (bsontype.Type, []byte, error) {
+	return tvm.t, tvm.buf, tvm.err
+}
+
+type testValueUnmarshaler struct {
+	t   bsontype.Type
+	val []byte
+	err error
+}
+
+func (tvu *testValueUnmarshaler) UnmarshalBSONValue(t bsontype.Type, val []byte) error {
+	tvu.t, tvu.val = t, val
+	return tvu.err
+}
+func (tvu testValueUnmarshaler) Equal(tvu2 testValueUnmarshaler) bool {
+	return tvu.t == tvu2.t && bytes.Equal(tvu.val, tvu2.val)
+}
+
+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
+}

+ 92 - 0
src/go.mongodb.org/mongo-driver/bson/raw.go

@@ -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"
+	"io"
+
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+// ErrNilReader indicates that an operation was attempted on a nil bson.Reader.
+var ErrNilReader = errors.New("nil reader")
+var errValidateDone = errors.New("validation loop complete")
+
+// Raw is a wrapper around a byte slice. It will interpret the slice as a
+// BSON document. This type is a wrapper around a bsoncore.Document. Errors returned from the
+// methods on this type and associated types come from the bsoncore package.
+type Raw []byte
+
+// NewFromIOReader reads in a document from the given io.Reader and constructs a Raw from
+// it.
+func NewFromIOReader(r io.Reader) (Raw, error) {
+	doc, err := bsoncore.NewDocumentFromReader(r)
+	return Raw(doc), err
+}
+
+// Validate validates the document. This method only validates the first document in
+// the slice, to validate other documents, the slice must be resliced.
+func (r Raw) Validate() (err error) { return bsoncore.Document(r).Validate() }
+
+// Lookup search the document, potentially recursively, for the given key. If
+// there are multiple keys provided, this method will recurse down, as long as
+// the top and intermediate nodes are either documents or arrays.If an error
+// occurs or if the value doesn't exist, an empty RawValue is returned.
+func (r Raw) Lookup(key ...string) RawValue {
+	return convertFromCoreValue(bsoncore.Document(r).Lookup(key...))
+}
+
+// LookupErr searches the document and potentially subdocuments or arrays for the
+// provided key. Each key provided to this method represents a layer of depth.
+func (r Raw) LookupErr(key ...string) (RawValue, error) {
+	val, err := bsoncore.Document(r).LookupErr(key...)
+	return convertFromCoreValue(val), err
+}
+
+// Elements returns this document as a slice of elements. The returned slice will contain valid
+// elements. If the document is not valid, the elements up to the invalid point will be returned
+// along with an error.
+func (r Raw) Elements() ([]RawElement, error) {
+	elems, err := bsoncore.Document(r).Elements()
+	relems := make([]RawElement, 0, len(elems))
+	for _, elem := range elems {
+		relems = append(relems, RawElement(elem))
+	}
+	return relems, err
+}
+
+// Values returns this document as a slice of values. The returned slice will contain valid values.
+// If the document is not valid, the values up to the invalid point will be returned along with an
+// error.
+func (r Raw) Values() ([]RawValue, error) {
+	vals, err := bsoncore.Document(r).Values()
+	rvals := make([]RawValue, 0, len(vals))
+	for _, val := range vals {
+		rvals = append(rvals, convertFromCoreValue(val))
+	}
+	return rvals, err
+}
+
+// Index searches for and retrieves the element at the given index. This method will panic if
+// the document is invalid or if the index is out of bounds.
+func (r Raw) Index(index uint) RawElement { return RawElement(bsoncore.Document(r).Index(index)) }
+
+// IndexErr searches for and retrieves the element at the given index.
+func (r Raw) IndexErr(index uint) (RawElement, error) {
+	elem, err := bsoncore.Document(r).IndexErr(index)
+	return RawElement(elem), err
+}
+
+// String implements the fmt.Stringer interface.
+func (r Raw) String() string { return bsoncore.Document(r).String() }
+
+// readi32 is a helper function for reading an int32 from slice of bytes.
+func readi32(b []byte) int32 {
+	_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
+	return int32(b[0]) | int32(b[1])<<8 | int32(b[2])<<16 | int32(b[3])<<24
+}

+ 51 - 0
src/go.mongodb.org/mongo-driver/bson/raw_element.go

@@ -0,0 +1,51 @@
+// 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/x/bsonx/bsoncore"
+)
+
+// RawElement represents a BSON element in byte form. This type provides a simple way to
+// transform a slice of bytes into a BSON element and extract information from it.
+//
+// RawElement is a thin wrapper around a bsoncore.Element.
+type RawElement []byte
+
+// Key returns the key for this element. If the element is not valid, this method returns an empty
+// string. If knowing if the element is valid is important, use KeyErr.
+func (re RawElement) Key() string { return bsoncore.Element(re).Key() }
+
+// KeyErr returns the key for this element, returning an error if the element is not valid.
+func (re RawElement) KeyErr() (string, error) { return bsoncore.Element(re).KeyErr() }
+
+// Value returns the value of this element. If the element is not valid, this method returns an
+// empty Value. If knowing if the element is valid is important, use ValueErr.
+func (re RawElement) Value() RawValue { return convertFromCoreValue(bsoncore.Element(re).Value()) }
+
+// ValueErr returns the value for this element, returning an error if the element is not valid.
+func (re RawElement) ValueErr() (RawValue, error) {
+	val, err := bsoncore.Element(re).ValueErr()
+	return convertFromCoreValue(val), err
+}
+
+// Validate ensures re is a valid BSON element.
+func (re RawElement) Validate() error { return bsoncore.Element(re).Validate() }
+
+// String implements the fmt.Stringer interface. The output will be in extended JSON format.
+func (re RawElement) String() string {
+	doc := bsoncore.BuildDocument(nil, re)
+	j, err := MarshalExtJSON(Raw(doc), true, false)
+	if err != nil {
+		return "<malformed>"
+	}
+	return string(j)
+}
+
+// DebugString outputs a human readable version of RawElement. It will attempt to stringify the
+// valid components of the element even if the entire element is not valid.
+func (re RawElement) DebugString() string { return bsoncore.Element(re).DebugString() }

+ 347 - 0
src/go.mongodb.org/mongo-driver/bson/raw_test.go

@@ -0,0 +1,347 @@
+// 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/binary"
+	"fmt"
+	"io"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+func ExampleRaw_Validate() {
+	rdr := make(Raw, 500)
+	rdr[250], rdr[251], rdr[252], rdr[253], rdr[254] = '\x05', '\x00', '\x00', '\x00', '\x00'
+	err := rdr[250:].Validate()
+	fmt.Println(err)
+
+	// Output: <nil>
+}
+
+func BenchmarkRawValidate(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		rdr := make(Raw, 500)
+		rdr[250], rdr[251], rdr[252], rdr[253], rdr[254] = '\x05', '\x00', '\x00', '\x00', '\x00'
+		_ = rdr[250:].Validate()
+	}
+
+}
+
+func TestRaw(t *testing.T) {
+	t.Run("Validate", func(t *testing.T) {
+		t.Run("TooShort", func(t *testing.T) {
+			want := bsoncore.NewInsufficientBytesError(nil, nil)
+			got := Raw{'\x00', '\x00'}.Validate()
+			if !compareErrors(got, want) {
+				t.Errorf("Did not get expected error. got %v; want %v", got, want)
+			}
+		})
+		t.Run("InvalidLength", func(t *testing.T) {
+			want := bsoncore.DocumentValidationError("document length exceeds available bytes. length=200 remainingBytes=5")
+			r := make(Raw, 5)
+			binary.LittleEndian.PutUint32(r[0:4], 200)
+			got := r.Validate()
+			if got != want {
+				t.Errorf("Did not get expected error. got %v; want %v", got, want)
+			}
+		})
+		t.Run("keyLength-error", func(t *testing.T) {
+			want := bsoncore.ErrMissingNull
+			r := make(Raw, 8)
+			binary.LittleEndian.PutUint32(r[0:4], 8)
+			r[4], r[5], r[6], r[7] = '\x02', 'f', 'o', 'o'
+			got := r.Validate()
+			if got != want {
+				t.Errorf("Did not get expected error. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Missing-Null-Terminator", func(t *testing.T) {
+			want := bsoncore.ErrMissingNull
+			r := make(Raw, 9)
+			binary.LittleEndian.PutUint32(r[0:4], 9)
+			r[4], r[5], r[6], r[7], r[8] = '\x0A', 'f', 'o', 'o', '\x00'
+			got := r.Validate()
+			if got != want {
+				t.Errorf("Did not get expected error. got %v; want %v", got, want)
+			}
+		})
+		t.Run("validateValue-error", func(t *testing.T) {
+			want := bsoncore.ErrMissingNull
+			r := make(Raw, 11)
+			binary.LittleEndian.PutUint32(r[0:4], 11)
+			r[4], r[5], r[6], r[7], r[8], r[9], r[10] = '\x01', 'f', 'o', 'o', '\x00', '\x01', '\x02'
+			got := r.Validate()
+			if !compareErrors(got, want) {
+				t.Errorf("Did not get expected error. got %v; want %v", got, want)
+			}
+		})
+		testCases := []struct {
+			name string
+			r    Raw
+			err  error
+		}{
+			{"null", Raw{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}, nil},
+			{"subdocument",
+				Raw{
+					'\x15', '\x00', '\x00', '\x00',
+					'\x03',
+					'f', 'o', 'o', '\x00',
+					'\x0B', '\x00', '\x00', '\x00', '\x0A', 'a', '\x00',
+					'\x0A', 'b', '\x00', '\x00', '\x00',
+				},
+				nil,
+			},
+			{"array",
+				Raw{
+					'\x15', '\x00', '\x00', '\x00',
+					'\x04',
+					'f', 'o', 'o', '\x00',
+					'\x0B', '\x00', '\x00', '\x00', '\x0A', '1', '\x00',
+					'\x0A', '2', '\x00', '\x00', '\x00',
+				},
+				nil,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				err := tc.r.Validate()
+				if err != tc.err {
+					t.Errorf("Returned error does not match. got %v; want %v", err, tc.err)
+				}
+			})
+		}
+	})
+	t.Run("Lookup", func(t *testing.T) {
+		t.Run("empty-key", func(t *testing.T) {
+			rdr := Raw{'\x05', '\x00', '\x00', '\x00', '\x00'}
+			_, err := rdr.LookupErr()
+			if err != bsoncore.ErrEmptyKey {
+				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", err, bsoncore.ErrEmptyKey)
+			}
+		})
+		t.Run("corrupted-subdocument", func(t *testing.T) {
+			rdr := Raw{
+				'\x0D', '\x00', '\x00', '\x00',
+				'\x03', 'x', '\x00',
+				'\x06', '\x00', '\x00', '\x00',
+				'\x01',
+				'\x00',
+				'\x00',
+			}
+			_, err := rdr.LookupErr("x", "y")
+			want := bsoncore.NewInsufficientBytesError(nil, nil)
+			if !compareErrors(err, want) {
+				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", err, want)
+			}
+		})
+		t.Run("corrupted-array", func(t *testing.T) {
+			rdr := Raw{
+				'\x0D', '\x00', '\x00', '\x00',
+				'\x04', 'x', '\x00',
+				'\x06', '\x00', '\x00', '\x00',
+				'\x01',
+				'\x00',
+				'\x00',
+			}
+			_, err := rdr.LookupErr("x", "y")
+			want := bsoncore.NewInsufficientBytesError(nil, nil)
+			if !compareErrors(err, want) {
+				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", err, want)
+			}
+		})
+		t.Run("invalid-traversal", func(t *testing.T) {
+			rdr := Raw{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}
+			_, err := rdr.LookupErr("x", "y")
+			want := bsoncore.InvalidDepthTraversalError{Key: "x", Type: bsontype.Null}
+			if !compareErrors(err, want) {
+				t.Errorf("Empty key lookup did not return expected result. got %v; want %v", err, want)
+			}
+		})
+		testCases := []struct {
+			name string
+			r    Raw
+			key  []string
+			want RawValue
+			err  error
+		}{
+			{"first",
+				Raw{
+					'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00',
+				},
+				[]string{"x"},
+				RawValue{Type: bsontype.Null}, nil,
+			},
+			{"first-second",
+				Raw{
+					'\x15', '\x00', '\x00', '\x00',
+					'\x03',
+					'f', 'o', 'o', '\x00',
+					'\x0B', '\x00', '\x00', '\x00', '\x0A', 'a', '\x00',
+					'\x0A', 'b', '\x00', '\x00', '\x00',
+				},
+				[]string{"foo", "b"},
+				RawValue{Type: bsontype.Null}, nil,
+			},
+			{"first-second-array",
+				Raw{
+					'\x15', '\x00', '\x00', '\x00',
+					'\x04',
+					'f', 'o', 'o', '\x00',
+					'\x0B', '\x00', '\x00', '\x00', '\x0A', '1', '\x00',
+					'\x0A', '2', '\x00', '\x00', '\x00',
+				},
+				[]string{"foo", "2"},
+				RawValue{Type: bsontype.Null}, nil,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				got, err := tc.r.LookupErr(tc.key...)
+				if err != tc.err {
+					t.Errorf("Returned error does not match. got %v; want %v", err, tc.err)
+				}
+				if !cmp.Equal(got, tc.want) {
+					t.Errorf("Returned element does not match expected element. got %v; want %v", got, tc.want)
+				}
+			})
+		}
+	})
+	t.Run("ElementAt", func(t *testing.T) {
+		t.Run("Out of bounds", func(t *testing.T) {
+			rdr := Raw{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0}
+			_, err := rdr.IndexErr(3)
+			if err != bsoncore.ErrOutOfBounds {
+				t.Errorf("Out of bounds should be returned when accessing element beyond end of document. got %v; want %v", err, bsoncore.ErrOutOfBounds)
+			}
+		})
+		t.Run("Validation Error", func(t *testing.T) {
+			rdr := Raw{0x07, 0x00, 0x00, 0x00, 0x00}
+			_, err := rdr.IndexErr(1)
+			want := bsoncore.NewInsufficientBytesError(nil, nil)
+			if !compareErrors(err, want) {
+				t.Errorf("Did not receive expected error. got %v; want %v", err, want)
+			}
+		})
+		testCases := []struct {
+			name  string
+			rdr   Raw
+			index uint
+			want  RawElement
+		}{
+			{"first",
+				Raw{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
+				0, bsoncore.AppendNullElement(nil, "x")},
+			{"second",
+				Raw{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
+				1, bsoncore.AppendNullElement(nil, "y")},
+			{"third",
+				Raw{0xe, 0x0, 0x0, 0x0, 0xa, 0x78, 0x0, 0xa, 0x79, 0x0, 0xa, 0x7a, 0x0, 0x0},
+				2, bsoncore.AppendNullElement(nil, "z")},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				got, err := tc.rdr.IndexErr(tc.index)
+				if err != nil {
+					t.Errorf("Unexpected error from ElementAt: %s", err)
+				}
+				if diff := cmp.Diff(got, tc.want); diff != "" {
+					t.Errorf("Documents differ: (-got +want)\n%s", diff)
+				}
+			})
+		}
+	})
+	t.Run("NewFromIOReader", func(t *testing.T) {
+		testCases := []struct {
+			name       string
+			ioReader   io.Reader
+			bsonReader Raw
+			err        error
+		}{
+			{
+				"nil reader",
+				nil,
+				nil,
+				ErrNilReader,
+			},
+			{
+				"premature end of reader",
+				bytes.NewBuffer([]byte{}),
+				nil,
+				io.EOF,
+			},
+			{
+				"empty document",
+				bytes.NewBuffer([]byte{5, 0, 0, 0, 0}),
+				[]byte{5, 0, 0, 0, 0},
+				nil,
+			},
+			{
+				"non-empty document",
+				bytes.NewBuffer([]byte{
+					// length
+					0x17, 0x0, 0x0, 0x0,
+
+					// type - string
+					0x2,
+					// key - "foo"
+					0x66, 0x6f, 0x6f, 0x0,
+					// value - string length
+					0x4, 0x0, 0x0, 0x0,
+					// value - string "bar"
+					0x62, 0x61, 0x72, 0x0,
+
+					// type - null
+					0xa,
+					// key - "baz"
+					0x62, 0x61, 0x7a, 0x0,
+
+					// null terminator
+					0x0,
+				}),
+				[]byte{
+					// length
+					0x17, 0x0, 0x0, 0x0,
+
+					// type - string
+					0x2,
+					// key - "foo"
+					0x66, 0x6f, 0x6f, 0x0,
+					// value - string length
+					0x4, 0x0, 0x0, 0x0,
+					// value - string "bar"
+					0x62, 0x61, 0x72, 0x0,
+
+					// type - null
+					0xa,
+					// key - "baz"
+					0x62, 0x61, 0x7a, 0x0,
+
+					// null terminator
+					0x0,
+				},
+				nil,
+			},
+		}
+
+		for _, tc := range testCases {
+			t.Run(tc.name, func(t *testing.T) {
+				reader, err := NewFromIOReader(tc.ioReader)
+				require.Equal(t, err, tc.err)
+				require.True(t, bytes.Equal(tc.bsonReader, reader))
+			})
+		}
+	})
+}

+ 287 - 0
src/go.mongodb.org/mongo-driver/bson/raw_value.go

@@ -0,0 +1,287 @@
+// 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"
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/bsoncodec"
+	"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"
+)
+
+// ErrNilContext is returned when the provided DecodeContext is nil.
+var ErrNilContext = errors.New("DecodeContext cannot be nil")
+
+// ErrNilRegistry is returned when the provided registry is nil.
+var ErrNilRegistry = errors.New("Registry cannot be nil")
+
+// RawValue represents a BSON value in byte form. It can be used to hold unprocessed BSON or to
+// defer processing of BSON. Type is the BSON type of the value and Value are the raw bytes that
+// represent the element.
+//
+// This type wraps bsoncore.Value for most of it's functionality.
+type RawValue struct {
+	Type  bsontype.Type
+	Value []byte
+
+	r *bsoncodec.Registry
+}
+
+// Unmarshal deserializes BSON into the provided val. If RawValue cannot be unmarshaled into val, an
+// error is returned. This method will use the registry used to create the RawValue, if the RawValue
+// was created from partial BSON processing, or it will use the default registry. Users wishing to
+// specify the registry to use should use UnmarshalWithRegistry.
+func (rv RawValue) Unmarshal(val interface{}) error {
+	reg := rv.r
+	if reg == nil {
+		reg = DefaultRegistry
+	}
+	return rv.UnmarshalWithRegistry(reg, val)
+}
+
+// Equal compares rv and rv2 and returns true if they are equal.
+func (rv RawValue) Equal(rv2 RawValue) bool {
+	if rv.Type != rv2.Type {
+		return false
+	}
+
+	if !bytes.Equal(rv.Value, rv2.Value) {
+		return false
+	}
+
+	return true
+}
+
+// UnmarshalWithRegistry performs the same unmarshalling as Unmarshal but uses the provided registry
+// instead of the one attached or the default registry.
+func (rv RawValue) UnmarshalWithRegistry(r *bsoncodec.Registry, val interface{}) error {
+	if r == nil {
+		return ErrNilRegistry
+	}
+
+	vr := bsonrw.NewBSONValueReader(rv.Type, rv.Value)
+	rval := reflect.ValueOf(val)
+	if rval.Kind() != reflect.Ptr {
+		return fmt.Errorf("argument to Unmarshal* must be a pointer to a type, but got %v", rval)
+	}
+	rval = rval.Elem()
+	dec, err := r.LookupDecoder(rval.Type())
+	if err != nil {
+		return err
+	}
+	return dec.DecodeValue(bsoncodec.DecodeContext{Registry: r}, vr, rval)
+}
+
+// UnmarshalWithContext performs the same unmarshalling as Unmarshal but uses the provided DecodeContext
+// instead of the one attached or the default registry.
+func (rv RawValue) UnmarshalWithContext(dc *bsoncodec.DecodeContext, val interface{}) error {
+	if dc == nil {
+		return ErrNilContext
+	}
+
+	vr := bsonrw.NewBSONValueReader(rv.Type, rv.Value)
+	rval := reflect.ValueOf(val)
+	if rval.Kind() != reflect.Ptr {
+		return fmt.Errorf("argument to Unmarshal* must be a pointer to a type, but got %v", rval)
+	}
+	rval = rval.Elem()
+	dec, err := dc.LookupDecoder(rval.Type())
+	if err != nil {
+		return err
+	}
+	return dec.DecodeValue(*dc, vr, rval)
+}
+
+func convertFromCoreValue(v bsoncore.Value) RawValue { return RawValue{Type: v.Type, Value: v.Data} }
+func convertToCoreValue(v RawValue) bsoncore.Value   { return bsoncore.Value{Type: v.Type, Data: v.Value} }
+
+// Validate ensures the value is a valid BSON value.
+func (rv RawValue) Validate() error { return convertToCoreValue(rv).Validate() }
+
+// IsNumber returns true if the type of v is a numeric BSON type.
+func (rv RawValue) IsNumber() bool { return convertToCoreValue(rv).IsNumber() }
+
+// String implements the fmt.String interface. This method will return values in extended JSON
+// format. If the value is not valid, this returns an empty string
+func (rv RawValue) String() string { return convertToCoreValue(rv).String() }
+
+// DebugString outputs a human readable version of Document. It will attempt to stringify the
+// valid components of the document even if the entire document is not valid.
+func (rv RawValue) DebugString() string { return convertToCoreValue(rv).DebugString() }
+
+// Double returns the float64 value for this element.
+// It panics if e's BSON type is not bsontype.Double.
+func (rv RawValue) Double() float64 { return convertToCoreValue(rv).Double() }
+
+// DoubleOK is the same as Double, but returns a boolean instead of panicking.
+func (rv RawValue) DoubleOK() (float64, bool) { return convertToCoreValue(rv).DoubleOK() }
+
+// StringValue returns the string value for this element.
+// It panics if e's BSON type is not bsontype.String.
+//
+// NOTE: This method is called StringValue to avoid a collision with the String method which
+// implements the fmt.Stringer interface.
+func (rv RawValue) StringValue() string { return convertToCoreValue(rv).StringValue() }
+
+// StringValueOK is the same as StringValue, but returns a boolean instead of
+// panicking.
+func (rv RawValue) StringValueOK() (string, bool) { return convertToCoreValue(rv).StringValueOK() }
+
+// Document returns the BSON document the Value represents as a Document. It panics if the
+// value is a BSON type other than document.
+func (rv RawValue) Document() Raw { return Raw(convertToCoreValue(rv).Document()) }
+
+// DocumentOK is the same as Document, except it returns a boolean
+// instead of panicking.
+func (rv RawValue) DocumentOK() (Raw, bool) {
+	doc, ok := convertToCoreValue(rv).DocumentOK()
+	return Raw(doc), ok
+}
+
+// Array returns the BSON array the Value represents as an Array. It panics if the
+// value is a BSON type other than array.
+func (rv RawValue) Array() Raw { return Raw(convertToCoreValue(rv).Array()) }
+
+// ArrayOK is the same as Array, except it returns a boolean instead
+// of panicking.
+func (rv RawValue) ArrayOK() (Raw, bool) {
+	doc, ok := convertToCoreValue(rv).ArrayOK()
+	return Raw(doc), ok
+}
+
+// Binary returns the BSON binary value the Value represents. It panics if the value is a BSON type
+// other than binary.
+func (rv RawValue) Binary() (subtype byte, data []byte) { return convertToCoreValue(rv).Binary() }
+
+// BinaryOK is the same as Binary, except it returns a boolean instead of
+// panicking.
+func (rv RawValue) BinaryOK() (subtype byte, data []byte, ok bool) {
+	return convertToCoreValue(rv).BinaryOK()
+}
+
+// ObjectID returns the BSON objectid value the Value represents. It panics if the value is a BSON
+// type other than objectid.
+func (rv RawValue) ObjectID() primitive.ObjectID { return convertToCoreValue(rv).ObjectID() }
+
+// ObjectIDOK is the same as ObjectID, except it returns a boolean instead of
+// panicking.
+func (rv RawValue) ObjectIDOK() (primitive.ObjectID, bool) { return convertToCoreValue(rv).ObjectIDOK() }
+
+// Boolean returns the boolean value the Value represents. It panics if the
+// value is a BSON type other than boolean.
+func (rv RawValue) Boolean() bool { return convertToCoreValue(rv).Boolean() }
+
+// BooleanOK is the same as Boolean, except it returns a boolean instead of
+// panicking.
+func (rv RawValue) BooleanOK() (bool, bool) { return convertToCoreValue(rv).BooleanOK() }
+
+// DateTime returns the BSON datetime value the Value represents as a
+// unix timestamp. It panics if the value is a BSON type other than datetime.
+func (rv RawValue) DateTime() int64 { return convertToCoreValue(rv).DateTime() }
+
+// DateTimeOK is the same as DateTime, except it returns a boolean instead of
+// panicking.
+func (rv RawValue) DateTimeOK() (int64, bool) { return convertToCoreValue(rv).DateTimeOK() }
+
+// Time returns the BSON datetime value the Value represents. It panics if the value is a BSON
+// type other than datetime.
+func (rv RawValue) Time() time.Time { return convertToCoreValue(rv).Time() }
+
+// TimeOK is the same as Time, except it returns a boolean instead of
+// panicking.
+func (rv RawValue) TimeOK() (time.Time, bool) { return convertToCoreValue(rv).TimeOK() }
+
+// Regex returns the BSON regex value the Value represents. It panics if the value is a BSON
+// type other than regex.
+func (rv RawValue) Regex() (pattern, options string) { return convertToCoreValue(rv).Regex() }
+
+// RegexOK is the same as Regex, except it returns a boolean instead of
+// panicking.
+func (rv RawValue) RegexOK() (pattern, options string, ok bool) {
+	return convertToCoreValue(rv).RegexOK()
+}
+
+// DBPointer returns the BSON dbpointer value the Value represents. It panics if the value is a BSON
+// type other than DBPointer.
+func (rv RawValue) DBPointer() (string, primitive.ObjectID) { return convertToCoreValue(rv).DBPointer() }
+
+// DBPointerOK is the same as DBPoitner, except that it returns a boolean
+// instead of panicking.
+func (rv RawValue) DBPointerOK() (string, primitive.ObjectID, bool) {
+	return convertToCoreValue(rv).DBPointerOK()
+}
+
+// JavaScript returns the BSON JavaScript code value the Value represents. It panics if the value is
+// a BSON type other than JavaScript code.
+func (rv RawValue) JavaScript() string { return convertToCoreValue(rv).JavaScript() }
+
+// JavaScriptOK is the same as Javascript, excepti that it returns a boolean
+// instead of panicking.
+func (rv RawValue) JavaScriptOK() (string, bool) { return convertToCoreValue(rv).JavaScriptOK() }
+
+// Symbol returns the BSON symbol value the Value represents. It panics if the value is a BSON
+// type other than symbol.
+func (rv RawValue) Symbol() string { return convertToCoreValue(rv).Symbol() }
+
+// SymbolOK is the same as Symbol, excepti that it returns a boolean
+// instead of panicking.
+func (rv RawValue) SymbolOK() (string, bool) { return convertToCoreValue(rv).SymbolOK() }
+
+// CodeWithScope returns the BSON JavaScript code with scope the Value represents.
+// It panics if the value is a BSON type other than JavaScript code with scope.
+func (rv RawValue) CodeWithScope() (string, Raw) {
+	code, scope := convertToCoreValue(rv).CodeWithScope()
+	return code, Raw(scope)
+}
+
+// CodeWithScopeOK is the same as CodeWithScope, except that it returns a boolean instead of
+// panicking.
+func (rv RawValue) CodeWithScopeOK() (string, Raw, bool) {
+	code, scope, ok := convertToCoreValue(rv).CodeWithScopeOK()
+	return code, Raw(scope), ok
+}
+
+// Int32 returns the int32 the Value represents. It panics if the value is a BSON type other than
+// int32.
+func (rv RawValue) Int32() int32 { return convertToCoreValue(rv).Int32() }
+
+// Int32OK is the same as Int32, except that it returns a boolean instead of
+// panicking.
+func (rv RawValue) Int32OK() (int32, bool) { return convertToCoreValue(rv).Int32OK() }
+
+// Timestamp returns the BSON timestamp value the Value represents. It panics if the value is a
+// BSON type other than timestamp.
+func (rv RawValue) Timestamp() (t, i uint32) { return convertToCoreValue(rv).Timestamp() }
+
+// TimestampOK is the same as Timestamp, except that it returns a boolean
+// instead of panicking.
+func (rv RawValue) TimestampOK() (t, i uint32, ok bool) { return convertToCoreValue(rv).TimestampOK() }
+
+// Int64 returns the int64 the Value represents. It panics if the value is a BSON type other than
+// int64.
+func (rv RawValue) Int64() int64 { return convertToCoreValue(rv).Int64() }
+
+// Int64OK is the same as Int64, except that it returns a boolean instead of
+// panicking.
+func (rv RawValue) Int64OK() (int64, bool) { return convertToCoreValue(rv).Int64OK() }
+
+// Decimal128 returns the decimal the Value represents. It panics if the value is a BSON type other than
+// decimal.
+func (rv RawValue) Decimal128() primitive.Decimal128 { return convertToCoreValue(rv).Decimal128() }
+
+// Decimal128OK is the same as Decimal128, except that it returns a boolean
+// instead of panicking.
+func (rv RawValue) Decimal128OK() (primitive.Decimal128, bool) {
+	return convertToCoreValue(rv).Decimal128OK()
+}

+ 124 - 0
src/go.mongodb.org/mongo-driver/bson/raw_value_test.go

@@ -0,0 +1,124 @@
+// 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"
+	"reflect"
+	"testing"
+
+	"go.mongodb.org/mongo-driver/bson/bsoncodec"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
+)
+
+func TestRawValue(t *testing.T) {
+	t.Run("Unmarshal", func(t *testing.T) {
+		t.Run("Uses registry attached to value", func(t *testing.T) {
+			reg := bsoncodec.NewRegistryBuilder().Build()
+			val := RawValue{Type: bsontype.String, Value: bsoncore.AppendString(nil, "foobar"), r: reg}
+			var s string
+			want := bsoncodec.ErrNoDecoder{Type: reflect.TypeOf(s)}
+			got := val.Unmarshal(&s)
+			if !compareErrors(got, want) {
+				t.Errorf("Expected errors to match. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Uses default registry if no registry attached", func(t *testing.T) {
+			want := "foobar"
+			val := RawValue{Type: bsontype.String, Value: bsoncore.AppendString(nil, want)}
+			var got string
+			err := val.Unmarshal(&got)
+			noerr(t, err)
+			if got != want {
+				t.Errorf("Expected strings to match. got %s; want %s", got, want)
+			}
+		})
+	})
+	t.Run("UnmarshalWithRegistry", func(t *testing.T) {
+		t.Run("Returns error when registry is nil", func(t *testing.T) {
+			want := ErrNilRegistry
+			var val RawValue
+			got := val.UnmarshalWithRegistry(nil, &D{})
+			if got != want {
+				t.Errorf("Expected errors to match. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Returns lookup error", func(t *testing.T) {
+			reg := bsoncodec.NewRegistryBuilder().Build()
+			var val RawValue
+			var s string
+			want := bsoncodec.ErrNoDecoder{Type: reflect.TypeOf(s)}
+			got := val.UnmarshalWithRegistry(reg, &s)
+			if !compareErrors(got, want) {
+				t.Errorf("Expected errors to match. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Returns DecodeValue error", func(t *testing.T) {
+			reg := NewRegistryBuilder().Build()
+			val := RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)}
+			var s string
+			want := fmt.Errorf("cannot decode %v into a string type", bsontype.Double)
+			got := val.UnmarshalWithRegistry(reg, &s)
+			if !compareErrors(got, want) {
+				t.Errorf("Expected errors to match. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Success", func(t *testing.T) {
+			reg := NewRegistryBuilder().Build()
+			want := float64(3.14159)
+			val := RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, want)}
+			var got float64
+			err := val.UnmarshalWithRegistry(reg, &got)
+			noerr(t, err)
+			if got != want {
+				t.Errorf("Expected results to match. got %g; want %g", got, want)
+			}
+		})
+	})
+	t.Run("UnmarshalWithContext", func(t *testing.T) {
+		t.Run("Returns error when DecodeContext is nil", func(t *testing.T) {
+			want := ErrNilContext
+			var val RawValue
+			got := val.UnmarshalWithContext(nil, &D{})
+			if got != want {
+				t.Errorf("Expected errors to match. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Returns lookup error", func(t *testing.T) {
+			dc := bsoncodec.DecodeContext{Registry: bsoncodec.NewRegistryBuilder().Build()}
+			var val RawValue
+			var s string
+			want := bsoncodec.ErrNoDecoder{Type: reflect.TypeOf(s)}
+			got := val.UnmarshalWithContext(&dc, &s)
+			if !compareErrors(got, want) {
+				t.Errorf("Expected errors to match. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Returns DecodeValue error", func(t *testing.T) {
+			dc := bsoncodec.DecodeContext{Registry: NewRegistryBuilder().Build()}
+			val := RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, 3.14159)}
+			var s string
+			want := fmt.Errorf("cannot decode %v into a string type", bsontype.Double)
+			got := val.UnmarshalWithContext(&dc, &s)
+			if !compareErrors(got, want) {
+				t.Errorf("Expected errors to match. got %v; want %v", got, want)
+			}
+		})
+		t.Run("Success", func(t *testing.T) {
+			dc := bsoncodec.DecodeContext{Registry: NewRegistryBuilder().Build()}
+			want := float64(3.14159)
+			val := RawValue{Type: bsontype.Double, Value: bsoncore.AppendDouble(nil, want)}
+			var got float64
+			err := val.UnmarshalWithContext(&dc, &got)
+			noerr(t, err)
+			if got != want {
+				t.Errorf("Expected results to match. got %g; want %g", got, want)
+			}
+		})
+	})
+}

+ 24 - 0
src/go.mongodb.org/mongo-driver/bson/registry.go

@@ -0,0 +1,24 @@
+// 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"
+
+// DefaultRegistry is the default bsoncodec.Registry. It contains the default codecs and the
+// primitive codecs.
+var DefaultRegistry = NewRegistryBuilder().Build()
+
+// NewRegistryBuilder creates a new RegistryBuilder configured with the default encoders and
+// deocders 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)
+	primitiveCodecs.RegisterPrimitiveCodecs(rb)
+	return rb
+}

+ 63 - 0
src/go.mongodb.org/mongo-driver/bson/truncation_test.go

@@ -0,0 +1,63 @@
+package bson
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"go.mongodb.org/mongo-driver/bson/bsoncodec"
+)
+
+type inputArgs struct {
+	Name string
+	Val  *float64
+}
+
+type outputArgs struct {
+	Name string
+	Val  *int64
+}
+
+func TestTruncation(t *testing.T) {
+	t.Run("truncation", func(t *testing.T) {
+		inputName := "truncation"
+		inputVal := 4.7892
+
+		input := inputArgs{Name: inputName, Val: &inputVal}
+		ec := bsoncodec.EncodeContext{Registry: DefaultRegistry}
+
+		doc, err := MarshalWithContext(ec, &input)
+		assert.Nil(t, err)
+
+		var output outputArgs
+		dc := bsoncodec.DecodeContext{
+			Registry: DefaultRegistry,
+			Truncate: true,
+		}
+
+		err = UnmarshalWithContext(dc, doc, &output)
+		assert.Nil(t, err)
+
+		assert.Equal(t, inputName, output.Name)
+		assert.Equal(t, int64(inputVal), *output.Val)
+	})
+	t.Run("no truncation", func(t *testing.T) {
+		inputName := "no truncation"
+		inputVal := 7.382
+
+		input := inputArgs{Name: inputName, Val: &inputVal}
+		ec := bsoncodec.EncodeContext{Registry: DefaultRegistry}
+
+		doc, err := MarshalWithContext(ec, &input)
+		assert.Nil(t, err)
+
+		var output outputArgs
+		dc := bsoncodec.DecodeContext{
+			Registry: DefaultRegistry,
+			Truncate: false,
+		}
+
+		// case throws an error when truncation is disabled
+		err = UnmarshalWithContext(dc, doc, &output)
+		assert.NotNil(t, err)
+	})
+}

+ 85 - 0
src/go.mongodb.org/mongo-driver/bson/types.go

@@ -0,0 +1,85 @@
+// 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 (
+	"reflect"
+	"time"
+
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+// These constants uniquely refer to each BSON type.
+const (
+	TypeDouble           = bsontype.Double
+	TypeString           = bsontype.String
+	TypeEmbeddedDocument = bsontype.EmbeddedDocument
+	TypeArray            = bsontype.Array
+	TypeBinary           = bsontype.Binary
+	TypeUndefined        = bsontype.Undefined
+	TypeObjectID         = bsontype.ObjectID
+	TypeBoolean          = bsontype.Boolean
+	TypeDateTime         = bsontype.DateTime
+	TypeNull             = bsontype.Null
+	TypeRegex            = bsontype.Regex
+	TypeDBPointer        = bsontype.DBPointer
+	TypeJavaScript       = bsontype.JavaScript
+	TypeSymbol           = bsontype.Symbol
+	TypeCodeWithScope    = bsontype.CodeWithScope
+	TypeInt32            = bsontype.Int32
+	TypeTimestamp        = bsontype.Timestamp
+	TypeInt64            = bsontype.Int64
+	TypeDecimal128       = bsontype.Decimal128
+	TypeMinKey           = bsontype.MinKey
+	TypeMaxKey           = bsontype.MaxKey
+)
+
+var tBinary = reflect.TypeOf(primitive.Binary{})
+var tBool = reflect.TypeOf(false)
+var tCodeWithScope = reflect.TypeOf(primitive.CodeWithScope{})
+var tDBPointer = reflect.TypeOf(primitive.DBPointer{})
+var tDecimal = reflect.TypeOf(primitive.Decimal128{})
+var tD = reflect.TypeOf(D{})
+var tA = reflect.TypeOf(A{})
+var tDateTime = reflect.TypeOf(primitive.DateTime(0))
+var tUndefined = reflect.TypeOf(primitive.Undefined{})
+var tNull = reflect.TypeOf(primitive.Null{})
+var tRawValue = reflect.TypeOf(RawValue{})
+var tFloat32 = reflect.TypeOf(float32(0))
+var tFloat64 = reflect.TypeOf(float64(0))
+var tInt = reflect.TypeOf(int(0))
+var tInt8 = reflect.TypeOf(int8(0))
+var tInt16 = reflect.TypeOf(int16(0))
+var tInt32 = reflect.TypeOf(int32(0))
+var tInt64 = reflect.TypeOf(int64(0))
+var tJavaScript = reflect.TypeOf(primitive.JavaScript(""))
+var tOID = reflect.TypeOf(primitive.ObjectID{})
+var tRaw = reflect.TypeOf(Raw(nil))
+var tRegex = reflect.TypeOf(primitive.Regex{})
+var tString = reflect.TypeOf("")
+var tSymbol = reflect.TypeOf(primitive.Symbol(""))
+var tTime = reflect.TypeOf(time.Time{})
+var tTimestamp = reflect.TypeOf(primitive.Timestamp{})
+var tUint = reflect.TypeOf(uint(0))
+var tUint8 = reflect.TypeOf(uint8(0))
+var tUint16 = reflect.TypeOf(uint16(0))
+var tUint32 = reflect.TypeOf(uint32(0))
+var tUint64 = reflect.TypeOf(uint64(0))
+var tMinKey = reflect.TypeOf(primitive.MinKey{})
+var tMaxKey = reflect.TypeOf(primitive.MaxKey{})
+
+var tEmpty = reflect.TypeOf((*interface{})(nil)).Elem()
+var tEmptySlice = reflect.TypeOf([]interface{}(nil))
+
+var zeroVal reflect.Value
+
+// this references the quantity of milliseconds between zero time and
+// the unix epoch. useful for making sure that we convert time.Time
+// objects correctly to match the legacy bson library's handling of
+// time.Time values.
+const zeroEpochMs = int64(62135596800000)

Vissa filer visades inte eftersom för många filer har ändrats