Repository: mongodb/mongo-go-driver Branch: master Commit: 8fe7e71d20cb Files: 745 Total size: 6.4 MB Directory structure: gitextract_b5ez4ucg/ ├── .errcheck-excludes ├── .evergreen/ │ ├── config.yml │ ├── krb5.config │ ├── ocsp-requirements.txt │ ├── run-mongodb-aws-ecs-test.sh │ ├── run-task.sh │ └── setup-system.sh ├── .github/ │ ├── dependabot.yml │ ├── labeler.yml │ ├── release.yml │ └── workflows/ │ ├── check-labels.yml │ ├── codeql.yml │ ├── create-release-branch.yml │ ├── labeler.yml │ ├── merge-up.yml │ ├── release.yml │ ├── scorecard.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .golangci.yml ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── THIRD-PARTY-NOTICES ├── Taskfile.yml ├── bson/ │ ├── array_codec.go │ ├── benchmark_test.go │ ├── bson_binary_vector_spec_test.go │ ├── bson_corpus_spec_test.go │ ├── bson_test.go │ ├── bsoncodec.go │ ├── bsoncodec_test.go │ ├── bsonrw_test.go │ ├── buffered_byte_src.go │ ├── buffered_byte_src_test.go │ ├── byte_slice_codec.go │ ├── codec_cache.go │ ├── codec_cache_test.go │ ├── cond_addr_codec.go │ ├── cond_addr_codec_test.go │ ├── copier.go │ ├── copier_test.go │ ├── decimal.go │ ├── decimal_test.go │ ├── decode_value_fuzz_test.go │ ├── decoder.go │ ├── decoder_example_test.go │ ├── decoder_test.go │ ├── default_value_decoders.go │ ├── default_value_decoders_test.go │ ├── default_value_encoders.go │ ├── default_value_encoders_test.go │ ├── doc.go │ ├── empty_interface_codec.go │ ├── encoder.go │ ├── encoder_example_test.go │ ├── encoder_test.go │ ├── example_test.go │ ├── extjson_parser.go │ ├── extjson_parser_test.go │ ├── extjson_prose_test.go │ ├── extjson_reader.go │ ├── extjson_reader_test.go │ ├── extjson_tables.go │ ├── extjson_wrappers.go │ ├── extjson_writer.go │ ├── extjson_writer_test.go │ ├── fuzz_test.go │ ├── json_scanner.go │ ├── json_scanner_test.go │ ├── map_codec.go │ ├── marshal.go │ ├── marshal_test.go │ ├── marshal_value_cases_test.go │ ├── marshal_value_test.go │ ├── marshaling_cases_test.go │ ├── mgocompat/ │ │ ├── doc.go │ │ └── registry.go │ ├── mgoregistry.go │ ├── mgoregistry_test.go │ ├── mode.go │ ├── objectid.go │ ├── objectid_test.go │ ├── pointer_codec.go │ ├── primitive.go │ ├── primitive_codecs.go │ ├── primitive_codecs_test.go │ ├── raw.go │ ├── raw_array.go │ ├── raw_array_test.go │ ├── raw_element.go │ ├── raw_test.go │ ├── raw_value.go │ ├── raw_value_test.go │ ├── reader.go │ ├── registry.go │ ├── registry_examples_test.go │ ├── registry_test.go │ ├── slice_codec.go │ ├── streaming_byte_src.go │ ├── string_codec.go │ ├── struct_codec.go │ ├── struct_codec_test.go │ ├── struct_tag_parser.go │ ├── struct_tag_parser_test.go │ ├── testdata/ │ │ ├── fuzz/ │ │ │ └── FuzzDecode/ │ │ │ ├── 002ae7d43f636100116fede772a03d07726ed75c3c3b83da865fe9b718adf8ae │ │ │ ├── 0de854041b0055ca1e5e6e54a7fb667ed38461db171af267665c21776f9a9ef4 │ │ │ ├── 718592474a0a3626039f3471449b9aa374c746754d4925fcfe4ba747e7101504 │ │ │ ├── 93c43e3c1cf35c19b7618a618d128cea0ce05cef0711fdd91e403fe3b2f45628 │ │ │ └── c3ffbb42eb85b743ede396f00b7706e6ad0529c32689c63ca663dae37d072627 │ │ └── lorem.txt │ ├── time_codec.go │ ├── time_codec_test.go │ ├── truncation_test.go │ ├── type_test.go │ ├── types.go │ ├── uint_codec.go │ ├── unmarshal.go │ ├── unmarshal_test.go │ ├── unmarshal_value_test.go │ ├── unmarshaling_cases_test.go │ ├── value_reader.go │ ├── value_reader_test.go │ ├── value_reader_writer_test.go │ ├── value_writer.go │ ├── value_writer_test.go │ ├── vector.go │ └── writer.go ├── docs/ │ ├── CODEOWNERS │ ├── CONTRIBUTING.md │ ├── SECURITY.md │ ├── common-issues.md │ ├── migration-2.0.md │ └── pull_request_template.md ├── etc/ │ ├── api_report.sh │ ├── check_fmt.sh │ ├── check_license.sh │ ├── check_modules.sh │ ├── cherry-picker.sh │ ├── docker_entry.sh │ ├── gen-ec-certs/ │ │ ├── client.ext │ │ ├── empty.cnf │ │ ├── gen-ec-certs.sh │ │ └── server.ext │ ├── generate_notices.pl │ ├── golangci-lint.sh │ ├── govulncheck.sh │ ├── install-libmongocrypt.sh │ ├── perf-pr-comment.sh │ ├── pr-task.sh │ ├── profile-test.sh │ ├── run-awskms-test.sh │ ├── run-azurekms-test.sh │ ├── run-compile-check-test.sh │ ├── run-fuzz.sh │ ├── run-gcpkms-test.sh │ ├── run-goleak-test.sh │ ├── run-mongodb-aws-ecs-test.sh │ ├── run-mongodb-aws-test.sh │ ├── run-oidc-remote-test.sh │ ├── run-oidc-test.sh │ ├── run_docker.sh │ ├── setup-encryption.sh │ ├── setup-test.sh │ └── update_spec_tests.sh ├── event/ │ ├── description.go │ ├── doc.go │ ├── examples_test.go │ └── monitoring.go ├── examples/ │ ├── _example_customdns_test.go │ ├── _example_overload_error_test.go │ └── _logger/ │ ├── logrus/ │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── zap/ │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── zerolog/ │ ├── go.mod │ ├── go.sum │ └── main.go ├── go.mod ├── go.sum ├── go.work ├── internal/ │ ├── assert/ │ │ ├── assertbson/ │ │ │ ├── assertbson.go │ │ │ └── assertbson_test.go │ │ ├── assertion_compare.go │ │ ├── assertion_compare_can_convert.go │ │ ├── assertion_compare_go1.17_test.go │ │ ├── assertion_compare_legacy.go │ │ ├── assertion_compare_test.go │ │ ├── assertion_format.go │ │ ├── assertion_mongo.go │ │ ├── assertion_mongo_test.go │ │ ├── assertions.go │ │ ├── assertions_test.go │ │ ├── difflib.go │ │ └── difflib_test.go │ ├── aws/ │ │ ├── awserr/ │ │ │ ├── error.go │ │ │ └── types.go │ │ ├── credentials/ │ │ │ ├── chain_provider.go │ │ │ ├── chain_provider_test.go │ │ │ ├── credentials.go │ │ │ └── credentials_test.go │ │ ├── signer/ │ │ │ └── v4/ │ │ │ ├── header_rules.go │ │ │ ├── request.go │ │ │ ├── uri_path.go │ │ │ ├── v4.go │ │ │ └── v4_test.go │ │ └── types.go │ ├── binaryutil/ │ │ ├── binaryutil.go │ │ ├── binaryutil_test.go │ │ └── doc.go │ ├── bsoncoreutil/ │ │ ├── bsoncoreutil.go │ │ └── bsoncoreutil_test.go │ ├── bsonutil/ │ │ └── bsonutil.go │ ├── cmd/ │ │ ├── benchmark/ │ │ │ ├── benchmark_test.go │ │ │ ├── go.mod │ │ │ └── go.sum │ │ ├── build-oss-fuzz-corpus/ │ │ │ └── main.go │ │ ├── faas/ │ │ │ └── awslambda/ │ │ │ ├── Makefile │ │ │ ├── mongodb/ │ │ │ │ ├── bootstrap.go │ │ │ │ ├── go.mod │ │ │ │ └── go.sum │ │ │ └── template.yaml │ │ ├── parse-api-report/ │ │ │ └── main.go │ │ ├── testatlas/ │ │ │ └── atlas_test.go │ │ ├── testentauth/ │ │ │ └── main.go │ │ └── testkms/ │ │ └── main.go │ ├── codecutil/ │ │ ├── encoding.go │ │ └── encoding_test.go │ ├── credproviders/ │ │ ├── assume_role_provider.go │ │ ├── ec2_provider.go │ │ ├── ecs_provider.go │ │ ├── env_provider.go │ │ ├── imds_provider.go │ │ └── static_provider.go │ ├── csfle/ │ │ └── csfle.go │ ├── csot/ │ │ ├── csot.go │ │ └── csot_test.go │ ├── decimal128/ │ │ └── decimal128.go │ ├── docexamples/ │ │ ├── README │ │ ├── examples.go │ │ └── examples_test.go │ ├── driverutil/ │ │ ├── description.go │ │ ├── hello.go │ │ ├── operation.go │ │ └── operation_test.go │ ├── errutil/ │ │ ├── join.go │ │ ├── join_go1.19.go │ │ ├── join_go1.20.go │ │ └── join_test.go │ ├── eventtest/ │ │ └── eventtest.go │ ├── failpoint/ │ │ └── failpoint.go │ ├── handshake/ │ │ └── handshake.go │ ├── httputil/ │ │ ├── httputil.go │ │ └── httputil_test.go │ ├── integration/ │ │ ├── causal_consistency_test.go │ │ ├── change_stream_test.go │ │ ├── clam_prose_test.go │ │ ├── client_options_test.go │ │ ├── client_side_encryption_prose_test.go │ │ ├── client_side_encryption_spec_test.go │ │ ├── client_side_encryption_test.go │ │ ├── client_test.go │ │ ├── cmd_monitoring_helpers_test.go │ │ ├── collection_test.go │ │ ├── crud_helpers_test.go │ │ ├── crud_prose_test.go │ │ ├── csot_cse_prose_test.go │ │ ├── csot_prose_test.go │ │ ├── csot_test.go │ │ ├── cursor_test.go │ │ ├── database_test.go │ │ ├── errors_test.go │ │ ├── gridfs_test.go │ │ ├── handshake_test.go │ │ ├── index_view_test.go │ │ ├── initial_dns_seedlist_discovery_test.go │ │ ├── json_helpers_test.go │ │ ├── load_balancer_prose_test.go │ │ ├── log_helpers_test.go │ │ ├── main.go │ │ ├── main_test.go │ │ ├── mock_find_test.go │ │ ├── mongointernal_test.go │ │ ├── mongos_pinning_test.go │ │ ├── mtest/ │ │ │ ├── csfle_enabled.go │ │ │ ├── csfle_not_enabled.go │ │ │ ├── deployment_helpers.go │ │ │ ├── doc.go │ │ │ ├── global_state.go │ │ │ ├── mongotest.go │ │ │ ├── options.go │ │ │ ├── proxy_capture.go │ │ │ ├── proxy_dialer.go │ │ │ ├── received_message.go │ │ │ ├── sent_message.go │ │ │ ├── setup.go │ │ │ ├── setup_options.go │ │ │ └── wiremessage_helpers.go │ │ ├── primary_stepdown_test.go │ │ ├── retryable_reads_prose_test.go │ │ ├── retryable_writes_prose_test.go │ │ ├── sdam_error_handling_test.go │ │ ├── sdam_prose_test.go │ │ ├── search_index_prose_test.go │ │ ├── server_selection_prose_test.go │ │ ├── sessions_mongocryptd_prose_test.go │ │ ├── sessions_test.go │ │ ├── unified/ │ │ │ ├── admin_helpers.go │ │ │ ├── bsonutil.go │ │ │ ├── bucket_options.go │ │ │ ├── bulkwrite_helpers.go │ │ │ ├── client_encryption_operation_execution.go │ │ │ ├── client_entity.go │ │ │ ├── client_operation_execution.go │ │ │ ├── collection_data.go │ │ │ ├── collection_operation_execution.go │ │ │ ├── common_options.go │ │ │ ├── context.go │ │ │ ├── crud_helpers.go │ │ │ ├── cursor_entity.go │ │ │ ├── cursor_operation_execution.go │ │ │ ├── database_operation_execution.go │ │ │ ├── db_collection_options.go │ │ │ ├── entity.go │ │ │ ├── entity_test.go │ │ │ ├── error.go │ │ │ ├── event.go │ │ │ ├── event_verification.go │ │ │ ├── gridfs_bucket_operation_execution.go │ │ │ ├── logger.go │ │ │ ├── logger_verification.go │ │ │ ├── main_test.go │ │ │ ├── matches.go │ │ │ ├── matches_test.go │ │ │ ├── operation.go │ │ │ ├── options.go │ │ │ ├── result.go │ │ │ ├── schema_version.go │ │ │ ├── server_api_options.go │ │ │ ├── session_operation_execution.go │ │ │ ├── session_options.go │ │ │ ├── testrunner_operation.go │ │ │ ├── unified_spec_runner.go │ │ │ └── unified_spec_test.go │ │ ├── unified_runner_events_helper_test.go │ │ ├── unified_runner_thread_helpers_test.go │ │ └── unified_spec_test.go │ ├── integtest/ │ │ └── integtest.go │ ├── israce/ │ │ ├── norace.go │ │ └── race.go │ ├── logger/ │ │ ├── component.go │ │ ├── component_test.go │ │ ├── context.go │ │ ├── context_test.go │ │ ├── io_sink.go │ │ ├── level.go │ │ ├── logger.go │ │ └── logger_test.go │ ├── mongoutil/ │ │ ├── mongoutil.go │ │ └── mongoutil_test.go │ ├── optionsutil/ │ │ └── options.go │ ├── ptrutil/ │ │ ├── int64.go │ │ ├── int64_test.go │ │ └── ptr.go │ ├── rand/ │ │ ├── arith128_test.go │ │ ├── bits.go │ │ ├── example_test.go │ │ ├── exp.go │ │ ├── modulo_test.go │ │ ├── normal.go │ │ ├── race_test.go │ │ ├── rand.go │ │ ├── rand_test.go │ │ ├── regress_test.go │ │ └── rng.go │ ├── randutil/ │ │ ├── randutil.go │ │ └── randutil_test.go │ ├── require/ │ │ └── require.go │ ├── serverselector/ │ │ ├── server_selector.go │ │ └── server_selector_test.go │ ├── spectest/ │ │ ├── skip.go │ │ └── spectest.go │ ├── test/ │ │ ├── aws/ │ │ │ └── aws_test.go │ │ ├── compilecheck/ │ │ │ ├── compile_check_test.go │ │ │ ├── go.mod │ │ │ └── go.sum │ │ ├── goleak/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ └── goleak_test.go │ │ └── oidcauth/ │ │ └── oidcauth_test.go │ ├── testutil/ │ │ └── reflect.go │ └── uuid/ │ ├── uuid.go │ └── uuid_test.go ├── mongo/ │ ├── address/ │ │ ├── addr.go │ │ └── addr_test.go │ ├── background_context.go │ ├── background_context_test.go │ ├── batch_cursor.go │ ├── bson_helpers_test.go │ ├── bulk_write.go │ ├── bulk_write_models.go │ ├── change_stream.go │ ├── change_stream_deployment.go │ ├── change_stream_test.go │ ├── client.go │ ├── client_bulk_write.go │ ├── client_bulk_write_models.go │ ├── client_bulk_write_test.go │ ├── client_encryption.go │ ├── client_encryption_test.go │ ├── client_examples_test.go │ ├── client_side_encryption_examples_test.go │ ├── client_test.go │ ├── collection.go │ ├── collection_test.go │ ├── crud_examples_test.go │ ├── crypt_retrievers.go │ ├── cursor.go │ ├── cursor_test.go │ ├── database.go │ ├── database_test.go │ ├── doc.go │ ├── errors.go │ ├── errors_test.go │ ├── gridfs_bucket.go │ ├── gridfs_bucket_test.go │ ├── gridfs_download_stream.go │ ├── gridfs_examples_test.go │ ├── gridfs_test.go │ ├── gridfs_upload_stream.go │ ├── index_view.go │ ├── insert.go │ ├── mongo.go │ ├── mongo_test.go │ ├── mongocryptd.go │ ├── mongointernal.go │ ├── ocsp_test.go │ ├── options/ │ │ ├── aggregateoptions.go │ │ ├── autoencryptionoptions.go │ │ ├── bulkwriteoptions.go │ │ ├── changestreamoptions.go │ │ ├── clientbulkwriteoptions.go │ │ ├── clientencryptionoptions.go │ │ ├── clientoptions.go │ │ ├── clientoptions_test.go │ │ ├── collectionoptions.go │ │ ├── countoptions.go │ │ ├── createcollectionoptions.go │ │ ├── datakeyoptions.go │ │ ├── dboptions.go │ │ ├── deleteoptions.go │ │ ├── distinctoptions.go │ │ ├── doc.go │ │ ├── dropcollectionoptions.go │ │ ├── encryptoptions.go │ │ ├── estimatedcountoptions.go │ │ ├── example_test.go │ │ ├── findoptions.go │ │ ├── gridfsoptions.go │ │ ├── indexoptions.go │ │ ├── insertoptions.go │ │ ├── listcollectionsoptions.go │ │ ├── listdatabasesoptions.go │ │ ├── lister.go │ │ ├── loggeroptions.go │ │ ├── mongooptions.go │ │ ├── replaceoptions.go │ │ ├── rewrapdatakeyoptions.go │ │ ├── runcmdoptions.go │ │ ├── searchindexoptions.go │ │ ├── serverapioptions.go │ │ ├── sessionoptions.go │ │ ├── testdata/ │ │ │ ├── ca-key.pem │ │ │ ├── ca-with-intermediates-first.pem │ │ │ ├── ca-with-intermediates-second.pem │ │ │ ├── ca-with-intermediates-third.pem │ │ │ ├── ca-with-intermediates.pem │ │ │ ├── ca.pem │ │ │ ├── cert.pem │ │ │ ├── certificate.pem │ │ │ ├── csr.json │ │ │ ├── empty-ca.pem │ │ │ ├── key.pem │ │ │ ├── malformed-ca.pem │ │ │ ├── nopass/ │ │ │ │ ├── cert.pem │ │ │ │ ├── certificate.pem │ │ │ │ └── key.pem │ │ │ └── one-pk-multiple-certs.pem │ │ ├── transactionoptions.go │ │ └── updateoptions.go │ ├── read_write_concern_spec_test.go │ ├── readconcern/ │ │ └── readconcern.go │ ├── readpref/ │ │ ├── mode.go │ │ ├── mode_test.go │ │ ├── options.go │ │ ├── options_example_test.go │ │ ├── readpref.go │ │ └── readpref_test.go │ ├── results.go │ ├── search_index_view.go │ ├── session.go │ ├── single_result.go │ ├── single_result_test.go │ ├── with_transactions_test.go │ └── writeconcern/ │ ├── writeconcern.go │ ├── writeconcern_example_test.go │ └── writeconcern_test.go ├── sbom.json ├── tag/ │ ├── tag.go │ └── tag_test.go ├── testdata/ │ ├── client-side-encryption-prose/ │ │ ├── change-streams-test.json │ │ ├── corpus-encrypted.json │ │ ├── corpus-key-aws.json │ │ ├── corpus-key-azure.json │ │ ├── corpus-key-gcp.json │ │ ├── corpus-key-kmip.json │ │ ├── corpus-key-local.json │ │ ├── corpus-schema.json │ │ ├── corpus.json │ │ ├── encrypted-fields.json │ │ ├── encryptedFields-prefix-suffix.json │ │ ├── external-key.json │ │ ├── external-schema.json │ │ ├── key1-document.json │ │ ├── limits-doc.json │ │ ├── limits-key.json │ │ ├── limits-schema.json │ │ ├── range-encryptedFields-Date.json │ │ ├── range-encryptedFields-DecimalNoPrecision.json │ │ ├── range-encryptedFields-DecimalPrecision.json │ │ ├── range-encryptedFields-DoubleNoPrecision.json │ │ ├── range-encryptedFields-DoublePrecision.json │ │ ├── range-encryptedFields-Int.json │ │ └── range-encryptedFields-Long.json │ ├── kmip-certs/ │ │ ├── ca-ec.pem │ │ ├── client-ec.pem │ │ └── server-ec.pem │ └── mongocrypt/ │ ├── collection-info.json │ ├── command-reply.json │ ├── command.json │ ├── encrypted-command-reply.json │ ├── encrypted-command.json │ ├── encrypted-value.json │ ├── json-schema.json │ ├── key-document.json │ ├── key-filter-keyAltName.json │ ├── key-filter.json │ ├── kms-reply.txt │ ├── list-collections-filter.json │ ├── local-key-document.json │ ├── mongocryptd-command-local.json │ ├── mongocryptd-command-remote.json │ └── mongocryptd-reply.json ├── version/ │ └── version.go └── x/ ├── README.md ├── bsonx/ │ └── bsoncore/ │ ├── array.go │ ├── array_test.go │ ├── bson_arraybuilder.go │ ├── bson_arraybuilder_test.go │ ├── bson_documentbuilder.go │ ├── bson_documentbuilder_test.go │ ├── bsoncore.go │ ├── bsoncore_test.go │ ├── doc.go │ ├── document.go │ ├── document_test.go │ ├── element.go │ ├── element_test.go │ ├── iterator.go │ ├── iterator_test.go │ ├── tables.go │ ├── type.go │ ├── value.go │ └── value_test.go └── mongo/ └── driver/ ├── auth/ │ ├── auth.go │ ├── auth_spec_test.go │ ├── auth_test.go │ ├── aws_conv.go │ ├── conversation.go │ ├── cred.go │ ├── creds/ │ │ ├── awscreds.go │ │ ├── azurecreds.go │ │ ├── credscaching_test.go │ │ ├── doc.go │ │ └── gcpcreds.go │ ├── default.go │ ├── doc.go │ ├── gssapi.go │ ├── gssapi_not_enabled.go │ ├── gssapi_not_supported.go │ ├── gssapi_test.go │ ├── internal/ │ │ └── gssapi/ │ │ ├── gss.go │ │ ├── gss_wrapper.c │ │ ├── gss_wrapper.h │ │ ├── sspi.go │ │ ├── sspi_wrapper.c │ │ └── sspi_wrapper.h │ ├── mongodbaws.go │ ├── mongodbaws_test.go │ ├── oidc.go │ ├── oidc_test.go │ ├── plain.go │ ├── plain_test.go │ ├── sasl.go │ ├── scram.go │ ├── scram_test.go │ ├── speculative_scram_test.go │ ├── speculative_x509_test.go │ ├── util.go │ └── x509.go ├── batch_cursor.go ├── batch_cursor_test.go ├── batches.go ├── batches_test.go ├── command_monitoring_test.go ├── compression.go ├── compression_test.go ├── connstring/ │ ├── connstring.go │ ├── connstring_spec_test.go │ ├── connstring_test.go │ └── initial_dns_seedlist_discovery_prose_test.go ├── crypt.go ├── description/ │ ├── server.go │ └── topology.go ├── dns/ │ └── dns.go ├── driver.go ├── drivertest/ │ ├── channel_conn.go │ ├── channel_netconn.go │ ├── doc.go │ ├── opmsg_deployment.go │ └── opmsg_deployment_test.go ├── errors.go ├── integration/ │ ├── aggregate_test.go │ ├── compressor_test.go │ ├── doc.go │ ├── integration.go │ ├── main_test.go │ └── scram_test.go ├── legacy.go ├── mnet/ │ └── connection.go ├── mongocrypt/ │ ├── binary.go │ ├── binary_test.go │ ├── errors.go │ ├── errors_not_enabled.go │ ├── mongocrypt.go │ ├── mongocrypt_context.go │ ├── mongocrypt_context_not_enabled.go │ ├── mongocrypt_kms_context.go │ ├── mongocrypt_kms_context_not_enabled.go │ ├── mongocrypt_not_enabled.go │ ├── mongocrypt_test.go │ ├── options/ │ │ ├── doc.go │ │ ├── mongocrypt_context_options.go │ │ └── mongocrypt_options.go │ └── state.go ├── ocsp/ │ ├── cache.go │ ├── cache_test.go │ ├── config.go │ ├── ocsp.go │ ├── ocsp_test.go │ └── options.go ├── operation/ │ ├── abort_transaction.go │ ├── aggregate.go │ ├── command.go │ ├── commit_transaction.go │ ├── count.go │ ├── create.go │ ├── create_indexes.go │ ├── create_search_indexes.go │ ├── delete.go │ ├── distinct.go │ ├── doc.go │ ├── drop_collection.go │ ├── drop_database.go │ ├── drop_indexes.go │ ├── drop_search_index.go │ ├── end_sessions.go │ ├── errors.go │ ├── find.go │ ├── find_and_modify.go │ ├── hello.go │ ├── hello_test.go │ ├── list_collections.go │ ├── list_databases.go │ ├── list_indexes.go │ ├── update.go │ └── update_search_index.go ├── operation.go ├── operation_exhaust.go ├── operation_test.go ├── serverapioptions.go ├── session/ │ ├── client_session.go │ ├── client_session_test.go │ ├── cluster_clock.go │ ├── cluster_clock_test.go │ ├── doc.go │ ├── options.go │ ├── server_session.go │ ├── server_session_test.go │ ├── session_pool.go │ └── session_pool_test.go ├── testdata/ │ └── compression.go ├── topology/ │ ├── CMAP_spec_test.go │ ├── DESIGN.md │ ├── cmap_prose_test.go │ ├── connection.go │ ├── connection_errors_test.go │ ├── connection_legacy.go │ ├── connection_options.go │ ├── connection_test.go │ ├── context_listener.go │ ├── diff.go │ ├── diff_test.go │ ├── errors.go │ ├── example_test.go │ ├── fsm.go │ ├── fsm_test.go │ ├── polling_srv_records_test.go │ ├── pool.go │ ├── pool_generation_counter.go │ ├── pool_test.go │ ├── rtt_monitor.go │ ├── rtt_monitor_test.go │ ├── sdam_spec_test.go │ ├── server.go │ ├── server_options.go │ ├── server_rtt_test.go │ ├── server_test.go │ ├── stats.go │ ├── stats_test.go │ ├── tls_connection_source_1_16.go │ ├── tls_connection_source_1_17.go │ ├── topology.go │ ├── topology_errors_test.go │ ├── topology_options.go │ ├── topology_options_test.go │ └── topology_test.go ├── wiremessage/ │ ├── wiremessage.go │ └── wiremessage_test.go └── xoptions/ ├── options.go └── options_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .errcheck-excludes ================================================ (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 ================================================ FILE: .evergreen/config.yml ================================================ ######################################## # 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 # Fail builds when pre tasks fail. pre_error_fails_task: true # Protect the CI from long or indefinite runtimes. exec_timeout_secs: 3600 # What to do when evergreen hits the timeout (`post:` tasks are run automatically) timeout: - command: subprocess.exec params: binary: bash args: [ls, -la] functions: setup-system: # Executes clone and applies the submitted patch, if any - command: git.get_project type: system params: directory: src/go.mongodb.org/mongo-driver - command: shell.exec params: working_dir: "src/go.mongodb.org/mongo-driver" script: | git submodule update --init # Make an env.sh and evergreen expansion file with dynamic values - command: subprocess.exec params: binary: bash working_dir: src/go.mongodb.org/mongo-driver env: GOROOT: ${GO_DIST} IS_PATCH: ${is_patch} VERSION_ID: ${version_id} # Define an alias for the task runner script. TASK_RUNNER_ALIAS: &task-runner src/go.mongodb.org/mongo-driver/.evergreen/run-task.sh args: [.evergreen/setup-system.sh] - command: expansions.update params: file: src/go.mongodb.org/mongo-driver/expansion.yml - command: subprocess.exec params: binary: bash include_expansions_in_env: ["PROJECT_DIRECTORY"] args: - "${DRIVERS_TOOLS}/.evergreen/setup.sh" handle-test-artifacts: - command: gotest.parse_files params: optional_output: "true" files: - "src/go.mongodb.org/mongo-driver/*.suite" - command: ec2.assume_role params: role_arn: ${assume_role_arn} - command: s3.put params: aws_key: ${AWS_ACCESS_KEY_ID} aws_secret: ${AWS_SECRET_ACCESS_KEY} aws_session_token: ${AWS_SESSION_TOKEN} local_file: ${DRIVERS_TOOLS}/.evergreen/test_logs.tar.gz remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-drivers-tools-logs.tar.gz bucket: ${aws_bucket} permissions: public-read content_type: ${content_type|application/x-gzip} display_name: "drivers-tools-logs.tar.gz" - command: s3.put params: aws_key: ${AWS_ACCESS_KEY_ID} aws_secret: ${AWS_SECRET_ACCESS_KEY} aws_session_token: ${AWS_SESSION_TOKEN} optional: true local_file: ${PROJECT_DIRECTORY}/fuzz.tgz remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/${task_id}-${execution}-fuzz.tgz bucket: ${aws_bucket} permissions: public-read content_type: application/x-gzip display_name: "fuzz.tgz" - command: subprocess.exec params: binary: bash args: [*task-runner, evg-gather-test-suites] - command: s3.put params: aws_key: ${AWS_ACCESS_KEY_ID} aws_secret: ${AWS_SECRET_ACCESS_KEY} aws_session_token: ${AWS_SESSION_TOKEN} local_file: src/go.mongodb.org/mongo-driver/test_suite.tgz optional: true remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-test_suite.tgz bucket: ${aws_bucket} permissions: public-read content_type: ${content_type|text/plain} display_name: test_suite.tgz bootstrap-mongohoused: - command: ec2.assume_role params: role_arn: ${aws_test_secrets_role} - command: subprocess.exec params: binary: bash add_expansions_to_env: true args: - ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/pull-mongohouse-image.sh - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-image.sh bootstrap-mongo-orchestration: - command: subprocess.exec params: binary: bash env: MONGODB_VERSION: ${VERSION} include_expansions_in_env: [TOPOLOGY, AUTH, SSL, ORCHESTRATION_FILE, REQUIRE_API_VERSION, LOAD_BALANCER] args: ["${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh"] - command: expansions.update params: file: mo-expansion.yml ocsp-bootstrap-mongo-orchestration: - command: subprocess.exec params: binary: bash env: MONGODB_VERSION: ${VERSION} include_expansions_in_env: [TOPOLOGY, AUTH, SSL, ORCHESTRATION_FILE] args: ["${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh"] - command: expansions.update params: file: mo-expansion.yml teardown: - command: subprocess.exec params: binary: bash args: # Ensure the instance profile is reassigned for aws tests. - ${DRIVERS_TOOLS}/.evergreen/auth_aws/teardown.sh - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/csfle/teardown.sh - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/ocsp/teardown.sh - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/teardown.sh assume-test-secrets-ec2-role: - command: ec2.assume_role params: role_arn: ${aws_test_secrets_role} duration_seconds: 1800 run-oidc-auth-test-with-test-credentials: - command: subprocess.exec type: test params: binary: bash env: OIDC: oidc include_expansions_in_env: [DRIVERS_TOOLS, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] args: [*task-runner, test-oidc] run-tests: - command: subprocess.exec params: binary: bash env: GO_BUILD_TAGS: "cse,mongointernal" include_expansions_in_env: ["TOPOLOGY", "AUTH", "SSL", "SKIP_CSOT_TESTS", "MONGODB_URI", "CRYPT_SHARED_LIB_PATH", "SKIP_CRYPT_SHARED_LIB", "RACE", "MONGO_GO_DRIVER_COMPRESSOR", "REQUIRE_API_VERSION", "LOAD_BALANCER"] args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: bash args: [*task-runner, "${DEFAULT_TASK}"] create-api-report: - command: subprocess.exec type: test params: binary: bash add_expansions_to_env: true env: BASE_SHA: "${revision}" HEAD_SHA: "${github_commit}" args: [*task-runner, api-report] "backport pr": - command: subprocess.exec type: test params: binary: bash env: COMMIT: "${github_commit}" PR_TASK: backport-pr args: [*task-runner, pr-task] send-perf-data: # Here we begin to generate the request to send the data to SPS - command: shell.exec params: script: | # We use the requester expansion to determine whether the data is from a mainline evergreen run or not if [ "${requester}" == "commit" ]; then is_mainline=true else is_mainline=false fi # We parse the username out of the order_id as patches append that in and SPS does not need that information parsed_order_id=$(echo "${revision_order_id}" | awk -F'_' '{print $NF}') # Submit the performance data to the SPS endpoint response=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X 'POST' \ "https://performance-monitoring-api.corp.mongodb.com/raw_perf_results/cedar_report?project=${project_id}&version=${version_id}&variant=${build_variant}&order=$parsed_order_id&task_name=${task_name}&task_id=${task_id}&execution=${execution}&mainline=$is_mainline" \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d @src/go.mongodb.org/mongo-driver/perf.json) http_status=$(echo "$response" | grep "HTTP_STATUS" | awk -F':' '{print $2}') response_body=$(echo "$response" | sed '/HTTP_STATUS/d') # We want to throw an error if the data was not successfully submitted if [ "$http_status" -ne 200 ]; then echo "Error: Received HTTP status $http_status" echo "Response Body: $response_body" exit 1 fi echo "Response Body: $response_body" echo "HTTP Status: $http_status" send-perf-pr-comment: - command: subprocess.exec type: test params: binary: bash add_expansions_to_env: true env: VERSION_ID: ${version_id} BASE_SHA: "${revision}" HEAD_SHA: "${github_commit}" include_expansions_in_env: [PERF_URI_PRIVATE_ENDPOINT] args: [*task-runner, perf-pr-comment] run-enterprise-auth-tests: - command: ec2.assume_role params: role_arn: "${aws_test_secrets_role}" - command: subprocess.exec params: binary: bash include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] env: TEST_ENTERPRISE_AUTH: plain args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: bash args: [*task-runner, --silent, evg-test-enterprise-auth] run-enterprise-gssapi-auth-tests: - command: ec2.assume_role params: role_arn: "${aws_test_secrets_role}" - command: subprocess.exec params: binary: bash include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] env: TEST_ENTERPRISE_AUTH: gssapi args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: bash args: [*task-runner, --silent, evg-test-enterprise-auth] run-atlas-test: - command: ec2.assume_role params: role_arn: "${aws_test_secrets_role}" - command: subprocess.exec params: binary: bash include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] env: TEST_ATLAS_CONNECT: "1" args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: bash args: [*task-runner, test-atlas-connect] run-ocsp-test: - command: subprocess.exec params: binary: bash env: TOPOLOGY: server AUTH: auth SSL: ssl include_expansions_in_env: [OCSP_ALGORITHM, MONGODB_URI] args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: bash include_expansions_in_env: [OCSP_TLS_SHOULD_SUCCEED] args: [*task-runner, evg-test-ocsp] run-versioned-api-test: - command: subprocess.exec params: binary: bash env: GO_BUILD_TAGS: cse include_expansions_in_env: [AUTH, SSL, MONGODB_URI, TOPOLOGY, MONGO_GO_DRIVER_COMPRESSOR, REQUIRE_API_VERSION, SKIP_CRYPT_SHARED_LIB, CRYPT_SHARED_LIB_PATH] args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: bash args: [*task-runner, evg-test-versioned-api] run-load-balancer-tests: - command: subprocess.exec params: binary: bash include_expansions_in_env: [SINGLE_MONGOS_LB_URI, MULTI_MONGOS_LB_URI, AUTH, SSL, MONGO_GO_DRIVER_COMPRESSOR] env: LOAD_BALANCER: "true" args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: bash args: [*task-runner, evg-test-load-balancers] run-docker-test: - command: subprocess.exec type: test params: binary: "bash" env: TASKFILE_TARGET: test-short args: [*task-runner, run-docker] - command: subprocess.exec type: test params: binary: "bash" env: TOPOLOGY: sharded_cluster TASKFILE_TARGET: test-short args: [*task-runner, run-docker] run-goleak-test: - command: subprocess.exec type: test params: binary: "bash" include_expansions_in_env: ["MONGODB_URI"] args: [*task-runner, test-goleak] "run oidc k8s test": - command: subprocess.exec type: test params: binary: bash include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, VARIANT, DRIVERS_TOOLS] env: OIDC_ENV: k8s args: [*task-runner, test-oidc-remote] run-ocsp-server: - command: subprocess.exec params: binary: bash background: true include_expansions_in_env: [SERVER_TYPE, OCSP_ALGORITHM] args: - ${DRIVERS_TOOLS}/.evergreen/ocsp/setup.sh run-load-balancer: - command: subprocess.exec params: binary: "bash" include_expansions_in_env: ["MONGODB-AWS", "MONGODB_URI"] args: ["${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh", start] - command: expansions.update params: file: lb-expansion.yml run-search-index-tests: - command: subprocess.exec type: test params: binary: "bash" env: SEARCH_INDEX_URI: "${SEARCH_INDEX_URI}" args: [*task-runner, evg-test-search-index] add-aws-auth-variables-to-file: - command: ec2.assume_role params: role_arn: ${aws_test_secrets_role} - command: subprocess.exec type: test params: include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] binary: "bash" args: - ${DRIVERS_TOOLS}/.evergreen/auth_aws/setup-secrets.sh run-aws-auth-test-with-regular-aws-credentials: - command: subprocess.exec type: test params: binary: "bash" env: AWS_TEST: regular args: [*task-runner, evg-test-aws] run-aws-auth-test-with-assume-role-credentials: - command: subprocess.exec type: test params: binary: "bash" env: AWS_TEST: assume-role args: [*task-runner, evg-test-aws] run-aws-auth-test-with-aws-EC2-credentials: - command: subprocess.exec type: test params: binary: bash include_expansions_in_env: [SKIP_EC2_AUTH_TEST] env: AWS_TEST: ec2 args: [*task-runner, evg-test-aws] run-aws-auth-test-with-aws-credentials-as-environment-variables: - command: subprocess.exec type: test params: binary: "bash" env: AWS_TEST: env-creds args: [*task-runner, evg-test-aws] run-aws-auth-test-with-aws-credentials-and-session-token-as-environment-variables: - command: subprocess.exec type: test params: binary: "bash" env: AWS_TEST: session-creds args: [*task-runner, evg-test-aws] run-aws-ECS-auth-test: - command: subprocess.exec type: test params: binary: "bash" include_expansions_in_env: [SKIP_ECS_AUTH_TEST] args: [*task-runner, evg-test-aws-ecs] run-aws-auth-test-with-aws-web-identity-credentials: - command: subprocess.exec type: test params: binary: bash include_expansions_in_env: [SKIP_WEB_IDENTITY_AUTH_TEST] env: AWS_TEST: web-identity args: [*task-runner, evg-test-aws] - command: subprocess.exec type: test params: binary: bash env: AWS_ROLE_SESSION_NAME: test AWS_TEST: web-identity include_expansions_in_env: [SKIP_WEB_IDENTITY_AUTH_TEST] args: [*task-runner, evg-test-aws] start-cse-servers: - command: ec2.assume_role params: role_arn: ${aws_test_secrets_role} - command: subprocess.exec params: working_dir: src/go.mongodb.org/mongo-driver binary: bash background: true include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "DRIVERS_TOOLS"] # This cannot use task because it will hang on Windows. args: [etc/setup-encryption.sh] - command: subprocess.exec params: binary: bash args: ["${DRIVERS_TOOLS}/.evergreen/csfle/await-servers.sh"] run-kmip-tests: - command: subprocess.exec params: binary: "bash" env: GO_BUILD_TAGS: cse include_expansions_in_env: [AUTH, SSL, MONGODB_URI, TOPOLOGY, MONGO_GO_DRIVER_COMPRESSOR] args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: "bash" env: KMS_MOCK_SERVERS_RUNNING: "true" args: [*task-runner, evg-test-kmip] run-client-side-encryption-test: - command: subprocess.exec params: binary: "bash" env: GO_BUILD_TAGS: cse include_expansions_in_env: [AUTH, SSL, MONGODB_URI, TOPOLOGY, MONGO_GO_DRIVER_COMPRESSOR, CRYPT_SHARED_LIB_PATH] args: [*task-runner, setup-test] - command: subprocess.exec type: test retry_on_failure: true params: binary: "bash" env: KMS_MOCK_SERVERS_RUNNING: "true" KMS_FAILPOINT_CA_FILE: "${DRIVERS_TOOLS}/.evergreen/x509gen/ca.pem" KMS_FAILPOINT_SERVER_RUNNING: "true" args: [*task-runner, evg-test-client-side-encryption] run-fuzz-tests: - command: subprocess.exec type: test params: binary: "bash" args: [*task-runner, run-fuzz] pre: - func: setup-system post: - func: teardown - func: handle-test-artifacts tasks: - name: static-analysis tags: ["static-analysis"] commands: - command: subprocess.exec params: binary: bash args: [*task-runner, check-fmt, check-license, check-modules, lint] - name: govulncheck tags: ["static-analysis"] commands: - command: subprocess.exec type: test params: binary: bash args: [*task-runner, govulncheck] - name: pull-request-helpers allowed_requesters: ["patch", "github_pr"] commands: - func: assume-test-secrets-ec2-role - func: "create-api-report" - name: backport-pr allowed_requesters: ["commit"] commands: - func: "backport pr" - name: perf tags: ["performance"] exec_timeout_secs: 7200 commands: - func: bootstrap-mongo-orchestration vars: VERSION: "v6.0-perf" TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" SKIP_LEGACY_SHELL: "true" - command: subprocess.exec params: binary: bash args: [*task-runner, driver-benchmark] - func: assume-test-secrets-ec2-role - func: send-perf-data - func: send-perf-pr-comment - name: test-standalone-noauth-nossl tags: ["test", "standalone"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - name: test-standalone-noauth-nossl-snappy-compression tags: ["test", "standalone", "compression", "snappy"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" 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: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" 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: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" MONGO_GO_DRIVER_COMPRESSOR: "zstd" - name: test-standalone-auth-ssl tags: ["test", "standalone", "authssl"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" AUTH: "auth" SSL: "ssl" - name: test-standalone-auth-nossl tags: ["test", "standalone", "authssl"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "auth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" AUTH: "auth" SSL: "nossl" - name: test-standalone-auth-ssl-snappy-compression tags: ["test", "standalone", "authssl", "compression", "snappy"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" 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: TOPOLOGY: "server" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" 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: TOPOLOGY: "server" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "server" AUTH: "auth" SSL: "ssl" MONGO_GO_DRIVER_COMPRESSOR: "zstd" - name: test-ocsp-rsa-valid-cert-server-staples tags: ["ocsp", "ocsp-rsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: valid - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-rsa-invalid-cert-server-staples tags: ["ocsp", "ocsp-rsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: revoked - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-rsa-valid-cert-server-does-not-staple tags: ["ocsp", "ocsp-rsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: valid - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-rsa-invalid-cert-server-does-not-staple tags: ["ocsp", "ocsp-rsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: revoked - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-rsa-soft-fail tags: ["ocsp", "ocsp-rsa"] commands: - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-rsa-malicious-invalid-cert-mustStaple-server-does-not-staple tags: ["ocsp", "ocsp-rsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: revoked - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-mustStaple-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-rsa-malicious-no-responder-mustStaple-server-does-not-staple tags: ["ocsp", "ocsp-rsa"] commands: - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-mustStaple-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-rsa-delegate-valid-cert-server-staples tags: ["ocsp", "ocsp-rsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: valid-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-rsa-delegate-invalid-cert-server-staples tags: ["ocsp", "ocsp-rsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: revoked-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-rsa-delegate-valid-cert-server-does-not-staple tags: ["ocsp", "ocsp-rsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: valid-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-rsa-delegate-invalid-cert-server-does-not-staple tags: ["ocsp", "ocsp-rsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: revoked-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-rsa-delegate-malicious-invalid-cert-mustStaple-server-does-not-staple tags: ["ocsp", "ocsp-rsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "rsa" SERVER_TYPE: revoked-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "rsa-basic-tls-ocsp-mustStaple-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "rsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-ecdsa-valid-cert-server-staples tags: ["ocsp", "ocsp-ecdsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: valid - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-ecdsa-invalid-cert-server-staples tags: ["ocsp", "ocsp-ecdsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: revoked - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple tags: ["ocsp", "ocsp-ecdsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: valid - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-ecdsa-invalid-cert-server-does-not-staple tags: ["ocsp", "ocsp-ecdsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: revoked - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-ecdsa-soft-fail tags: ["ocsp", "ocsp-ecdsa"] commands: - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-ecdsa-malicious-invalid-cert-mustStaple-server-does-not-staple tags: ["ocsp", "ocsp-ecdsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: revoked - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-ecdsa-malicious-no-responder-mustStaple-server-does-not-staple tags: ["ocsp", "ocsp-ecdsa"] commands: - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-ecdsa-delegate-valid-cert-server-staples tags: ["ocsp", "ocsp-ecdsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: valid-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-ecdsa-delegate-invalid-cert-server-staples tags: ["ocsp", "ocsp-ecdsa", "ocsp-staple"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: revoked-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-mustStaple.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-ecdsa-delegate-valid-cert-server-does-not-staple tags: ["ocsp", "ocsp-ecdsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: valid-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "true" - name: test-ocsp-ecdsa-delegate-invalid-cert-server-does-not-staple tags: ["ocsp", "ocsp-ecdsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: revoked-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-ocsp-ecdsa-delegate-malicious-invalid-cert-mustStaple-server-does-not-staple tags: ["ocsp", "ocsp-ecdsa"] commands: - func: run-ocsp-server vars: OCSP_ALGORITHM: "ecdsa" SERVER_TYPE: revoked-delegate - func: ocsp-bootstrap-mongo-orchestration vars: ORCHESTRATION_FILE: "ecdsa-basic-tls-ocsp-mustStaple-disableStapling.json" - func: run-ocsp-test vars: OCSP_ALGORITHM: "ecdsa" OCSP_TLS_SHOULD_SUCCEED: "false" - name: test-docker-runner commands: - func: bootstrap-mongo-orchestration - func: run-docker-test - name: test-goroutine-leaks-replicaset tags: ["goleak"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "replica_set" AUTH: "noauth" SSL: "nossl" - func: run-goleak-test - name: test-goroutine-leaks-sharded tags: ["goleak"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" - func: run-goleak-test - name: test-load-balancer-noauth-nossl tags: ["load-balancer"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" LOAD_BALANCER: "true" - func: run-load-balancer - func: run-load-balancer-tests vars: AUTH: "noauth" SSL: "nossl" - name: test-load-balancer-auth-ssl tags: ["load-balancer"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "ssl" LOAD_BALANCER: "true" - func: run-load-balancer - func: run-load-balancer-tests vars: AUTH: "auth" SSL: "ssl" - name: test-race tags: ["race"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "replica_set" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "replica_set" AUTH: "noauth" SSL: "nossl" RACE: "-race" - name: test-replicaset-noauth-nossl tags: ["test", "replicaset"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "replica_set" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "replica_set" AUTH: "noauth" SSL: "nossl" - name: test-replicaset-auth-ssl tags: ["test", "replicaset", "authssl"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "replica_set" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "replica_set" AUTH: "auth" SSL: "ssl" - name: test-replicaset-auth-ssl-mongocryptd tags: ["test", "replicaset", "authssl", "mongocryptd"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "replica_set" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "replica_set" AUTH: "auth" SSL: "ssl" # Don't use the crypt_shared library, which should cause all of the tests to fall # back to using mongocryptd instead of crypt_shared. SKIP_CRYPT_SHARED_LIB: "true" - name: test-replicaset-auth-nossl tags: ["test", "replicaset", "authssl"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "replica_set" AUTH: "auth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "replica_set" AUTH: "auth" SSL: "nossl" - name: test-sharded-noauth-nossl tags: ["test", "sharded"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" - name: test-sharded-noauth-nossl-snappy-compression tags: ["test", "sharded", "compression", "snappy"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" 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: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" 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: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" AUTH: "noauth" SSL: "nossl" MONGO_GO_DRIVER_COMPRESSOR: "zstd" - name: test-sharded-auth-ssl tags: ["test", "sharded", "authssl"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "ssl" - name: test-sharded-auth-ssl-snappy-compression tags: ["test", "sharded", "authssl", "compression", "snappy"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" 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: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" 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: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "ssl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "ssl" MONGO_GO_DRIVER_COMPRESSOR: "zstd" - name: test-sharded-auth-nossl tags: ["test", "sharded", "authssl"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "nossl" - func: start-cse-servers - func: run-tests vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "nossl" - name: test-enterprise-auth-plain tags: ["test", "enterprise-auth"] commands: - func: run-enterprise-auth-tests - name: test-enterprise-auth-gssapi tags: ["test", "enterprise-auth"] commands: - func: run-enterprise-gssapi-auth-tests vars: MONGO_GO_DRIVER_COMPRESSOR: "snappy" # Build the compilecheck submodule with all supported versions of Go >= # the minimum supported version. - name: go-build tags: ["compile-check"] commands: - command: subprocess.exec params: binary: bash args: [*task-runner, build-compile-check-all] # Build with the same Go version that we're using for tests. - name: build tags: ["compile-check"] commands: - command: subprocess.exec params: binary: bash # Set the GO_VERSION to empty string to use the Go installation in the # PATH. env: GO_VERSION: "" args: [*task-runner, build] - name: "atlas-test" commands: - func: "run-atlas-test" - name: "aws-auth-test" commands: - func: bootstrap-mongo-orchestration vars: AUTH: "auth" ORCHESTRATION_FILE: "auth-aws.json" TOPOLOGY: "server" - func: add-aws-auth-variables-to-file - func: run-aws-auth-test-with-regular-aws-credentials - func: run-aws-auth-test-with-assume-role-credentials - func: run-aws-auth-test-with-aws-credentials-as-environment-variables - func: run-aws-auth-test-with-aws-credentials-and-session-token-as-environment-variables - func: run-aws-auth-test-with-aws-EC2-credentials - func: run-aws-ECS-auth-test - func: run-aws-auth-test-with-aws-web-identity-credentials - name: "test-standalone-versioned-api" tags: ["versioned-api"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "auth" SSL: "nossl" REQUIRE_API_VERSION: true - func: start-cse-servers - func: run-versioned-api-test vars: TOPOLOGY: "server" AUTH: "auth" SSL: "nossl" REQUIRE_API_VERSION: true - name: "test-sharded-versioned-api" tags: ["versioned-api"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "nossl" REQUIRE_API_VERSION: true - func: start-cse-servers - func: run-versioned-api-test vars: TOPOLOGY: "sharded_cluster" AUTH: "auth" SSL: "nossl" REQUIRE_API_VERSION: true - name: "test-standalone-versioned-api-test-commands" tags: ["versioned-api"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" ORCHESTRATION_FILE: "versioned-api-testing.json" - func: start-cse-servers - func: run-versioned-api-test vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - name: "test-kms-kmip" tags: ["kms-kmip"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-kmip-tests vars: TOPOLOGY: "server" AUTH: "noauth" SSL: "nossl" - name: "test-client-side-encryption" tags: ["client-side-encryption-test"] commands: - func: bootstrap-mongo-orchestration vars: TOPOLOGY: "replica_set" AUTH: "noauth" SSL: "nossl" - func: start-cse-servers - func: run-client-side-encryption-test vars: TOPOLOGY: "replica_set" AUTH: "noauth" SSL: "nossl" - name: "testgcpkms-task" commands: - command: subprocess.exec type: test params: binary: bash args: [*task-runner, test-gcpkms] - name: "testgcpkms-fail-task" # testgcpkms-fail-task runs in a non-GCE environment. # It is expected to fail to obtain GCE credentials. commands: - command: subprocess.exec type: test params: binary: bash env: EXPECT_ERROR: "1" args: [*task-runner, test-gcpkms] - name: "testawskms-task" commands: - func: assume-test-secrets-ec2-role - command: subprocess.exec type: test params: binary: "bash" add_expansions_to_env: true args: [*task-runner, test-awskms] - name: "testawskms-fail-task" # testawskms-fail-task runs without environment variables. # It is expected to fail to obtain credentials. commands: - func: assume-test-secrets-ec2-role - command: subprocess.exec type: test params: binary: "bash" add_expansions_to_env: true env: EXPECT_ERROR: 'status=400' args: [*task-runner, test-awskms] - name: "testazurekms-task" commands: - command: subprocess.exec type: test params: binary: bash add_expansions_to_env: true args: [*task-runner, test-azurekms] - name: "testazurekms-fail-task" # testazurekms-fail-task runs without environment variables. # It is expected to fail to obtain credentials. commands: - func: assume-test-secrets-ec2-role - command: subprocess.exec type: test params: binary: bash add_expansions_to_env: true env: EXPECT_ERROR: "1" args: [*task-runner, test-azurekms] - name: "test-fuzz" commands: - func: bootstrap-mongo-orchestration - func: run-fuzz-tests - name: "test-aws-lambda-deployed" commands: - command: subprocess.exec type: test params: binary: bash add_expansions_to_env: true env: TEST_LAMBDA_DIRECTORY: ${PROJECT_DIRECTORY}/internal/cmd/faas/awslambda LAMBDA_STACK_NAME: dbx-go-lambda AWS_REGION: us-east-1 args: [*task-runner, evg-test-deployed-lambda-aws] - name: "oidc-auth-test" commands: - func: "run-oidc-auth-test-with-test-credentials" - name: "oidc-auth-test-azure" commands: - command: subprocess.exec type: test params: binary: bash env: OIDC_ENV: azure args: [*task-runner, test-oidc-remote] - name: "oidc-auth-test-gcp" commands: - command: subprocess.exec type: test params: binary: bash env: OIDC_ENV: gcp args: [*task-runner, test-oidc-remote] - name: "oidc-auth-test-k8s" commands: - func: assume-test-secrets-ec2-role - func: "run oidc k8s test" vars: VARIANT: eks - func: "run oidc k8s test" vars: VARIANT: gke - func: "run oidc k8s test" vars: VARIANT: aks - name: "test-search-index" commands: - func: "bootstrap-mongo-orchestration" vars: VERSION: "latest" TOPOLOGY: "replica_set" - func: "run-search-index-tests" axes: - id: version display_name: MongoDB Version values: - id: "8.0" display_name: "8.0" variables: VERSION: "8.0" - id: "7.0" display_name: "7.0" variables: VERSION: "7.0" - id: "6.0" display_name: "6.0" variables: VERSION: "6.0" - id: "5.0" display_name: "5.0" variables: VERSION: "5.0" - id: "4.4" display_name: "4.4" variables: VERSION: "4.4" - id: "4.2" display_name: "4.2" variables: VERSION: "4.2" - id: "rapid" display_name: "rapid" variables: VERSION: "rapid" - id: "latest" display_name: "latest" variables: VERSION: "latest" # OSes that require >= 3.2 for SSL - id: os-ssl-32 display_name: OS values: - id: "windows-64" display_name: "Windows 64-bit" run_on: - windows-2022-latest-small variables: GCC_PATH: "/cygdrive/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin" GO_DIST: "C:\\golang\\go1.25" VENV_BIN_DIR: "Scripts" DEFAULT_TASK: evg-test # CSOT tests are unreliable on our slow Windows hosts. SKIP_CSOT_TESTS: true - id: "rhel87-64" display_name: "RHEL 8.7" run_on: rhel8.7-large variables: GO_DIST: "/opt/golang/go1.25" DEFAULT_TASK: evg-test - id: "macos" display_name: "MacOS 14.0" run_on: macos-14 batchtime: 1440 # Run at most once per 24 hours. variables: GO_DIST: "/opt/golang/go1.25" DEFAULT_TASK: evg-test-load-balancers # CSOT tests are unreliable on our slow macOS hosts. SKIP_CSOT_TESTS: true # OSes that require >= 4.0 for SSL - id: os-ssl-40 display_name: OS values: - id: "windows-64" display_name: "Windows 64-bit" run_on: - windows-2022-latest-small variables: GCC_PATH: "/cygdrive/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin" GO_DIST: "C:\\golang\\go1.25" VENV_BIN_DIR: "Scripts" DEFAULT_TASK: evg-test # CSOT tests are unreliable on our slow Windows hosts. SKIP_CSOT_TESTS: true - id: "rhel87-64" display_name: "RHEL 8.7" run_on: rhel8.7-large variables: GO_DIST: "/opt/golang/go1.25" DEFAULT_TASK: evg-test - id: "macos" display_name: "MacOS 14.0" run_on: macos-14 batchtime: 1440 # Run at most once per 24 hours. variables: GO_DIST: "/opt/golang/go1.25" DEFAULT_TASK: evg-test-load-balancers # CSOT tests are unreliable on our slow macOS hosts. SKIP_CSOT_TESTS: true - id: ocsp-rhel-87 display_name: OS values: - id: "rhel87" display_name: "RHEL 8.7" run_on: rhel8.7-large variables: GO_DIST: "/opt/golang/go1.25" DEFAULT_TASK: evg-test - id: os-aws-auth display_name: OS values: - id: "windows-64-2022-latest-small" display_name: "Windows 64-bit" run_on: - windows-2022-latest-small variables: GCC_PATH: "/cygdrive/c/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin" GO_DIST: "C:\\golang\\go1.25" SKIP_ECS_AUTH_TEST: true DEFAULT_TASK: evg-test # CSOT tests are unreliable on our slow Windows hosts. SKIP_CSOT_TESTS: true - id: "ubuntu2004-64" display_name: "Ubuntu 20.04" run_on: ubuntu2004-test variables: GO_DIST: "/opt/golang/go1.25" DEFAULT_TASK: evg-test - id: "macos" display_name: "MacOS 14.0" run_on: macos-14 batchtime: 1440 # Run at most once per 24 hours. variables: GO_DIST: "/opt/golang/go1.25" SKIP_ECS_AUTH_TEST: true SKIP_EC2_AUTH_TEST: true SKIP_WEB_IDENTITY_AUTH_TEST: true DEFAULT_TASK: evg-test-load-balancers # CSOT tests are unreliable on our slow macOS hosts. SKIP_CSOT_TESTS: true - id: os-faas-80 display_name: OS values: - id: "rhel87-large" display_name: "RHEL 8.7" run_on: rhel8.7-large variables: GO_DIST: "/opt/golang/go1.25" task_groups: - name: testgcpkms_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: binary: "bash" add_expansions_to_env: true args: - ${DRIVERS_TOOLS}/.evergreen/csfle/gcpkms/setup.sh teardown_group: - command: subprocess.exec params: binary: "bash" args: - ${DRIVERS_TOOLS}/.evergreen/csfle/gcpkms/teardown.sh - func: teardown - func: handle-test-artifacts tasks: - testgcpkms-task - name: testazurekms_task_group setup_group_can_fail_task: true teardown_task_can_fail_task: true setup_group_timeout_secs: 1800 # 30 minutes setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: binary: bash add_expansions_to_env: true env: AZUREKMS_VMNAME_PREFIX: GODRIVER args: - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/setup.sh teardown_group: - command: subprocess.exec params: binary: "bash" args: - ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/teardown.sh - func: teardown - func: handle-test-artifacts tasks: - testazurekms-task - name: testoidc_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 # TODO(DRIVERS-3141): Uncomment the following line once the teardown bug is # fixed. See DRIVERS-3141 for more context. # # teardown_task_can_fail_task: true teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: binary: bash include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] env: MONGODB_VERSION: "8.0" args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/setup.sh teardown_task: - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/teardown.sh - func: teardown - func: handle-test-artifacts tasks: - oidc-auth-test - name: testazureoidc_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 teardown_task_can_fail_task: true teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: binary: bash add_expansions_to_env: true env: AZUREOIDC_VMNAME_PREFIX: "GO_DRIVER" args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/setup.sh teardown_task: - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/teardown.sh - func: teardown - func: handle-test-artifacts tasks: - oidc-auth-test-azure - name: testgcpoidc_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 teardown_task_can_fail_task: true teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: binary: bash add_expansions_to_env: true env: AZUREOIDC_VMNAME_PREFIX: "GO_DRIVER" args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh teardown_task: - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh - func: teardown - func: handle-test-artifacts tasks: - oidc-auth-test-gcp - name: testk8soidc_task_group setup_group_can_fail_task: true setup_group_timeout_secs: 1800 teardown_task_can_fail_task: true teardown_group_timeout_secs: 180 # 3 minutes (max allowed time) setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: binary: bash include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh teardown_group: - command: subprocess.exec params: binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh - func: teardown - func: handle-test-artifacts tasks: - oidc-auth-test-k8s - name: test-aws-lambda-task-group setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: working_dir: src/go.mongodb.org/mongo-driver binary: bash include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN] env: MONGODB_VERSION: ${VERSION} LAMBDA_STACK_NAME: dbx-go-lambda args: - ${DRIVERS_TOOLS}/.evergreen/atlas/setup.sh teardown_group: - command: subprocess.exec params: working_dir: src/go.mongodb.org/mongo-driver binary: bash add_expansions_to_env: true env: LAMBDA_STACK_NAME: dbx-go-lambda AWS_REGION: us-east-1 args: - ${DRIVERS_TOOLS}/.evergreen/atlas/teardown.sh - func: teardown - func: handle-test-artifacts setup_group_can_fail_task: true setup_group_timeout_secs: 1800 tasks: - test-aws-lambda-deployed - name: test-search-index-task-group setup_group: - func: setup-system - func: assume-test-secrets-ec2-role - command: subprocess.exec params: working_dir: src/go.mongodb.org/mongo-driver binary: bash include_expansions_in_env: [AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, MONGODB_URI] env: MONGODB_VERSION: ${VERSION} LAMBDA_STACK_NAME: dbx-go-lambda args: - ${DRIVERS_TOOLS}/.evergreen/atlas/setup.sh - command: expansions.update params: file: src/go.mongodb.org/mongo-driver/atlas-expansion.yml - command: shell.exec params: working_dir: src/go.mongodb.org/mongo-driver shell: bash script: |- echo "SEARCH_INDEX_URI: ${MONGODB_URI}" > atlas-expansion.yml - command: expansions.update params: file: src/go.mongodb.org/mongo-driver/atlas-expansion.yml teardown_group: - command: subprocess.exec params: working_dir: src/go.mongodb.org/mongo-driver binary: bash args: - ${DRIVERS_TOOLS}/.evergreen/atlas/teardown.sh - func: teardown - func: handle-test-artifacts setup_group_can_fail_task: true setup_group_timeout_secs: 1800 tasks: - test-search-index buildvariants: - name: static-analysis tags: ["pullrequest"] display_name: "Static Analysis" run_on: - rhel8.7-small expansions: # Keep this in sync with go version used in etc/golangci-lint.sh GO_DIST: "/opt/golang/go1.25" tasks: - name: ".static-analysis" - name: pull-request-helpers tags: ["pullrequest"] display_name: "Pull Request Helpers" run_on: - rhel8.7-small expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: "pull-request-helpers" - name: perf tags: ["pullrequest"] display_name: "Performance" run_on: rhel90-dbx-perf-large expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: ".performance" - name: build-check tags: ["pullrequest"] display_name: "Compile Only Checks" run_on: - ubuntu2204-small expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: ".compile-check" - name: backport-pr display_name: "Backport PR" run_on: - rhel8.7-small expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: "backport-pr" - name: atlas-test tags: ["pullrequest"] display_name: "Atlas test" run_on: - rhel8.7-large expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: "atlas-test" - name: docker-runner-test tags: ["pullrequest"] display_name: "Docker Runner Test" run_on: - ubuntu2204-large expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: "test-docker-runner" - name: goroutine-leaks-test tags: ["pullrequest"] display_name: "Goroutine Leaks Test" run_on: - ubuntu2204-large expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: ".goleak" - matrix_name: "tests-rhel-44-plus-zlib-zstd-support" tags: ["pullrequest"] matrix_spec: {version: ["4.2", "4.4", "5.0", "6.0", "7.0", "8.0"], os-ssl-40: ["rhel87-64"]} display_name: "${version} ${os-ssl-40}" tasks: - name: ".test !.enterprise-auth !.snappy" - matrix_name: "tests-windows-42-plus-zlib-zstd-support" matrix_spec: {version: ["4.2", "4.4", "5.0", "6.0", "7.0"], os-ssl-40: ["windows-64"]} display_name: "${version} ${os-ssl-40}" tasks: - name: ".test !.enterprise-auth !.snappy" - matrix_name: "tests-windows-80-zlib-zstd-support" tags: ["pullrequest"] matrix_spec: {version: ["8.0"], os-ssl-40: ["windows-64"]} display_name: "${version} ${os-ssl-40}" tasks: - name: ".test !.enterprise-auth !.snappy" - matrix_name: "tests-latest-rapid-zlib-zstd-support" matrix_spec: {version: ["latest", "rapid"], os-ssl-40: ["windows-64", "rhel87-64"]} display_name: "${version} ${os-ssl-40}" 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" - matrix_name: "aws-auth-test" matrix_spec: {version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest", "rapid"], os-aws-auth: "*"} display_name: "MONGODB-AWS Auth ${version} ${os-aws-auth}" tasks: - name: "aws-auth-test" - matrix_name: "ocsp-test" matrix_spec: {version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest", "rapid"], ocsp-rhel-87: ["rhel87"]} display_name: "OCSP ${version} ${ocsp-rhel-87}" batchtime: 20160 # Use a batchtime of 14 days as suggested by the OCSP test README tasks: - name: ".ocsp" - matrix_name: "ocsp-test-windows" matrix_spec: {version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest", "rapid"], os-ssl-40: ["windows-64"]} display_name: "OCSP ${version} ${os-ssl-40}" batchtime: 20160 # Use a batchtime of 14 days as suggested by the OCSP test README tasks: # Windows MongoDB servers do not staple OCSP responses and only support RSA. - name: ".ocsp-rsa !.ocsp-staple" - matrix_name: "ocsp-test-macos" matrix_spec: {version: ["4.4", "5.0", "6.0", "7.0", "8.0", "latest", "rapid"], os-ssl-40: ["macos"]} display_name: "OCSP ${version} ${os-ssl-40}" batchtime: 20160 # Use a batchtime of 14 days as suggested by the OCSP test README tasks: # macos MongoDB servers do not staple OCSP responses and only support RSA. - name: ".ocsp-rsa !.ocsp-staple" - matrix_name: "race-test" tags: ["pullrequest"] matrix_spec: {version: ["7.0"], os-ssl-40: ["rhel87-64"]} display_name: "Race Detector Test" tasks: - name: ".race" - matrix_name: "versioned-api-test" tags: ["pullrequest"] matrix_spec: {version: ["5.0", "6.0", "7.0", "8.0"], os-ssl-40: ["windows-64", "rhel87-64"]} display_name: "API Version ${version} ${os-ssl-40}" tasks: - name: ".versioned-api" - matrix_name: "versioned-api-latest-rapid-test" matrix_spec: {version: ["latest", "rapid"], os-ssl-40: ["windows-64", "rhel87-64"]} display_name: "API Version ${version} ${os-ssl-40}" tasks: - name: ".versioned-api" - matrix_name: "client-side-encryption-test" matrix_spec: {version: ["latest", "rapid"], os-ssl-40: ["rhel87-64"]} display_name: "Client Side Encryption Tests ${version} ${os-ssl-40}" tasks: - name: ".client-side-encryption-test" - matrix_name: "load-balancer-test" tags: ["pullrequest"] matrix_spec: {version: ["5.0", "6.0", "7.0", "8.0"], os-ssl-40: ["rhel87-64"]} display_name: "Load Balancer Support ${version} ${os-ssl-40}" tasks: - name: ".load-balancer" - matrix_name: "load-balancer-latest-rapid-test" matrix_spec: {version: ["latest", "rapid"], os-ssl-40: ["rhel87-64"]} display_name: "Load Balancer Support ${version} ${os-ssl-40}" tasks: - name: ".load-balancer" - matrix_name: "kms-kmip-test" matrix_spec: {version: ["7.0"], os-ssl-40: ["rhel87-64"]} display_name: "KMS KMIP ${os-ssl-40}" tasks: - name: ".kms-kmip" - matrix_name: "fuzz-test" matrix_spec: {version: ["5.0"], os-ssl-40: ["rhel87-64"]} display_name: "Fuzz ${version} ${os-ssl-40}" tasks: - name: "test-fuzz" batchtime: 1440 # Run at most once per 24 hours. - matrix_name: "faas-test" matrix_spec: {version: ["7.0"], os-faas-80: ["rhel87-large"]} display_name: "FaaS ${version} ${os-faas-80}" tasks: - test-aws-lambda-task-group - matrix_name: "searchindex-test" matrix_spec: {version: ["7.0"], os-faas-80: ["rhel87-large"]} display_name: "Search Index ${version} ${os-faas-80}" tasks: - test-search-index-task-group - name: testgcpkms-variant display_name: "GCP KMS" run_on: - rhel8.7-small expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: testgcpkms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README - testgcpkms-fail-task - name: testawskms-variant display_name: "AWS KMS" run_on: - rhel8.7-small expansions: GO_DIST: "/opt/golang/go1.25" tasks: - testawskms-task - testawskms-fail-task - name: testazurekms-variant display_name: "AZURE KMS" run_on: - rhel8.7-small expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: testazurekms_task_group batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README - testazurekms-fail-task - name: testoidc-variant display_name: "OIDC" run_on: - ubuntu2204-small expansions: GO_DIST: "/opt/golang/go1.25" tasks: - name: testoidc_task_group - name: testazureoidc_task_group - name: testgcpoidc_task_group - name: testk8soidc_task_group ================================================ FILE: .evergreen/krb5.config ================================================ [realms] LDAPTEST.10GEN.CC = { kdc = ldaptest.10gen.cc admin_server = ldaptest.10gen.cc } [libdefaults] rdns = false ================================================ FILE: .evergreen/ocsp-requirements.txt ================================================ asn1crypto==1.3.0 bottle==0.12.20 oscrypto==1.2.0 ================================================ FILE: .evergreen/run-mongodb-aws-ecs-test.sh ================================================ #!/bin/bash set -o errexit # Exit the script with error if any of the commands fail ############################################ # Main Program # ############################################ if [[ -z "$1" ]]; then echo "usage: $0 " exit 1 fi export MONGODB_URI="$1" echo "Running MONGODB-AWS ECS authentication tests" if echo "$MONGODB_URI" | grep -q "@"; then echo "MONGODB_URI unexpectedly contains user credentials in ECS test!"; exit 1 fi ./src/main ================================================ FILE: .evergreen/run-task.sh ================================================ #!/usr/bin/env bash # # Source the env.sh file and run the given task set -eu SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PROJECT_DIRECTORY=$(dirname $SCRIPT_DIR) pushd ${PROJECT_DIRECTORY} > /dev/null source env.sh task "$@" popd > /dev/null ================================================ FILE: .evergreen/setup-system.sh ================================================ #!/usr/bin/env bash # # Set up environment and write env.sh and expansion.yml files. set -eu # Set up default environment variables. SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) PROJECT_DIRECTORY=$(dirname $SCRIPT_DIR) pushd $PROJECT_DIRECTORY ROOT_DIR=$(dirname $PROJECT_DIRECTORY) DRIVERS_TOOLS=${DRIVERS_TOOLS:-${ROOT_DIR}/drivers-evergreen-tools} MONGO_ORCHESTRATION_HOME="${DRIVERS_TOOLS}/.evergreen/orchestration" MONGODB_BINARIES="${DRIVERS_TOOLS}/mongodb/bin" OS="${OS:-""}" # Set Golang environment vars. GOROOT is wherever current Go distribution is, and is set in evergreen config. # GOPATH is always 3 directories up from pwd on EVG; GOCACHE is under .cache in the pwd. GOROOT=${GOROOT:-$(dirname "$(dirname "$(which go)")")} export GOPATH=${GOPATH:-$ROOT_DIR} export GOCACHE="${GO_CACHE:-$PROJECT_DIRECTORY/.cache}" # Handle paths on Windows. if [ "Windows_NT" = "${OS:-}" ]; then # Magic variable in cygwin GOPATH=$(cygpath -m $GOPATH) GOCACHE=$(cygpath -w $GOCACHE) DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY) EXTRA_PATH=/cygdrive/c/libmongocrypt/bin MONGO_ORCHESTRATION_HOME=$(cygpath -m $MONGO_ORCHESTRATION_HOME) MONGODB_BINARIES=$(cygpath -m $MONGODB_BINARIES) # Set home variables for Windows, too. USERPROFILE=$(cygpath -w "$ROOT_DIR") HOME=$USERPROFILE else EXTRA_PATH=${GCC:-} fi # Add binaries to the path. PATH="${GOROOT}/bin:${GOPATH}/bin:${MONGODB_BINARIES}:${EXTRA_PATH}:${PATH}" # 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 # Ensure a checkout of drivers-tools. if [ ! -d "$DRIVERS_TOOLS" ]; then git clone https://github.com/mongodb-labs/drivers-evergreen-tools $DRIVERS_TOOLS fi # Write the .env file for drivers-tools. cat < ${DRIVERS_TOOLS}/.env SKIP_LEGACY_SHELL=1 DRIVERS_TOOLS="$DRIVERS_TOOLS" MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME" MONGODB_BINARIES="$MONGODB_BINARIES" TMPDIR="$MONGO_ORCHESTRATION_HOME/db" EOT # Check Go installation. go version go env # Install taskfile. go install github.com/go-task/task/v3/cmd/task@v3.39.1 # Write our own env file. cat < env.sh export GOROOT="$GOROOT" export GOPATH="$GOPATH" export GOCACHE="$GOCACHE" export DRIVERS_TOOLS="$DRIVERS_TOOLS" export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" export MONGODB_BINARIES="$MONGODB_BINARIES" export PATH="$PATH" EOT if [ "Windows_NT" = "$OS" ]; then echo "export USERPROFILE=$USERPROFILE" >> env.sh echo "export HOME=$HOME" >> env.sh fi # source the env.sh file and write the expansion file. cat < expansion.yml CURRENT_VERSION: "$CURRENT_VERSION" DRIVERS_TOOLS: "$DRIVERS_TOOLS" PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" RUN_TASK: "$PROJECT_DIRECTORY/.evergreen/run-task.sh" EOT cat env.sh popd ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" groups: actions: patterns: - "*" - package-ecosystem: gomod directory: / schedule: interval: "weekly" - package-ecosystem: "gitsubmodule" directory: "/" schedule: interval: "monthly" ================================================ FILE: .github/labeler.yml ================================================ review-priority-normal: - changed-files: - any-glob-to-any-file: "*" documentation: - changed-files: - any-glob-to-any-file: - docs/** - examples/** dependencies: - changed-files: - any-glob-to-any-file: - go.mod ================================================ FILE: .github/release.yml ================================================ changelog: exclude: labels: - ignore-for-release - github_actions - submodules authors: - mongodb-drivers-pr-bot categories: - title: ⚠️ Breaking Changes labels: - breaking - title: ✨ New Features labels: - enhancement - feature - title: 🐛 Fixed labels: - bug - title: 📦 Dependency Updates labels: - dependencies - title: 📝 Other Changes labels: - "*" ================================================ FILE: .github/workflows/check-labels.yml ================================================ name: Label Checker on: pull_request: types: - opened - synchronize - reopened - labeled - unlabeled permissions: pull-requests: read jobs: check_labels: name: Check labels runs-on: ubuntu-latest steps: - uses: docker://agilepathway/pull-request-label-checker@sha256:65e57fd98ba3ab6ca4fcbc5a0aef288dd984ee4ab988f124d83424d19b55b801 with: one_of: bug,feature,enhancement,documentation,dependencies,ignore-for-release,ci/cd repo_token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/codeql.yml ================================================ name: "CodeQL" on: push: branches: - "v1" - "cloud-*" - "master" - "release/*" - "feature/*" pull_request: branches: - "v1" - "cloud-*" - "master" - "release/*" - "feature/*" schedule: - cron: "36 17 * * 0" workflow_call: inputs: ref: required: true type: string permissions: contents: read jobs: analyze: name: Analyze (go) runs-on: "ubuntu-latest" timeout-minutes: 360 permissions: # required for all workflows contents: read security-events: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Set up Go uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 #v6.3.0 with: go-version: "1.25.0" # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4.33.0 #immutable with: languages: go build-mode: manual - name: Build (CodeQL-instrumented) shell: bash run: | # TODO(GODRIVER-3723): Run using taskfile targets. go build ./... go test -short -run ^$$ ./... go test -v ./internal/test/compilecheck -run '^TestCompileCheck/go:1\.19$' - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4.33.0 #immutable with: category: "/language:go" ================================================ FILE: .github/workflows/create-release-branch.yml ================================================ name: Create Release Branch on: workflow_dispatch: inputs: branch_name: description: The name of the new branch required: true version: description: The version to set on the branch required: true base_ref: description: The base reference for the branch push_changes: description: Whether to push the changes default: "true" permissions: contents: read concurrency: group: create-branch-${{ github.ref }} cancel-in-progress: true defaults: run: shell: bash -eux {0} jobs: create-branch: environment: release runs-on: ubuntu-latest permissions: id-token: write contents: write outputs: version: ${{ steps.pre-publish.outputs.version }} steps: - uses: mongodb-labs/drivers-github-tools/secure-checkout@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} - uses: mongodb-labs/drivers-github-tools/setup@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} aws_secret_id: ${{ secrets.AWS_SECRET_ID }} artifactory_username: ${{ vars.ARTIFACTORY_USERNAME }} - uses: mongodb-labs/drivers-github-tools/create-branch@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 id: create-branch with: branch_name: ${{ inputs.branch_name }} version: ${{ inputs.version }} base_ref: ${{ inputs.base_ref }} push_changes: ${{ inputs.push_changes }} version_bump_script: "go run ${{ github.action_path }}/bump-version.go" evergreen_project: mongo-go-driver-release release_workflow_path: ./.github/workflows/release.yml ================================================ FILE: .github/workflows/labeler.yml ================================================ name: "Pull Request Labeler" on: - pull_request_target permissions: contents: read jobs: labeler: permissions: contents: read pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b #v6.0.1 ================================================ FILE: .github/workflows/merge-up.yml ================================================ name: Merge up on: push: branches: - release/*.* - v* permissions: contents: read jobs: merge-up: name: Create merge up pull request runs-on: ubuntu-latest permissions: id-token: write contents: write pull-requests: write steps: - uses: mongodb-labs/drivers-github-tools/secure-checkout@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: app_id: ${{ vars.PR_APP_ID }} private_key: ${{ secrets.PR_APP_PRIVATE_KEY }} # Make sure to include fetch-depth 0 so all branches are fetched, not # just the current one fetch-depth: 0 - name: Create pull request id: create-pull-request uses: alcaeus/automatic-merge-up-action@e23e7c71d58f12531cbaba73473bbe78fd14baf0 #v1.0.1 with: ref: ${{ github.ref_name }} branchNamePattern: "release/." devBranchNamePattern: "v" fallbackBranch: "master" ignoredBranches: ${{ vars.IGNORED_MERGE_UP_BRANCHES }} enableAutoMerge: true ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch: inputs: version: description: "The new version to set" required: true prev_version: description: "The previous tagged version" required: true push_changes: description: "Push changes?" default: true type: boolean permissions: contents: read defaults: run: shell: bash -eux {0} env: # Changes per branch SILK_ASSET_GROUP: mongodb-go-driver EVERGREEN_PROJECT: mongo-go-driver jobs: pre-publish: environment: release runs-on: ubuntu-latest permissions: id-token: write contents: write outputs: prev_version: ${{ steps.pre-publish.outputs.prev_version }} steps: - uses: mongodb-labs/drivers-github-tools/secure-checkout@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} - uses: mongodb-labs/drivers-github-tools/setup@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} aws_secret_id: ${{ secrets.AWS_SECRET_ID }} artifactory_username: ${{ vars.ARTIFACTORY_USERNAME }} - name: Pre Publish id: pre-publish uses: mongodb-labs/drivers-github-tools/golang/pre-publish@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: version: ${{ inputs.version }} push_changes: ${{ inputs.push_changes }} ignored_branches: ${{ vars.IGNORED_MERGE_UP_BRANCHES }} static-scan: needs: [pre-publish] permissions: contents: read security-events: write uses: ./.github/workflows/codeql.yml with: ref: ${{ github.ref }} publish: needs: [pre-publish, static-scan] runs-on: ubuntu-latest environment: release permissions: id-token: write contents: write security-events: read steps: - uses: mongodb-labs/drivers-github-tools/secure-checkout@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} - uses: mongodb-labs/drivers-github-tools/setup@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} aws_secret_id: ${{ secrets.AWS_SECRET_ID }} artifactory_username: ${{ vars.ARTIFACTORY_USERNAME }} - name: Publish uses: mongodb-labs/drivers-github-tools/golang/publish@fac300d2aa6fe2ddd6316aa673df485941b882f0 #v3 with: version: ${{ inputs.version }} silk_asset_group: ${{ env.SILK_ASSET_GROUP }} evergreen_project: ${{ env.EVERGREEN_PROJECT }} prev_version: ${{ inputs.prev_version }} push_changes: ${{ inputs.push_changes }} token: ${{ env.GH_TOKEN }} ================================================ FILE: .github/workflows/scorecard.yml ================================================ # This workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: "24 21 * * 1" push: branches: ["master"] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write # Uncomment the permissions below if installing in a private repository. # contents: read # actions: read steps: - name: "Checkout code" uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # - you want to enable the Branch-Protection check on a *public* repository, or # - you are installing Scorecard on a *private* repository # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. # For private repositories: # - `publish_results` will always be set to `false`, regardless # of the value entered here. publish_results: true # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore # file_mode: git # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@v4.33.0 #immutable with: sarif_file: results.sarif ================================================ FILE: .github/workflows/test.yml ================================================ name: GoDriver Tests on: push: pull_request: permissions: contents: read concurrency: group: test-${{ github.ref }} cancel-in-progress: true defaults: run: shell: bash -eux {0} jobs: pre_commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 #v6.3.0 with: go-version: 'stable' - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd #v3.0.1 ================================================ FILE: .gitignore ================================================ .vscode debug .idea *.iml *.ipr *.iws .idea *.sublime-project *.sublime-workspace driver-test-data.tar.gz perf perf.json perf.suite **mongocryptd.pid *.test .DS_Store install main.so .cache install libmongocrypt venv test.suite go.work.sum .task env.sh expansion.yml bin # AWS SAM-generated files internal/cmd/faas/awslambda/.aws-sam internal/cmd/faas/awslambda/events/event.json # Ignore api report files api-report.md api-report.txt # Ignore perf report files perf-report.md perf-report.txt # Ignore secrets files secrets-expansion.yml secrets-export.sh .test.env ================================================ FILE: .gitmodules ================================================ [submodule "specifications"] path = testdata/specifications url = https://github.com/mongodb/specifications ================================================ FILE: .golangci.yml ================================================ version: "2" linters: default: none enable: - errcheck - gocritic # TODO(GODRIVER-3712): Enable gosec in golangci-lint version 2.8.0 #- gosec - govet - ineffassign - makezero - misspell - nakedret - paralleltest - prealloc - revive - staticcheck - unconvert - unparam - unused settings: errcheck: exclude-functions: - .errcheck-excludes govet: disable: - cgocall - composites paralleltest: # Ignore missing calls to `t.Parallel()` and only report incorrect uses of # `t.Parallel()`. ignore-missing: true staticcheck: checks: - all # Disable deprecation warnings for now. - -SA1012 # Disable "do not pass a nil Context" to allow testing nil contexts in # tests. - -SA1019 exclusions: generated: lax rules: # Ignore some linters for example code that is intentionally simplified. - linters: - errcheck - revive path: examples/ # Disable "unused" linter for code files that depend on the # "mongocrypt.MongoCrypt" type because the linter build doesn't work # correctly with CGO enabled. As a result, all calls to a # "mongocrypt.MongoCrypt" API appear to always panic (see # mongocrypt_not_enabled.go), leading to confusing messages about unused # code. - linters: - unused path: x/mongo/driver/crypt.go|mongo/(crypt_retrievers|mongocryptd).go # Ignore "TLS MinVersion too low", "TLS InsecureSkipVerify set true", and # "Use of weak random number generator (math/rand instead of crypto/rand)" # in tests. Disable gosec entirely for test files to reduce noise. - linters: - gosec path: _test\.go # Ignore prealloc warnings for test files. - linters: - prealloc path: _test\.go # Ignore missing comments for exported variable/function/type for code in # the "internal" and "benchmark" directories. - path: (internal\/|benchmark\/) text: exported (.+) should have comment( \(or a comment on this block\))? or be unexported # Ignore missing package comments for directories that aren't frequently # used by external users. - path: (internal\/|benchmark\/|x\/|cmd\/|mongo\/integration\/) text: should have a package comment # Add all default excluded issues except issues related to exported # types/functions not having comments; we want those warnings. The # defaults are copied from the "--exclude-use-default" documentation on # https://golangci-lint.run/usage/configuration/#command-line-options # ## Defaults ## # # EXC0001 errcheck: Almost all programs ignore errors on these functions # and in most cases it's ok - path: (.+)\.go$ text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked # EXC0003 golint: False positive when tests are defined in package 'test' - path: (.+)\.go$ text: func name will be used as test\.Test.* by other packages, and that stutters; consider calling this # EXC0004 govet: Common false positives - path: (.+)\.go$ text: (possible misuse of unsafe.Pointer|should have signature) # EXC0005 staticcheck: Developers tend to write in C-style with an explicit 'break' in a 'switch', so it's ok to ignore - path: (.+)\.go$ text: ineffective break statement. Did you mean to break out of the outer loop # EXC0006 gosec: Too many false-positives on 'unsafe' usage - path: (.+)\.go$ text: Use of unsafe calls should be audited # EXC0007 gosec: Too many false-positives for parametrized shell calls - path: (.+)\.go$ text: Subprocess launch(ed with variable|ing should be audited) # EXC0008 gosec: Duplicated errcheck checks - path: (.+)\.go$ text: (G104|G307) # EXC0009 gosec: Too many issues in popular repos - path: (.+)\.go$ text: (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less) # EXC0010 gosec: False positive is triggered by # 'src, err := ioutil.ReadFile(filename)' - path: (.+)\.go$ text: Potential file inclusion via variable ## End Defaults ## # Ignore capitalization warning for this weird field name. - path: (.+)\.go$ text: "var-naming: struct field CqCssWxW should be CqCSSWxW" # Ignore warnings for common "wiremessage.Read..." usage because the # safest way to use that API is by assigning possibly unused returned byte # buffers. - path: (.+)\.go$ text: "SA4006: this value of `wm` is never used" - path: (.+)\.go$ text: "SA4006: this value of `rem` is never used" - path: (.+)\.go$ text: ineffectual assignment to wm - path: (.+)\.go$ text: ineffectual assignment to rem paths: - (^|/)testdata($|/) - (^|/)etc($|/) # Disable all linters for copied third-party code. - internal/rand - internal/aws - internal/assert formatters: enable: - goimports exclusions: generated: lax paths: - (^|/)testdata($|/) - (^|/)etc($|/) - internal/rand - internal/aws - internal/assert ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-case-conflict - id: check-executables-have-shebangs - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-json - id: end-of-file-fixer exclude: ^(vendor/|bson/testdata/lorem.txt) exclude_types: [json, yaml] - id: trailing-whitespace exclude: ^(vendor/|internal/assert/assertions_test.go|bson/testdata/lorem.txt) exclude_types: [json, yaml] - repo: https://github.com/executablebooks/mdformat rev: 0.7.17 hooks: - id: mdformat exclude: ^vendor/ - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.27.0 hooks: - id: check-github-workflows # We use the Python version instead of the original version which seems to require Docker # https://github.com/koalaman/shellcheck-precommit - repo: https://github.com/shellcheck-py/shellcheck-py rev: v0.9.0.6 hooks: - id: shellcheck name: shellcheck args: ["--severity=warning"] - repo: https://github.com/codespell-project/codespell rev: "v2.2.6" hooks: - id: codespell args: ["-L", "te,fo,fle,alo,nin,compres,wil,collone,asess,sav,ot,wll,dne,nulll,hellow,aks"] exclude: ^(vendor/|internal/cmd/benchmark/operation_test.go|bson/testdata/) exclude_types: [json, yaml, pem] - repo: https://github.com/tcort/markdown-link-check rev: v3.11.2 hooks: - id: markdown-link-check exclude: ^(vendor) # If the endpoint returns HTTP 429 (Too Many Requests), consider it a # successful check. args: ["-a 200,206,429"] - repo: local hooks: - id: executable-shell name: executable-shell entry: chmod +x language: system types: [shell] - id: gofumpt name: gofumpt entry: gofumpt -w language: golang types: [go] exclude: ^vendor/ additional_dependencies: [mvdan.cc/gofumpt@v0.9.2] - id: golangci-lint name: golangci-lint language: system types: [go] require_serial: true pass_filenames: false entry: etc/golangci-lint.sh - id: check-licenses name: check-licenses language: system types: [go] entry: etc/check_license.sh - repo: https://github.com/google/yamlfmt rev: v0.10.0 hooks: - id: yamlfmt ================================================ FILE: Dockerfile ================================================ # Dockerfile for Go Driver local development. # sha found via this command: docker inspect --format='{{index .RepoDigests 0}}' golang:1.25.6-trixie FROM golang:1.25.6-trixie@sha256:fb4b74a39c7318d53539ebda43ccd3ecba6e447a78591889c0efc0a7235ea8b3 AS base # Build libmongocrypt in a separate build stage. FROM base AS libmongocrypt RUN apt-get -qq update && \ apt-get -qqy install --no-install-recommends \ git \ ca-certificates \ curl \ build-essential \ libssl-dev \ pkg-config \ python3 \ python3-packaging \ python-is-python3 && \ rm -rf /var/lib/apt/lists/* COPY etc/install-libmongocrypt.sh /root/install-libmongocrypt.sh RUN cd /root && bash ./install-libmongocrypt.sh # Final dev image (already has Go 1.25.x). FROM base RUN export DEBIAN_FRONTEND=noninteractive && \ export TZ=Etc/UTC && \ apt-get -qq update && \ apt-get -qqy install --reinstall --no-install-recommends \ git \ ca-certificates \ curl \ wget \ tzdata \ pkg-config \ gpg \ apt-utils \ libc6-dev \ gcc \ make \ libkrb5-dev && \ update-ca-certificates && \ rm -rf /var/lib/apt/lists/* # Install taskfile RUN go install github.com/go-task/task/v3/cmd/task@v3.39.2 COPY etc/docker_entry.sh /root/docker_entry.sh COPY --from=libmongocrypt /root/install /root/install # Copy the Go driver source for local development and compile checks. COPY . /mongo-go-driver ENV DOCKER_RUNNING=true ENTRYPOINT ["/bin/bash", "/root/docker_entry.sh"] ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: README.md ================================================

docs docs OpenSSF Scorecard

# MongoDB Go Driver The MongoDB supported driver for Go. See the following resources to learn more about upgrading from version 1.x to 2.0.: - [v2.0 Migration Guide](docs/migration-2.0.md) - [v2.0 What's New](https://www.mongodb.com/docs/drivers/go/upcoming/whats-new/#what-s-new-in-2.0) The MongoDB Go driver follows [semantic versioning](https://semver.org/) for its releases. ## Requirements - Go 1.19 or higher. We aim to support the latest versions of Go. - Go 1.25 or higher is required to run the driver test suite. - MongoDB 4.2 and higher. ## Installation The recommended way to get started using the MongoDB Go driver is by using Go modules to install the dependency in your project. This can be done either by importing packages from `go.mongodb.org/mongo-driver` and having the build step install the dependency or by explicitly running ```bash go get go.mongodb.org/mongo-driver/v2/mongo ``` When using a version of Go that does not support modules, the driver can be installed using `dep` by running ```bash dep ensure -add "go.mongodb.org/mongo-driver/v2/mongo" ``` ## Usage To get started with the driver, import the `mongo` package and create a `mongo.Client` with the `Connect` function: ```go import ( "context" "time" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readpref" ) client, _ := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017")) ``` Make sure to defer a call to `Disconnect` after instantiating your client: ```go defer func() { if err := client.Disconnect(ctx); err != nil { panic(err) } }() ``` For more advanced configuration and authentication, see the [documentation for mongo.Connect](https://pkg.go.dev/go.mongodb.org/mongo-driver/v2/mongo#Connect). Calling `Connect` does not block for server discovery. If you wish to know if a MongoDB server has been found and connected to, use the `Ping` method: ```go ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = client.Ping(ctx, readpref.Primary()) ``` To insert a document into a collection, first retrieve a `Database` and then `Collection` instance from the `Client`: ```go collection := client.Database("testing").Collection("numbers") ``` The `Collection` instance can then be used to insert documents: ```go ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() res, _ := collection.InsertOne(ctx, bson.D{{"name", "pi"}, {"value", 3.14159}}) id := res.InsertedID ``` To use `bson.D`, you will need to add `"go.mongodb.org/mongo-driver/v2/bson"` to your imports. Your import statement should now look like this: ```go import ( "context" "log" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readpref" ) ``` Several query methods return a cursor, which can be used like this: ```go ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cur, err := collection.Find(ctx, bson.D{}) if err != nil { log.Fatal(err) } defer cur.Close(ctx) for cur.Next(ctx) { var result bson.D if err := cur.Decode(&result); err != nil { log.Fatal(err) } // do something with result.... } if err := cur.Err(); err != nil { log.Fatal(err) } ``` For methods that return a single item, a `SingleResult` instance is returned: ```go var result struct { Value float64 } filter := bson.D{{"name", "pi"}} ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() err := collection.FindOne(ctx, filter).Decode(&result) if errors.Is(err, mongo.ErrNoDocuments) { // Do something when no record was found } else if err != nil { log.Fatal(err) } // Do something with result... ``` Additional examples and documentation can be found under the examples directory and [on the MongoDB Documentation website](https://www.mongodb.com/docs/drivers/go/current/). ### Network Compression Network compression will reduce bandwidth requirements between MongoDB and the application. The Go Driver supports the following compression algorithms: 1. [Snappy](https://google.github.io/snappy/) (`snappy`): available in MongoDB 3.4 and later. 1. [Zlib](https://zlib.net/) (`zlib`): available in MongoDB 3.6 and later. 1. [Zstandard](https://github.com/facebook/zstd/) (`zstd`): available in MongoDB 4.2 and later. #### Specify Compression Algorithms Compression can be enabled using the `compressors` parameter on the connection string or by using [`ClientOptions.SetCompressors`](https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo/options#ClientOptions.SetCompressors): ```go opts := options.Client().ApplyURI("mongodb://localhost:27017/?compressors=snappy,zlib,zstd") client, _ := mongo.Connect(opts) ``` ```go opts := options.Client().SetCompressors([]string{"snappy", "zlib", "zstd"}) client, _ := mongo.Connect(opts) ``` If compressors are set, the Go Driver negotiates with the server to select the first common compressor. For server configuration and defaults, refer to [`networkMessageCompressors`](https://www.mongodb.com/docs/manual/reference/program/mongod/#std-option-mongod.--networkMessageCompressors). Messages compress when both parties enable network compression; otherwise, messages remain uncompressed ## Support / Feedback For issues with, questions about, or feedback for the Go Driver, please look into our [support channels](https://www.mongodb.com/docs/manual/support/), including [StackOverflow](https://stackoverflow.com/questions/tagged/mongodb%20go?sort=Newest). New features and bugs can be reported on the [GODRIVER Jira project](https://jira.mongodb.org/browse/GODRIVER). ## Contribution Check out the [GODRIVER Jira project](https://jira.mongodb.org/browse/GODRIVER) for tickets that need completing. See our [contribution guidelines](docs/CONTRIBUTING.md) for details. ## Continuous Integration Commits to master are run automatically on [evergreen](https://evergreen.mongodb.com/waterfall/mongo-go-driver). ## Frequently Encountered Issues See our [common issues](docs/common-issues.md) documentation for troubleshooting frequently encountered issues. ## Thanks and Acknowledgement - The Go Gopher artwork by [@ashleymcnamara](https://github.com/ashleymcnamara) - The original Go Gopher was designed by [Renee French](http://reneefrench.blogspot.com/) ## License The MongoDB Go Driver is licensed under the [Apache License](LICENSE). ================================================ FILE: THIRD-PARTY-NOTICES ================================================ ---------------------------------------------------------------------- License notice for AWS V4 signing code from github.com/aws/aws-sdk-go AWS SDK for Go Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. Copyright 2014-2015 Stripe, Inc. ---------------------------------------------------------------------- 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 gopkg.in/mgo.v2/bson --------------------------------------------------------------------- BSON library for Go Copyright (c) 2010-2013 - Gustavo Niemeyer 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 rand code from golang.org/x/exp --------------------------------------------------------------------- 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 Add64 and Mul64 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 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/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/klauspost/compress ---------------------------------------------------------------------- Copyright (c) 2012 The Go Authors. All rights reserved. Copyright (c) 2019 Klaus Post. 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/klauspost/compress/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/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/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/pkg/errors ---------------------------------------------------------------------- Copyright (c) 2015, Dave Cheney 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/stretchr/testify ---------------------------------------------------------------------- MIT License Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. 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-go/pbkdf2 ---------------------------------------------------------------------- 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-go/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-go/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 github.com/youmark/pkcs8 ---------------------------------------------------------------------- The MIT License (MIT) Copyright (c) 2014 youmark 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 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/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/xerrors ---------------------------------------------------------------------- Copyright (c) 2019 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 gopkg.in/yaml.v3 ---------------------------------------------------------------------- This project is covered by two different licenses: MIT and Apache. #### MIT License #### The following files were ported to Go from C files of libyaml, and thus are still covered by their original MIT license, with the additional copyright staring in 2011 when the project was ported over: apic.go emitterc.go parserc.go readerc.go scannerc.go writerc.go yamlh.go yamlprivateh.go Copyright (c) 2006-2010 Kirill Simonov Copyright (c) 2006-2011 Kirill Simonov 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. ### Apache License ### All the remaining project files are covered by the Apache license: Copyright (c) 2011-2019 Canonical Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this 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. ================================================ FILE: Taskfile.yml ================================================ # See https://taskfile.dev/usage/ version: "3" env: TEST_TIMEOUT: 1800 LONG_TEST_TIMEOUT: 3600 dotenv: [".test.env"] tasks: ### Utility tasks. ### default: deps: [build, check-license, check-fmt, check-modules, lint, test-short] add-license: bash etc/check_license.sh -a check-license: bash etc/check_license.sh init-submodule: git submodule update --init build: deps: [install-libmongocrypt] cmds: - go build ./... - go build ${BUILD_TAGS} ./... - task: build-tests - task: compilecheck-119 build-tests: go test -short ${BUILD_TAGS} -run ^$$ ./... compilecheck-119: dir: internal/test/compilecheck cmds: - go mod download - GOTOOLCHAIN=auto go test -v -run '^TestCompileCheck/go:1\.19$' build-compile-check-all: bash etc/run-compile-check-test.sh build-aws-ecs-test: go test -c ./internal/test/aws -o aws.testbin check-fmt: deps: [install-lll, install-gofumpt] cmds: - bash etc/check_fmt.sh check-modules: bash etc/check_modules.sh doc: godoc -http=:6060 -index fmt: deps: [install-gofumpt] cmds: - gofumpt -w . api-report: bash etc/api_report.sh install-libmongocrypt: cmds: [bash etc/install-libmongocrypt.sh] status: - test -d install || test -d /cygdrive/c/libmongocrypt/bin run-docker: bash etc/run_docker.sh run-fuzz: bash etc/run-fuzz.sh cherry-picker: bash etc/cherry-picker.sh pr-task: bash etc/pr-task.sh perf-pr-comment: bash etc/perf-pr-comment.sh # Lint with various GOOS and GOARCH tasks to catch static analysis failures that may only affect # specific operating systems or architectures. For example, staticcheck will only check for 64-bit # alignment of atomically accessed variables on 32-bit architectures (see # https://staticcheck.io/docs/checks#SA1027) lint: cmds: - GOOS=linux GOARCH=386 etc/golangci-lint.sh - GOOS=linux GOARCH=arm etc/golangci-lint.sh - GOOS=linux GOARCH=arm64 etc/golangci-lint.sh - GOOS=linux GOARCH=amd64 etc/golangci-lint.sh - GOOS=linux GOARCH=ppc64le etc/golangci-lint.sh - GOOS=linux GOARCH=s390x etc/golangci-lint.sh govulncheck: bash etc/govulncheck.sh update-notices: bash etc/generate_notices.pl > THIRD-PARTY-NOTICES ### Local testing tasks. ### test: go test ${BUILD_TAGS} -timeout {{.TEST_TIMEOUT}}s -p 1 ./... test-cover: - go test ${BUILD_TAGS} -timeout {{.TEST_TIMEOUT}}s -cover ${COVER_ARGS} -p 1 ./... test-race: - go test ${BUILD_TAGS} -timeout {{.TEST_TIMEOUT}}s -race -p 1 ./... test-short: go test ${BUILD_TAGS} -timeout 60s -short -race ./... test-oidc: bash etc/run-oidc-test.sh test-oidc-remote: bash etc/run-oidc-remote-test.sh test-atlas-connect: - go test -v -run ^TestAtlas$ go.mongodb.org/mongo-driver/v2/internal/cmd/testatlas -tags atlastest >> test.suite test-awskms: bash etc/run-awskms-test.sh test-azurekms: bash etc/run-azurekms-test.sh test-gcpkms: bash etc/run-gcpkms-test.sh test-goleak: bash etc/run-goleak-test.sh ### Local FaaS tasks. ### build-faas-awslambda: requires: vars: [MONGODB_URI] cmds: - make -c internal/cmd/faas/awslambda ### Evergreen specific tasks. ### setup-test: bash etc/setup-test.sh setup-encryption: bash etc/setup-encryption.sh evg-test: - go test -exec "env PKG_CONFIG_PATH=${PKG_CONFIG_PATH} LD_LIBRARY_PATH=${LD_LIBRARY_PATH} DYLD_LIBRARY_PATH=$MACOS_LIBRARY_PATH}" ${BUILD_TAGS} -v -timeout {{.TEST_TIMEOUT}}s -p 1 ./... >> test.suite evg-test-enterprise-auth: - go run -tags gssapi ./internal/cmd/testentauth/main.go evg-test-oidc-auth: - go test -v ./internal/test/oidcauth/... >> test.suite - go test -v -race ./internal/test/oidcauth/... >> test.suite evg-test-kmip: - go test -exec "env PKG_CONFIG_PATH=${PKG_CONFIG_PATH} LD_LIBRARY_PATH=${LD_LIBRARY_PATH} DYLD_LIBRARY_PATH=${MACOS_LIBRARY_PATH}" ${BUILD_TAGS} -v -timeout {{.TEST_TIMEOUT}}s ./internal/integration -run TestClientSideEncryptionSpec/kmipKMS >> test.suite evg-test-client-side-encryption: - go test -exec "env PKG_CONFIG_PATH=${PKG_CONFIG_PATH} LD_LIBRARY_PATH=${LD_LIBRARY_PATH} DYLD_LIBRARY_PATH=${MACOS_LIBRARY_PATH}" ${BUILD_TAGS} -v -timeout {{.TEST_TIMEOUT}}s ./internal/integration -run TestClientSideEncryptionProse >> test.suite evg-test-load-balancers: # Load balancer should be tested with all unified tests as well as tests in the following # components: retryable reads, retryable writes, change streams, initial DNS seedlist discovery. - go test ${BUILD_TAGS} ./internal/integration -run TestInitialDNSSeedlistDiscoverySpec/load_balanced -v -timeout {{.TEST_TIMEOUT}}s >> test.suite - go test ${BUILD_TAGS} ./internal/integration -run TestLoadBalancerSupport -v -timeout {{.TEST_TIMEOUT}}s >> test.suite - go test ${BUILD_TAGS} ./internal/integration -run TestLoadBalancedConnectionHandshake -v -timeout {{.TEST_TIMEOUT}}s >> test.suite - go test ${BUILD_TAGS} ./internal/integration/unified -run TestUnifiedSpec -v -timeout {{.TEST_TIMEOUT}}s >> test.suite evg-test-search-index: # Use the long timeout to wait for the responses from the server. - go test ./internal/integration -run TestSearchIndexProse -v -timeout {{.LONG_TEST_TIMEOUT}}s >> test.suite evg-test-ocsp: - go test -v ./mongo -run TestOCSP ${OCSP_TLS_SHOULD_SUCCEED} >> test.suite evg-test-versioned-api: # Versioned API related tests are in the mongo, integration and unified packages. - go test -exec "env PKG_CONFIG_PATH=${PKG_CONFIG_PATH} LD_LIBRARY_PATH=${LD_LIBRARY_PATH} DYLD_LIBRARY_PATH=${MACOS_LIBRARY_PATH}" ${BUILD_TAGS} -v -timeout {{.TEST_TIMEOUT}}s ./mongo >> test.suite - go test -exec "env PKG_CONFIG_PATH=${PKG_CONFIG_PATH} LD_LIBRARY_PATH=${LD_LIBRARY_PATH} DYLD_LIBRARY_PATH=${MACOS_LIBRARY_PATH}" ${BUILD_TAGS} -v -timeout {{.TEST_TIMEOUT}}s ./internal/integration >> test.suite - go test -exec "env PKG_CONFIG_PATH=${PKG_CONFIG_PATH} LD_LIBRARY_PATH=${LD_LIBRARY_PATH} DYLD_LIBRARY_PATH=${MACOS_LIBRARY_PATH}" ${BUILD_TAGS} -v -timeout {{.TEST_TIMEOUT}}s ./internal/integration/unified >> test.suite evg-test-aws: bash etc/run-mongodb-aws-test.sh evg-test-aws-ecs: bash etc/run-mongodb-aws-ecs-test.sh evg-test-deployed-lambda-aws: bash ${DRIVERS_TOOLS}/.evergreen/aws_lambda/run-deployed-lambda-aws-tests.sh evg-gather-test-suites: find . -name \*.suite | xargs --no-run-if-empty tar czf test_suite.tgz build-kms-test: go build ${BUILD_TAGS} ./internal/cmd/testkms ### Benchmark specific tasks and support. ### benchmark: deps: [perf-files] cmds: - go test ${BUILD_TAGS} -benchmem -bench=. ./benchmark | test benchmark.suite driver-benchmark: cmds: - go test ./internal/cmd/benchmark -v --fullRun | tee perf.suite ### Internal tasks. ### install-gofumpt: internal: true cmds: - go install mvdan.cc/gofumpt@v0.9.2 status: - command -v gofumpt install-lll: internal: true cmds: - go install github.com/walle/lll/...@latest ================================================ FILE: bson/array_codec.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) // arrayCodec is the Codec used for bsoncore.Array values. type arrayCodec struct{} // EncodeValue is the ValueEncoder for bsoncore.Array values. func (ac *arrayCodec) EncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tCoreArray { return ValueEncoderError{Name: "CoreArrayEncodeValue", Types: []reflect.Type{tCoreArray}, Received: val} } arr := val.Interface().(bsoncore.Array) return copyArrayFromBytes(vw, arr) } // DecodeValue is the ValueDecoder for bsoncore.Array values. func (ac *arrayCodec) DecodeValue(_ DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tCoreArray { return ValueDecoderError{Name: "CoreArrayDecodeValue", Types: []reflect.Type{tCoreArray}, Received: val} } if vrType := vr.Type(); vrType != TypeArray { return fmt.Errorf("cannot decode %v into a %s", vrType, val.Type()) } if val.IsNil() { val.Set(reflect.MakeSlice(val.Type(), 0, 0)) } val.SetLen(0) arr, err := appendArrayBytes(val.Interface().(bsoncore.Array), vr) val.Set(reflect.ValueOf(arr)) return err } ================================================ FILE: bson/benchmark_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "compress/gzip" "encoding/json" "fmt" "io" "io/ioutil" "os" "path" "sync" "testing" ) var encodetestBsonD D func init() { b, err := Marshal(encodetestInstance) if err != nil { panic(fmt.Sprintf("error marshling struct: %v", err)) } err = Unmarshal(b, &encodetestBsonD) if err != nil { panic(fmt.Sprintf("error unmarshaling BSON: %v", err)) } } type encodetest struct { Field1String string Field1Int64 int64 Field1Float64 float64 Field2String string Field2Int64 int64 Field2Float64 float64 Field3String string Field3Int64 int64 Field3Float64 float64 Field4String string Field4Int64 int64 Field4Float64 float64 } type nestedtest1 struct { Nested nestedtest2 } type nestedtest2 struct { Nested nestedtest3 } type nestedtest3 struct { Nested nestedtest4 } type nestedtest4 struct { Nested nestedtest5 } type nestedtest5 struct { Nested nestedtest6 } type nestedtest6 struct { Nested nestedtest7 } type nestedtest7 struct { Nested nestedtest8 } type nestedtest8 struct { Nested nestedtest9 } type nestedtest9 struct { Nested nestedtest10 } type nestedtest10 struct { Nested nestedtest11 } type nestedtest11 struct { Nested encodetest } var encodetestInstance = encodetest{ Field1String: "foo", Field1Int64: 1, Field1Float64: 3.0, Field2String: "bar", Field2Int64: 2, Field2Float64: 3.1, Field3String: "baz", Field3Int64: 3, Field3Float64: 3.14, Field4String: "qux", Field4Int64: 4, Field4Float64: 3.141, } var nestedInstance = nestedtest1{ nestedtest2{ nestedtest3{ nestedtest4{ nestedtest5{ nestedtest6{ nestedtest7{ nestedtest8{ nestedtest9{ nestedtest10{ nestedtest11{ encodetest{ Field1String: "foo", Field1Int64: 1, Field1Float64: 3.0, Field2String: "bar", Field2Int64: 2, Field2Float64: 3.1, Field3String: "baz", Field3Int64: 3, Field3Float64: 3.14, Field4String: "qux", Field4Int64: 4, Field4Float64: 3.141, }, }, }, }, }, }, }, }, }, }, }, } const extendedBSONDir = "../testdata/extended_bson" var ( extJSONFiles map[string]map[string]any extJSONFilesMu sync.Mutex ) // readExtJSONFile reads the GZIP-compressed extended JSON document from the given filename in the // "extended BSON" test data directory (../testdata/extended_bson) and returns it as a // map[string]any. It panics on any errors. func readExtJSONFile(filename string) map[string]any { extJSONFilesMu.Lock() defer extJSONFilesMu.Unlock() if v, ok := extJSONFiles[filename]; ok { return v } filePath := path.Join(extendedBSONDir, filename) file, err := os.Open(filePath) if err != nil { panic(fmt.Sprintf("error opening file %q: %s", filePath, err)) } defer func() { _ = file.Close() }() gz, err := gzip.NewReader(file) if err != nil { panic(fmt.Sprintf("error creating GZIP reader: %s", err)) } defer func() { _ = gz.Close() }() data, err := ioutil.ReadAll(gz) if err != nil { panic(fmt.Sprintf("error reading GZIP contents of file: %s", err)) } var v map[string]any err = UnmarshalExtJSON(data, false, &v) if err != nil { panic(fmt.Sprintf("error unmarshalling extended JSON: %s", err)) } if extJSONFiles == nil { extJSONFiles = make(map[string]map[string]any) } extJSONFiles[filename] = v return v } func BenchmarkMarshal(b *testing.B) { cases := []struct { desc string value any }{ { desc: "simple struct", value: encodetestInstance, }, { desc: "nested struct", value: nestedInstance, }, { desc: "simple D", value: encodetestBsonD, }, { desc: "deep_bson.json.gz", value: readExtJSONFile("deep_bson.json.gz"), }, { desc: "flat_bson.json.gz", value: readExtJSONFile("flat_bson.json.gz"), }, { desc: "full_bson.json.gz", value: readExtJSONFile("full_bson.json.gz"), }, } b.Run("BSON", func(b *testing.B) { for _, tc := range cases { tc := tc // Capture range variable. b.Run(tc.desc, func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := Marshal(tc.value) if err != nil { b.Errorf("error marshalling BSON: %s", err) } } }) }) } }) b.Run("extJSON", func(b *testing.B) { for _, tc := range cases { tc := tc // Capture range variable. b.Run(tc.desc, func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := MarshalExtJSON(tc.value, true, false) if err != nil { b.Errorf("error marshalling extended JSON: %s", err) } } }) }) } }) b.Run("JSON", func(b *testing.B) { for _, tc := range cases { tc := tc // Capture range variable. b.Run(tc.desc, func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := json.Marshal(tc.value) if err != nil { b.Errorf("error marshalling JSON: %s", err) } } }) }) } }) } func BenchmarkUnmarshal(b *testing.B) { type testcase struct { desc string value any dst func() any } cases := []testcase{ { desc: "simple struct", value: encodetestInstance, dst: func() any { return &encodetest{} }, }, { desc: "nested struct", value: nestedInstance, dst: func() any { return &encodetest{} }, }, } inputs := []struct { name string value any }{ { name: "simple", value: encodetestInstance, }, { name: "nested", value: nestedInstance, }, { name: "deep_bson.json.gz", value: readExtJSONFile("deep_bson.json.gz"), }, { name: "flat_bson.json.gz", value: readExtJSONFile("flat_bson.json.gz"), }, { name: "full_bson.json.gz", value: readExtJSONFile("full_bson.json.gz"), }, } destinations := []struct { name string dst func() any }{ { name: "to map", dst: func() any { return &map[string]any{} }, }, { name: "to D", dst: func() any { return &D{} }, }, } for _, input := range inputs { for _, dest := range destinations { cases = append(cases, testcase{ desc: input.name + " " + dest.name, value: input.value, dst: dest.dst, }) } } b.Run("BSON", func(b *testing.B) { for _, tc := range cases { tc := tc // Capture range variable. b.Run(tc.desc, func(b *testing.B) { b.ReportAllocs() data, err := Marshal(tc.value) if err != nil { b.Errorf("error marshalling BSON: %s", err) return } b.SetBytes(int64(len(data))) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { val := tc.dst() err := Unmarshal(data, val) if err != nil { b.Errorf("error unmarshalling BSON: %s", err) } } }) }) } }) b.Run("extJSON", func(b *testing.B) { for _, tc := range cases { tc := tc // Capture range variable. b.Run(tc.desc, func(b *testing.B) { b.ReportAllocs() data, err := MarshalExtJSON(tc.value, true, false) if err != nil { b.Errorf("error marshalling extended JSON: %s", err) return } b.SetBytes(int64(len(data))) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { val := tc.dst() err := UnmarshalExtJSON(data, true, val) if err != nil { b.Errorf("error unmarshalling extended JSON: %s", err) } } }) }) } }) b.Run("JSON", func(b *testing.B) { for _, tc := range cases { tc := tc // Capture range variable. b.Run(tc.desc, func(b *testing.B) { b.ReportAllocs() data, err := json.Marshal(tc.value) if err != nil { b.Errorf("error marshalling JSON: %s", err) return } b.SetBytes(int64(len(data))) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { val := tc.dst() err := json.Unmarshal(data, val) if err != nil { b.Errorf("error unmarshalling JSON: %s", err) } } }) }) } }) } // The following benchmarks are copied from the Go standard library's // encoding/json package. type codeResponse struct { Tree *codeNode `json:"tree"` Username string `json:"username"` } type codeNode struct { Name string `json:"name"` Kids []*codeNode `json:"kids"` CLWeight float64 `json:"cl_weight"` Touches int `json:"touches"` MinT int64 `json:"min_t"` MaxT int64 `json:"max_t"` MeanT int64 `json:"mean_t"` } var ( codeJSON []byte codeBSON []byte codeStruct codeResponse ) func codeInit() { f, err := os.Open("testdata/code.json.gz") if err != nil { panic(err) } defer f.Close() gz, err := gzip.NewReader(f) if err != nil { panic(err) } data, err := io.ReadAll(gz) if err != nil { panic(err) } codeJSON = data if err := json.Unmarshal(codeJSON, &codeStruct); err != nil { panic("json.Unmarshal code.json: " + err.Error()) } if data, err = json.Marshal(&codeStruct); err != nil { panic("json.Marshal code.json: " + err.Error()) } if codeBSON, err = Marshal(&codeStruct); err != nil { panic("Marshal code.json: " + err.Error()) } if !bytes.Equal(data, codeJSON) { println("different lengths", len(data), len(codeJSON)) for i := 0; i < len(data) && i < len(codeJSON); i++ { if data[i] != codeJSON[i] { println("re-marshal: changed at byte", i) println("orig: ", string(codeJSON[i-10:i+10])) println("new: ", string(data[i-10:i+10])) break } } panic("re-marshal code.json: different result") } } func BenchmarkCodeUnmarshal(b *testing.B) { if codeJSON == nil { b.StopTimer() codeInit() b.StartTimer() } b.Run("BSON", func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { var r codeResponse if err := Unmarshal(codeBSON, &r); err != nil { b.Fatal("Unmarshal:", err) } } }) b.SetBytes(int64(len(codeBSON))) }) b.Run("JSON", func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { var r codeResponse if err := json.Unmarshal(codeJSON, &r); err != nil { b.Fatal("json.Unmarshal:", err) } } }) b.SetBytes(int64(len(codeJSON))) }) } func BenchmarkCodeMarshal(b *testing.B) { if codeJSON == nil { b.StopTimer() codeInit() b.StartTimer() } b.Run("BSON", func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := Marshal(&codeStruct); err != nil { b.Fatal("Marshal:", err) } } }) b.SetBytes(int64(len(codeBSON))) }) b.Run("JSON", func(b *testing.B) { b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := json.Marshal(&codeStruct); err != nil { b.Fatal("json.Marshal:", err) } } }) b.SetBytes(int64(len(codeJSON))) }) } ================================================ FILE: bson/bson_binary_vector_spec_test.go ================================================ // Copyright (C) MongoDB, Inc. 2024-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "os" "path" "testing" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/internal/spectest" ) var bsonBinaryVectorDir = spectest.Path("bson-binary-vector") type bsonBinaryVectorTests struct { Description string `json:"description"` TestKey string `json:"test_key"` Tests []bsonBinaryVectorTestCase `json:"tests"` } type bsonBinaryVectorTestCase struct { Description string `json:"description"` Valid bool `json:"valid"` Vector json.RawMessage `json:"vector"` DtypeHex string `json:"dtype_hex"` DtypeAlias string `json:"dtype_alias"` Padding int `json:"padding"` CanonicalBson string `json:"canonical_bson"` } func TestBsonBinaryVectorSpec(t *testing.T) { t.Parallel() jsonFiles, err := findJSONFilesInDir(bsonBinaryVectorDir) require.NoErrorf(t, err, "error finding JSON files in %s: %v", bsonBinaryVectorDir, err) for _, file := range jsonFiles { filepath := path.Join(bsonBinaryVectorDir, file) content, err := os.ReadFile(filepath) require.NoErrorf(t, err, "reading test file %s", filepath) var tests bsonBinaryVectorTests require.NoErrorf(t, json.Unmarshal(content, &tests), "parsing test file %s", filepath) t.Run(tests.Description, func(t *testing.T) { t.Parallel() for _, test := range tests.Tests { test := test t.Run(test.Description, func(t *testing.T) { t.Parallel() runBsonBinaryVectorTest(t, tests.TestKey, test) }) } }) } t.Run("Padding specified with no vector data PACKED_BIT", func(t *testing.T) { t.Parallel() t.Run("Marshaling", func(t *testing.T) { _, err := NewPackedBitVector(nil, 1) require.EqualError(t, err, errNonZeroVectorPadding.Error()) }) }) t.Run("Exceeding maximum padding PACKED_BIT", func(t *testing.T) { t.Parallel() t.Run("Marshaling", func(t *testing.T) { _, err := NewPackedBitVector(nil, 8) require.EqualError(t, err, errVectorPaddingTooLarge.Error()) }) }) } func decodeTestSlice[T int8 | float32 | byte](t *testing.T, data []byte) []T { t.Helper() if len(data) == 0 { return nil } var s []float64 err := UnmarshalExtJSON(data, true, &s) require.NoError(t, err) v := make([]T, len(s)) for i, e := range s { v[i] = T(e) } return v } func runBsonBinaryVectorTest(t *testing.T, testKey string, test bsonBinaryVectorTestCase) { testVector := make(map[string]Vector) switch alias := test.DtypeHex; alias { case "0x03": testVector[testKey] = Vector{ dType: Int8Vector, int8Data: decodeTestSlice[int8](t, test.Vector), } case "0x27": testVector[testKey] = Vector{ dType: Float32Vector, float32Data: decodeTestSlice[float32](t, test.Vector), } case "0x10": testVector[testKey] = Vector{ dType: PackedBitVector, bitData: decodeTestSlice[byte](t, test.Vector), bitPadding: uint8(test.Padding), } default: t.Fatalf("unsupported vector type: %s", alias) } testBSON, err := hex.DecodeString(test.CanonicalBson) require.NoError(t, err, "decoding canonical BSON") t.Run("Unmarshaling", func(t *testing.T) { spectest.CheckSkip(t) errMap := map[string]string{ "FLOAT32 with padding": "padding must be 0", "INT8 with padding": "padding must be 0", "Padding specified with no vector data PACKED_BIT": "padding must be 0", "Exceeding maximum padding PACKED_BIT": "padding cannot be larger than 7", } t.Parallel() var got map[string]Vector err := Unmarshal(testBSON, &got) if test.Valid { require.NoError(t, err) require.Equal(t, testVector, got) } else if errMsg, ok := errMap[test.Description]; ok { require.ErrorContains(t, err, errMsg) } else { require.Error(t, err) } }) t.Run("Marshaling", func(t *testing.T) { spectest.CheckSkip(t) t.Parallel() got, err := Marshal(testVector) require.NoError(t, err) require.Equal(t, testBSON, got) }) } ================================================ FILE: bson/bson_corpus_spec_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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/hex" "encoding/json" "fmt" "os" "path" "reflect" "strconv" "strings" "testing" "unicode" "unicode/utf8" "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/internal/spectest" ) 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"` } var dataDir = spectest.Path("bson-corpus/tests") func findJSONFilesInDir(dir string) ([]string, error) { files := make([]string, 0) entries, err := os.ReadDir(dir) if err != nil { return nil, err } for _, entry := range entries { if entry.IsDir() || path.Ext(entry.Name()) != ".json" { continue } files = append(files, entry.Name()) } return files, nil } // seedExtJSON will add the byte representation of the "extJSON" string to the fuzzer's coprus. func seedExtJSON(f *testing.F, extJSON string, extJSONType string, desc string) { jbytes, err := jsonToBytes(extJSON, extJSONType, desc) if err != nil { f.Fatalf("failed to convert JSON to bytes: %v", err) } f.Add(jbytes) } // seedTestCase will add the byte representation for each "extJSON" string of each valid test case to the fuzzer's // corpus. func seedTestCase(f *testing.F, tcase *testCase) { for _, vtc := range tcase.Valid { seedExtJSON(f, vtc.CanonicalExtJSON, "canonical", vtc.Description) // Seed the relaxed extended JSON. if vtc.RelaxedExtJSON != nil { seedExtJSON(f, *vtc.RelaxedExtJSON, "relaxed", vtc.Description) } // Seed the degenerate extended JSON. if vtc.DegenerateExtJSON != nil { seedExtJSON(f, *vtc.DegenerateExtJSON, "degenerate", vtc.Description) } // Seed the converted extended JSON. if vtc.ConvertedExtJSON != nil { seedExtJSON(f, *vtc.ConvertedExtJSON, "converted", vtc.Description) } } } // seedBSONCorpus will unmarshal the data from "testdata/bson-corpus" into a slice of "testCase" structs and then // marshal the "*_extjson" field of each "validityTestCase" into a slice of bytes to seed the fuzz corpus. func seedBSONCorpus(f *testing.F) { fileNames, err := findJSONFilesInDir(dataDir) if err != nil { f.Fatalf("failed to find JSON files in directory %q: %v", dataDir, err) } for _, fileName := range fileNames { filePath := path.Join(dataDir, fileName) file, err := os.Open(filePath) if err != nil { f.Fatalf("failed to open file %q: %v", filePath, err) } var tcase testCase if err := json.NewDecoder(file).Decode(&tcase); err != nil { f.Fatal(err) } seedTestCase(f, &tcase) } } func needsEscapedUnicode(bsonType string) bool { return bsonType == "0x02" || bsonType == "0x0D" || bsonType == "0x0E" || bsonType == "0x0F" } func unescapeUnicode(s, bsonType string) string { if !needsEscapedUnicode(bsonType) { return s } newS := "" for i := 0; i < len(s); i++ { c := s[i] switch c { case '\\': switch s[i+1] { case 'u': us := s[i : i+6] u, err := strconv.Unquote(strings.Replace(strconv.Quote(us), `\\u`, `\u`, 1)) if err != nil { return "" } for _, r := range u { if r < ' ' { newS += fmt.Sprintf(`\u%04x`, r) } else { newS += string(r) } } i += 5 default: newS += string(c) } default: if c > unicode.MaxASCII { r, size := utf8.DecodeRune([]byte(s[i:])) newS += string(r) i += size - 1 } else { newS += string(c) } } } return newS } func normalizeCanonicalDouble(t *testing.T, key string, cEJ string) string { // Unmarshal string into map cEJMap := make(map[string]map[string]string) err := json.Unmarshal([]byte(cEJ), &cEJMap) require.NoError(t, err) // Parse the float contained by the map. expectedString := cEJMap[key]["$numberDouble"] expectedFloat, err := strconv.ParseFloat(expectedString, 64) require.NoError(t, err) // Normalize the string return fmt.Sprintf(`{"%s":{"$numberDouble":"%s"}}`, key, formatDouble(expectedFloat)) } func normalizeRelaxedDouble(t *testing.T, key string, rEJ string) string { // Unmarshal string into map rEJMap := make(map[string]float64) err := json.Unmarshal([]byte(rEJ), &rEJMap) if err != nil { return normalizeCanonicalDouble(t, key, rEJ) } // Parse the float contained by the map. expectedFloat := rEJMap[key] // Normalize the string return fmt.Sprintf(`{"%s":%s}`, key, formatDouble(expectedFloat)) } // bsonToNative decodes the BSON bytes (b) into a native Document func bsonToNative(t *testing.T, b []byte, bType, testDesc string) D { var doc D err := Unmarshal(b, &doc) require.NoErrorf(t, err, "%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) require.NoErrorf(t, err, "%s: encoding %s BSON", testDesc, bType) if diff := cmp.Diff(cB, actual); diff != "" { t.Errorf("%s: 'native_to_bson(%s) = cB' failed (-want, +got):\n-%v\n+%v\n", testDesc, docSrcDesc, cB, actual) t.FailNow() } } // jsonToNative decodes the extended JSON string (ej) into a native Document func jsonToNative(ej, ejType, testDesc string) (D, error) { var doc D if err := UnmarshalExtJSON([]byte(ej), ejType != "relaxed", &doc); err != nil { return nil, fmt.Errorf("%s: decoding %s extended JSON: %w", testDesc, ejType, err) } return doc, nil } // jsonToBytes decodes the extended JSON string (ej) into canonical BSON and then encodes it into a byte slice. func jsonToBytes(ej, ejType, testDesc string) ([]byte, error) { native, err := jsonToNative(ej, ejType, testDesc) if err != nil { return nil, err } b, err := Marshal(native) if err != nil { return nil, fmt.Errorf("%s: encoding %s BSON: %w", testDesc, ejType, err) } return b, nil } // nativeToJSON encodes the native Document (doc) into an extended JSON string func nativeToJSON(t *testing.T, ej string, doc D, testDesc, ejType, ejShortName, docSrcDesc string) { actualEJ, err := MarshalExtJSON(doc, ejType != "relaxed", true) require.NoErrorf(t, err, "%s: encoding %s extended JSON", testDesc, ejType) if diff := cmp.Diff(ej, string(actualEJ)); diff != "" { t.Errorf("%s: 'native_to_%s_extended_json(%s) = %s' failed (-want, +got):\n%s\n", testDesc, ejType, docSrcDesc, ejShortName, diff) t.FailNow() } } func runTest(t *testing.T, file string) { filepath := path.Join(dataDir, file) content, err := os.ReadFile(filepath) require.NoError(t, err) t.Run(file, func(t *testing.T) { var test testCase require.NoError(t, json.Unmarshal(content, &test)) t.Run("valid", func(t *testing.T) { for _, v := range test.Valid { t.Run(v.Description, func(t *testing.T) { // get canonical BSON cB, err := hex.DecodeString(v.CanonicalBson) require.NoErrorf(t, err, "%s: reading canonical BSON", v.Description) // get canonical extended JSON var compactEJ bytes.Buffer require.NoError(t, json.Compact(&compactEJ, []byte(v.CanonicalExtJSON))) cEJ := unescapeUnicode(compactEJ.String(), 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 { var compactEJ bytes.Buffer require.NoError(t, json.Compact(&compactEJ, []byte(*v.RelaxedExtJSON))) rEJ := unescapeUnicode(compactEJ.String(), test.BsonType) if test.BsonType == "0x01" { rEJ = normalizeRelaxedDouble(t, *test.TestKey, rEJ) } nativeToJSON(t, rEJ, doc, v.Description, "relaxed", "rEJ", "bson_to_native(cB)") /*** relaxed extended JSON round-trip tests (if exists) ***/ doc, err = jsonToNative(rEJ, "relaxed", v.Description) require.NoError(t, err) // native_to_relaxed_extended_json(json_to_native(rEJ)) = rEJ nativeToJSON(t, rEJ, doc, v.Description, "relaxed", "eJR", "json_to_native(rEJ)") } /*** canonical extended JSON round-trip tests ***/ doc, err = jsonToNative(cEJ, "canonical", v.Description) require.NoError(t, err) // native_to_canonical_extended_json(json_to_native(cEJ)) = cEJ nativeToJSON(t, cEJ, doc, v.Description, "canonical", "cEJ", "json_to_native(cEJ)") // native_to_bson(json_to_native(cEJ)) = cb (unless lossy) if v.Lossy == nil || !*v.Lossy { nativeToBSON(t, cB, doc, v.Description, "canonical", "json_to_native(cEJ)") } /*** degenerate BSON round-trip tests (if exists) ***/ if v.DegenerateBSON != nil { dB, err := hex.DecodeString(*v.DegenerateBSON) require.NoErrorf(t, err, "%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 { var compactEJ bytes.Buffer require.NoError(t, json.Compact(&compactEJ, []byte(*v.DegenerateExtJSON))) dEJ := unescapeUnicode(compactEJ.String(), test.BsonType) if test.BsonType == "0x01" { dEJ = normalizeCanonicalDouble(t, *test.TestKey, dEJ) } doc, err = jsonToNative(dEJ, "degenerate canonical", v.Description) require.NoError(t, err) // native_to_canonical_extended_json(json_to_native(dEJ)) = cEJ nativeToJSON(t, cEJ, doc, v.Description, "degenerate canonical", "cEJ", "json_to_native(dEJ)") // native_to_bson(json_to_native(dEJ)) = cB (unless lossy) if v.Lossy == nil || !*v.Lossy { nativeToBSON(t, cB, doc, v.Description, "canonical", "json_to_native(dEJ)") } } }) } }) t.Run("decode error", func(t *testing.T) { for _, d := range test.DecodeErrors { t.Run(d.Description, func(t *testing.T) { b, err := hex.DecodeString(d.Bson) require.NoError(t, err, d.Description) var doc D err = Unmarshal(b, &doc) // The driver unmarshals invalid UTF-8 strings without error. Loop over the unmarshalled elements // and assert that there was no error if any of the string or DBPointer values contain invalid UTF-8 // characters. for _, elem := range doc { value := reflect.ValueOf(elem.Value) invalidString := (value.Kind() == reflect.String) && !utf8.ValidString(value.String()) dbPtr, ok := elem.Value.(DBPointer) invalidDBPtr := ok && !utf8.ValidString(dbPtr.DB) if invalidString || invalidDBPtr { require.NoError(t, err, d.Description) return } } require.Errorf(t, err, "%s: expected decode error", d.Description) }) } }) t.Run("parse error", func(t *testing.T) { for _, p := range test.ParseErrors { t.Run(p.Description, func(t *testing.T) { s := unescapeUnicode(p.String, test.BsonType) if test.BsonType == "0x13" { s = fmt.Sprintf(`{"decimal128": {"$numberDecimal": "%s"}}`, s) } switch test.BsonType { case "0x00", "0x05", "0x13": var doc D err := UnmarshalExtJSON([]byte(s), true, &doc) // Null bytes are validated when marshaling to BSON if strings.Contains(p.Description, "Null") { _, err = Marshal(doc) } require.Errorf(t, err, "%s: expected parse error", p.Description) default: t.Errorf("Update test to check for parse errors for type %s", test.BsonType) t.Fail() } }) } }) }) } func TestBSONCorpus(t *testing.T) { jsonFiles, err := findJSONFilesInDir(dataDir) require.NoErrorf(t, err, "error finding JSON files in %s: %v", dataDir, err) for _, file := range jsonFiles { runTest(t, file) } } func TestRelaxedUUIDValidation(t *testing.T) { testCases := []struct { description string canonicalExtJSON string degenerateExtJSON string expectedErr string }{ { "valid uuid", "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}", "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}", "", }, { "invalid uuid--no hyphens", "", "{\"x\" : { \"$uuid\" : \"73ffd26444b34c6990e8e7d1dfc035d4\"}}", "$uuid value does not follow RFC 4122 format regarding length and hyphens", }, { "invalid uuid--trailing hyphens", "", "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035--\"}}", "$uuid value does not follow RFC 4122 format regarding length and hyphens", }, { "invalid uuid--malformed hex", "", "{\"x\" : { \"$uuid\" : \"q3@fd26l-44b3-4c69-90e8-e7d1dfc035d4\"}}", "$uuid value does not follow RFC 4122 format regarding hex bytes: encoding/hex: invalid byte: U+0071 'q'", }, } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { // get canonical extended JSON (if provided) cEJ := "" if tc.canonicalExtJSON != "" { var compactCEJ bytes.Buffer require.NoError(t, json.Compact(&compactCEJ, []byte(tc.canonicalExtJSON))) cEJ = unescapeUnicode(compactCEJ.String(), "0x05") } // get degenerate extended JSON var compactDEJ bytes.Buffer require.NoError(t, json.Compact(&compactDEJ, []byte(tc.degenerateExtJSON))) dEJ := unescapeUnicode(compactDEJ.String(), "0x05") // convert dEJ to native doc var doc D err := UnmarshalExtJSON([]byte(dEJ), true, &doc) if tc.expectedErr != "" { assert.Equal(t, tc.expectedErr, err.Error(), "expected error %v, got %v", tc.expectedErr, err) } else { assert.Nil(t, err, "expected no error, got error: %v", err) // Marshal doc into extended JSON and compare with cEJ nativeToJSON(t, cEJ, doc, tc.description, "degenerate canonical", "cEJ", "json_to_native(dEJ)") } }) } } ================================================ FILE: bson/bson_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "reflect" "strconv" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/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 TestTimestamp(t *testing.T) { t.Parallel() testCases := []struct { description string tp Timestamp tp2 Timestamp expectedAfter bool expectedBefore bool expectedEqual bool expectedCompare int }{ { description: "equal", tp: Timestamp{T: 12345, I: 67890}, tp2: Timestamp{T: 12345, I: 67890}, expectedBefore: false, expectedAfter: false, expectedEqual: true, expectedCompare: 0, }, { description: "T greater than", tp: Timestamp{T: 12345, I: 67890}, tp2: Timestamp{T: 2345, I: 67890}, expectedBefore: false, expectedAfter: true, expectedEqual: false, expectedCompare: 1, }, { description: "I greater than", tp: Timestamp{T: 12345, I: 67890}, tp2: Timestamp{T: 12345, I: 7890}, expectedBefore: false, expectedAfter: true, expectedEqual: false, expectedCompare: 1, }, { description: "T less than", tp: Timestamp{T: 12345, I: 67890}, tp2: Timestamp{T: 112345, I: 67890}, expectedBefore: true, expectedAfter: false, expectedEqual: false, expectedCompare: -1, }, { description: "I less than", tp: Timestamp{T: 12345, I: 67890}, tp2: Timestamp{T: 12345, I: 167890}, expectedBefore: true, expectedAfter: false, expectedEqual: false, expectedCompare: -1, }, } for _, tc := range testCases { tc := tc // Capture range variable. t.Run(tc.description, func(t *testing.T) { t.Parallel() assert.Equal(t, tc.expectedAfter, tc.tp.After(tc.tp2), "expected After results to be the same") assert.Equal(t, tc.expectedBefore, tc.tp.Before(tc.tp2), "expected Before results to be the same") assert.Equal(t, tc.expectedEqual, tc.tp.Equal(tc.tp2), "expected Equal results to be the same") assert.Equal(t, tc.expectedCompare, tc.tp.Compare(tc.tp2), "expected Compare result to be the same") }) } } func TestPrimitiveIsZero(t *testing.T) { testcases := []struct { name string zero Zeroer nonzero Zeroer }{ {"binary", Binary{}, Binary{Data: []byte{0x01, 0x02, 0x03}, Subtype: 0xFF}}, {"decimal128", Decimal128{}, NewDecimal128(1, 2)}, {"objectID", ObjectID{}, NewObjectID()}, {"regex", Regex{}, Regex{Pattern: "foo", Options: "bar"}}, {"dbPointer", DBPointer{}, DBPointer{DB: "foobar", Pointer: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}}}, {"timestamp", Timestamp{}, Timestamp{T: 12345, I: 67890}}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { require.True(t, tc.zero.IsZero()) require.False(t, tc.nonzero.IsZero()) }) } } func TestRegexCompare(t *testing.T) { testcases := []struct { name string r1 Regex r2 Regex eq bool }{ {"equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo1", Options: "bar1"}, true}, {"not equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo2", Options: "bar2"}, false}, {"not equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo1", Options: "bar2"}, false}, {"not equal", Regex{Pattern: "foo1", Options: "bar1"}, Regex{Pattern: "foo2", Options: "bar1"}, false}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { require.True(t, tc.r1.Equal(tc.r2) == tc.eq) }) } } func TestDateTime(t *testing.T) { t.Run("json", func(t *testing.T) { t.Run("round trip", func(t *testing.T) { original := DateTime(1000) jsonBytes, err := json.Marshal(original) assert.Nil(t, err, "Marshal error: %v", err) var unmarshalled DateTime err = json.Unmarshal(jsonBytes, &unmarshalled) assert.Nil(t, err, "Unmarshal error: %v", err) assert.Equal(t, original, unmarshalled, "expected DateTime %v, got %v", original, unmarshalled) }) t.Run("decode null", func(t *testing.T) { jsonBytes := []byte("null") var dt DateTime err := json.Unmarshal(jsonBytes, &dt) assert.Nil(t, err, "Unmarshal error: %v", err) assert.Equal(t, DateTime(0), dt, "expected DateTime value to be 0, got %v", dt) }) t.Run("UTC", func(t *testing.T) { dt := DateTime(1681145535123) jsonBytes, err := json.Marshal(dt) assert.Nil(t, err, "Marshal error: %v", err) assert.Equal(t, `"2023-04-10T16:52:15.123Z"`, string(jsonBytes)) }) }) t.Run("NewDateTimeFromTime", func(t *testing.T) { t.Run("range is not limited", func(t *testing.T) { // If the implementation internally calls time.Time.UnixNano(), the constructor cannot handle times after // the year 2262. timeFormat := "2006-01-02T15:04:05.999Z07:00" timeString := "3001-01-01T00:00:00Z" tt, err := time.Parse(timeFormat, timeString) assert.Nil(t, err, "Parse error: %v", err) dt := NewDateTimeFromTime(tt) assert.True(t, dt > 0, "expected a valid DateTime greater than 0, got %v", dt) }) }) } 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) } }) } func TestDStringer(t *testing.T) { got := D{{"a", 1}, {"b", 2}}.String() want := `{"a":{"$numberInt":"1"},"b":{"$numberInt":"2"}}` assert.Equal(t, want, got, "expected: %s, got: %s", want, got) } func TestMStringer(t *testing.T) { type msg struct { A json.RawMessage `json:"a"` B json.RawMessage `json:"b"` } var res msg got := M{"a": 1, "b": 2}.String() err := json.Unmarshal([]byte(got), &res) require.NoError(t, err, "Unmarshal error") want := msg{ A: json.RawMessage(`{"$numberInt":"1"}`), B: json.RawMessage(`{"$numberInt":"2"}`), } assert.Equal(t, want, res, "returned string did not unmarshal to the expected document, returned string: %s", got) } func TestD_MarshalJSON(t *testing.T) { t.Parallel() testcases := []struct { name string test D expected any }{ { "nil", nil, nil, }, { "empty", D{}, struct{}{}, }, { "non-empty", D{ {"a", 42}, {"b", true}, {"c", "answer"}, {"d", nil}, {"e", 2.71828}, {"f", A{42, true, "answer", nil, 2.71828}}, {"g", D{{"foo", "bar"}}}, }, struct { A int `json:"a"` B bool `json:"b"` C string `json:"c"` D any `json:"d"` E float32 `json:"e"` F []any `json:"f"` G map[string]any `json:"g"` }{ A: 42, B: true, C: "answer", D: nil, E: 2.71828, F: []any{42, true, "answer", nil, 2.71828}, G: map[string]any{"foo": "bar"}, }, }, } for _, tc := range testcases { tc := tc t.Run("json.Marshal "+tc.name, func(t *testing.T) { t.Parallel() got, err := json.Marshal(tc.test) assert.NoError(t, err) want, _ := json.Marshal(tc.expected) assert.Equal(t, want, got) }) } for _, tc := range testcases { tc := tc t.Run("json.MarshalIndent "+tc.name, func(t *testing.T) { t.Parallel() got, err := json.MarshalIndent(tc.test, "", "") assert.NoError(t, err) want, _ := json.MarshalIndent(tc.expected, "", "") assert.Equal(t, want, got) }) } } func TestD_UnmarshalJSON(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() for _, tc := range []struct { name string test []byte expected D }{ { "nil", []byte(`null`), nil, }, { "empty", []byte(`{}`), D{}, }, { "non-empty", []byte(`{"hello":"world","pi":3.142,"boolean":true,"nothing":null,"list":["hello world",3.142,false,null,{"Lorem":"ipsum"}],"document":{"foo":"bar"}}`), D{ {"hello", "world"}, {"pi", 3.142}, {"boolean", true}, {"nothing", nil}, {"list", []any{"hello world", 3.142, false, nil, D{{"Lorem", "ipsum"}}}}, {"document", D{{"foo", "bar"}}}, }, }, } { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() var got D err := json.Unmarshal(tc.test, &got) assert.NoError(t, err) assert.Equal(t, tc.expected, got) }) } }) t.Run("failure", func(t *testing.T) { t.Parallel() for _, tc := range []struct { name string test string }{ { "illegal", `nil`, }, { "invalid", `{"pi": 3.142ipsum}`, }, { "malformatted", `{"pi", 3.142}`, }, { "truncated", `{"pi": 3.142`, }, { "array type", `["pi", 3.142]`, }, { "boolean type", `true`, }, } { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() var a map[string]any want := json.Unmarshal([]byte(tc.test), &a) var b D got := json.Unmarshal([]byte(tc.test), &b) w := new(json.UnmarshalTypeError) if errors.As(want, &w) { w.Type = reflect.TypeOf(b) require.IsType(t, want, got) g := new(json.UnmarshalTypeError) assert.True(t, errors.As(got, &g)) assert.Equal(t, w, g) } else { assert.Equal(t, want, got) } }) } }) } type stringerString string func (ss stringerString) String() string { return "bar" } type keyBool bool func (kb keyBool) MarshalKey() (string, error) { return fmt.Sprintf("%v", kb), nil } func (kb *keyBool) UnmarshalKey(key string) error { switch key { case "true": *kb = true case "false": *kb = false default: return fmt.Errorf("invalid bool value %v", key) } return nil } type keyStruct struct { val int64 } func (k keyStruct) MarshalText() (text []byte, err error) { str := strconv.FormatInt(k.val, 10) return []byte(str), nil } func (k *keyStruct) UnmarshalText(text []byte) error { val, err := strconv.ParseInt(string(text), 10, 64) if err != nil { return err } *k = keyStruct{ val: val, } return nil } func TestMapCodec(t *testing.T) { t.Run("EncodeKeysWithStringer", func(t *testing.T) { strstr := stringerString("foo") mapObj := map[stringerString]int{strstr: 1} testCases := []struct { name string mapCodec *mapCodec key string }{ {"default", &mapCodec{}, "foo"}, {"true", &mapCodec{encodeKeysWithStringer: true}, "bar"}, {"false", &mapCodec{encodeKeysWithStringer: false}, "foo"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { mapRegistry := NewRegistry() mapRegistry.RegisterKindEncoder(reflect.Map, tc.mapCodec) buf := new(bytes.Buffer) vw := NewDocumentWriter(buf) enc := NewEncoder(vw) enc.SetRegistry(mapRegistry) err := enc.Encode(mapObj) assert.Nil(t, err, "Encode error: %v", err) str := buf.String() assert.True(t, strings.Contains(str, tc.key), "expected result to contain %v, got: %v", tc.key, str) }) } }) t.Run("keys implements keyMarshaler and keyUnmarshaler", func(t *testing.T) { mapObj := map[keyBool]int{keyBool(true): 1} doc, err := Marshal(mapObj) assert.Nil(t, err, "Marshal error: %v", err) idx, want := bsoncore.AppendDocumentStart(nil) want = bsoncore.AppendInt32Element(want, "true", 1) want, _ = bsoncore.AppendDocumentEnd(want, idx) assert.Equal(t, want, doc, "expected result %v, got %v", string(want), string(doc)) var got map[keyBool]int err = Unmarshal(doc, &got) assert.Nil(t, err, "Unmarshal error: %v", err) assert.Equal(t, mapObj, got, "expected result %v, got %v", mapObj, got) }) t.Run("keys implements encoding.TextMarshaler and encoding.TextUnmarshaler", func(t *testing.T) { mapObj := map[keyStruct]int{ {val: 10}: 100, } doc, err := Marshal(mapObj) assert.Nil(t, err, "Marshal error: %v", err) idx, want := bsoncore.AppendDocumentStart(nil) want = bsoncore.AppendInt32Element(want, "10", 100) want, _ = bsoncore.AppendDocumentEnd(want, idx) assert.Equal(t, want, doc, "expected result %v, got %v", string(want), string(doc)) var got map[keyStruct]int err = Unmarshal(doc, &got) assert.Nil(t, err, "Unmarshal error: %v", err) assert.Equal(t, mapObj, got, "expected result %v, got %v", mapObj, got) }) } func TestExtJSONEscapeKey(t *testing.T) { doc := D{ { Key: "\\usb#", Value: int32(1), }, { Key: "regex", Value: Regex{Pattern: "ab\\\\\\\"ab", Options: "\""}, }, } b, err := MarshalExtJSON(&doc, false, false) noerr(t, err) want := `{"\\usb#":1,"regex":{"$regularExpression":{"pattern":"ab\\\\\\\"ab","options":"\""}}}` if diff := cmp.Diff(want, string(b)); diff != "" { t.Errorf("Marshaled documents do not match. got %v, want %v", string(b), want) } var got D err = UnmarshalExtJSON(b, false, &got) noerr(t, err) if !cmp.Equal(got, doc) { t.Errorf("Unmarshaled documents do not match. got %v; want %v", got, doc) } } func TestBsoncoreArray(t *testing.T) { type BSONDocumentArray struct { Array []D `bson:"array"` } type BSONArray struct { Array bsoncore.Array `bson:"array"` } bda := BSONDocumentArray{ Array: []D{ {{"x", 1}}, {{"x", 2}}, {{"x", 3}}, }, } expectedBSON, err := Marshal(bda) assert.Nil(t, err, "Marshal bsoncore.Document array error: %v", err) var ba BSONArray err = Unmarshal(expectedBSON, &ba) assert.Nil(t, err, "Unmarshal error: %v", err) actualBSON, err := Marshal(ba) assert.Nil(t, err, "Marshal bsoncore.Array error: %v", err) assert.Equal(t, expectedBSON, actualBSON, "expected BSON to be %v after Marshalling again; got %v", expectedBSON, actualBSON) doc := bsoncore.Document(actualBSON) v := doc.Lookup("array") assert.Equal(t, bsoncore.TypeArray, v.Type, "expected type array, got %v", v.Type) } var baseTime = time.Date(2024, 10, 11, 12, 13, 14, 12345678, time.UTC) func BenchmarkDateTimeMarshalJSON(b *testing.B) { t := NewDateTimeFromTime(baseTime) data, err := t.MarshalJSON() if err != nil { b.Fatal(err) } b.ReportAllocs() b.SetBytes(int64(len(data))) for i := 0; i < b.N; i++ { if _, err := t.MarshalJSON(); err != nil { b.Fatal(err) } } } func BenchmarkDateTimeUnmarshalJSON(b *testing.B) { t := NewDateTimeFromTime(baseTime) data, err := t.MarshalJSON() if err != nil { b.Fatal(err) } b.ReportAllocs() b.SetBytes(int64(len(data))) for i := 0; i < b.N; i++ { var dt DateTime if err := dt.UnmarshalJSON(data); err != nil { b.Fatal(err) } } } ================================================ FILE: bson/bsoncodec.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "strings" ) var emptyValue = reflect.Value{} // 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() } if !vde.Received.CanSet() { received = "unsettable " + received } 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 causes the Encoder to marshal Go integer values (int, int8, int16, int32, int64, // uint, uint8, uint16, uint32, or uint64) as the minimum BSON int size (either 32 or 64 bits) // that can represent the integer value. minSize bool errorOnInlineDuplicates bool stringifyMapKeysWithFmt bool nilMapAsEmpty bool nilSliceAsEmpty bool nilByteSliceAsEmpty bool omitZeroStruct bool omitEmpty bool useJSONStructTags bool } // DecodeContext is the contextual information required for a Codec to decode a // value. type DecodeContext struct { *Registry // truncate, if true, instructs decoders to to truncate the fractional part of BSON "double" // values when attempting to unmarshal them into a Go integer (int, int8, int16, int32, int64, // uint, uint8, uint16, uint32, or uint64) struct field. The truncation logic does not apply to // BSON "decimal128" values. truncate bool // defaultDocumentType specifies the Go type to decode top-level and nested BSON documents into. In particular, the // usage for this field is restricted to data typed as "any" or "map[string]any". If DocumentType is // set to a type that a BSON document cannot be unmarshaled into (e.g. "string"), unmarshalling will result in an // error. defaultDocumentType reflect.Type binaryAsSlice bool // a false value results in a decoding error. objectIDAsHexString bool useJSONStructTags bool useLocalTimeZone bool zeroMaps bool zeroStructs bool } // ValueEncoder is the interface implemented by types that can encode a provided Go type to BSON. // The value to encode is provided as a reflect.Value and a bson.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. type ValueEncoder interface { EncodeValue(EncodeContext, 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, ValueWriter, reflect.Value) error // EncodeValue implements the ValueEncoder interface. func (fn ValueEncoderFunc) EncodeValue(ec EncodeContext, vw ValueWriter, val reflect.Value) error { return fn(ec, vw, val) } // ValueDecoder is the interface implemented by types that can decode BSON to a provided Go type. // 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. type ValueDecoder interface { DecodeValue(DecodeContext, 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, ValueReader, reflect.Value) error // DecodeValue implements the ValueDecoder interface. func (fn ValueDecoderFunc) DecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { return fn(dc, vr, val) } // typeDecoder is the interface implemented by types that can handle the decoding of a value given its type. type typeDecoder interface { decodeType(DecodeContext, ValueReader, reflect.Type) (reflect.Value, error) } // typeDecoderFunc is an adapter function that allows a function with the correct signature to be used as a typeDecoder. type typeDecoderFunc func(DecodeContext, ValueReader, reflect.Type) (reflect.Value, error) func (fn typeDecoderFunc) decodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { return fn(dc, vr, t) } // decodeAdapter allows two functions with the correct signatures to be used as both a ValueDecoder and typeDecoder. type decodeAdapter struct { ValueDecoderFunc typeDecoderFunc } var ( _ ValueDecoder = decodeAdapter{} _ typeDecoder = decodeAdapter{} ) func decodeTypeOrValueWithInfo(vd ValueDecoder, dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if td, _ := vd.(typeDecoder); td != nil { val, err := td.decodeType(dc, vr, t) if err == nil && val.Type() != t { // This conversion step is necessary for slices and maps. If a user declares variables like: // // type myBool bool // var m map[string]myBool // // and tries to decode BSON bytes into the map, the decoding will fail if this conversion is not present // because we'll try to assign a value of type bool to one of type myBool. val = val.Convert(t) } return val, err } val := reflect.New(t).Elem() err := vd.DecodeValue(dc, vr, val) return val, err } ================================================ FILE: bson/bsoncodec_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" ) func ExampleValueEncoder() { var _ ValueEncoderFunc = func(_ EncodeContext, vw 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(_ DecodeContext, vr 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() != TypeString { 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 } } type llCodec struct { t *testing.T decodeval any encodeval any err error } func (llc *llCodec) EncodeValue(_ EncodeContext, _ ValueWriter, i any) error { if llc.err != nil { return llc.err } llc.encodeval = i return nil } func (llc *llCodec) DecodeValue(_ DecodeContext, _ 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 } ================================================ FILE: bson/bsonrw_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) var ( _ ValueReader = &valueReaderWriter{} _ ValueWriter = &valueReaderWriter{} ) // 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 "" } } // valueReaderWriter is a test implementation of a bsonrw.ValueReader and bsonrw.ValueWriter type valueReaderWriter struct { T *testing.T invoked invoked Return any // Can be a primitive or a bsoncore.Value 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 ValueReader interface. func (llvrw *valueReaderWriter) Type() Type { llvrw.checkdepth() return llvrw.BSONType } // Skip implements the 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 ValueReader interface. func (llvrw *valueReaderWriter) ReadArray() (ArrayReader, error) { llvrw.checkdepth() llvrw.invoked = readArray if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // ReadBinary implements the 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 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 ValueReader interface. func (llvrw *valueReaderWriter) ReadDocument() (DocumentReader, error) { llvrw.checkdepth() llvrw.invoked = readDocument if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // ReadCodeWithScope implements the ValueReader interface. func (llvrw *valueReaderWriter) ReadCodeWithScope() (code string, dr DocumentReader, err error) { llvrw.checkdepth() llvrw.invoked = readCodeWithScope if llvrw.ErrAfter == llvrw.invoked { return "", nil, llvrw.Err } return "", llvrw, nil } // ReadDBPointer implements the ValueReader interface. func (llvrw *valueReaderWriter) ReadDBPointer() (ns string, oid ObjectID, err error) { llvrw.checkdepth() llvrw.invoked = readDBPointer if llvrw.ErrAfter == llvrw.invoked { return "", 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 "", ObjectID{}, nil } return ns, oid, nil default: llvrw.T.Errorf("Incorrect type provided for return value of ReadDBPointer: %T", llvrw.Return) return "", ObjectID{}, nil } } // ReadDateTime implements the 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 ValueReader interface. func (llvrw *valueReaderWriter) ReadDecimal128() (Decimal128, error) { llvrw.checkdepth() llvrw.invoked = readDecimal128 if llvrw.ErrAfter == llvrw.invoked { return Decimal128{}, llvrw.Err } d128, ok := llvrw.Return.(Decimal128) if !ok { llvrw.T.Errorf("Incorrect type provided for return value of ReadDecimal128: %T", llvrw.Return) return Decimal128{}, nil } return d128, nil } // ReadDouble implements the 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 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 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 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 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 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 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 ValueReader interface. func (llvrw *valueReaderWriter) ReadObjectID() (ObjectID, error) { llvrw.checkdepth() llvrw.invoked = readObjectID if llvrw.ErrAfter == llvrw.invoked { return ObjectID{}, llvrw.Err } oid, ok := llvrw.Return.(ObjectID) if !ok { llvrw.T.Errorf("Incorrect type provided for return value of ReadObjectID: %T", llvrw.Return) return ObjectID{}, nil } return oid, nil } // ReadRegex implements the 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 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 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 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 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 ValueWriter interface. func (llvrw *valueReaderWriter) WriteArray() (ArrayWriter, error) { llvrw.checkdepth() llvrw.invoked = writeArray if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // WriteBinary implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteBinary([]byte) error { llvrw.checkdepth() llvrw.invoked = writeBinary if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteBinaryWithSubtype implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteBinaryWithSubtype([]byte, byte) error { llvrw.checkdepth() llvrw.invoked = writeBinaryWithSubtype if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteBoolean implements the 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 ValueWriter interface. func (llvrw *valueReaderWriter) WriteCodeWithScope(string) (DocumentWriter, error) { llvrw.checkdepth() llvrw.invoked = writeCodeWithScope if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // WriteDBPointer implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteDBPointer(string, ObjectID) error { llvrw.checkdepth() llvrw.invoked = writeDBPointer if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteDateTime implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteDateTime(int64) error { llvrw.checkdepth() llvrw.invoked = writeDateTime if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteDecimal128 implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteDecimal128(Decimal128) error { llvrw.checkdepth() llvrw.invoked = writeDecimal128 if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteDouble implements the 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 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 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 ValueWriter interface. func (llvrw *valueReaderWriter) WriteJavascript(string) error { llvrw.checkdepth() llvrw.invoked = writeJavascript if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteMaxKey implements the 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 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 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 ValueWriter interface. func (llvrw *valueReaderWriter) WriteObjectID(ObjectID) error { llvrw.checkdepth() llvrw.invoked = writeObjectID if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteRegex implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteRegex(string, string) error { llvrw.checkdepth() llvrw.invoked = writeRegex if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteString implements the 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 ValueWriter interface. func (llvrw *valueReaderWriter) WriteDocument() (DocumentWriter, error) { llvrw.checkdepth() llvrw.invoked = writeDocument if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // WriteSymbol implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteSymbol(string) error { llvrw.checkdepth() llvrw.invoked = writeSymbol if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteTimestamp implements the ValueWriter interface. func (llvrw *valueReaderWriter) WriteTimestamp(uint32, uint32) error { llvrw.checkdepth() llvrw.invoked = writeTimestamp if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } // WriteUndefined implements the 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 DocumentReader interface. func (llvrw *valueReaderWriter) ReadElement() (string, ValueReader, error) { llvrw.checkdepth() llvrw.invoked = readElement if llvrw.ErrAfter == llvrw.invoked { return "", nil, llvrw.Err } return "", llvrw, nil } // WriteDocumentElement implements the DocumentWriter interface. func (llvrw *valueReaderWriter) WriteDocumentElement(string) (ValueWriter, error) { llvrw.checkdepth() llvrw.invoked = writeDocumentElement if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // WriteDocumentEnd implements the 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 ArrayReader interface. func (llvrw *valueReaderWriter) ReadValue() (ValueReader, error) { llvrw.checkdepth() llvrw.invoked = readValue if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // WriteArrayElement implements the ArrayWriter interface. func (llvrw *valueReaderWriter) WriteArrayElement() (ValueWriter, error) { llvrw.checkdepth() llvrw.invoked = writeArrayElement if llvrw.ErrAfter == llvrw.invoked { return nil, llvrw.Err } return llvrw, nil } // WriteArrayEnd implements the ArrayWriter interface. func (llvrw *valueReaderWriter) WriteArrayEnd() error { llvrw.checkdepth() llvrw.invoked = writeArrayEnd if llvrw.ErrAfter == llvrw.invoked { return llvrw.Err } return nil } ================================================ FILE: bson/buffered_byte_src.go ================================================ // Copyright (C) MongoDB, Inc. 2025-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "io" ) // bufferedByteSrc implements the low-level byteSrc interface by reading // directly from an in-memory byte slice. It provides efficient, zero-copy // access for parsing BSON when the entire document is buffered in memory. type bufferedByteSrc struct { buf []byte // entire BSON document offset int64 // Current read index into buf } var _ byteSrc = (*bufferedByteSrc)(nil) // Read reads up to len(p) bytes from the in-memory buffer, advancing the offset // by the number of bytes read. func (b *bufferedByteSrc) readExact(p []byte) (int, error) { if b.offset >= int64(len(b.buf)) { return 0, io.EOF } n := copy(p, b.buf[b.offset:]) b.offset += int64(n) return n, nil } // ReadByte returns the single byte at buf[offset] and advances offset by 1. func (b *bufferedByteSrc) ReadByte() (byte, error) { if b.offset >= int64(len(b.buf)) { return 0, io.EOF } b.offset++ return b.buf[b.offset-1], nil } // peek returns buf[offset:offset+n] without advancing offset. func (b *bufferedByteSrc) peek(n int) ([]byte, error) { // Ensure we don't read past the end of the buffer. if int64(n)+b.offset > int64(len(b.buf)) { return b.buf[b.offset:], io.EOF } // Return the next n bytes without advancing the offset return b.buf[b.offset : b.offset+int64(n)], nil } // discard advances offset by n bytes, returning the number of bytes discarded. func (b *bufferedByteSrc) discard(n int) (int, error) { // Ensure we don't read past the end of the buffer. if int64(n)+b.offset > int64(len(b.buf)) { // If we have exceeded the buffer length, discard only up to the end. left := len(b.buf) - int(b.offset) b.offset = int64(len(b.buf)) return left, io.EOF } // Advance the read position b.offset += int64(n) return n, nil } // readSlice reads buf[offset:] for the first occurrence of delim, returning // buf[offset:idx+1], and advances offset past it; errors if delim not found. func (b *bufferedByteSrc) readSlice(delim byte) ([]byte, error) { // Ensure we don't read past the end of the buffer. if b.offset >= int64(len(b.buf)) { return nil, io.EOF } // Look for the delimiter in the remaining bytes rem := b.buf[b.offset:] idx := bytes.IndexByte(rem, delim) if idx < 0 { return nil, io.EOF } // Build the result slice up through the delimiter. result := rem[:idx+1] // Advance the offset past the delimiter. b.offset += int64(idx + 1) return result, nil } // pos returns the current read position in the buffer. func (b *bufferedByteSrc) pos() int64 { return b.offset } // regexLength will return the total byte length of a BSON regex value. func (b *bufferedByteSrc) regexLength() (int32, error) { rem := b.buf[b.offset:] // Find end of the first C-string (pattern). i := bytes.IndexByte(rem, 0x00) if i < 0 { return 0, io.EOF } // Find end of second C-string (options). j := bytes.IndexByte(rem[i+1:], 0x00) if j < 0 { return 0, io.EOF } // Total length = first C-string length (pattern) + second C-string length // (options) + 2 null terminators return int32(i + j + 2), nil } func (*bufferedByteSrc) streamable() bool { return false } func (b *bufferedByteSrc) reset() { b.buf = nil b.offset = 0 } ================================================ FILE: bson/buffered_byte_src_test.go ================================================ // Copyright (C) MongoDB, Inc. 2025-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "io" "testing" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/require" ) func TestBufferedvalueReader_discard(t *testing.T) { tests := []struct { name string buf []byte n int want int wantOffset int64 wantErr error }{ { name: "nothing", buf: bytes.Repeat([]byte("a"), 1024), n: 0, want: 0, wantOffset: 0, wantErr: nil, }, { name: "amount less than buffer size", buf: bytes.Repeat([]byte("a"), 1024), n: 100, want: 100, wantOffset: 100, wantErr: nil, }, { name: "amount greater than buffer size", buf: bytes.Repeat([]byte("a"), 1024), n: 10000, want: 1024, wantOffset: 1024, wantErr: io.EOF, }, { name: "exact buffer size", buf: bytes.Repeat([]byte("a"), 1024), n: 1024, want: 1024, wantOffset: 1024, wantErr: nil, }, { name: "from empty buffer", buf: []byte{}, n: 10, want: 0, wantOffset: 0, wantErr: io.EOF, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := &bufferedByteSrc{buf: tt.buf, offset: 0} n, err := reader.discard(tt.n) if tt.wantErr != nil { assert.ErrorIs(t, err, tt.wantErr, "Expected error %v, got %v", tt.wantErr, err) } else { require.NoError(t, err, "Expected no error when discarding %d bytes", tt.n) } assert.Equal(t, tt.want, n, "Expected to discard %d bytes, got %d", tt.want, n) assert.Equal(t, tt.wantOffset, reader.offset, "Expected offset to be %d, got %d", tt.wantOffset, reader.offset) }) } } func TestBufferedvalueReader_peek(t *testing.T) { tests := []struct { name string buf []byte n int offset int64 want []byte wantErr error }{ { name: "nothing", buf: bytes.Repeat([]byte("a"), 1024), n: 0, want: []byte{}, wantErr: nil, }, { name: "amount less than buffer size", buf: bytes.Repeat([]byte("a"), 1024), n: 100, want: bytes.Repeat([]byte("a"), 100), wantErr: nil, }, { name: "amount greater than buffer size", buf: bytes.Repeat([]byte("a"), 1024), n: 10000, want: bytes.Repeat([]byte("a"), 1024), wantErr: io.EOF, }, { name: "exact buffer size", buf: bytes.Repeat([]byte("a"), 1024), n: 1024, want: bytes.Repeat([]byte("a"), 1024), wantErr: nil, }, { name: "from empty buffer", buf: []byte{}, n: 10, want: []byte{}, wantErr: io.EOF, }, { name: "peek with offset", buf: append(bytes.Repeat([]byte("a"), 100), bytes.Repeat([]byte("b"), 100)...), offset: 100, n: 100, want: bytes.Repeat([]byte("b"), 100), wantErr: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { reader := &bufferedByteSrc{buf: tt.buf, offset: tt.offset} n, err := reader.peek(tt.n) if tt.wantErr != nil { assert.ErrorIs(t, err, tt.wantErr, "Expected error %v, got %v", tt.wantErr, err) } else { require.NoError(t, err, "Expected no error when peeking %d bytes", tt.n) } assert.Equal(t, tt.want, n, "Expected to peek %d bytes, got %d", len(tt.want), len(n)) assert.Equal(t, tt.offset, reader.offset, "Expected offset to be %d, got %d", tt.offset, reader.offset) }) } } ================================================ FILE: bson/byte_slice_codec.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" ) // byteSliceCodec is the Codec used for []byte values. type byteSliceCodec struct { // encodeNilAsEmpty causes EncodeValue to marshal nil Go byte slices as empty BSON binary values // instead of BSON null. encodeNilAsEmpty bool } // Assert that byteSliceCodec satisfies the typeDecoder interface, which allows it to be // used by collection type decoders (e.g. map, slice, etc) to set individual values in a // collection. var _ typeDecoder = &byteSliceCodec{} // EncodeValue is the ValueEncoder for []byte. func (bsc *byteSliceCodec) EncodeValue(ec EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tByteSlice { return ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: val} } if val.IsNil() && !bsc.encodeNilAsEmpty && !ec.nilByteSliceAsEmpty { return vw.WriteNull() } return vw.WriteBinary(val.Interface().([]byte)) } func (bsc *byteSliceCodec) decodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tByteSlice { return emptyValue, ValueDecoderError{ Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.Zero(t), } } var data []byte var err error switch vrType := vr.Type(); vrType { case TypeString: str, err := vr.ReadString() if err != nil { return emptyValue, err } data = []byte(str) case TypeSymbol: sym, err := vr.ReadSymbol() if err != nil { return emptyValue, err } data = []byte(sym) case TypeBinary: var subtype byte data, subtype, err = vr.ReadBinary() if err != nil { return emptyValue, err } if subtype != TypeBinaryGeneric && subtype != TypeBinaryBinaryOld { return emptyValue, decodeBinaryError{subtype: subtype, typeName: "[]byte"} } case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a []byte", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(data), nil } // DecodeValue is the ValueDecoder for []byte. func (bsc *byteSliceCodec) DecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tByteSlice { return ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: val} } elem, err := bsc.decodeType(dc, vr, tByteSlice) if err != nil { return err } val.Set(elem) return nil } ================================================ FILE: bson/codec_cache.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "sync" "sync/atomic" ) // Runtime check that the kind encoder and decoder caches can store any valid // reflect.Kind constant. func init() { if s := reflect.Kind(len(kindEncoderCache{}.entries)).String(); s != "kind27" { panic("The capacity of kindEncoderCache is too small.\n" + "This is due to a new type being added to reflect.Kind.") } } // statically assert array size var ( _ = (kindEncoderCache{}).entries[reflect.UnsafePointer] _ = (kindDecoderCache{}).entries[reflect.UnsafePointer] ) type typeEncoderCache struct { cache sync.Map // map[reflect.Type]ValueEncoder } func (c *typeEncoderCache) Store(rt reflect.Type, enc ValueEncoder) { c.cache.Store(rt, enc) } func (c *typeEncoderCache) Load(rt reflect.Type) (ValueEncoder, bool) { if v, _ := c.cache.Load(rt); v != nil { return v.(ValueEncoder), true } return nil, false } func (c *typeEncoderCache) LoadOrStore(rt reflect.Type, enc ValueEncoder) ValueEncoder { if v, loaded := c.cache.LoadOrStore(rt, enc); loaded { enc = v.(ValueEncoder) } return enc } func (c *typeEncoderCache) Clone() *typeEncoderCache { cc := new(typeEncoderCache) c.cache.Range(func(k, v any) bool { if k != nil && v != nil { cc.cache.Store(k, v) } return true }) return cc } type typeDecoderCache struct { cache sync.Map // map[reflect.Type]ValueDecoder } func (c *typeDecoderCache) Store(rt reflect.Type, dec ValueDecoder) { c.cache.Store(rt, dec) } func (c *typeDecoderCache) Load(rt reflect.Type) (ValueDecoder, bool) { if v, _ := c.cache.Load(rt); v != nil { return v.(ValueDecoder), true } return nil, false } func (c *typeDecoderCache) LoadOrStore(rt reflect.Type, dec ValueDecoder) ValueDecoder { if v, loaded := c.cache.LoadOrStore(rt, dec); loaded { dec = v.(ValueDecoder) } return dec } func (c *typeDecoderCache) Clone() *typeDecoderCache { cc := new(typeDecoderCache) c.cache.Range(func(k, v any) bool { if k != nil && v != nil { cc.cache.Store(k, v) } return true }) return cc } // atomic.Value requires that all calls to Store() have the same concrete type // so we wrap the ValueEncoder with a kindEncoderCacheEntry to ensure the type // is always the same (since different concrete types may implement the // ValueEncoder interface). type kindEncoderCacheEntry struct { enc ValueEncoder } type kindEncoderCache struct { entries [reflect.UnsafePointer + 1]atomic.Value // *kindEncoderCacheEntry } func (c *kindEncoderCache) Store(rt reflect.Kind, enc ValueEncoder) { if enc != nil && rt < reflect.Kind(len(c.entries)) { c.entries[rt].Store(&kindEncoderCacheEntry{enc: enc}) } } func (c *kindEncoderCache) Load(rt reflect.Kind) (ValueEncoder, bool) { if rt < reflect.Kind(len(c.entries)) { if ent, ok := c.entries[rt].Load().(*kindEncoderCacheEntry); ok { return ent.enc, ent.enc != nil } } return nil, false } func (c *kindEncoderCache) Clone() *kindEncoderCache { cc := new(kindEncoderCache) for i, v := range c.entries { if val := v.Load(); val != nil { cc.entries[i].Store(val) } } return cc } // atomic.Value requires that all calls to Store() have the same concrete type // so we wrap the ValueDecoder with a kindDecoderCacheEntry to ensure the type // is always the same (since different concrete types may implement the // ValueDecoder interface). type kindDecoderCacheEntry struct { dec ValueDecoder } type kindDecoderCache struct { entries [reflect.UnsafePointer + 1]atomic.Value // *kindDecoderCacheEntry } func (c *kindDecoderCache) Store(rt reflect.Kind, dec ValueDecoder) { if rt < reflect.Kind(len(c.entries)) { c.entries[rt].Store(&kindDecoderCacheEntry{dec: dec}) } } func (c *kindDecoderCache) Load(rt reflect.Kind) (ValueDecoder, bool) { if rt < reflect.Kind(len(c.entries)) { if ent, ok := c.entries[rt].Load().(*kindDecoderCacheEntry); ok { return ent.dec, ent.dec != nil } } return nil, false } func (c *kindDecoderCache) Clone() *kindDecoderCache { cc := new(kindDecoderCache) for i, v := range c.entries { if val := v.Load(); val != nil { cc.entries[i].Store(val) } } return cc } ================================================ FILE: bson/codec_cache_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "strconv" "strings" "testing" ) // NB(charlie): the array size is a power of 2 because we use the remainder of // it (mod) in benchmarks and that is faster when the size is a power of 2. var codecCacheTestTypes = [16]reflect.Type{ reflect.TypeOf(uint8(0)), reflect.TypeOf(uint16(0)), reflect.TypeOf(uint32(0)), reflect.TypeOf(uint64(0)), reflect.TypeOf(uint(0)), reflect.TypeOf(uintptr(0)), reflect.TypeOf(int8(0)), reflect.TypeOf(int16(0)), reflect.TypeOf(int32(0)), reflect.TypeOf(int64(0)), reflect.TypeOf(int(0)), reflect.TypeOf(float32(0)), reflect.TypeOf(float64(0)), reflect.TypeOf(true), reflect.TypeOf(struct{ A int }{}), reflect.TypeOf(map[int]int{}), } func TestTypeCache(t *testing.T) { rt := reflect.TypeOf(int(0)) ec := new(typeEncoderCache) dc := new(typeDecoderCache) codec := new(fakeCodec) ec.Store(rt, codec) dc.Store(rt, codec) if v, ok := ec.Load(rt); !ok || !reflect.DeepEqual(v, codec) { t.Errorf("Load(%s) = %v, %t; want: %v, %t", rt, v, ok, codec, true) } if v, ok := dc.Load(rt); !ok || !reflect.DeepEqual(v, codec) { t.Errorf("Load(%s) = %v, %t; want: %v, %t", rt, v, ok, codec, true) } // Make sure we overwrite the stored value with nil ec.Store(rt, nil) dc.Store(rt, nil) if v, ok := ec.Load(rt); ok || v != nil { t.Errorf("Load(%s) = %v, %t; want: %v, %t", rt, v, ok, nil, false) } if v, ok := dc.Load(rt); ok || v != nil { t.Errorf("Load(%s) = %v, %t; want: %v, %t", rt, v, ok, nil, false) } } func TestTypeCacheClone(t *testing.T) { codec := new(fakeCodec) ec1 := new(typeEncoderCache) dc1 := new(typeDecoderCache) for _, rt := range codecCacheTestTypes { ec1.Store(rt, codec) dc1.Store(rt, codec) } ec2 := ec1.Clone() dc2 := dc1.Clone() for _, rt := range codecCacheTestTypes { if v, _ := ec2.Load(rt); !reflect.DeepEqual(v, codec) { t.Errorf("Load(%s) = %#v; want: %#v", rt, v, codec) } if v, _ := dc2.Load(rt); !reflect.DeepEqual(v, codec) { t.Errorf("Load(%s) = %#v; want: %#v", rt, v, codec) } } } func TestKindCacheArray(t *testing.T) { // Check array bounds var c kindEncoderCache codec := new(fakeCodec) c.Store(reflect.UnsafePointer, codec) // valid c.Store(reflect.UnsafePointer+1, codec) // ignored if v, ok := c.Load(reflect.UnsafePointer); !ok || v != codec { t.Errorf("Load(reflect.UnsafePointer) = %v, %t; want: %v, %t", v, ok, codec, true) } if v, ok := c.Load(reflect.UnsafePointer + 1); ok || v != nil { t.Errorf("Load(reflect.UnsafePointer + 1) = %v, %t; want: %v, %t", v, ok, nil, false) } // Make sure that reflect.UnsafePointer is the last/largest reflect.Type. // // The String() method of invalid reflect.Type types are of the format // "kind{NUMBER}". for rt := reflect.UnsafePointer + 1; rt < reflect.UnsafePointer+16; rt++ { s := rt.String() if !strings.Contains(s, strconv.Itoa(int(rt))) { t.Errorf("reflect.Type(%d) appears to be valid: %q", rt, s) } } } func TestKindCacheClone(t *testing.T) { e1 := new(kindEncoderCache) d1 := new(kindDecoderCache) codec := new(fakeCodec) for k := reflect.Invalid; k <= reflect.UnsafePointer; k++ { e1.Store(k, codec) d1.Store(k, codec) } e2 := e1.Clone() for k := reflect.Invalid; k <= reflect.UnsafePointer; k++ { v1, ok1 := e1.Load(k) v2, ok2 := e2.Load(k) if ok1 != ok2 || !reflect.DeepEqual(v1, v2) || v1 == nil || v2 == nil { t.Errorf("Encoder(%s): %#v, %t != %#v, %t", k, v1, ok1, v2, ok2) } } d2 := d1.Clone() for k := reflect.Invalid; k <= reflect.UnsafePointer; k++ { v1, ok1 := d1.Load(k) v2, ok2 := d2.Load(k) if ok1 != ok2 || !reflect.DeepEqual(v1, v2) || v1 == nil || v2 == nil { t.Errorf("Decoder(%s): %#v, %t != %#v, %t", k, v1, ok1, v2, ok2) } } } func TestKindCacheEncoderNilEncoder(t *testing.T) { t.Run("Encoder", func(t *testing.T) { c := new(kindEncoderCache) c.Store(reflect.Invalid, ValueEncoder(nil)) v, ok := c.Load(reflect.Invalid) if v != nil || ok { t.Errorf("Load of nil ValueEncoder should return: nil, false; got: %v, %t", v, ok) } }) t.Run("Decoder", func(t *testing.T) { c := new(kindDecoderCache) c.Store(reflect.Invalid, ValueDecoder(nil)) v, ok := c.Load(reflect.Invalid) if v != nil || ok { t.Errorf("Load of nil ValueDecoder should return: nil, false; got: %v, %t", v, ok) } }) } func BenchmarkEncoderCacheLoad(b *testing.B) { c := new(typeEncoderCache) codec := new(fakeCodec) typs := codecCacheTestTypes for _, t := range typs { c.Store(t, codec) } b.RunParallel(func(pb *testing.PB) { for i := 0; pb.Next(); i++ { c.Load(typs[i%len(typs)]) } }) } func BenchmarkEncoderCacheStore(b *testing.B) { c := new(typeEncoderCache) codec := new(fakeCodec) b.RunParallel(func(pb *testing.PB) { typs := codecCacheTestTypes for i := 0; pb.Next(); i++ { c.Store(typs[i%len(typs)], codec) } }) } ================================================ FILE: bson/cond_addr_codec.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" ) // condAddrEncoder is the encoder used when a pointer to the encoding value has an encoder. type condAddrEncoder struct { canAddrEnc ValueEncoder elseEnc ValueEncoder } var _ ValueEncoder = &condAddrEncoder{} // newCondAddrEncoder returns an condAddrEncoder. func newCondAddrEncoder(canAddrEnc, elseEnc ValueEncoder) *condAddrEncoder { encoder := condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc} return &encoder } // EncodeValue is the ValueEncoderFunc for a value that may be addressable. func (cae *condAddrEncoder) EncodeValue(ec EncodeContext, vw ValueWriter, val reflect.Value) error { if val.CanAddr() { return cae.canAddrEnc.EncodeValue(ec, vw, val) } if cae.elseEnc != nil { return cae.elseEnc.EncodeValue(ec, vw, val) } return errNoEncoder{Type: val.Type()} } // condAddrDecoder is the decoder used when a pointer to the value has a decoder. type condAddrDecoder struct { canAddrDec ValueDecoder elseDec ValueDecoder } var _ ValueDecoder = &condAddrDecoder{} // newCondAddrDecoder returns an CondAddrDecoder. func newCondAddrDecoder(canAddrDec, elseDec ValueDecoder) *condAddrDecoder { decoder := condAddrDecoder{canAddrDec: canAddrDec, elseDec: elseDec} return &decoder } // DecodeValue is the ValueDecoderFunc for a value that may be addressable. func (cad *condAddrDecoder) DecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if val.CanAddr() { return cad.canAddrDec.DecodeValue(dc, vr, val) } if cad.elseDec != nil { return cad.elseDec.DecodeValue(dc, vr, val) } return errNoDecoder{Type: val.Type()} } ================================================ FILE: bson/cond_addr_codec_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "testing" "go.mongodb.org/mongo-driver/v2/internal/assert" ) func TestCondAddrCodec(t *testing.T) { var inner int canAddrVal := reflect.ValueOf(&inner) addressable := canAddrVal.Elem() unaddressable := reflect.ValueOf(inner) rw := &valueReaderWriter{} t.Run("addressEncode", func(t *testing.T) { invoked := 0 encode1 := ValueEncoderFunc(func(EncodeContext, ValueWriter, reflect.Value) error { invoked = 1 return nil }) encode2 := ValueEncoderFunc(func(EncodeContext, ValueWriter, reflect.Value) error { invoked = 2 return nil }) condEncoder := newCondAddrEncoder(encode1, encode2) testCases := []struct { name string val reflect.Value invoked int }{ {"canAddr", addressable, 1}, {"else", unaddressable, 2}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { err := condEncoder.EncodeValue(EncodeContext{}, rw, tc.val) assert.Nil(t, err, "CondAddrEncoder error: %v", err) assert.Equal(t, invoked, tc.invoked, "Expected function %v to be called, called %v", tc.invoked, invoked) }) } t.Run("error", func(t *testing.T) { errEncoder := newCondAddrEncoder(encode1, nil) err := errEncoder.EncodeValue(EncodeContext{}, rw, unaddressable) want := errNoEncoder{Type: unaddressable.Type()} assert.Equal(t, err, want, "expected error %v, got %v", want, err) }) }) t.Run("addressDecode", func(t *testing.T) { invoked := 0 decode1 := ValueDecoderFunc(func(DecodeContext, ValueReader, reflect.Value) error { invoked = 1 return nil }) decode2 := ValueDecoderFunc(func(DecodeContext, ValueReader, reflect.Value) error { invoked = 2 return nil }) condDecoder := newCondAddrDecoder(decode1, decode2) testCases := []struct { name string val reflect.Value invoked int }{ {"canAddr", addressable, 1}, {"else", unaddressable, 2}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { err := condDecoder.DecodeValue(DecodeContext{}, rw, tc.val) assert.Nil(t, err, "CondAddrDecoder error: %v", err) assert.Equal(t, invoked, tc.invoked, "Expected function %v to be called, called %v", tc.invoked, invoked) }) } t.Run("error", func(t *testing.T) { errDecoder := newCondAddrDecoder(decode1, nil) err := errDecoder.DecodeValue(DecodeContext{}, rw, unaddressable) want := errNoDecoder{Type: unaddressable.Type()} assert.Equal(t, err, want, "expected error %v, got %v", want, err) }) }) } ================================================ FILE: bson/copier.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "io" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) // copyDocument handles copying one document from the src to the dst. func 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 copyDocumentCore(dw, dr) } // copyArrayFromBytes copies the values from a BSON array represented as a // []byte to a ValueWriter. func copyArrayFromBytes(dst ValueWriter, src []byte) error { aw, err := dst.WriteArray() if err != nil { return err } err = copyBytesToArrayWriter(aw, src) if err != nil { return err } return aw.WriteArrayEnd() } // copyDocumentFromBytes copies the values from a BSON document represented as a // []byte to a ValueWriter. func copyDocumentFromBytes(dst ValueWriter, src []byte) error { dw, err := dst.WriteDocument() if err != nil { return err } err = copyBytesToDocumentWriter(dw, src) if err != nil { return err } return dw.WriteDocumentEnd() } type writeElementFn func(key string) (ValueWriter, error) // copyBytesToArrayWriter copies the values from a BSON Array represented as a []byte to an // ArrayWriter. func copyBytesToArrayWriter(dst ArrayWriter, src []byte) error { wef := func(_ string) (ValueWriter, error) { return dst.WriteArrayElement() } return copyBytesToValueWriter(src, wef) } // copyBytesToDocumentWriter copies the values from a BSON document represented as a []byte to a // DocumentWriter. func copyBytesToDocumentWriter(dst DocumentWriter, src []byte) error { wef := func(key string) (ValueWriter, error) { return dst.WriteDocumentElement(key) } return copyBytesToValueWriter(src, wef) } func copyBytesToValueWriter(src []byte, wef writeElementFn) error { // TODO(skriptble): Create errors types here. Anything that is 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 bsoncore.Type var key string var val bsoncore.Value for { t, rem, ok = bsoncore.ReadType(rem) if !ok { return io.EOF } if t == bsoncore.Type(0) { if len(rem) != 0 { return fmt.Errorf("document end byte found before end of document. remaining bytes=%v", rem) } break } key, rem, ok = bsoncore.ReadKey(rem) if !ok { return fmt.Errorf("invalid key found. remaining bytes=%v", rem) } // write as either array element or document element using writeElementFn vw, err := wef(key) if err != nil { return err } val, rem, ok = bsoncore.ReadValue(rem, t) if !ok { return fmt.Errorf("not enough bytes available to read type. bytes=%d type=%s", len(rem), t) } err = copyValueFromBytes(vw, Type(t), val.Data) if err != nil { return err } } return nil } // copyDocumentToBytes copies an entire document from the ValueReader and // returns it as bytes. func copyDocumentToBytes(src ValueReader) ([]byte, error) { return appendDocumentBytes(nil, src) } // appendDocumentBytes functions the same as CopyDocumentToBytes, but will // append the result to dst. func 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 putValueWriter(vw) vw.reset(dst) err := copyDocument(vw, src) dst = vw.buf return dst, err } // appendArrayBytes copies an array from the ValueReader to dst. func appendArrayBytes(dst []byte, src ValueReader) ([]byte, error) { if br, ok := src.(bytesReader); ok { _, dst, err := br.readValueBytes(dst) return dst, err } vw := vwPool.Get().(*valueWriter) defer putValueWriter(vw) vw.reset(dst) err := copyArray(vw, src) dst = vw.buf return dst, err } // copyValueFromBytes will write the value represtend by t and src to dst. func copyValueFromBytes(dst ValueWriter, t Type, src []byte) error { if wvb, ok := dst.(bytesWriter); ok { return wvb.writeValueBytes(t, src) } vr := newBufferedDocumentReader(src) vr.advanceFrame() vr.stack[vr.frame].mode = mElement vr.stack[vr.frame].vType = t return copyValue(dst, vr) } // copyValueToBytes copies a value from src and returns it as a Type and a // []byte. func copyValueToBytes(src ValueReader) (Type, []byte, error) { if br, ok := src.(bytesReader); ok { return br.readValueBytes(nil) } vw := vwPool.Get().(*valueWriter) defer putValueWriter(vw) vw.reset(nil) vw.push(mElement) err := copyValue(vw, src) if err != nil { return 0, nil, err } return Type(vw.buf[0]), vw.buf[2:], nil } // copyValue will copy a single value from src to dst. func copyValue(dst ValueWriter, src ValueReader) error { var err error switch src.Type() { case TypeDouble: var f64 float64 f64, err = src.ReadDouble() if err != nil { break } err = dst.WriteDouble(f64) case TypeString: var str string str, err = src.ReadString() if err != nil { return err } err = dst.WriteString(str) case TypeEmbeddedDocument: err = copyDocument(dst, src) case TypeArray: err = copyArray(dst, src) case TypeBinary: var data []byte var subtype byte data, subtype, err = src.ReadBinary() if err != nil { break } err = dst.WriteBinaryWithSubtype(data, subtype) case TypeUndefined: err = src.ReadUndefined() if err != nil { break } err = dst.WriteUndefined() case TypeObjectID: var oid ObjectID oid, err = src.ReadObjectID() if err != nil { break } err = dst.WriteObjectID(oid) case TypeBoolean: var b bool b, err = src.ReadBoolean() if err != nil { break } err = dst.WriteBoolean(b) case TypeDateTime: var dt int64 dt, err = src.ReadDateTime() if err != nil { break } err = dst.WriteDateTime(dt) case TypeNull: err = src.ReadNull() if err != nil { break } err = dst.WriteNull() case TypeRegex: var pattern, options string pattern, options, err = src.ReadRegex() if err != nil { break } err = dst.WriteRegex(pattern, options) case TypeDBPointer: var ns string var pointer ObjectID ns, pointer, err = src.ReadDBPointer() if err != nil { break } err = dst.WriteDBPointer(ns, pointer) case TypeJavaScript: var js string js, err = src.ReadJavascript() if err != nil { break } err = dst.WriteJavascript(js) case TypeSymbol: var symbol string symbol, err = src.ReadSymbol() if err != nil { break } err = dst.WriteSymbol(symbol) case TypeCodeWithScope: 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 = copyDocumentCore(dstScope, srcScope) case TypeInt32: var i32 int32 i32, err = src.ReadInt32() if err != nil { break } err = dst.WriteInt32(i32) case TypeTimestamp: var t, i uint32 t, i, err = src.ReadTimestamp() if err != nil { break } err = dst.WriteTimestamp(t, i) case TypeInt64: var i64 int64 i64, err = src.ReadInt64() if err != nil { break } err = dst.WriteInt64(i64) case TypeDecimal128: var d128 Decimal128 d128, err = src.ReadDecimal128() if err != nil { break } err = dst.WriteDecimal128(d128) case TypeMinKey: err = src.ReadMinKey() if err != nil { break } err = dst.WriteMinKey() case TypeMaxKey: 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 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 errors.Is(err, ErrEOA) { break } if err != nil { return err } vw, err := aw.WriteArrayElement() if err != nil { return err } err = copyValue(vw, vr) if err != nil { return err } } return aw.WriteArrayEnd() } func copyDocumentCore(dw DocumentWriter, dr DocumentReader) error { for { key, vr, err := dr.ReadElement() if errors.Is(err, ErrEOD) { break } if err != nil { return err } vw, err := dw.WriteDocumentElement(key) if err != nil { return err } err = copyValue(vw, vr) if err != nil { return err } } return dw.WriteDocumentEnd() } // bytesReader is the interface used to read BSON bytes from a valueReader. // // The bytes of the value will be appended to dst. type bytesReader interface { readValueBytes(dst []byte) (Type, []byte, error) } // bytesWriter is the interface used to write BSON bytes to a valueWriter. type bytesWriter interface { writeValueBytes(t Type, b []byte) error } ================================================ FILE: bson/copier_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "testing" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/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 := copyDocument(nil, src) if !assert.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 := copyDocument(dst, src) if !assert.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 := newBufferedDocumentReader(doc) dst := newValueWriterFromSlice(make([]byte, 0)) want := doc err = 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 := copyArray(nil, src) if !assert.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 := copyArray(dst, src) if !assert.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 := newBufferedDocumentReader(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 = 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: TypeDouble, err: errors.New("1"), errAfter: llvrwReadDouble}, errors.New("1"), }, { "Double/dst/error", &TestValueReaderWriter{bsontype: TypeDouble, err: errors.New("2"), errAfter: llvrwWriteDouble}, &TestValueReaderWriter{bsontype: TypeDouble, readval: float64(3.14159)}, errors.New("2"), }, { "String/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeString, err: errors.New("1"), errAfter: llvrwReadString}, errors.New("1"), }, { "String/dst/error", &TestValueReaderWriter{bsontype: TypeString, err: errors.New("2"), errAfter: llvrwWriteString}, &TestValueReaderWriter{bsontype: TypeString, readval: "hello, world"}, errors.New("2"), }, { "Document/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeEmbeddedDocument, err: errors.New("1"), errAfter: llvrwReadDocument}, errors.New("1"), }, { "Array/dst/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeArray, err: errors.New("2"), errAfter: llvrwReadArray}, errors.New("2"), }, { "Binary/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeBinary, err: errors.New("1"), errAfter: llvrwReadBinary}, errors.New("1"), }, { "Binary/dst/error", &TestValueReaderWriter{bsontype: TypeBinary, err: errors.New("2"), errAfter: llvrwWriteBinaryWithSubtype}, &TestValueReaderWriter{ bsontype: TypeBinary, readval: bsoncore.Value{ Type: bsoncore.TypeBinary, Data: []byte{0x03, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x02, 0x03}, }, }, errors.New("2"), }, { "Undefined/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeUndefined, err: errors.New("1"), errAfter: llvrwReadUndefined}, errors.New("1"), }, { "Undefined/dst/error", &TestValueReaderWriter{bsontype: TypeUndefined, err: errors.New("2"), errAfter: llvrwWriteUndefined}, &TestValueReaderWriter{bsontype: TypeUndefined}, errors.New("2"), }, { "ObjectID/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeObjectID, err: errors.New("1"), errAfter: llvrwReadObjectID}, errors.New("1"), }, { "ObjectID/dst/error", &TestValueReaderWriter{bsontype: TypeObjectID, err: errors.New("2"), errAfter: llvrwWriteObjectID}, &TestValueReaderWriter{bsontype: TypeObjectID, readval: ObjectID{0x01, 0x02, 0x03}}, errors.New("2"), }, { "Boolean/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeBoolean, err: errors.New("1"), errAfter: llvrwReadBoolean}, errors.New("1"), }, { "Boolean/dst/error", &TestValueReaderWriter{bsontype: TypeBoolean, err: errors.New("2"), errAfter: llvrwWriteBoolean}, &TestValueReaderWriter{bsontype: TypeBoolean, readval: bool(true)}, errors.New("2"), }, { "DateTime/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeDateTime, err: errors.New("1"), errAfter: llvrwReadDateTime}, errors.New("1"), }, { "DateTime/dst/error", &TestValueReaderWriter{bsontype: TypeDateTime, err: errors.New("2"), errAfter: llvrwWriteDateTime}, &TestValueReaderWriter{bsontype: TypeDateTime, readval: int64(1234567890)}, errors.New("2"), }, { "Null/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeNull, err: errors.New("1"), errAfter: llvrwReadNull}, errors.New("1"), }, { "Null/dst/error", &TestValueReaderWriter{bsontype: TypeNull, err: errors.New("2"), errAfter: llvrwWriteNull}, &TestValueReaderWriter{bsontype: TypeNull}, errors.New("2"), }, { "Regex/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeRegex, err: errors.New("1"), errAfter: llvrwReadRegex}, errors.New("1"), }, { "Regex/dst/error", &TestValueReaderWriter{bsontype: TypeRegex, err: errors.New("2"), errAfter: llvrwWriteRegex}, &TestValueReaderWriter{ bsontype: TypeRegex, readval: bsoncore.Value{ Type: bsoncore.TypeRegex, Data: bsoncore.AppendRegex(nil, "hello", "world"), }, }, errors.New("2"), }, { "DBPointer/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeDBPointer, err: errors.New("1"), errAfter: llvrwReadDBPointer}, errors.New("1"), }, { "DBPointer/dst/error", &TestValueReaderWriter{bsontype: TypeDBPointer, err: errors.New("2"), errAfter: llvrwWriteDBPointer}, &TestValueReaderWriter{ bsontype: TypeDBPointer, readval: bsoncore.Value{ Type: bsoncore.TypeDBPointer, Data: bsoncore.AppendDBPointer(nil, "foo", ObjectID{0x01, 0x02, 0x03}), }, }, errors.New("2"), }, { "Javascript/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeJavaScript, err: errors.New("1"), errAfter: llvrwReadJavascript}, errors.New("1"), }, { "Javascript/dst/error", &TestValueReaderWriter{bsontype: TypeJavaScript, err: errors.New("2"), errAfter: llvrwWriteJavascript}, &TestValueReaderWriter{bsontype: TypeJavaScript, readval: "hello, world"}, errors.New("2"), }, { "Symbol/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeSymbol, err: errors.New("1"), errAfter: llvrwReadSymbol}, errors.New("1"), }, { "Symbol/dst/error", &TestValueReaderWriter{bsontype: TypeSymbol, err: errors.New("2"), errAfter: llvrwWriteSymbol}, &TestValueReaderWriter{ bsontype: TypeSymbol, readval: bsoncore.Value{ Type: bsoncore.TypeSymbol, Data: bsoncore.AppendSymbol(nil, "hello, world"), }, }, errors.New("2"), }, { "CodeWithScope/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeCodeWithScope, err: errors.New("1"), errAfter: llvrwReadCodeWithScope}, errors.New("1"), }, { "CodeWithScope/dst/error", &TestValueReaderWriter{bsontype: TypeCodeWithScope, err: errors.New("2"), errAfter: llvrwWriteCodeWithScope}, &TestValueReaderWriter{bsontype: TypeCodeWithScope}, errors.New("2"), }, { "CodeWithScope/dst/copyDocumentCore error", &TestValueReaderWriter{err: errors.New("3"), errAfter: llvrwWriteDocumentElement}, &TestValueReaderWriter{bsontype: TypeCodeWithScope}, errors.New("3"), }, { "Int32/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeInt32, err: errors.New("1"), errAfter: llvrwReadInt32}, errors.New("1"), }, { "Int32/dst/error", &TestValueReaderWriter{bsontype: TypeInt32, err: errors.New("2"), errAfter: llvrwWriteInt32}, &TestValueReaderWriter{bsontype: TypeInt32, readval: int32(12345)}, errors.New("2"), }, { "Timestamp/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeTimestamp, err: errors.New("1"), errAfter: llvrwReadTimestamp}, errors.New("1"), }, { "Timestamp/dst/error", &TestValueReaderWriter{bsontype: TypeTimestamp, err: errors.New("2"), errAfter: llvrwWriteTimestamp}, &TestValueReaderWriter{ bsontype: TypeTimestamp, readval: bsoncore.Value{ Type: bsoncore.TypeTimestamp, Data: bsoncore.AppendTimestamp(nil, 12345, 67890), }, }, errors.New("2"), }, { "Int64/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeInt64, err: errors.New("1"), errAfter: llvrwReadInt64}, errors.New("1"), }, { "Int64/dst/error", &TestValueReaderWriter{bsontype: TypeInt64, err: errors.New("2"), errAfter: llvrwWriteInt64}, &TestValueReaderWriter{bsontype: TypeInt64, readval: int64(1234567890)}, errors.New("2"), }, { "Decimal128/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeDecimal128, err: errors.New("1"), errAfter: llvrwReadDecimal128}, errors.New("1"), }, { "Decimal128/dst/error", &TestValueReaderWriter{bsontype: TypeDecimal128, err: errors.New("2"), errAfter: llvrwWriteDecimal128}, &TestValueReaderWriter{bsontype: TypeDecimal128, readval: NewDecimal128(12345, 67890)}, errors.New("2"), }, { "MinKey/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeMinKey, err: errors.New("1"), errAfter: llvrwReadMinKey}, errors.New("1"), }, { "MinKey/dst/error", &TestValueReaderWriter{bsontype: TypeMinKey, err: errors.New("2"), errAfter: llvrwWriteMinKey}, &TestValueReaderWriter{bsontype: TypeMinKey}, errors.New("2"), }, { "MaxKey/src/error", &TestValueReaderWriter{}, &TestValueReaderWriter{bsontype: TypeMaxKey, err: errors.New("1"), errAfter: llvrwReadMaxKey}, errors.New("1"), }, { "MaxKey/dst/error", &TestValueReaderWriter{bsontype: TypeMaxKey, err: errors.New("2"), errAfter: llvrwWriteMaxKey}, &TestValueReaderWriter{bsontype: TypeMaxKey}, errors.New("2"), }, { "Unknown BSON type error", &TestValueReaderWriter{}, &TestValueReaderWriter{}, fmt.Errorf("cannot copy unknown BSON type %s", Type(0)), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { tc.dst.t, tc.src.t = t, t err := copyValue(tc.dst, tc.src) if !assert.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 = copyValueFromBytes(vw, TypeString, 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 := copyValueFromBytes(llvrw, TypeString, 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 := newBufferedDocumentReader(b) _, err = vr.ReadDocument() noerr(t, err) _, _, err = vr.ReadElement() noerr(t, err) btype, got, err := copyValueToBytes(vr) noerr(t, err) want := bsoncore.AppendString(nil, "world") if btype != TypeString { t.Errorf("Incorrect type returned. got %v; want %v", btype, TypeString) } 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: TypeString, readval: "Hello, world!"} btype, got, err := copyValueToBytes(llvrw) noerr(t, err) want := bsoncore.AppendString(nil, "Hello, world!") if btype != TypeString { t.Errorf("Incorrect type returned. got %v; want %v", btype, TypeString) } 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 := newBufferedDocumentReader(b) _, err = vr.ReadDocument() noerr(t, err) _, _, err = vr.ReadElement() noerr(t, err) btype, got, err := copyValueToBytes(vr) noerr(t, err) want := bsoncore.AppendString(nil, "world") if btype != TypeString { t.Errorf("Incorrect type returned. got %v; want %v", btype, TypeString) } 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: TypeString, readval: "Hello, world!"} btype, got, err := copyValueToBytes(llvrw) noerr(t, err) want := bsoncore.AppendString(nil, "Hello, world!") if btype != TypeString { t.Errorf("Incorrect type returned. got %v; want %v", btype, TypeString) } 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: TypeString, err: want, errAfter: llvrwReadString} _, _, got := copyValueToBytes(llvrw) if !assert.CompareErrors(got, want) { t.Errorf("Errors do not match. got %v; want %v", got, want) } }) }) } ================================================ FILE: bson/decimal.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // // Based on gopkg.in/mgo.v2/bson by Gustavo Niemeyer // See THIRD-PARTY-NOTICES for original license terms. package bson import ( "encoding/json" "errors" "fmt" "math/big" "regexp" "strconv" "strings" "go.mongodb.org/mongo-driver/v2/internal/decimal128" ) // 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 { return decimal128.String(d.h, d.l) } // BigInt returns significand as big.Int and exponent, bi * 10 ^ exp. func (d Decimal128) BigInt() (*big.Int, int, error) { high, low := d.GetBytes() posSign := high>>63&1 == 0 // positive sign switch high >> 58 & (1<<5 - 1) { case 0x1F: return nil, 0, ErrParseNaN case 0x1E: if posSign { return nil, 0, ErrParseInf } return nil, 0, ErrParseNegInf } var exp int if high>>61&3 == 3 { // Bits: 1*sign 2*ignored 14*exponent 111*significand. // Implicit 0b100 prefix in significand. exp = int(high >> 47 & (1<<14 - 1)) // 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 &= (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 new(big.Int), 0, nil } bi := big.NewInt(0) const host32bit = ^uint(0)>>32 == 0 if host32bit { bi.SetBits([]big.Word{big.Word(low), big.Word(low >> 32), big.Word(high), big.Word(high >> 32)}) } else { bi.SetBits([]big.Word{big.Word(low), big.Word(high)}) } if !posSign { return bi.Neg(bi), exp, nil } return bi, exp, nil } // IsNaN returns whether d is NaN. func (d Decimal128) IsNaN() bool { return d.h>>58&(1<<5-1) == 0x1F } // IsInf returns: // // +1 d == Infinity // 0 other case // -1 d == -Infinity func (d Decimal128) IsInf() int { if d.h>>58&(1<<5-1) != 0x1E { return 0 } if d.h>>63&1 == 0 { return 1 } return -1 } // IsZero returns true if d is the empty Decimal128. func (d Decimal128) IsZero() bool { return d.h == 0 && d.l == 0 } // MarshalJSON returns Decimal128 as a string. func (d Decimal128) MarshalJSON() ([]byte, error) { return json.Marshal(d.String()) } // UnmarshalJSON creates a Decimal128 from a JSON string, an extended JSON $numberDecimal value, or the string // "null". If b is a JSON string or extended JSON value, d will have the value of that string, and if b is "null", d will // be unchanged. func (d *Decimal128) UnmarshalJSON(b []byte) error { // Ignore "null" to keep parity with the standard library. Decoding a JSON null into a non-pointer Decimal128 field // will leave the field unchanged. For pointer values, encoding/json will set the pointer to nil and will not // enter the UnmarshalJSON hook. if string(b) == "null" { return nil } var res any err := json.Unmarshal(b, &res) if err != nil { return err } str, ok := res.(string) // Extended JSON if !ok { m, ok := res.(map[string]any) if !ok { return errors.New("not an extended JSON Decimal128: expected document") } d128, ok := m["$numberDecimal"] if !ok { return errors.New("not an extended JSON Decimal128: expected key $numberDecimal") } str, ok = d128.(string) if !ok { return errors.New("not an extended JSON Decimal128: expected decimal to be string") } } *d, err = ParseDecimal128(str) return err } var ( dNaN = Decimal128{0x1F << 58, 0} dPosInf = Decimal128{0x1E << 58, 0} 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[-+]?\d*)?(?:\.(?P\d*))?(?:[Ee](?P[-+]?\d+))?$`) // ParseDecimal128 takes the given string and attempts to parse it into a valid // Decimal128 value. func ParseDecimal128(s string) (Decimal128, error) { if s == "" { return dErr(s) } matches := normalNumber.FindStringSubmatch(s) if len(matches) == 0 { orig := s neg := s[0] == '-' if neg || s[0] == '+' { s = s[1:] } if s == "NaN" || s == "nan" || strings.EqualFold(s, "nan") { return dNaN, nil } if s == "Inf" || s == "inf" || strings.EqualFold(s, "inf") || strings.EqualFold(s, "infinity") { if neg { return dNegInf, nil } return dPosInf, nil } return dErr(orig) } intPart := matches[1] decPart := matches[2] expPart := matches[3] var err error exp := 0 if expPart != "" { exp, err = strconv.Atoi(expPart) if err != nil { return dErr(s) } } if decPart != "" { exp -= len(decPart) } if len(strings.Trim(intPart+decPart, "-0")) > 35 { return dErr(s) } // Parse the significand (i.e. the non-exponent part) as a big.Int. bi, ok := new(big.Int).SetString(intPart+decPart, 10) if !ok { return dErr(s) } d, ok := ParseDecimal128FromBigInt(bi, exp) if !ok { return dErr(s) } if bi.Sign() == 0 && s[0] == '-' { d.h |= 1 << 63 } return d, nil } var ( ten = big.NewInt(10) zero = new(big.Int) maxS, _ = new(big.Int).SetString("9999999999999999999999999999999999", 10) ) // ParseDecimal128FromBigInt attempts to parse the given significand and exponent into a valid Decimal128 value. func ParseDecimal128FromBigInt(bi *big.Int, exp int) (Decimal128, bool) { // copy bi = new(big.Int).Set(bi) q := new(big.Int) r := new(big.Int) // If the significand is zero, the logical value will always be zero, independent of the // exponent. However, the loops for handling out-of-range exponent values below may be extremely // slow for zero values because the significand never changes. Limit the exponent value to the // supported range here to prevent entering the loops below. if bi.Cmp(zero) == 0 { if exp > MaxDecimal128Exp { exp = MaxDecimal128Exp } if exp < MinDecimal128Exp { exp = MinDecimal128Exp } } for bigIntCmpAbs(bi, maxS) == 1 { bi, _ = q.QuoRem(bi, ten, r) if r.Cmp(zero) != 0 { return Decimal128{}, false } exp++ if exp > MaxDecimal128Exp { return Decimal128{}, false } } for exp < MinDecimal128Exp { // Subnormal. bi, _ = q.QuoRem(bi, ten, r) if r.Cmp(zero) != 0 { return Decimal128{}, false } exp++ } for exp > MaxDecimal128Exp { // Clamped. bi.Mul(bi, ten) if bigIntCmpAbs(bi, maxS) == 1 { return Decimal128{}, false } exp-- } b := bi.Bytes() var h, l uint64 for i := 0; i < len(b); i++ { if i < len(b)-8 { h = h<<8 | uint64(b[i]) continue } l = l<<8 | uint64(b[i]) } h |= uint64(exp-MinDecimal128Exp) & uint64(1<<14-1) << 49 if bi.Sign() == -1 { h |= 1 << 63 } return Decimal128{h: h, l: l}, true } // bigIntCmpAbs computes big.Int.Cmp(absoluteValue(x), absoluteValue(y)). func bigIntCmpAbs(x, y *big.Int) int { xAbs := bigIntAbsValue(x) yAbs := bigIntAbsValue(y) return xAbs.Cmp(yAbs) } // bigIntAbsValue returns a big.Int containing the absolute value of b. // If b is already a non-negative number, it is returned without any changes or copies. func bigIntAbsValue(b *big.Int) *big.Int { if b.Sign() >= 0 { return b // already positive } return new(big.Int).Abs(b) } ================================================ FILE: bson/decimal_test.go ================================================ // Copyright (C) MongoDB, Inc. 2022-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package bson import ( "encoding/json" "fmt" "math/big" "testing" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/require" ) type bigIntTestCase struct { s string h uint64 l uint64 bi *big.Int exp int remark string } func parseBigInt(s string) *big.Int { bi, _ := new(big.Int).SetString(s, 10) return bi } var ( one = big.NewInt(1) biMaxS = new(big.Int).SetBytes([]byte{0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) biNMaxS = new(big.Int).Neg(biMaxS) biOverflow = new(big.Int).Add(biMaxS, one) biNOverflow = new(big.Int).Neg(biOverflow) bi12345 = parseBigInt("12345") biN12345 = parseBigInt("-12345") bi9_14 = parseBigInt("90123456789012") biN9_14 = parseBigInt("-90123456789012") bi9_34 = parseBigInt("9999999999999999999999999999999999") biN9_34 = parseBigInt("-9999999999999999999999999999999999") ) var bigIntTestCases = []bigIntTestCase{ {s: "12345", h: 0x3040000000000000, l: 12345, bi: bi12345}, {s: "-12345", h: 0xB040000000000000, l: 12345, bi: biN12345}, {s: "90123456.789012", h: 0x3034000000000000, l: 90123456789012, bi: bi9_14, exp: -6}, {s: "-90123456.789012", h: 0xB034000000000000, l: 90123456789012, bi: biN9_14, exp: -6}, {s: "9.0123456789012E+22", h: 0x3052000000000000, l: 90123456789012, bi: bi9_14, exp: 9}, {s: "-9.0123456789012E+22", h: 0xB052000000000000, l: 90123456789012, bi: biN9_14, exp: 9}, {s: "9.0123456789012E-8", h: 0x3016000000000000, l: 90123456789012, bi: bi9_14, exp: -21}, {s: "-9.0123456789012E-8", h: 0xB016000000000000, l: 90123456789012, bi: biN9_14, exp: -21}, {s: "9999999999999999999999999999999999", h: 3477321013416265664, l: 4003012203950112767, bi: bi9_34}, {s: "-9999999999999999999999999999999999", h: 12700693050271041472, l: 4003012203950112767, bi: biN9_34}, {s: "0.9999999999999999999999999999999999", h: 3458180714999941056, l: 4003012203950112767, bi: bi9_34, exp: -34}, {s: "-0.9999999999999999999999999999999999", h: 12681552751854716864, l: 4003012203950112767, bi: biN9_34, exp: -34}, {s: "99999999999999999.99999999999999999", h: 3467750864208103360, l: 4003012203950112767, bi: bi9_34, exp: -17}, {s: "-99999999999999999.99999999999999999", h: 12691122901062879168, l: 4003012203950112767, bi: biN9_34, exp: -17}, {s: "9.999999999999999999999999999999999E+35", h: 3478446913323108288, l: 4003012203950112767, bi: bi9_34, exp: 2}, {s: "-9.999999999999999999999999999999999E+35", h: 12701818950177884096, l: 4003012203950112767, bi: biN9_34, exp: 2}, {s: "9.999999999999999999999999999999999E+40", h: 3481261663090214848, l: 4003012203950112767, bi: bi9_34, exp: 7}, {s: "-9.999999999999999999999999999999999E+40", h: 12704633699944990656, l: 4003012203950112767, bi: biN9_34, exp: 7}, {s: "99999999999999999999999999999.99999", h: 3474506263649159104, l: 4003012203950112767, bi: bi9_34, exp: -5}, {s: "-99999999999999999999999999999.99999", h: 12697878300503934912, l: 4003012203950112767, bi: biN9_34, exp: -5}, {s: "1.038459371706965525706099265844019E-6143", remark: "subnormal", h: 0x333333333333, l: 0x3333333333333333, bi: parseBigInt("10384593717069655257060992658440190"), exp: MinDecimal128Exp - 1}, {s: "-1.038459371706965525706099265844019E-6143", remark: "subnormal", h: 0x8000333333333333, l: 0x3333333333333333, bi: parseBigInt("-10384593717069655257060992658440190"), exp: MinDecimal128Exp - 1}, {s: "rounding overflow 1", remark: "overflow", bi: parseBigInt("103845937170696552570609926584401910"), exp: MaxDecimal128Exp}, {s: "rounding overflow 2", remark: "overflow", bi: parseBigInt("103845937170696552570609926584401910"), exp: MaxDecimal128Exp}, {s: "subnormal overflow 1", remark: "overflow", bi: biMaxS, exp: MinDecimal128Exp - 1}, {s: "subnormal overflow 2", remark: "overflow", bi: biNMaxS, exp: MinDecimal128Exp - 1}, {s: "clamped overflow 1", remark: "overflow", bi: biMaxS, exp: MaxDecimal128Exp + 1}, {s: "clamped overflow 2", remark: "overflow", bi: biNMaxS, exp: MaxDecimal128Exp + 1}, {s: "biMaxS+1 overflow", remark: "overflow", bi: biOverflow, exp: MaxDecimal128Exp}, {s: "biNMaxS-1 overflow", remark: "overflow", bi: biNOverflow, exp: MaxDecimal128Exp}, {s: "NaN", h: 0x7c00000000000000, l: 0, remark: "NaN"}, {s: "Infinity", h: 0x7800000000000000, l: 0, remark: "Infinity"}, {s: "-Infinity", h: 0xf800000000000000, l: 0, remark: "-Infinity"}, } func TestDecimal128_BigInt(t *testing.T) { for _, c := range bigIntTestCases { t.Run(c.s, func(t *testing.T) { switch c.remark { case "NaN", "Infinity", "-Infinity": d128 := NewDecimal128(c.h, c.l) _, _, err := d128.BigInt() require.Error(t, err, "case %s", c.s) case "": d128 := NewDecimal128(c.h, c.l) bi, e, err := d128.BigInt() require.NoError(t, err, "case %s", c.s) require.Equal(t, 0, c.bi.Cmp(bi), "case %s e:%s a:%s", c.s, c.bi.String(), bi.String()) require.Equal(t, c.exp, e, "case %s", c.s, d128.String()) } }) } } func TestParseDecimal128FromBigInt(t *testing.T) { for _, c := range bigIntTestCases { switch c.remark { case "overflow": d128, ok := ParseDecimal128FromBigInt(c.bi, c.exp) require.Equal(t, false, ok, "case %s %s", c.s, d128.String(), c.remark) case "", "rounding", "subnormal", "clamped": d128, ok := ParseDecimal128FromBigInt(c.bi, c.exp) require.Equal(t, true, ok, "case %s", c.s) require.Equal(t, c.s, d128.String(), "case %s", c.s) require.Equal(t, c.h, d128.h, "case %s", c.s, d128.l) require.Equal(t, c.l, d128.l, "case %s", c.s, d128.h) } } } func TestParseDecimal128(t *testing.T) { cases := make([]bigIntTestCase, 0, len(bigIntTestCases)) cases = append(cases, bigIntTestCases...) cases = append(cases, bigIntTestCase{s: "-0001231.453454000000565600000000E-21", h: 0xafe6000003faa269, l: 0x81cfeceaabdb1800}, bigIntTestCase{s: "12345E+21", h: 0x306a000000000000, l: 12345}, bigIntTestCase{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"}, bigIntTestCase{s: ".125e1", h: 0x303c000000000000, l: 125}, bigIntTestCase{s: ".125", h: 0x303a000000000000, l: 125}, // Test that parsing negative zero returns negative zero with a zero exponent. bigIntTestCase{s: "-0", h: 0xb040000000000000, l: 0}, // Test that parsing negative zero with an in-range exponent returns negative zero and // preserves the specified exponent value. bigIntTestCase{s: "-0E999", h: 0xb80e000000000000, l: 0}, // Test that parsing zero with an out-of-range positive exponent returns zero with the // maximum positive exponent (i.e. 0e+6111). bigIntTestCase{s: "0E2000000000000", h: 0x5ffe000000000000, l: 0}, // Test that parsing zero with an out-of-range negative exponent returns zero with the // minimum negative exponent (i.e. 0e-6176). bigIntTestCase{s: "-0E2000000000000", h: 0xdffe000000000000, l: 0}, bigIntTestCase{s: "", remark: "parse fail"}) for _, c := range cases { t.Run(c.s, func(t *testing.T) { switch c.remark { case "overflow", "parse fail": _, err := ParseDecimal128(c.s) assert.Error(t, err, "ParseDecimal128(%q) should return an error", c.s) default: got, err := ParseDecimal128(c.s) require.NoError(t, err, "ParseDecimal128(%q) error", c.s) want := Decimal128{h: c.h, l: c.l} // Decimal128 doesn't implement an equality function, so compare the expected // low/high uint64 values directly. Also print the string representation of each // number to make debugging failures easier. assert.Equal(t, want, got, "ParseDecimal128(%q) = %s, want %s", c.s, got, want) } }) } } func TestDecimal128_JSON(t *testing.T) { t.Run("roundTrip", func(t *testing.T) { decimal := NewDecimal128(0x3040000000000000, 12345) bytes, err := json.Marshal(decimal) assert.Nil(t, err, "json.Marshal error: %v", err) got := NewDecimal128(0, 0) err = json.Unmarshal(bytes, &got) assert.Nil(t, err, "json.Unmarshal error: %v", err) assert.Equal(t, decimal.h, got.h, "expected h: %v got: %v", decimal.h, got.h) assert.Equal(t, decimal.l, got.l, "expected l: %v got: %v", decimal.l, got.l) }) t.Run("unmarshal extendedJSON", func(t *testing.T) { want := NewDecimal128(0x3040000000000000, 12345) extJSON := fmt.Sprintf(`{"$numberDecimal": %q}`, want.String()) got := NewDecimal128(0, 0) err := json.Unmarshal([]byte(extJSON), &got) assert.Nil(t, err, "json.Unmarshal error: %v", err) assert.Equal(t, want.h, got.h, "expected h: %v got: %v", want.h, got.h) assert.Equal(t, want.l, got.l, "expected l: %v got: %v", want.l, got.l) }) t.Run("unmarshal null", func(t *testing.T) { want := NewDecimal128(0, 0) extJSON := `null` got := NewDecimal128(0, 0) err := json.Unmarshal([]byte(extJSON), &got) assert.Nil(t, err, "json.Unmarshal error: %v", err) assert.Equal(t, want.h, got.h, "expected h: %v got: %v", want.h, got.h) assert.Equal(t, want.l, got.l, "expected l: %v got: %v", want.l, got.l) }) t.Run("unmarshal", func(t *testing.T) { cases := make([]bigIntTestCase, 0, len(bigIntTestCases)) cases = append(cases, bigIntTestCases...) cases = append(cases, bigIntTestCase{s: "-0001231.453454000000565600000000E-21", h: 0xafe6000003faa269, l: 0x81cfeceaabdb1800}, bigIntTestCase{s: "12345E+21", h: 0x306a000000000000, l: 12345}, bigIntTestCase{s: "0.10000000000000000000000000000000000000000001", remark: "parse fail"}, bigIntTestCase{s: ".125e1", h: 0x303c000000000000, l: 125}, bigIntTestCase{s: ".125", h: 0x303a000000000000, l: 125}) for _, c := range cases { t.Run(c.s, func(t *testing.T) { input := fmt.Sprintf(`{"foo": %q}`, c.s) var got map[string]Decimal128 err := json.Unmarshal([]byte(input), &got) switch c.remark { case "overflow", "parse fail": assert.NotNil(t, err, "expected Unmarshal error, got nil") default: assert.Nil(t, err, "Unmarshal error: %v", err) gotDecimal := got["foo"] assert.Equal(t, c.h, gotDecimal.h, "expected h: %v got: %v", c.h, gotDecimal.l) assert.Equal(t, c.l, gotDecimal.l, "expected l: %v got: %v", c.l, gotDecimal.h) } }) } }) } ================================================ FILE: bson/decode_value_fuzz_test.go ================================================ // Copyright (C) MongoDB, Inc. 2025-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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 ( "math" "strings" "testing" ) func FuzzDecodeValue(f *testing.F) { // Seed the fuzz corpus with all BSON values from the MarshalValue test // cases. for _, tc := range marshalValueTestCases { f.Add(byte(tc.bsontype), tc.bytes) } // Also seed the fuzz corpus with special values that we want to test. values := []any{ // int32, including max and min values. int32(0), int32(math.MaxInt32), int32(math.MinInt32), // int64, including max and min values. int64(0), int64(math.MaxInt64), int64(math.MinInt64), // string, including empty and large string. "", strings.Repeat("z", 10_000), // map map[string]any{"nested": []any{1, "two", map[string]any{"three": 3}}}, // array []any{1, 2, 3, "four"}, } for _, v := range values { typ, b, err := MarshalValue(v) if err != nil { f.Fatal(err) } f.Add(byte(typ), b) } f.Fuzz(func(t *testing.T, bsonType byte, data []byte) { var v any if err := UnmarshalValue(Type(bsonType), data, &v); err != nil { return } // There is no value encoder for Go "nil" (nil values handled // differently by each type encoder), so skip anything that unmarshals // to "nil". It's not clear if MarshalValue should support "nil", but // for now we skip it. if v == nil { t.Logf("data unmarshaled to nil: %v", data) return } typ, encoded, err := MarshalValue(v) if err != nil { t.Fatalf("failed to marshal: %v", err) } var v2 any if err := UnmarshalValue(typ, encoded, &v2); err != nil { t.Fatalf("failed to unmarshal: %v", err) } }) } ================================================ FILE: bson/decoder.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" ) // 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() any { return new(Decoder) }, } // A Decoder reads and decodes BSON documents from a stream. It reads from a ValueReader as // the source of BSON data. type Decoder struct { dc DecodeContext vr ValueReader } // NewDecoder returns a new decoder that reads from vr. func NewDecoder(vr ValueReader) *Decoder { return &Decoder{ dc: DecodeContext{Registry: defaultRegistry}, vr: vr, } } // Decode reads the next BSON document from the stream and decodes it into the // value pointed to by val. // // See [Unmarshal] for details about BSON unmarshaling behavior. func (d *Decoder) Decode(val any) error { if unmarshaler, ok := val.(Unmarshaler); ok { // TODO(skriptble): Reuse a []byte here and use the AppendDocumentBytes method. buf, err := copyDocumentToBytes(d.vr) if err != nil { return err } return unmarshaler.UnmarshalBSON(buf) } rval := reflect.ValueOf(val) switch rval.Kind() { case reflect.Ptr: if rval.IsNil() { return ErrDecodeToNil } rval = rval.Elem() case reflect.Map: if rval.IsNil() { return ErrDecodeToNil } default: return fmt.Errorf("argument to Decode must be a pointer or a map, but got %v", rval) } decoder, err := d.dc.LookupDecoder(rval.Type()) if err != nil { return err } 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 ValueReader) { d.vr = vr } // SetRegistry replaces the current registry of the decoder with r. func (d *Decoder) SetRegistry(r *Registry) { d.dc.Registry = r } // DefaultDocumentM causes the Decoder to always unmarshal documents into the bson.M type. This // behavior is restricted to data typed as "any" or "map[string]any". func (d *Decoder) DefaultDocumentM() { d.dc.defaultDocumentType = reflect.TypeOf(M{}) } // DefaultDocumentMap causes the Decoder to always unmarshal documents into the // map[string]any type. This behavior is restricted to data typed as "any" or // "map[string]any". func (d *Decoder) DefaultDocumentMap() { d.dc.defaultDocumentType = reflect.TypeOf(map[string]any{}) } // AllowTruncatingDoubles causes the Decoder to truncate the fractional part of BSON "double" values // when attempting to unmarshal them into a Go integer (int, int8, int16, int32, or int64) struct // field. The truncation logic does not apply to BSON "decimal128" values. func (d *Decoder) AllowTruncatingDoubles() { d.dc.truncate = true } // BinaryAsSlice causes the Decoder to unmarshal BSON binary field values that are the "Generic" or // "Old" BSON binary subtype as a Go byte slice instead of a bson.Binary. func (d *Decoder) BinaryAsSlice() { d.dc.binaryAsSlice = true } // ObjectIDAsHexString causes the Decoder to decode object IDs to their hex representation. func (d *Decoder) ObjectIDAsHexString() { d.dc.objectIDAsHexString = true } // UseJSONStructTags causes the Decoder to fall back to using the "json" struct tag if a "bson" // struct tag is not specified. func (d *Decoder) UseJSONStructTags() { d.dc.useJSONStructTags = true } // UseLocalTimeZone causes the Decoder to unmarshal time.Time values in the local timezone instead // of the UTC timezone. func (d *Decoder) UseLocalTimeZone() { d.dc.useLocalTimeZone = true } // ZeroMaps causes the Decoder to delete any existing values from Go maps in the destination value // passed to Decode before unmarshaling BSON documents into them. func (d *Decoder) ZeroMaps() { d.dc.zeroMaps = true } // ZeroStructs causes the Decoder to delete any existing values from Go structs in the destination // value passed to Decode before unmarshaling BSON documents into them. func (d *Decoder) ZeroStructs() { d.dc.zeroStructs = true } ================================================ FILE: bson/decoder_example_test.go ================================================ // Copyright (C) MongoDB, Inc. 2023-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package bson_test import ( "bytes" "encoding/json" "errors" "fmt" "io" "go.mongodb.org/mongo-driver/v2/bson" ) func ExampleDecoder() { // Marshal a BSON document that contains the name, SKU, and price (in cents) // of a product. doc := bson.D{ {Key: "name", Value: "Cereal Rounds"}, {Key: "sku", Value: "AB12345"}, {Key: "price_cents", Value: 399}, } data, err := bson.Marshal(doc) if err != nil { panic(err) } // Create a Decoder that reads the marshaled BSON document and use it to // unmarshal the document into a Product struct. decoder := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(data))) type Product struct { Name string `bson:"name"` SKU string `bson:"sku"` Price int64 `bson:"price_cents"` } var res Product err = decoder.Decode(&res) if err != nil { panic(err) } fmt.Printf("%+v\n", res) // Output: {Name:Cereal Rounds SKU:AB12345 Price:399} } func ExampleDecoder_DefaultDocumentM() { // Marshal a BSON document that contains a city name and a nested document // with various city properties. doc := bson.D{ {Key: "name", Value: "New York"}, {Key: "properties", Value: bson.D{ {Key: "state", Value: "NY"}, {Key: "population", Value: 8_804_190}, {Key: "elevation", Value: 10}, }}, } data, err := bson.Marshal(doc) if err != nil { panic(err) } // Create a Decoder that reads the marshaled BSON document and use it to unmarshal the document // into a City struct. decoder := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(data))) type City struct { Name string `bson:"name"` Properties any `bson:"properties"` } // Configure the Decoder to default to decoding BSON documents as the M // type if the decode destination has no type information. The Properties // field in the City struct will be decoded as a "M" (i.e. map) instead // of the default "D". decoder.DefaultDocumentM() var res City err = decoder.Decode(&res) if err != nil { panic(err) } data, err = json.Marshal(res) if err != nil { panic(err) } fmt.Printf("%+v\n", string(data)) // Output: {"Name":"New York","Properties":{"elevation":10,"population":8804190,"state":"NY"}} } func ExampleDecoder_DefaultDocumentMap() { // Marshal a BSON document that contains a city name and a nested document // with various city properties. doc := bson.D{ {Key: "name", Value: "New York"}, {Key: "properties", Value: bson.D{ {Key: "state", Value: "NY"}, {Key: "population", Value: 8_804_190}, {Key: "elevation", Value: 10}, }}, } data, err := bson.Marshal(doc) if err != nil { panic(err) } // Create a Decoder that reads the marshaled BSON document and use it to unmarshal the document // into a City struct. decoder := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(data))) type City struct { Name string `bson:"name"` Properties any `bson:"properties"` } // Configure the Decoder to default to decoding BSON documents as a // map[string]any type if the decode destination has no type information. The // Properties field in the City struct will be decoded as map[string]any // instead of the default "D". decoder.DefaultDocumentMap() var res City err = decoder.Decode(&res) if err != nil { panic(err) } data, err = json.Marshal(res) if err != nil { panic(err) } fmt.Printf("%+v\n", string(data)) // Output: {"Name":"New York","Properties":{"elevation":10,"population":8804190,"state":"NY"}} } func ExampleDecoder_UseJSONStructTags() { // Marshal a BSON document that contains the name, SKU, and price (in cents) // of a product. doc := bson.D{ {Key: "name", Value: "Cereal Rounds"}, {Key: "sku", Value: "AB12345"}, {Key: "price_cents", Value: 399}, } data, err := bson.Marshal(doc) if err != nil { panic(err) } // Create a Decoder that reads the marshaled BSON document and use it to // unmarshal the document into a Product struct. decoder := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(data))) type Product struct { Name string `json:"name"` SKU string `json:"sku"` Price int64 `json:"price_cents"` } // Configure the Decoder to use "json" struct tags when decoding if "bson" // struct tags are not present. decoder.UseJSONStructTags() var res Product err = decoder.Decode(&res) if err != nil { panic(err) } fmt.Printf("%+v\n", res) // Output: {Name:Cereal Rounds SKU:AB12345 Price:399} } func ExampleDecoder_extendedJSON() { // Define an Extended JSON document that contains the name, SKU, and price // (in cents) of a product. data := []byte(`{"name":"Cereal Rounds","sku":"AB12345","price_cents":{"$numberLong":"399"}}`) // Create a Decoder that reads the Extended JSON document and use it to // unmarshal the document into a Product struct. vr, err := bson.NewExtJSONValueReader(bytes.NewReader(data), true) if err != nil { panic(err) } decoder := bson.NewDecoder(vr) type Product struct { Name string `bson:"name"` SKU string `bson:"sku"` Price int64 `bson:"price_cents"` } var res Product err = decoder.Decode(&res) if err != nil { panic(err) } fmt.Printf("%+v\n", res) // Output: {Name:Cereal Rounds SKU:AB12345 Price:399} } func ExampleDecoder_multipleExtendedJSONDocuments() { // Define a newline-separated sequence of Extended JSON documents that // contain X,Y coordinates. data := []byte(` {"x":{"$numberInt":"0"},"y":{"$numberInt":"0"}} {"x":{"$numberInt":"1"},"y":{"$numberInt":"1"}} {"x":{"$numberInt":"2"},"y":{"$numberInt":"2"}} {"x":{"$numberInt":"3"},"y":{"$numberInt":"3"}} {"x":{"$numberInt":"4"},"y":{"$numberInt":"4"}} `) // Create a Decoder that reads the Extended JSON documents and use it to // unmarshal the documents Coordinate structs. vr, err := bson.NewExtJSONValueReader(bytes.NewReader(data), true) if err != nil { panic(err) } decoder := bson.NewDecoder(vr) type Coordinate struct { X int Y int } // Read and unmarshal each Extended JSON document from the sequence. If // Decode returns error io.EOF, that means the Decoder has reached the end // of the input, so break the loop. for { var res Coordinate err = decoder.Decode(&res) if errors.Is(err, io.EOF) { break } if err != nil { panic(err) } fmt.Printf("%+v\n", res) } // Output: // {X:0 Y:0} // {X:1 Y:1} // {X:2 Y:2} // {X:3 Y:3} // {X:4 Y:4} } ================================================ FILE: bson/decoder_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "time" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) func TestDecodeValue(t *testing.T) { t.Parallel() for _, tc := range unmarshalingTestCases() { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() got := reflect.New(tc.sType).Elem() vr := NewDocumentReader(bytes.NewReader(tc.data)) reg := defaultRegistry decoder, err := reg.LookupDecoder(reflect.TypeOf(got)) noerr(t, err) err = decoder.DecodeValue(DecodeContext{Registry: reg}, vr, got) noerr(t, err) assert.Equal(t, tc.want, got.Addr().Interface(), "Results do not match.") }) } } func TestDecodingInterfaces(t *testing.T) { t.Parallel() type testCase struct { name string stub func() ([]byte, any, func(*testing.T)) } testCases := []testCase{ { name: "struct with interface containing a concrete value", stub: func() ([]byte, any, func(*testing.T)) { type testStruct struct { Value any } var value string data := docToBytes(struct { Value string }{ Value: "foo", }) receiver := testStruct{&value} check := func(t *testing.T) { t.Helper() assert.Equal(t, "foo", value) } return data, &receiver, check }, }, { name: "struct with interface containing a struct", stub: func() ([]byte, any, func(*testing.T)) { type demo struct { Data string } type testStruct struct { Value any } var value demo data := docToBytes(struct { Value demo }{ Value: demo{"foo"}, }) receiver := testStruct{&value} check := func(t *testing.T) { t.Helper() assert.Equal(t, "foo", value.Data) } return data, &receiver, check }, }, { name: "struct with interface containing a slice", stub: func() ([]byte, any, func(*testing.T)) { type testStruct struct { Values any } var values []string data := docToBytes(struct { Values []string }{ Values: []string{"foo", "bar"}, }) receiver := testStruct{&values} check := func(t *testing.T) { t.Helper() assert.Equal(t, []string{"foo", "bar"}, values) } return data, &receiver, check }, }, { name: "struct with interface containing an array", stub: func() ([]byte, any, func(*testing.T)) { type testStruct struct { Values any } var values [2]string data := docToBytes(struct { Values []string }{ Values: []string{"foo", "bar"}, }) receiver := testStruct{&values} check := func(t *testing.T) { t.Helper() assert.Equal(t, [2]string{"foo", "bar"}, values) } return data, &receiver, check }, }, { name: "struct with interface array containing concrete values", stub: func() ([]byte, any, func(*testing.T)) { type testStruct struct { Values [3]any } var str string var i, j int data := docToBytes(struct { Values []any }{ Values: []any{"foo", 42, nil}, }) receiver := testStruct{[3]any{&str, &i, &j}} check := func(t *testing.T) { t.Helper() assert.Equal(t, "foo", str) assert.Equal(t, 42, i) assert.Equal(t, 0, j) assert.Equal(t, testStruct{[3]any{&str, &i, nil}}, receiver) } return data, &receiver, check }, }, { name: "overwriting prepopulated slice", stub: func() ([]byte, any, func(*testing.T)) { type testStruct struct { Values []any } data := docToBytes(struct { Values []any }{ Values: []any{1, 2, 3}, }) receiver := testStruct{[]any{7, 8}} check := func(t *testing.T) { t.Helper() assert.Equal(t, testStruct{[]any{1, 2, int32(3)}}, receiver) } return data, &receiver, check }, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() data, receiver, check := tc.stub() got := reflect.ValueOf(receiver).Elem() vr := NewDocumentReader(bytes.NewReader(data)) reg := defaultRegistry decoder, err := reg.LookupDecoder(got.Type()) noerr(t, err) err = decoder.DecodeValue(DecodeContext{Registry: reg}, vr, got) noerr(t, err) check(t) }) } } func TestDecoder(t *testing.T) { t.Parallel() t.Run("Decode", func(t *testing.T) { t.Parallel() t.Run("basic", func(t *testing.T) { t.Parallel() for _, tc := range unmarshalingTestCases() { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() got := reflect.New(tc.sType).Interface() vr := NewDocumentReader(bytes.NewReader(tc.data)) dec := NewDecoder(vr) err := dec.Decode(got) noerr(t, err) assert.Equal(t, tc.want, got, "Results do not match.") }) } }) t.Run("stream", func(t *testing.T) { t.Parallel() var buf bytes.Buffer vr := NewDocumentReader(&buf) dec := NewDecoder(vr) for _, tc := range unmarshalingTestCases() { tc := tc t.Run(tc.name, func(t *testing.T) { buf.Write(tc.data) got := reflect.New(tc.sType).Interface() err := dec.Decode(got) noerr(t, err) assert.Equal(t, tc.want, got, "Results do not match.") }) } }) t.Run("lookup error", func(t *testing.T) { t.Parallel() type certainlydoesntexistelsewhereihope func(string, string) string // Avoid unused code lint error. _ = certainlydoesntexistelsewhereihope(func(string, string) string { return "" }) cdeih := func(string, string) string { return "certainlydoesntexistelsewhereihope" } dec := NewDecoder(NewDocumentReader(bytes.NewReader([]byte{}))) want := errNoDecoder{Type: reflect.TypeOf(cdeih)} got := dec.Decode(&cdeih) assert.Equal(t, want, got, "Received unexpected error.") }) t.Run("Unmarshaler", func(t *testing.T) { t.Parallel() testCases := []struct { name string err error vr ValueReader invoked bool }{ { "error", errors.New("Unmarshaler error"), &valueReaderWriter{BSONType: TypeEmbeddedDocument, Err: ErrEOD, ErrAfter: readElement}, true, }, { "copy error", errors.New("copy error"), &valueReaderWriter{Err: errors.New("copy error"), ErrAfter: readDocument}, false, }, { "success", nil, &valueReaderWriter{BSONType: TypeEmbeddedDocument, Err: ErrEOD, ErrAfter: readElement}, true, }, } for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() unmarshaler := &testUnmarshaler{Err: tc.err} dec := NewDecoder(tc.vr) got := dec.Decode(unmarshaler) want := tc.err if !assert.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 ValueReader", func(t *testing.T) { t.Parallel() want := bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159)) unmarshaler := &testUnmarshaler{} vr := NewDocumentReader(bytes.NewReader(want)) dec := NewDecoder(vr) err := dec.Decode(unmarshaler) noerr(t, err) got := unmarshaler.Val if !bytes.Equal(got, want) { t.Errorf("Did not unmarshal properly. got %v; want %v", got, want) } }) }) }) t.Run("NewDecoder", func(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() got := NewDecoder(NewDocumentReader(bytes.NewReader([]byte{}))) if got == nil { t.Errorf("Was expecting a non-nil Decoder, but got ") } }) }) t.Run("NewDecoderWithContext", func(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { t.Parallel() got := NewDecoder(NewDocumentReader(bytes.NewReader([]byte{}))) if got == nil { t.Errorf("Was expecting a non-nil Decoder, but got ") } }) }) t.Run("Decode doesn't zero struct", func(t *testing.T) { t.Parallel() type foo struct { Item string Qty int Bonus int } var got foo got.Item = "apple" got.Bonus = 2 data := docToBytes(D{{"item", "canvas"}, {"qty", 4}}) vr := NewDocumentReader(bytes.NewReader(data)) dec := NewDecoder(vr) err := dec.Decode(&got) noerr(t, err) want := foo{Item: "canvas", Qty: 4, Bonus: 2} assert.Equal(t, want, got, "Results do not match.") }) t.Run("Reset", func(t *testing.T) { t.Parallel() vr1, vr2 := NewDocumentReader(bytes.NewReader([]byte{})), NewDocumentReader(bytes.NewReader([]byte{})) dec := NewDecoder(vr1) if dec.vr != vr1 { t.Errorf("Decoder should use the value reader provided. got %v; want %v", dec.vr, vr1) } dec.Reset(vr2) if dec.vr != vr2 { t.Errorf("Decoder should use the value reader provided. got %v; want %v", dec.vr, vr2) } }) t.Run("SetRegistry", func(t *testing.T) { t.Parallel() r1, r2 := defaultRegistry, NewRegistry() dc1 := DecodeContext{Registry: r1} dc2 := DecodeContext{Registry: r2} dec := NewDecoder(NewDocumentReader(bytes.NewReader([]byte{}))) if !reflect.DeepEqual(dec.dc, dc1) { t.Errorf("Decoder should use the Registry provided. got %v; want %v", dec.dc, dc1) } dec.SetRegistry(r2) if !reflect.DeepEqual(dec.dc, dc2) { t.Errorf("Decoder should use the Registry provided. got %v; want %v", dec.dc, dc2) } }) t.Run("DecodeToNil", func(t *testing.T) { t.Parallel() data := docToBytes(D{{"item", "canvas"}, {"qty", 4}}) vr := NewDocumentReader(bytes.NewReader(data)) dec := NewDecoder(vr) var got *D err := dec.Decode(got) if !errors.Is(err, ErrDecodeToNil) { t.Fatalf("Decode error mismatch; expected %v, got %v", ErrDecodeToNil, err) } }) } type testUnmarshaler struct { Invoked bool Val []byte Err error } func (tu *testUnmarshaler) UnmarshalBSON(d []byte) error { tu.Invoked = true tu.Val = d return tu.Err } func TestDecoderConfiguration(t *testing.T) { type truncateDoublesTest struct { MyInt int MyInt8 int8 MyInt16 int16 MyInt32 int32 MyInt64 int64 MyUint uint MyUint8 uint8 MyUint16 uint16 MyUint32 uint32 MyUint64 uint64 } type objectIDTest struct { ID string } type jsonStructTest struct { StructFieldName string `json:"jsonFieldName"` } type localTimeZoneTest struct { MyTime time.Time } type zeroMapsTest struct { MyMap map[string]string } type zeroStructsTest struct { MyString string MyInt int } testCases := []struct { description string configure func(*Decoder) input []byte decodeInto func() any want any }{ // Test that AllowTruncatingDoubles causes the Decoder to unmarshal BSON doubles with // fractional parts into Go integer types by truncating the fractional part. { description: "AllowTruncatingDoubles", configure: func(dec *Decoder) { dec.AllowTruncatingDoubles() }, input: bsoncore.NewDocumentBuilder(). AppendDouble("myInt", 1.999). AppendDouble("myInt8", 1.999). AppendDouble("myInt16", 1.999). AppendDouble("myInt32", 1.999). AppendDouble("myInt64", 1.999). AppendDouble("myUint", 1.999). AppendDouble("myUint8", 1.999). AppendDouble("myUint16", 1.999). AppendDouble("myUint32", 1.999). AppendDouble("myUint64", 1.999). Build(), decodeInto: func() any { return &truncateDoublesTest{} }, want: &truncateDoublesTest{ MyInt: 1, MyInt8: 1, MyInt16: 1, MyInt32: 1, MyInt64: 1, MyUint: 1, MyUint8: 1, MyUint16: 1, MyUint32: 1, MyUint64: 1, }, }, // Test that BinaryAsSlice causes the Decoder to unmarshal BSON binary fields into Go byte // slices when there is no type information (e.g when unmarshaling into a bson.D). { description: "BinaryAsSlice", configure: func(dec *Decoder) { dec.BinaryAsSlice() }, input: bsoncore.NewDocumentBuilder(). AppendBinary("myBinary", TypeBinaryGeneric, []byte{}). Build(), decodeInto: func() any { return &D{} }, want: &D{{Key: "myBinary", Value: []byte{}}}, }, // Test that the default decoder always decodes BSON documents into bson.D values, // independent of the top-level Go value type. { description: "DocumentD nested by default", configure: func(_ *Decoder) {}, input: bsoncore.NewDocumentBuilder(). AppendDocument("myDocument", bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build()). Build(), decodeInto: func() any { return M{} }, want: M{ "myDocument": D{{Key: "myString", Value: "test value"}}, }, }, // Test that DefaultDocumentM always decodes BSON documents into bson.M values, // independent of the top-level Go value type. { description: "DefaultDocumentM nested", configure: func(dec *Decoder) { dec.DefaultDocumentM() }, input: bsoncore.NewDocumentBuilder(). AppendDocument("myDocument", bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build()). Build(), decodeInto: func() any { return &D{} }, want: &D{ {Key: "myDocument", Value: M{"myString": "test value"}}, }, }, // Test that DefaultDocumentMap always decodes BSON documents into // map[string]any values, independent of the top-level Go value type. { description: "DefaultDocumentMap nested", configure: func(dec *Decoder) { dec.DefaultDocumentMap() }, input: bsoncore.NewDocumentBuilder(). AppendDocument("myDocument", bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build()). Build(), decodeInto: func() any { return &D{} }, want: &D{ {Key: "myDocument", Value: map[string]any{"myString": "test value"}}, }, }, // Test that ObjectIDAsHexString causes the Decoder to decode object ID to hex. { description: "ObjectIDAsHexString", configure: func(dec *Decoder) { dec.ObjectIDAsHexString() }, input: bsoncore.NewDocumentBuilder(). AppendObjectID("id", func() ObjectID { id, _ := ObjectIDFromHex("5ef7fdd91c19e3222b41b839") return id }()). Build(), decodeInto: func() any { return &objectIDTest{} }, want: &objectIDTest{ID: "5ef7fdd91c19e3222b41b839"}, }, // Test that UseJSONStructTags causes the Decoder to fall back to "json" struct tags if // "bson" struct tags are not available. { description: "UseJSONStructTags", configure: func(dec *Decoder) { dec.UseJSONStructTags() }, input: bsoncore.NewDocumentBuilder(). AppendString("jsonFieldName", "test value"). Build(), decodeInto: func() any { return &jsonStructTest{} }, want: &jsonStructTest{StructFieldName: "test value"}, }, // Test that UseLocalTimeZone causes the Decoder to use the local time zone for decoded // time.Time values instead of UTC. { description: "UseLocalTimeZone", configure: func(dec *Decoder) { dec.UseLocalTimeZone() }, input: bsoncore.NewDocumentBuilder(). AppendDateTime("myTime", 1684349179939). Build(), decodeInto: func() any { return &localTimeZoneTest{} }, want: &localTimeZoneTest{MyTime: time.UnixMilli(1684349179939)}, }, // Test that ZeroMaps causes the Decoder to empty any Go map values before decoding BSON // documents into them. { description: "ZeroMaps", configure: func(dec *Decoder) { dec.ZeroMaps() }, input: bsoncore.NewDocumentBuilder(). AppendDocument("myMap", bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build()). Build(), decodeInto: func() any { return &zeroMapsTest{MyMap: map[string]string{"myExtraValue": "extra value"}} }, want: &zeroMapsTest{MyMap: map[string]string{"myString": "test value"}}, }, // Test that ZeroStructs causes the Decoder to empty any Go struct values before decoding // BSON documents into them. { description: "ZeroStructs", configure: func(dec *Decoder) { dec.ZeroStructs() }, input: bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build(), decodeInto: func() any { return &zeroStructsTest{MyInt: 1} }, want: &zeroStructsTest{MyString: "test value"}, }, } for _, tc := range testCases { tc := tc // Capture range variable. t.Run(tc.description, func(t *testing.T) { t.Parallel() dec := NewDecoder(NewDocumentReader(bytes.NewReader(tc.input))) tc.configure(dec) got := tc.decodeInto() err := dec.Decode(got) require.NoError(t, err, "Decode error") assert.Equal(t, tc.want, got, "expected and actual decode results do not match") }) } t.Run("Decoding an object ID to string", func(t *testing.T) { t.Parallel() type objectIDTest struct { ID string } doc := bsoncore.NewDocumentBuilder(). AppendObjectID("id", func() ObjectID { id, _ := ObjectIDFromHex("5ef7fdd91c19e3222b41b839") return id }()). Build() dec := NewDecoder(NewDocumentReader(bytes.NewReader(doc))) var got objectIDTest err := dec.Decode(&got) const want = "error decoding key id: decoding an object ID into a string is not supported by default (set Decoder.ObjectIDAsHexString to enable decoding as a hexadecimal string)" assert.EqualError(t, err, want) }) t.Run("DefaultDocumentM top-level", func(t *testing.T) { t.Parallel() input := bsoncore.NewDocumentBuilder(). AppendDocument("myDocument", bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build()). Build() dec := NewDecoder(NewDocumentReader(bytes.NewReader(input))) dec.DefaultDocumentM() var got any err := dec.Decode(&got) require.NoError(t, err, "Decode error") want := M{ "myDocument": M{ "myString": "test value", }, } assert.Equal(t, want, got, "expected and actual decode results do not match") }) t.Run("DefaultDocumentMap top-level", func(t *testing.T) { t.Parallel() input := bsoncore.NewDocumentBuilder(). AppendDocument("myDocument", bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build()). Build() dec := NewDecoder(NewDocumentReader(bytes.NewReader(input))) dec.DefaultDocumentMap() var got any err := dec.Decode(&got) require.NoError(t, err, "Decode error") want := map[string]any{ "myDocument": map[string]any{ "myString": "test value", }, } assert.Equal(t, want, got, "expected and actual decode results do not match") }) t.Run("Default decodes DocumentD for top-level", func(t *testing.T) { t.Parallel() input := bsoncore.NewDocumentBuilder(). AppendDocument("myDocument", bsoncore.NewDocumentBuilder(). AppendString("myString", "test value"). Build()). Build() dec := NewDecoder(NewDocumentReader(bytes.NewReader(input))) var got any err := dec.Decode(&got) require.NoError(t, err, "Decode error") want := D{ {Key: "myDocument", Value: D{ {Key: "myString", Value: "test value"}, }}, } assert.Equal(t, want, got, "expected and actual decode results do not match") }) } ================================================ FILE: bson/default_value_decoders.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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/json" "errors" "fmt" "math" "net/url" "reflect" "strconv" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) var errCannotTruncate = errors.New("float64 can only be truncated to a lower precision type when truncation is enabled") type decodeBinaryError struct { subtype byte typeName string } func (d decodeBinaryError) Error() string { return fmt.Sprintf("only binary values with subtype 0x00 or 0x02 can be decoded into %s, but got subtype %v", d.typeName, d.subtype) } // registerDefaultDecoders will register the decoder methods attached to DefaultValueDecoders with // the provided RegistryBuilder. // // There is no support for decoding map[string]any because there is no decoder for // any, so users must either register this decoder themselves or use the // EmptyInterfaceDecoder available in the bson package. func registerDefaultDecoders(reg *Registry) { intDecoder := decodeAdapter{intDecodeValue, intDecodeType} floatDecoder := decodeAdapter{floatDecodeValue, floatDecodeType} uintCodec := &uintCodec{} reg.RegisterTypeDecoder(tD, ValueDecoderFunc(dDecodeValue)) reg.RegisterTypeDecoder(tBinary, decodeAdapter{binaryDecodeValue, binaryDecodeType}) reg.RegisterTypeDecoder(tVector, decodeAdapter{vectorDecodeValue, vectorDecodeType}) reg.RegisterTypeDecoder(tUndefined, decodeAdapter{undefinedDecodeValue, undefinedDecodeType}) reg.RegisterTypeDecoder(tDateTime, decodeAdapter{dateTimeDecodeValue, dateTimeDecodeType}) reg.RegisterTypeDecoder(tNull, decodeAdapter{nullDecodeValue, nullDecodeType}) reg.RegisterTypeDecoder(tRegex, decodeAdapter{regexDecodeValue, regexDecodeType}) reg.RegisterTypeDecoder(tDBPointer, decodeAdapter{dbPointerDecodeValue, dbPointerDecodeType}) reg.RegisterTypeDecoder(tTimestamp, decodeAdapter{timestampDecodeValue, timestampDecodeType}) reg.RegisterTypeDecoder(tMinKey, decodeAdapter{minKeyDecodeValue, minKeyDecodeType}) reg.RegisterTypeDecoder(tMaxKey, decodeAdapter{maxKeyDecodeValue, maxKeyDecodeType}) reg.RegisterTypeDecoder(tJavaScript, decodeAdapter{javaScriptDecodeValue, javaScriptDecodeType}) reg.RegisterTypeDecoder(tSymbol, decodeAdapter{symbolDecodeValue, symbolDecodeType}) reg.RegisterTypeDecoder(tByteSlice, &byteSliceCodec{}) reg.RegisterTypeDecoder(tTime, &timeCodec{}) reg.RegisterTypeDecoder(tEmpty, &emptyInterfaceCodec{}) reg.RegisterTypeDecoder(tCoreArray, &arrayCodec{}) reg.RegisterTypeDecoder(tOID, decodeAdapter{objectIDDecodeValue, objectIDDecodeType}) reg.RegisterTypeDecoder(tDecimal, decodeAdapter{decimal128DecodeValue, decimal128DecodeType}) reg.RegisterTypeDecoder(tJSONNumber, decodeAdapter{jsonNumberDecodeValue, jsonNumberDecodeType}) reg.RegisterTypeDecoder(tURL, decodeAdapter{urlDecodeValue, urlDecodeType}) reg.RegisterTypeDecoder(tCoreDocument, ValueDecoderFunc(coreDocumentDecodeValue)) reg.RegisterTypeDecoder(tCodeWithScope, decodeAdapter{codeWithScopeDecodeValue, codeWithScopeDecodeType}) reg.RegisterKindDecoder(reflect.Bool, decodeAdapter{booleanDecodeValue, booleanDecodeType}) reg.RegisterKindDecoder(reflect.Int, intDecoder) reg.RegisterKindDecoder(reflect.Int8, intDecoder) reg.RegisterKindDecoder(reflect.Int16, intDecoder) reg.RegisterKindDecoder(reflect.Int32, intDecoder) reg.RegisterKindDecoder(reflect.Int64, intDecoder) reg.RegisterKindDecoder(reflect.Uint, uintCodec) reg.RegisterKindDecoder(reflect.Uint8, uintCodec) reg.RegisterKindDecoder(reflect.Uint16, uintCodec) reg.RegisterKindDecoder(reflect.Uint32, uintCodec) reg.RegisterKindDecoder(reflect.Uint64, uintCodec) reg.RegisterKindDecoder(reflect.Float32, floatDecoder) reg.RegisterKindDecoder(reflect.Float64, floatDecoder) reg.RegisterKindDecoder(reflect.Array, ValueDecoderFunc(arrayDecodeValue)) reg.RegisterKindDecoder(reflect.Map, &mapCodec{}) reg.RegisterKindDecoder(reflect.Slice, &sliceCodec{}) reg.RegisterKindDecoder(reflect.String, &stringCodec{}) reg.RegisterKindDecoder(reflect.Struct, newStructCodec(nil)) reg.RegisterKindDecoder(reflect.Ptr, &pointerCodec{}) reg.RegisterTypeMapEntry(TypeDouble, tFloat64) reg.RegisterTypeMapEntry(TypeString, tString) reg.RegisterTypeMapEntry(TypeArray, tA) reg.RegisterTypeMapEntry(TypeBinary, tBinary) reg.RegisterTypeMapEntry(TypeUndefined, tUndefined) reg.RegisterTypeMapEntry(TypeObjectID, tOID) reg.RegisterTypeMapEntry(TypeBoolean, tBool) reg.RegisterTypeMapEntry(TypeDateTime, tDateTime) reg.RegisterTypeMapEntry(TypeRegex, tRegex) reg.RegisterTypeMapEntry(TypeDBPointer, tDBPointer) reg.RegisterTypeMapEntry(TypeJavaScript, tJavaScript) reg.RegisterTypeMapEntry(TypeSymbol, tSymbol) reg.RegisterTypeMapEntry(TypeCodeWithScope, tCodeWithScope) reg.RegisterTypeMapEntry(TypeInt32, tInt32) reg.RegisterTypeMapEntry(TypeInt64, tInt64) reg.RegisterTypeMapEntry(TypeTimestamp, tTimestamp) reg.RegisterTypeMapEntry(TypeDecimal128, tDecimal) reg.RegisterTypeMapEntry(TypeMinKey, tMinKey) reg.RegisterTypeMapEntry(TypeMaxKey, tMaxKey) reg.RegisterTypeMapEntry(Type(0), tD) reg.RegisterTypeMapEntry(TypeEmbeddedDocument, tD) reg.RegisterInterfaceDecoder(tValueUnmarshaler, ValueDecoderFunc(valueUnmarshalerDecodeValue)) reg.RegisterInterfaceDecoder(tUnmarshaler, ValueDecoderFunc(unmarshalerDecodeValue)) } // dDecodeValue is the ValueDecoderFunc for D instances. func dDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.IsValid() || !val.CanSet() || val.Type() != tD { return ValueDecoderError{Name: "DDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: val} } switch vrType := vr.Type(); vrType { case Type(0), TypeEmbeddedDocument: break case TypeNull: val.Set(reflect.Zero(val.Type())) return vr.ReadNull() default: return fmt.Errorf("cannot decode %v into a D", vrType) } dr, err := vr.ReadDocument() if err != nil { return err } decoder, err := dc.LookupDecoder(tEmpty) if err != nil { return err } // Use the elements in the provided value if it's non nil. Otherwise, allocate a new D instance. var elems D if !val.IsNil() { val.SetLen(0) elems = val.Interface().(D) } else { elems = make(D, 0) } for { key, elemVr, err := dr.ReadElement() if errors.Is(err, ErrEOD) { break } else if err != nil { return err } var v any err = decoder.DecodeValue(dc, elemVr, reflect.ValueOf(&v).Elem()) if err != nil { return err } elems = append(elems, E{Key: key, Value: v}) } val.Set(reflect.ValueOf(elems)) return nil } func booleanDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t.Kind() != reflect.Bool { return emptyValue, ValueDecoderError{ Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.Zero(t), } } var b bool var err error switch vrType := vr.Type(); vrType { case TypeInt32: i32, err := vr.ReadInt32() if err != nil { return emptyValue, err } b = (i32 != 0) case TypeInt64: i64, err := vr.ReadInt64() if err != nil { return emptyValue, err } b = (i64 != 0) case TypeDouble: f64, err := vr.ReadDouble() if err != nil { return emptyValue, err } b = (f64 != 0) case TypeBoolean: b, err = vr.ReadBoolean() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a boolean", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(b), nil } // booleanDecodeValue is the ValueDecoderFunc for bool types. func booleanDecodeValue(dctx DecodeContext, vr ValueReader, val reflect.Value) error { if !val.IsValid() || !val.CanSet() || val.Kind() != reflect.Bool { return ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: val} } elem, err := booleanDecodeType(dctx, vr, val.Type()) if err != nil { return err } val.SetBool(elem.Bool()) return nil } func intDecodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { var i64 int64 var err error switch vrType := vr.Type(); vrType { case TypeInt32: i32, err := vr.ReadInt32() if err != nil { return emptyValue, err } i64 = int64(i32) case TypeInt64: i64, err = vr.ReadInt64() if err != nil { return emptyValue, err } case TypeDouble: f64, err := vr.ReadDouble() if err != nil { return emptyValue, err } if !dc.truncate && math.Floor(f64) != f64 { return emptyValue, errCannotTruncate } if f64 > float64(math.MaxInt64) { return emptyValue, fmt.Errorf("%g overflows int64", f64) } i64 = int64(f64) case TypeBoolean: b, err := vr.ReadBoolean() if err != nil { return emptyValue, err } if b { i64 = 1 } case TypeNull: if err = vr.ReadNull(); err != nil { return emptyValue, err } case TypeUndefined: if err = vr.ReadUndefined(); err != nil { return emptyValue, err } default: return emptyValue, fmt.Errorf("cannot decode %v into an integer type", vrType) } switch t.Kind() { case reflect.Int8: if i64 < math.MinInt8 || i64 > math.MaxInt8 { return emptyValue, fmt.Errorf("%d overflows int8", i64) } return reflect.ValueOf(int8(i64)), nil case reflect.Int16: if i64 < math.MinInt16 || i64 > math.MaxInt16 { return emptyValue, fmt.Errorf("%d overflows int16", i64) } return reflect.ValueOf(int16(i64)), nil case reflect.Int32: if i64 < math.MinInt32 || i64 > math.MaxInt32 { return emptyValue, fmt.Errorf("%d overflows int32", i64) } return reflect.ValueOf(int32(i64)), nil case reflect.Int64: return reflect.ValueOf(i64), nil case reflect.Int: if i64 > math.MaxInt { // Can we fit this inside of an int return emptyValue, fmt.Errorf("%d overflows int", i64) } return reflect.ValueOf(int(i64)), nil default: return emptyValue, ValueDecoderError{ Name: "IntDecodeValue", Kinds: []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int}, Received: reflect.Zero(t), } } } // intDecodeValue is the ValueDecoderFunc for int types. func intDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() { return ValueDecoderError{ Name: "IntDecodeValue", Kinds: []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int}, Received: val, } } elem, err := intDecodeType(dc, vr, val.Type()) if err != nil { return err } val.SetInt(elem.Int()) return nil } func floatDecodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { var f float64 var err error switch vrType := vr.Type(); vrType { case TypeInt32: i32, err := vr.ReadInt32() if err != nil { return emptyValue, err } f = float64(i32) case TypeInt64: i64, err := vr.ReadInt64() if err != nil { return emptyValue, err } f = float64(i64) case TypeDouble: f, err = vr.ReadDouble() if err != nil { return emptyValue, err } case TypeBoolean: b, err := vr.ReadBoolean() if err != nil { return emptyValue, err } if b { f = 1 } case TypeNull: if err = vr.ReadNull(); err != nil { return emptyValue, err } case TypeUndefined: if err = vr.ReadUndefined(); err != nil { return emptyValue, err } default: return emptyValue, fmt.Errorf("cannot decode %v into a float32 or float64 type", vrType) } switch t.Kind() { case reflect.Float32: if !dc.truncate && float64(float32(f)) != f { return emptyValue, errCannotTruncate } return reflect.ValueOf(float32(f)), nil case reflect.Float64: return reflect.ValueOf(f), nil default: return emptyValue, ValueDecoderError{ Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: reflect.Zero(t), } } } // floatDecodeValue is the ValueDecoderFunc for float types. func floatDecodeValue(ec DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() { return ValueDecoderError{ Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: val, } } elem, err := floatDecodeType(ec, vr, val.Type()) if err != nil { return err } val.SetFloat(elem.Float()) return nil } func javaScriptDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tJavaScript { return emptyValue, ValueDecoderError{ Name: "JavaScriptDecodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.Zero(t), } } var js string var err error switch vrType := vr.Type(); vrType { case TypeJavaScript: js, err = vr.ReadJavascript() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a JavaScript", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(JavaScript(js)), nil } // javaScriptDecodeValue is the ValueDecoderFunc for the JavaScript type. func javaScriptDecodeValue(dctx DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tJavaScript { return ValueDecoderError{Name: "JavaScriptDecodeValue", Types: []reflect.Type{tJavaScript}, Received: val} } elem, err := javaScriptDecodeType(dctx, vr, tJavaScript) if err != nil { return err } val.SetString(elem.String()) return nil } func symbolDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tSymbol { return emptyValue, ValueDecoderError{ Name: "SymbolDecodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.Zero(t), } } var symbol string var err error switch vrType := vr.Type(); vrType { case TypeString: symbol, err = vr.ReadString() case TypeSymbol: symbol, err = vr.ReadSymbol() case TypeBinary: data, subtype, err := vr.ReadBinary() if err != nil { return emptyValue, err } if subtype != TypeBinaryGeneric && subtype != TypeBinaryBinaryOld { return emptyValue, decodeBinaryError{subtype: subtype, typeName: "Symbol"} } symbol = string(data) case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a Symbol", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(Symbol(symbol)), nil } // symbolDecodeValue is the ValueDecoderFunc for the Symbol type. func symbolDecodeValue(dctx DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tSymbol { return ValueDecoderError{Name: "SymbolDecodeValue", Types: []reflect.Type{tSymbol}, Received: val} } elem, err := symbolDecodeType(dctx, vr, tSymbol) if err != nil { return err } val.SetString(elem.String()) return nil } func binaryDecode(vr ValueReader) (Binary, error) { var b Binary var data []byte var subtype byte var err error switch vrType := vr.Type(); vrType { case TypeBinary: data, subtype, err = vr.ReadBinary() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return b, fmt.Errorf("cannot decode %v into a Binary", vrType) } if err != nil { return b, err } b.Subtype = subtype b.Data = data return b, nil } func binaryDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tBinary { return emptyValue, ValueDecoderError{ Name: "BinaryDecodeValue", Types: []reflect.Type{tBinary}, Received: reflect.Zero(t), } } b, err := binaryDecode(vr) if err != nil { return emptyValue, err } return reflect.ValueOf(b), nil } // binaryDecodeValue is the ValueDecoderFunc for Binary. func binaryDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tBinary { return ValueDecoderError{Name: "BinaryDecodeValue", Types: []reflect.Type{tBinary}, Received: val} } elem, err := binaryDecodeType(dc, vr, tBinary) if err != nil { return err } val.Set(elem) return nil } func vectorDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tVector { return emptyValue, ValueDecoderError{ Name: "VectorDecodeValue", Types: []reflect.Type{tVector}, Received: reflect.Zero(t), } } b, err := binaryDecode(vr) if err != nil { return emptyValue, err } v, err := NewVectorFromBinary(b) if err != nil { return emptyValue, err } return reflect.ValueOf(v), nil } // vectorDecodeValue is the ValueDecoderFunc for Vector. func vectorDecodeValue(dctx DecodeContext, vr ValueReader, val reflect.Value) error { t := val.Type() if !val.CanSet() || t != tVector { return ValueDecoderError{ Name: "VectorDecodeValue", Types: []reflect.Type{tVector}, Received: val, } } elem, err := vectorDecodeType(dctx, vr, t) if err != nil { return err } val.Set(elem) return nil } func undefinedDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tUndefined { return emptyValue, ValueDecoderError{ Name: "UndefinedDecodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.Zero(t), } } var err error switch vrType := vr.Type(); vrType { case TypeUndefined: err = vr.ReadUndefined() case TypeNull: err = vr.ReadNull() default: return emptyValue, fmt.Errorf("cannot decode %v into an Undefined", vr.Type()) } if err != nil { return emptyValue, err } return reflect.ValueOf(Undefined{}), nil } // undefinedDecodeValue is the ValueDecoderFunc for Undefined. func undefinedDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tUndefined { return ValueDecoderError{Name: "UndefinedDecodeValue", Types: []reflect.Type{tUndefined}, Received: val} } elem, err := undefinedDecodeType(dc, vr, tUndefined) if err != nil { return err } val.Set(elem) return nil } // Accept both 12-byte string and pretty-printed 24-byte hex string formats. func objectIDDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tOID { return emptyValue, ValueDecoderError{ Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}, Received: reflect.Zero(t), } } var oid ObjectID var err error switch vrType := vr.Type(); vrType { case TypeObjectID: oid, err = vr.ReadObjectID() if err != nil { return emptyValue, err } case TypeString: str, err := vr.ReadString() if err != nil { return emptyValue, err } if oid, err = ObjectIDFromHex(str); err == nil { break } if len(str) != 12 { return emptyValue, fmt.Errorf("an ObjectID string must be exactly 12 bytes long (got %v)", len(str)) } byteArr := []byte(str) copy(oid[:], byteArr) case TypeNull: if err = vr.ReadNull(); err != nil { return emptyValue, err } case TypeUndefined: if err = vr.ReadUndefined(); err != nil { return emptyValue, err } default: return emptyValue, fmt.Errorf("cannot decode %v into an ObjectID", vrType) } return reflect.ValueOf(oid), nil } // objectIDDecodeValue is the ValueDecoderFunc for ObjectID. func objectIDDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tOID { return ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}, Received: val} } elem, err := objectIDDecodeType(dc, vr, tOID) if err != nil { return err } val.Set(elem) return nil } func dateTimeDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tDateTime { return emptyValue, ValueDecoderError{ Name: "DateTimeDecodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.Zero(t), } } var dt int64 var err error switch vrType := vr.Type(); vrType { case TypeDateTime: dt, err = vr.ReadDateTime() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a DateTime", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(DateTime(dt)), nil } // dateTimeDecodeValue is the ValueDecoderFunc for DateTime. func dateTimeDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tDateTime { return ValueDecoderError{Name: "DateTimeDecodeValue", Types: []reflect.Type{tDateTime}, Received: val} } elem, err := dateTimeDecodeType(dc, vr, tDateTime) if err != nil { return err } val.Set(elem) return nil } func nullDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tNull { return emptyValue, ValueDecoderError{ Name: "NullDecodeValue", Types: []reflect.Type{tNull}, Received: reflect.Zero(t), } } var err error switch vrType := vr.Type(); vrType { case TypeUndefined: err = vr.ReadUndefined() case TypeNull: err = vr.ReadNull() default: return emptyValue, fmt.Errorf("cannot decode %v into a Null", vr.Type()) } if err != nil { return emptyValue, err } return reflect.ValueOf(Null{}), nil } // nullDecodeValue is the ValueDecoderFunc for Null. func nullDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tNull { return ValueDecoderError{Name: "NullDecodeValue", Types: []reflect.Type{tNull}, Received: val} } elem, err := nullDecodeType(dc, vr, tNull) if err != nil { return err } val.Set(elem) return nil } func regexDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tRegex { return emptyValue, ValueDecoderError{ Name: "RegexDecodeValue", Types: []reflect.Type{tRegex}, Received: reflect.Zero(t), } } var pattern, options string var err error switch vrType := vr.Type(); vrType { case TypeRegex: pattern, options, err = vr.ReadRegex() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a Regex", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(Regex{Pattern: pattern, Options: options}), nil } // regexDecodeValue is the ValueDecoderFunc for Regex. func regexDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tRegex { return ValueDecoderError{Name: "RegexDecodeValue", Types: []reflect.Type{tRegex}, Received: val} } elem, err := regexDecodeType(dc, vr, tRegex) if err != nil { return err } val.Set(elem) return nil } func dbPointerDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tDBPointer { return emptyValue, ValueDecoderError{ Name: "DBPointerDecodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.Zero(t), } } var ns string var pointer ObjectID var err error switch vrType := vr.Type(); vrType { case TypeDBPointer: ns, pointer, err = vr.ReadDBPointer() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a DBPointer", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(DBPointer{DB: ns, Pointer: pointer}), nil } // dbPointerDecodeValue is the ValueDecoderFunc for DBPointer. func dbPointerDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tDBPointer { return ValueDecoderError{Name: "DBPointerDecodeValue", Types: []reflect.Type{tDBPointer}, Received: val} } elem, err := dbPointerDecodeType(dc, vr, tDBPointer) if err != nil { return err } val.Set(elem) return nil } func timestampDecodeType(_ DecodeContext, vr ValueReader, reflectType reflect.Type) (reflect.Value, error) { if reflectType != tTimestamp { return emptyValue, ValueDecoderError{ Name: "TimestampDecodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.Zero(reflectType), } } var t, incr uint32 var err error switch vrType := vr.Type(); vrType { case TypeTimestamp: t, incr, err = vr.ReadTimestamp() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a Timestamp", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(Timestamp{T: t, I: incr}), nil } // timestampDecodeValue is the ValueDecoderFunc for Timestamp. func timestampDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tTimestamp { return ValueDecoderError{Name: "TimestampDecodeValue", Types: []reflect.Type{tTimestamp}, Received: val} } elem, err := timestampDecodeType(dc, vr, tTimestamp) if err != nil { return err } val.Set(elem) return nil } func minKeyDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tMinKey { return emptyValue, ValueDecoderError{ Name: "MinKeyDecodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.Zero(t), } } var err error switch vrType := vr.Type(); vrType { case TypeMinKey: err = vr.ReadMinKey() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a MinKey", vr.Type()) } if err != nil { return emptyValue, err } return reflect.ValueOf(MinKey{}), nil } // minKeyDecodeValue is the ValueDecoderFunc for MinKey. func minKeyDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tMinKey { return ValueDecoderError{Name: "MinKeyDecodeValue", Types: []reflect.Type{tMinKey}, Received: val} } elem, err := minKeyDecodeType(dc, vr, tMinKey) if err != nil { return err } val.Set(elem) return nil } func maxKeyDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tMaxKey { return emptyValue, ValueDecoderError{ Name: "MaxKeyDecodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.Zero(t), } } var err error switch vrType := vr.Type(); vrType { case TypeMaxKey: err = vr.ReadMaxKey() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a MaxKey", vr.Type()) } if err != nil { return emptyValue, err } return reflect.ValueOf(MaxKey{}), nil } // maxKeyDecodeValue is the ValueDecoderFunc for MaxKey. func maxKeyDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tMaxKey { return ValueDecoderError{Name: "MaxKeyDecodeValue", Types: []reflect.Type{tMaxKey}, Received: val} } elem, err := maxKeyDecodeType(dc, vr, tMaxKey) if err != nil { return err } val.Set(elem) return nil } func decimal128DecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tDecimal { return emptyValue, ValueDecoderError{ Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.Zero(t), } } var d128 Decimal128 var err error switch vrType := vr.Type(); vrType { case TypeDecimal128: d128, err = vr.ReadDecimal128() case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a Decimal128", vr.Type()) } if err != nil { return emptyValue, err } return reflect.ValueOf(d128), nil } // decimal128DecodeValue is the ValueDecoderFunc for Decimal128. func decimal128DecodeValue(dctx DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tDecimal { return ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}, Received: val} } elem, err := decimal128DecodeType(dctx, vr, tDecimal) if err != nil { return err } val.Set(elem) return nil } func jsonNumberDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tJSONNumber { return emptyValue, ValueDecoderError{ Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.Zero(t), } } var jsonNum json.Number var err error switch vrType := vr.Type(); vrType { case TypeDouble: f64, err := vr.ReadDouble() if err != nil { return emptyValue, err } jsonNum = json.Number(strconv.FormatFloat(f64, 'f', -1, 64)) case TypeInt32: i32, err := vr.ReadInt32() if err != nil { return emptyValue, err } jsonNum = json.Number(strconv.FormatInt(int64(i32), 10)) case TypeInt64: i64, err := vr.ReadInt64() if err != nil { return emptyValue, err } jsonNum = json.Number(strconv.FormatInt(i64, 10)) case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a json.Number", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(jsonNum), nil } // jsonNumberDecodeValue is the ValueDecoderFunc for json.Number. func jsonNumberDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tJSONNumber { return ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}, Received: val} } elem, err := jsonNumberDecodeType(dc, vr, tJSONNumber) if err != nil { return err } val.Set(elem) return nil } func urlDecodeType(_ DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tURL { return emptyValue, ValueDecoderError{ Name: "URLDecodeValue", Types: []reflect.Type{tURL}, Received: reflect.Zero(t), } } urlPtr := &url.URL{} var err error switch vrType := vr.Type(); vrType { case TypeString: var str string // Declare str here to avoid shadowing err during the ReadString call. str, err = vr.ReadString() if err != nil { return emptyValue, err } urlPtr, err = url.Parse(str) case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a *url.URL", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(urlPtr).Elem(), nil } // urlDecodeValue is the ValueDecoderFunc for url.URL. func urlDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tURL { return ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}, Received: val} } elem, err := urlDecodeType(dc, vr, tURL) if err != nil { return err } val.Set(elem) return nil } // arrayDecodeValue is the ValueDecoderFunc for array types. func arrayDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.IsValid() || val.Kind() != reflect.Array { return ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: val} } switch vrType := vr.Type(); vrType { case TypeArray: case Type(0), TypeEmbeddedDocument: if val.Type().Elem() != tE { return fmt.Errorf("cannot decode document into %s", val.Type()) } case TypeBinary: if val.Type().Elem() != tByte { return fmt.Errorf("ArrayDecodeValue can only be used to decode binary into a byte array, got %v", vrType) } data, subtype, err := vr.ReadBinary() if err != nil { return err } if subtype != TypeBinaryGeneric && subtype != TypeBinaryBinaryOld { return fmt.Errorf("ArrayDecodeValue can only be used to decode subtype 0x00 or 0x02 for %s, got %v", TypeBinary, subtype) } if len(data) > val.Len() { return fmt.Errorf("more elements returned in array than can fit inside %s", val.Type()) } for idx, elem := range data { val.Index(idx).Set(reflect.ValueOf(elem)) } return nil case TypeNull: val.Set(reflect.Zero(val.Type())) return vr.ReadNull() case TypeUndefined: val.Set(reflect.Zero(val.Type())) return vr.ReadUndefined() default: return fmt.Errorf("cannot decode %v into an array", vrType) } var elemsFunc func(DecodeContext, ValueReader, reflect.Value) ([]reflect.Value, error) switch val.Type().Elem() { case tE: elemsFunc = decodeD default: elemsFunc = 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, got %v elements", val.Type(), len(elems)) } for idx, elem := range elems { val.Index(idx).Set(elem) } return nil } // valueUnmarshalerDecodeValue is the ValueDecoderFunc for ValueUnmarshaler implementations. func valueUnmarshalerDecodeValue(_ DecodeContext, vr 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 BSON value is null and the go value is a pointer, then don't call // UnmarshalBSONValue. Even if the Go pointer is already initialized (i.e., // non-nil), encountering null in BSON will result in the pointer being // directly set to nil here. Since the pointer is being replaced with nil, // there is no opportunity (or reason) for the custom UnmarshalBSONValue logic // to be called. if vr.Type() == TypeNull && val.Kind() == reflect.Ptr { val.Set(reflect.Zero(val.Type())) return vr.ReadNull() } 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 the type doesn't implement the interface, a pointer to it must. } t, src, err := copyValueToBytes(vr) if err != nil { return err } m, ok := val.Interface().(ValueUnmarshaler) if !ok { // NB: this error should be unreachable due to the above checks return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val} } return m.UnmarshalBSONValue(byte(t), src) } // unmarshalerDecodeValue is the ValueDecoderFunc for Unmarshaler implementations. func unmarshalerDecodeValue(_ DecodeContext, vr 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())) } _, src, err := copyValueToBytes(vr) if err != nil { return err } // If the target Go value is a pointer and the BSON field value is empty, set the value to the // zero value of the pointer (nil) and don't call UnmarshalBSON. UnmarshalBSON has no way to // change the pointer value from within the function (only the value at the pointer address), // so it can't set the pointer to "nil" itself. Since the most common Go value for an empty BSON // field value is "nil", we set "nil" here and don't call UnmarshalBSON. This behavior matches // the behavior of the Go "encoding/json" unmarshaler when the target Go value is a pointer and // the JSON field value is "null". if val.Kind() == reflect.Ptr && len(src) == 0 { val.Set(reflect.Zero(val.Type())) return nil } if !val.Type().Implements(tUnmarshaler) { if !val.CanAddr() { return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val} } val = val.Addr() // If the type doesn't implement the interface, a pointer to it must. } m, ok := val.Interface().(Unmarshaler) if !ok { // NB: this error should be unreachable due to the above checks return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val} } return m.UnmarshalBSON(src) } // coreDocumentDecodeValue is the ValueDecoderFunc for bsoncore.Document. func coreDocumentDecodeValue(_ DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tCoreDocument { return ValueDecoderError{Name: "CoreDocumentDecodeValue", Types: []reflect.Type{tCoreDocument}, Received: val} } vrType := vr.Type() isDocument := vrType == Type(0) || vrType == TypeEmbeddedDocument || vrType == TypeArray if !isDocument { return fmt.Errorf("cannot decode %v into a %s", vrType, val.Type()) } if val.IsNil() { val.Set(reflect.MakeSlice(val.Type(), 0, 0)) } val.SetLen(0) cdoc, err := appendDocumentBytes(val.Interface().(bsoncore.Document), vr) val.Set(reflect.ValueOf(cdoc)) return err } func decodeDefault(dc DecodeContext, vr 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() isInterfaceSlice := eType.Kind() == reflect.Interface && val.Len() > 0 // If this is not an interface slice with pre-populated elements, we can look up // the decoder for eType once. var vDecoder ValueDecoder if !isInterfaceSlice { vDecoder, err = dc.LookupDecoder(eType) if err != nil { return nil, err } } idx := 0 for { vr, err := ar.ReadValue() if errors.Is(err, ErrEOA) { break } if err != nil { return nil, err } var elem reflect.Value if isInterfaceSlice && idx < val.Len() { // Decode into an existing any slot. elem = val.Index(idx).Elem() switch { case elem.Kind() != reflect.Ptr || elem.IsNil(): valueDecoder, err := dc.LookupDecoder(elem.Type()) if err != nil { return nil, err } // If an element is allocated and unsettable, it must be overwritten. if !elem.CanSet() { elem = reflect.New(elem.Type()).Elem() } err = valueDecoder.DecodeValue(dc, vr, elem) if err != nil { return nil, newDecodeError(strconv.Itoa(idx), err) } case vr.Type() == TypeNull: if err = vr.ReadNull(); err != nil { return nil, err } elem = reflect.Zero(val.Index(idx).Type()) default: e := elem.Elem() valueDecoder, err := dc.LookupDecoder(e.Type()) if err != nil { return nil, err } err = valueDecoder.DecodeValue(dc, vr, e) if err != nil { return nil, newDecodeError(strconv.Itoa(idx), err) } } } else { // For non-interface slices, or if we've exhausted the pre-populated // slots, we create a fresh value. if vDecoder == nil { vDecoder, err = dc.LookupDecoder(eType) if err != nil { return nil, err } } elem, err = decodeTypeOrValueWithInfo(vDecoder, dc, vr, eType) if err != nil { return nil, newDecodeError(strconv.Itoa(idx), err) } } elems = append(elems, elem) idx++ } return elems, nil } func codeWithScopeDecodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tCodeWithScope { return emptyValue, ValueDecoderError{ Name: "CodeWithScopeDecodeValue", Types: []reflect.Type{tCodeWithScope}, Received: reflect.Zero(t), } } var cws CodeWithScope var err error switch vrType := vr.Type(); vrType { case TypeCodeWithScope: code, dr, err := vr.ReadCodeWithScope() if err != nil { return emptyValue, err } scope := reflect.New(tD).Elem() elems, err := decodeElemsFromDocumentReader(dc, dr) if err != nil { return emptyValue, err } scope.Set(reflect.MakeSlice(tD, 0, len(elems))) scope.Set(reflect.Append(scope, elems...)) cws = CodeWithScope{ Code: JavaScript(code), Scope: scope.Interface().(D), } case TypeNull: err = vr.ReadNull() case TypeUndefined: err = vr.ReadUndefined() default: return emptyValue, fmt.Errorf("cannot decode %v into a CodeWithScope", vrType) } if err != nil { return emptyValue, err } return reflect.ValueOf(cws), nil } // codeWithScopeDecodeValue is the ValueDecoderFunc for CodeWithScope. func codeWithScopeDecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tCodeWithScope { return ValueDecoderError{Name: "CodeWithScopeDecodeValue", Types: []reflect.Type{tCodeWithScope}, Received: val} } elem, err := codeWithScopeDecodeType(dc, vr, tCodeWithScope) if err != nil { return err } val.Set(elem) return nil } func decodeD(dc DecodeContext, vr ValueReader, _ reflect.Value) ([]reflect.Value, error) { switch vr.Type() { case Type(0), TypeEmbeddedDocument: default: return nil, fmt.Errorf("cannot decode %v into a D", vr.Type()) } dr, err := vr.ReadDocument() if err != nil { return nil, err } return decodeElemsFromDocumentReader(dc, dr) } func decodeElemsFromDocumentReader(dc DecodeContext, dr 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 errors.Is(err, ErrEOD) { break } if err != nil { return nil, err } val := reflect.New(tEmpty).Elem() err = decoder.DecodeValue(dc, vr, val) if err != nil { return nil, newDecodeError(key, err) } elems = append(elems, reflect.ValueOf(E{Key: key, Value: val.Interface()})) } return elems, nil } ================================================ FILE: bson/default_value_decoders_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "math" "net/url" "reflect" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) func TestDefaultValueDecoders(t *testing.T) { 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 type mystruct struct{} const cansetreflectiontest = "cansetreflectiontest" const cansettest = "cansettest" now := time.Now().Truncate(time.Millisecond) d128 := NewDecimal128(12345, 67890) pbool := func(b bool) *bool { return &b } pi32 := func(i32 int32) *int32 { return &i32 } pi64 := func(i64 int64) *int64 { return &i64 } type subtest struct { name string val any dctx *DecodeContext llvrw *valueReaderWriter invoke invoked err error } testCases := []struct { name string vd ValueDecoder subtests []subtest }{ { "BooleanDecodeValue", ValueDecoderFunc(booleanDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeBoolean}, nothing, ValueDecoderError{ Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not boolean", bool(false), nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a boolean", TypeString), }, { "fast path", bool(true), nil, &valueReaderWriter{BSONType: TypeBoolean, Return: bool(true)}, readBoolean, nil, }, { "reflection path", mybool(true), nil, &valueReaderWriter{BSONType: TypeBoolean, Return: bool(true)}, readBoolean, nil, }, { "reflection path error", mybool(true), nil, &valueReaderWriter{BSONType: TypeBoolean, Return: bool(true), Err: errors.New("ReadBoolean Error"), ErrAfter: readBoolean}, readBoolean, errors.New("ReadBoolean Error"), }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeBoolean}, nothing, ValueDecoderError{Name: "BooleanDecodeValue", Kinds: []reflect.Kind{reflect.Bool}}, }, { "decode null", mybool(false), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", mybool(false), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "IntDecodeValue", ValueDecoderFunc(intDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into an integer type", TypeString), }, { "ReadInt32 error", 0, nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: readInt32}, readInt32, errors.New("ReadInt32 error"), }, { "ReadInt64 error", 0, nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: readInt64}, readInt64, errors.New("ReadInt64 error"), }, { "ReadDouble error", 0, nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: readDouble}, readDouble, errors.New("ReadDouble error"), }, { "ReadDouble", int64(3), &DecodeContext{}, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.00)}, readDouble, nil, }, { "ReadDouble (truncate)", int64(3), &DecodeContext{truncate: true}, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, nil, }, { "ReadDouble (no truncate)", int64(0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, errCannotTruncate, }, { "ReadDouble overflows int64", int64(0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: math.MaxFloat64}, readDouble, fmt.Errorf("%g overflows int64", math.MaxFloat64), }, {"int8/fast path", int8(127), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(127)}, readInt32, nil}, {"int16/fast path", int16(32676), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(32676)}, readInt32, nil}, {"int32/fast path", int32(1234), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1234)}, readInt32, nil}, {"int64/fast path", int64(1234), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(1234)}, readInt64, nil}, {"int/fast path", int(1234), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(1234)}, readInt64, nil}, { "int8/fast path - nil", (*int8)(nil), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(129)}, readInt32, fmt.Errorf("%d overflows int8", 129), }, { "int16/fast path - overflow", int16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(32768)}, readInt32, fmt.Errorf("%d overflows int16", 32768), }, { "int32/fast path - overflow", int32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(2147483648)}, readInt64, fmt.Errorf("%d overflows int32", int64(2147483648)), }, { "int8/fast path - overflow (negative)", int8(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-129)}, readInt32, fmt.Errorf("%d overflows int8", -129), }, { "int16/fast path - overflow (negative)", int16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-32769)}, readInt32, fmt.Errorf("%d overflows int16", -32769), }, { "int32/fast path - overflow (negative)", int32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-2147483649)}, readInt64, fmt.Errorf("%d overflows int32", int64(-2147483649)), }, { "int8/reflection path", myint8(127), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(127)}, readInt32, nil, }, { "int16/reflection path", myint16(255), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(255)}, readInt32, nil, }, { "int32/reflection path", myint32(511), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(511)}, readInt32, nil, }, { "int64/reflection path", myint64(1023), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1023)}, readInt32, nil, }, { "int/reflection path", myint(2047), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(2047)}, readInt32, nil, }, { "int8/reflection path - overflow", myint8(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(129)}, readInt32, fmt.Errorf("%d overflows int8", 129), }, { "int16/reflection path - overflow", myint16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(32768)}, readInt32, fmt.Errorf("%d overflows int16", 32768), }, { "int32/reflection path - overflow", myint32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(2147483648)}, readInt64, fmt.Errorf("%d overflows int32", int64(2147483648)), }, { "int8/reflection path - overflow (negative)", myint8(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-129)}, readInt32, fmt.Errorf("%d overflows int8", -129), }, { "int16/reflection path - overflow (negative)", myint16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-32769)}, readInt32, fmt.Errorf("%d overflows int16", -32769), }, { "int32/reflection path - overflow (negative)", myint32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-2147483649)}, readInt64, fmt.Errorf("%d overflows int32", int64(-2147483649)), }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, nothing, ValueDecoderError{ Name: "IntDecodeValue", Kinds: []reflect.Kind{reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int}, }, }, { "decode null", myint(0), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", myint(0), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "defaultUIntCodec.DecodeValue", &uintCodec{}, []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into an integer type", TypeString), }, { "ReadInt32 error", uint(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: readInt32}, readInt32, errors.New("ReadInt32 error"), }, { "ReadInt64 error", uint(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: readInt64}, readInt64, errors.New("ReadInt64 error"), }, { "ReadDouble error", 0, nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: readDouble}, readDouble, errors.New("ReadDouble error"), }, { "ReadDouble", uint64(3), &DecodeContext{}, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.00)}, readDouble, nil, }, { "ReadDouble (truncate)", uint64(3), &DecodeContext{truncate: true}, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, nil, }, { "ReadDouble (no truncate)", uint64(0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, errCannotTruncate, }, { "ReadDouble overflows int64", uint64(0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: math.MaxFloat64}, readDouble, fmt.Errorf("%g overflows int64", math.MaxFloat64), }, {"uint8/fast path", uint8(127), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(127)}, readInt32, nil}, {"uint16/fast path", uint16(255), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(255)}, readInt32, nil}, {"uint32/fast path", uint32(1234), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1234)}, readInt32, nil}, {"uint64/fast path", uint64(1234), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(1234)}, readInt64, nil}, {"uint/fast path", uint(1234), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(1234)}, readInt64, nil}, { "uint8/fast path - nil", (*uint8)(nil), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, 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, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1 << 8)}, readInt32, fmt.Errorf("%d overflows uint8", 1<<8), }, { "uint16/fast path - overflow", uint16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1 << 16)}, readInt32, fmt.Errorf("%d overflows uint16", 1<<16), }, { "uint32/fast path - overflow", uint32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(1 << 32)}, readInt64, fmt.Errorf("%d overflows uint32", int64(1<<32)), }, { "uint8/fast path - overflow (negative)", uint8(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-1)}, readInt32, fmt.Errorf("%d overflows uint8", -1), }, { "uint16/fast path - overflow (negative)", uint16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-1)}, readInt32, fmt.Errorf("%d overflows uint16", -1), }, { "uint32/fast path - overflow (negative)", uint32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-1)}, readInt64, fmt.Errorf("%d overflows uint32", -1), }, { "uint64/fast path - overflow (negative)", uint64(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-1)}, readInt64, fmt.Errorf("%d overflows uint64", -1), }, { "uint/fast path - overflow (negative)", uint(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-1)}, readInt64, fmt.Errorf("%d overflows uint", -1), }, { "uint8/reflection path", myuint8(127), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(127)}, readInt32, nil, }, { "uint16/reflection path", myuint16(255), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(255)}, readInt32, nil, }, { "uint32/reflection path", myuint32(511), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(511)}, readInt32, nil, }, { "uint64/reflection path", myuint64(1023), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1023)}, readInt32, nil, }, { "uint/reflection path", myuint(2047), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(2047)}, readInt32, nil, }, { "uint8/reflection path - overflow", myuint8(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1 << 8)}, readInt32, fmt.Errorf("%d overflows uint8", 1<<8), }, { "uint16/reflection path - overflow", myuint16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(1 << 16)}, readInt32, fmt.Errorf("%d overflows uint16", 1<<16), }, { "uint32/reflection path - overflow", myuint32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(1 << 32)}, readInt64, fmt.Errorf("%d overflows uint32", int64(1<<32)), }, { "uint8/reflection path - overflow (negative)", myuint8(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-1)}, readInt32, fmt.Errorf("%d overflows uint8", -1), }, { "uint16/reflection path - overflow (negative)", myuint16(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(-1)}, readInt32, fmt.Errorf("%d overflows uint16", -1), }, { "uint32/reflection path - overflow (negative)", myuint32(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-1)}, readInt64, fmt.Errorf("%d overflows uint32", -1), }, { "uint64/reflection path - overflow (negative)", myuint64(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-1)}, readInt64, fmt.Errorf("%d overflows uint64", -1), }, { "uint/reflection path - overflow (negative)", myuint(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(-1)}, readInt64, fmt.Errorf("%d overflows uint", -1), }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0)}, nothing, ValueDecoderError{ Name: "UintDecodeValue", Kinds: []reflect.Kind{reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint}, }, }, }, }, { "FloatDecodeValue", ValueDecoderFunc(floatDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(0)}, readDouble, ValueDecoderError{ Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: reflect.ValueOf(wrong), }, }, { "type not double", 0, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a float32 or float64 type", TypeString), }, { "ReadDouble error", float64(0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(0), Err: errors.New("ReadDouble error"), ErrAfter: readDouble}, readDouble, errors.New("ReadDouble error"), }, { "ReadInt32 error", float64(0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(0), Err: errors.New("ReadInt32 error"), ErrAfter: readInt32}, readInt32, errors.New("ReadInt32 error"), }, { "ReadInt64 error", float64(0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(0), Err: errors.New("ReadInt64 error"), ErrAfter: readInt64}, readInt64, errors.New("ReadInt64 error"), }, { "float64/int32", float32(32.0), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(32)}, readInt32, nil, }, { "float64/int64", float32(64.0), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(64)}, readInt64, nil, }, { "float32/fast path (equal)", float32(3.0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.0)}, readDouble, nil, }, { "float64/fast path", float64(3.14159), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14159)}, readDouble, nil, }, { "float32/fast path (truncate)", float32(3.14), &DecodeContext{truncate: true}, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, nil, }, { "float32/fast path (no truncate)", float32(0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, errCannotTruncate, }, { "float32/fast path - nil", (*float32)(nil), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(0)}, readDouble, ValueDecoderError{ Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: reflect.ValueOf((*float32)(nil)), }, }, { "float64/fast path - nil", (*float64)(nil), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(0)}, readDouble, ValueDecoderError{ Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: reflect.ValueOf((*float64)(nil)), }, }, { "float32/reflection path (equal)", myfloat32(3.0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.0)}, readDouble, nil, }, { "float64/reflection path", myfloat64(3.14159), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14159)}, readDouble, nil, }, { "float32/reflection path (truncate)", myfloat32(3.14), &DecodeContext{truncate: true}, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, nil, }, { "float32/reflection path (no truncate)", myfloat32(0), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14)}, readDouble, errCannotTruncate, }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(0)}, nothing, ValueDecoderError{ Name: "FloatDecodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, }, }, }, }, { "defaultTimeCodec.DecodeValue", &timeCodec{}, []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeDateTime, Return: int64(0)}, nothing, ValueDecoderError{ Name: "TimeDecodeValue", Types: []reflect.Type{tTime}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "ReadDateTime error", time.Time{}, nil, &valueReaderWriter{BSONType: TypeDateTime, Return: int64(0), Err: errors.New("ReadDateTime error"), ErrAfter: readDateTime}, readDateTime, errors.New("ReadDateTime error"), }, { "time.Time", now, nil, &valueReaderWriter{BSONType: TypeDateTime, Return: now.UnixNano() / int64(time.Millisecond)}, readDateTime, nil, }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeDateTime, Return: int64(0)}, nothing, ValueDecoderError{Name: "TimeDecodeValue", Types: []reflect.Type{tTime}}, }, { "decode null", time.Time{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", time.Time{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "defaultMapCodec.DecodeValue", &mapCodec{}, []subtest{ { "wrong kind", wrong, nil, &valueReaderWriter{}, nothing, ValueDecoderError{ Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "wrong kind (non-string key)", map[bool]any{}, &DecodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{}, readElement, fmt.Errorf("unsupported key type: %T", false), }, { "ReadDocument Error", make(map[string]any), nil, &valueReaderWriter{Err: errors.New("rd error"), ErrAfter: readDocument}, readDocument, errors.New("rd error"), }, { "Lookup Error", map[string]string{}, &DecodeContext{Registry: newTestRegistry()}, &valueReaderWriter{}, readDocument, errNoDecoder{Type: reflect.TypeOf("")}, }, { "ReadElement Error", make(map[string]any), &DecodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("re error"), ErrAfter: readElement}, readElement, errors.New("re error"), }, { "can set false", cansettest, nil, &valueReaderWriter{}, nothing, ValueDecoderError{Name: "MapDecodeValue", Kinds: []reflect.Kind{reflect.Map}}, }, { "wrong BSON type", map[string]any{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, errors.New("cannot decode string into a map[string]interface {}"), }, { "decode null", (map[string]any)(nil), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", (map[string]any)(nil), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "ArrayDecodeValue", ValueDecoderFunc(arrayDecodeValue), []subtest{ { "wrong kind", wrong, nil, &valueReaderWriter{}, nothing, ValueDecoderError{ Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "can set false", cansettest, nil, &valueReaderWriter{}, nothing, ValueDecoderError{Name: "ArrayDecodeValue", Kinds: []reflect.Kind{reflect.Array}}, }, { "Not Type Array", [1]any{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, errors.New("cannot decode string into an array"), }, { "ReadArray Error", [1]any{}, nil, &valueReaderWriter{Err: errors.New("ra error"), ErrAfter: readArray, BSONType: TypeArray}, readArray, errors.New("ra error"), }, { "Lookup Error", [1]string{}, &DecodeContext{Registry: newTestRegistry()}, &valueReaderWriter{BSONType: TypeArray}, readArray, errNoDecoder{Type: reflect.TypeOf("")}, }, { "ReadValue Error", [1]string{}, &DecodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("rv error"), ErrAfter: readValue, BSONType: TypeArray}, readValue, errors.New("rv error"), }, { "DecodeValue Error", [1]string{}, &DecodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{BSONType: TypeArray}, readValue, &DecodeError{keys: []string{"0"}, wrapped: errors.New("cannot decode array into a string type")}, }, { "Document but not D", [1]string{}, nil, &valueReaderWriter{BSONType: Type(0)}, nothing, errors.New("cannot decode document into [1]string"), }, { "EmbeddedDocument but not D", [1]string{}, nil, &valueReaderWriter{BSONType: TypeEmbeddedDocument}, nothing, errors.New("cannot decode document into [1]string"), }, { "decode null", [1]string{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", [1]string{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "defaultSliceCodec.DecodeValue", &sliceCodec{}, []subtest{ { "wrong kind", wrong, nil, &valueReaderWriter{}, nothing, ValueDecoderError{ Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "can set false", cansettest, nil, &valueReaderWriter{}, nothing, ValueDecoderError{Name: "SliceDecodeValue", Kinds: []reflect.Kind{reflect.Slice}}, }, { "Not Type Array", []any{}, nil, &valueReaderWriter{BSONType: TypeInt32}, nothing, errors.New("cannot decode 32-bit integer into a slice"), }, { "ReadArray Error", []any{}, nil, &valueReaderWriter{Err: errors.New("ra error"), ErrAfter: readArray, BSONType: TypeArray}, readArray, errors.New("ra error"), }, { "Lookup Error", []string{}, &DecodeContext{Registry: newTestRegistry()}, &valueReaderWriter{BSONType: TypeArray}, readArray, errNoDecoder{Type: reflect.TypeOf("")}, }, { "ReadValue Error", []string{}, &DecodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("rv error"), ErrAfter: readValue, BSONType: TypeArray}, readValue, errors.New("rv error"), }, { "DecodeValue Error", []string{}, &DecodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{BSONType: TypeArray}, readValue, &DecodeError{keys: []string{"0"}, wrapped: errors.New("cannot decode array into a string type")}, }, { "Document but not D", []string{}, nil, &valueReaderWriter{BSONType: Type(0)}, nothing, errors.New("cannot decode document into []string"), }, { "EmbeddedDocument but not D", []string{}, nil, &valueReaderWriter{BSONType: TypeEmbeddedDocument}, nothing, errors.New("cannot decode document into []string"), }, { "decode null", ([]string)(nil), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", ([]string)(nil), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "ObjectIDDecodeValue", ValueDecoderFunc(objectIDDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeObjectID}, nothing, ValueDecoderError{ Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not objectID", ObjectID{}, nil, &valueReaderWriter{BSONType: TypeInt32}, nothing, fmt.Errorf("cannot decode %v into an ObjectID", TypeInt32), }, { "ReadObjectID Error", ObjectID{}, nil, &valueReaderWriter{BSONType: TypeObjectID, Err: errors.New("roid error"), ErrAfter: readObjectID}, readObjectID, errors.New("roid error"), }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeObjectID, Return: ObjectID{}}, nothing, ValueDecoderError{Name: "ObjectIDDecodeValue", Types: []reflect.Type{tOID}}, }, { "success", ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, nil, &valueReaderWriter{ BSONType: TypeObjectID, Return: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, }, readObjectID, nil, }, { "success/string", ObjectID{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62}, nil, &valueReaderWriter{ BSONType: TypeString, Return: "0123456789ab", }, readString, nil, }, { "success/string-hex", ObjectID{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62}, nil, &valueReaderWriter{ BSONType: TypeString, Return: "303132333435363738396162", }, readString, nil, }, { "decode null", ObjectID{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", ObjectID{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "Decimal128DecodeValue", ValueDecoderFunc(decimal128DecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeDecimal128}, nothing, ValueDecoderError{ Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not decimal128", Decimal128{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a Decimal128", TypeString), }, { "ReadDecimal128 Error", Decimal128{}, nil, &valueReaderWriter{BSONType: TypeDecimal128, Err: errors.New("rd128 error"), ErrAfter: readDecimal128}, readDecimal128, errors.New("rd128 error"), }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeDecimal128, Return: d128}, nothing, ValueDecoderError{Name: "Decimal128DecodeValue", Types: []reflect.Type{tDecimal}}, }, { "success", d128, nil, &valueReaderWriter{BSONType: TypeDecimal128, Return: d128}, readDecimal128, nil, }, { "decode null", Decimal128{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", Decimal128{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "JSONNumberDecodeValue", ValueDecoderFunc(jsonNumberDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeObjectID}, nothing, ValueDecoderError{ Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not double/int32/int64", json.Number(""), nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a json.Number", TypeString), }, { "ReadDouble Error", json.Number(""), nil, &valueReaderWriter{BSONType: TypeDouble, Err: errors.New("rd error"), ErrAfter: readDouble}, readDouble, errors.New("rd error"), }, { "ReadInt32 Error", json.Number(""), nil, &valueReaderWriter{BSONType: TypeInt32, Err: errors.New("ri32 error"), ErrAfter: readInt32}, readInt32, errors.New("ri32 error"), }, { "ReadInt64 Error", json.Number(""), nil, &valueReaderWriter{BSONType: TypeInt64, Err: errors.New("ri64 error"), ErrAfter: readInt64}, readInt64, errors.New("ri64 error"), }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeObjectID, Return: ObjectID{}}, nothing, ValueDecoderError{Name: "JSONNumberDecodeValue", Types: []reflect.Type{tJSONNumber}}, }, { "success/double", json.Number("3.14159"), nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14159)}, readDouble, nil, }, { "success/int32", json.Number("12345"), nil, &valueReaderWriter{BSONType: TypeInt32, Return: int32(12345)}, readInt32, nil, }, { "success/int64", json.Number("1234567890"), nil, &valueReaderWriter{BSONType: TypeInt64, Return: int64(1234567890)}, readInt64, nil, }, { "decode null", json.Number(""), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", json.Number(""), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "URLDecodeValue", ValueDecoderFunc(urlDecodeValue), []subtest{ { "wrong type", url.URL{}, nil, &valueReaderWriter{BSONType: TypeInt32}, nothing, fmt.Errorf("cannot decode %v into a *url.URL", TypeInt32), }, { "type not *url.URL", int64(0), nil, &valueReaderWriter{BSONType: TypeString, Return: "http://example.com"}, nothing, ValueDecoderError{ Name: "URLDecodeValue", Types: []reflect.Type{tURL}, Received: reflect.New(reflect.TypeOf(int64(0))).Elem(), }, }, { "ReadString error", url.URL{}, nil, &valueReaderWriter{BSONType: TypeString, Err: errors.New("rs error"), ErrAfter: readString}, readString, errors.New("rs error"), }, { "url.Parse error", url.URL{}, nil, &valueReaderWriter{BSONType: TypeString, Return: "not-valid-%%%%://"}, readString, &url.Error{ Op: "parse", URL: "not-valid-%%%%://", Err: errors.New("first path segment in URL cannot contain colon"), }, }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeString, Return: "http://example.com"}, nothing, ValueDecoderError{Name: "URLDecodeValue", Types: []reflect.Type{tURL}}, }, { "url.URL", url.URL{Scheme: "http", Host: "example.com"}, nil, &valueReaderWriter{BSONType: TypeString, Return: "http://example.com"}, readString, nil, }, { "decode null", url.URL{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", url.URL{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "defaultByteSliceCodec.DecodeValue", &byteSliceCodec{}, []subtest{ { "wrong type", []byte{}, nil, &valueReaderWriter{BSONType: TypeInt32}, nothing, fmt.Errorf("cannot decode %v into a []byte", TypeInt32), }, { "type not []byte", int64(0), nil, &valueReaderWriter{BSONType: TypeBinary, Return: bsoncore.Value{Type: bsoncore.TypeBinary}}, nothing, ValueDecoderError{ Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.New(reflect.TypeOf(int64(0))).Elem(), }, }, { "ReadBinary error", []byte{}, nil, &valueReaderWriter{BSONType: TypeBinary, Err: errors.New("rb error"), ErrAfter: readBinary}, readBinary, errors.New("rb error"), }, { "incorrect subtype", []byte{}, nil, &valueReaderWriter{ BSONType: TypeBinary, Return: bsoncore.Value{ Type: bsoncore.TypeBinary, Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}), }, }, readBinary, decodeBinaryError{subtype: byte(0xFF), typeName: "[]byte"}, }, { "can set false", cansettest, nil, &valueReaderWriter{BSONType: TypeBinary, Return: bsoncore.AppendBinary(nil, 0x00, []byte{0x01, 0x02, 0x03})}, nothing, ValueDecoderError{Name: "ByteSliceDecodeValue", Types: []reflect.Type{tByteSlice}}, }, { "decode null", ([]byte)(nil), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", ([]byte)(nil), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "defaultStringCodec.DecodeValue", &stringCodec{}, []subtest{ { "symbol", "var hello = 'world';", nil, &valueReaderWriter{BSONType: TypeSymbol, Return: "var hello = 'world';"}, readSymbol, nil, }, { "decode null", "", nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", "", nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "ValueUnmarshalerDecodeValue", ValueDecoderFunc(valueUnmarshalerDecodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueDecoderError{ Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "copy error", &testValueUnmarshaler{}, nil, &valueReaderWriter{BSONType: TypeString, Err: errors.New("copy error"), ErrAfter: readString}, readString, errors.New("copy error"), }, { "ValueUnmarshaler", &testValueUnmarshaler{t: TypeString, val: bsoncore.AppendString(nil, "hello, world")}, nil, &valueReaderWriter{BSONType: TypeString, Return: "hello, world"}, readString, nil, }, }, }, { "UnmarshalerDecodeValue", ValueDecoderFunc(unmarshalerDecodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueDecoderError{ Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "copy error", &testUnmarshaler{}, nil, &valueReaderWriter{BSONType: TypeString, Err: errors.New("copy error"), ErrAfter: readString}, readString, errors.New("copy error"), }, { // Only the pointer form of testUnmarshaler implements Unmarshaler "value does not implement Unmarshaler", &testUnmarshaler{ Invoked: true, Val: bsoncore.AppendDouble(nil, 3.14159), }, nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14159)}, readDouble, nil, }, { "Unmarshaler", &testUnmarshaler{ Invoked: true, Val: bsoncore.AppendDouble(nil, 3.14159), }, nil, &valueReaderWriter{BSONType: TypeDouble, Return: float64(3.14159)}, readDouble, nil, }, }, }, { "PointerCodec.DecodeValue", &pointerCodec{}, []subtest{ { "not valid", nil, nil, nil, nothing, ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: reflect.Value{}}, }, { "can set", cansettest, nil, nil, nothing, ValueDecoderError{Name: "PointerCodec.DecodeValue", Kinds: []reflect.Kind{reflect.Ptr}}, }, { "No Decoder", &wrong, &DecodeContext{Registry: buildDefaultRegistry()}, nil, nothing, errNoDecoder{Type: reflect.TypeOf(wrong)}, }, { "decode null", (*mystruct)(nil), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", (*mystruct)(nil), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "BinaryDecodeValue", ValueDecoderFunc(binaryDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{}, nothing, ValueDecoderError{ Name: "BinaryDecodeValue", Types: []reflect.Type{tBinary}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not binary", Binary{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a Binary", TypeString), }, { "ReadBinary Error", Binary{}, nil, &valueReaderWriter{BSONType: TypeBinary, Err: errors.New("rb error"), ErrAfter: readBinary}, readBinary, errors.New("rb error"), }, { "Binary/success", Binary{Data: []byte{0x01, 0x02, 0x03}, Subtype: 0xFF}, nil, &valueReaderWriter{ BSONType: TypeBinary, Return: bsoncore.Value{ Type: bsoncore.TypeBinary, Data: bsoncore.AppendBinary(nil, 0xFF, []byte{0x01, 0x02, 0x03}), }, }, readBinary, nil, }, { "decode null", Binary{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", Binary{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "UndefinedDecodeValue", ValueDecoderFunc(undefinedDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeUndefined}, nothing, ValueDecoderError{ Name: "UndefinedDecodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not undefined", Undefined{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into an Undefined", TypeString), }, { "ReadUndefined Error", Undefined{}, nil, &valueReaderWriter{BSONType: TypeUndefined, Err: errors.New("ru error"), ErrAfter: readUndefined}, readUndefined, errors.New("ru error"), }, { "ReadUndefined/success", Undefined{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, { "decode null", Undefined{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, }, }, { "DateTimeDecodeValue", ValueDecoderFunc(dateTimeDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeDateTime}, nothing, ValueDecoderError{ Name: "DateTimeDecodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not datetime", DateTime(0), nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a DateTime", TypeString), }, { "ReadDateTime Error", DateTime(0), nil, &valueReaderWriter{BSONType: TypeDateTime, Err: errors.New("rdt error"), ErrAfter: readDateTime}, readDateTime, errors.New("rdt error"), }, { "success", DateTime(1234567890), nil, &valueReaderWriter{BSONType: TypeDateTime, Return: int64(1234567890)}, readDateTime, nil, }, { "decode null", DateTime(0), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", DateTime(0), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "NullDecodeValue", ValueDecoderFunc(nullDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeNull}, nothing, ValueDecoderError{ Name: "NullDecodeValue", Types: []reflect.Type{tNull}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not null", Null{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a Null", TypeString), }, { "ReadNull Error", Null{}, nil, &valueReaderWriter{BSONType: TypeNull, Err: errors.New("rn error"), ErrAfter: readNull}, readNull, errors.New("rn error"), }, { "success", Null{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, }, }, { "RegexDecodeValue", ValueDecoderFunc(regexDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeRegex}, nothing, ValueDecoderError{ Name: "RegexDecodeValue", Types: []reflect.Type{tRegex}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not regex", Regex{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a Regex", TypeString), }, { "ReadRegex Error", Regex{}, nil, &valueReaderWriter{BSONType: TypeRegex, Err: errors.New("rr error"), ErrAfter: readRegex}, readRegex, errors.New("rr error"), }, { "success", Regex{Pattern: "foo", Options: "bar"}, nil, &valueReaderWriter{ BSONType: TypeRegex, Return: bsoncore.Value{ Type: bsoncore.TypeRegex, Data: bsoncore.AppendRegex(nil, "foo", "bar"), }, }, readRegex, nil, }, { "decode null", Regex{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", Regex{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "DBPointerDecodeValue", ValueDecoderFunc(dbPointerDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeDBPointer}, nothing, ValueDecoderError{ Name: "DBPointerDecodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not dbpointer", DBPointer{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a DBPointer", TypeString), }, { "ReadDBPointer Error", DBPointer{}, nil, &valueReaderWriter{BSONType: TypeDBPointer, Err: errors.New("rdbp error"), ErrAfter: readDBPointer}, readDBPointer, errors.New("rdbp error"), }, { "success", DBPointer{ DB: "foobar", Pointer: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, }, nil, &valueReaderWriter{ BSONType: TypeDBPointer, Return: bsoncore.Value{ Type: bsoncore.TypeDBPointer, Data: bsoncore.AppendDBPointer( nil, "foobar", ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, ), }, }, readDBPointer, nil, }, { "decode null", DBPointer{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", DBPointer{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "TimestampDecodeValue", ValueDecoderFunc(timestampDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeTimestamp}, nothing, ValueDecoderError{ Name: "TimestampDecodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not timestamp", Timestamp{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a Timestamp", TypeString), }, { "ReadTimestamp Error", Timestamp{}, nil, &valueReaderWriter{BSONType: TypeTimestamp, Err: errors.New("rt error"), ErrAfter: readTimestamp}, readTimestamp, errors.New("rt error"), }, { "success", Timestamp{T: 12345, I: 67890}, nil, &valueReaderWriter{ BSONType: TypeTimestamp, Return: bsoncore.Value{ Type: bsoncore.TypeTimestamp, Data: bsoncore.AppendTimestamp(nil, 12345, 67890), }, }, readTimestamp, nil, }, { "decode null", Timestamp{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", Timestamp{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "MinKeyDecodeValue", ValueDecoderFunc(minKeyDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeMinKey}, nothing, ValueDecoderError{ Name: "MinKeyDecodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not null", MinKey{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a MinKey", TypeString), }, { "ReadMinKey Error", MinKey{}, nil, &valueReaderWriter{BSONType: TypeMinKey, Err: errors.New("rn error"), ErrAfter: readMinKey}, readMinKey, errors.New("rn error"), }, { "success", MinKey{}, nil, &valueReaderWriter{BSONType: TypeMinKey}, readMinKey, nil, }, { "decode null", MinKey{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", MinKey{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "MaxKeyDecodeValue", ValueDecoderFunc(maxKeyDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeMaxKey}, nothing, ValueDecoderError{ Name: "MaxKeyDecodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not null", MaxKey{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a MaxKey", TypeString), }, { "ReadMaxKey Error", MaxKey{}, nil, &valueReaderWriter{BSONType: TypeMaxKey, Err: errors.New("rn error"), ErrAfter: readMaxKey}, readMaxKey, errors.New("rn error"), }, { "success", MaxKey{}, nil, &valueReaderWriter{BSONType: TypeMaxKey}, readMaxKey, nil, }, { "decode null", MaxKey{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", MaxKey{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "JavaScriptDecodeValue", ValueDecoderFunc(javaScriptDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeJavaScript, Return: ""}, nothing, ValueDecoderError{ Name: "JavaScriptDecodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not Javascript", JavaScript(""), nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a JavaScript", TypeString), }, { "ReadJavascript Error", JavaScript(""), nil, &valueReaderWriter{BSONType: TypeJavaScript, Err: errors.New("rjs error"), ErrAfter: readJavascript}, readJavascript, errors.New("rjs error"), }, { "JavaScript/success", JavaScript("var hello = 'world';"), nil, &valueReaderWriter{BSONType: TypeJavaScript, Return: "var hello = 'world';"}, readJavascript, nil, }, { "decode null", JavaScript(""), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", JavaScript(""), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "SymbolDecodeValue", ValueDecoderFunc(symbolDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeSymbol, Return: ""}, nothing, ValueDecoderError{ Name: "SymbolDecodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not Symbol", Symbol(""), nil, &valueReaderWriter{BSONType: TypeInt32}, nothing, fmt.Errorf("cannot decode %v into a Symbol", TypeInt32), }, { "ReadSymbol Error", Symbol(""), nil, &valueReaderWriter{BSONType: TypeSymbol, Err: errors.New("rjs error"), ErrAfter: readSymbol}, readSymbol, errors.New("rjs error"), }, { "Symbol/success", Symbol("var hello = 'world';"), nil, &valueReaderWriter{BSONType: TypeSymbol, Return: "var hello = 'world';"}, readSymbol, nil, }, { "decode null", Symbol(""), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", Symbol(""), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "CoreDocumentDecodeValue", ValueDecoderFunc(coreDocumentDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{}, nothing, ValueDecoderError{ Name: "CoreDocumentDecodeValue", Types: []reflect.Type{tCoreDocument}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "*bsoncore.Document is nil", (*bsoncore.Document)(nil), nil, nil, nothing, ValueDecoderError{ Name: "CoreDocumentDecodeValue", Types: []reflect.Type{tCoreDocument}, Received: reflect.New(reflect.TypeOf((*bsoncore.Document)(nil))).Elem(), }, }, { "Copy error", bsoncore.Document{}, nil, &valueReaderWriter{Err: errors.New("copy error"), ErrAfter: readDocument}, readDocument, errors.New("copy error"), }, }, }, { "StructCodec.DecodeValue", newStructCodec(nil), []subtest{ { "Not struct", reflect.New(reflect.TypeOf(struct{ Foo string }{})).Elem().Interface(), nil, &valueReaderWriter{BSONType: TypeString}, nothing, errors.New("cannot decode string into a struct { Foo string }"), }, { "decode null", reflect.New(reflect.TypeOf(struct{ Foo string }{})).Elem().Interface(), nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", reflect.New(reflect.TypeOf(struct{ Foo string }{})).Elem().Interface(), nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "CodeWithScopeDecodeValue", ValueDecoderFunc(codeWithScopeDecodeValue), []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{BSONType: TypeCodeWithScope}, nothing, ValueDecoderError{ Name: "CodeWithScopeDecodeValue", Types: []reflect.Type{tCodeWithScope}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "type not codewithscope", CodeWithScope{}, nil, &valueReaderWriter{BSONType: TypeString}, nothing, fmt.Errorf("cannot decode %v into a CodeWithScope", TypeString), }, { "ReadCodeWithScope Error", CodeWithScope{}, nil, &valueReaderWriter{BSONType: TypeCodeWithScope, Err: errors.New("rcws error"), ErrAfter: readCodeWithScope}, readCodeWithScope, errors.New("rcws error"), }, { "decodeDocument Error", CodeWithScope{ Code: "var hello = 'world';", Scope: D{{"foo", nil}}, }, &DecodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{BSONType: TypeCodeWithScope, Err: errors.New("dd error"), ErrAfter: readElement}, readElement, errors.New("dd error"), }, { "decode null", CodeWithScope{}, nil, &valueReaderWriter{BSONType: TypeNull}, readNull, nil, }, { "decode undefined", CodeWithScope{}, nil, &valueReaderWriter{BSONType: TypeUndefined}, readUndefined, nil, }, }, }, { "CoreArrayDecodeValue", &arrayCodec{}, []subtest{ { "wrong type", wrong, nil, &valueReaderWriter{}, nothing, ValueDecoderError{ Name: "CoreArrayDecodeValue", Types: []reflect.Type{tCoreArray}, Received: reflect.New(reflect.TypeOf(wrong)).Elem(), }, }, { "*bsoncore.Array is nil", (*bsoncore.Array)(nil), nil, nil, nothing, ValueDecoderError{ Name: "CoreArrayDecodeValue", Types: []reflect.Type{tCoreArray}, Received: reflect.New(reflect.TypeOf((*bsoncore.Array)(nil))).Elem(), }, }, }, }, } 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(valueReaderWriter) if rc.llvrw != nil { llvrw = rc.llvrw } llvrw.T = t // var got any if rc.val == cansetreflectiontest { // We're doing a CanSet reflection test err := tc.vd.DecodeValue(dc, llvrw, reflect.Value{}) if !assert.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 !assert.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 var wanterr ValueDecoderError if !errors.As(rc.err, &wanterr) { 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 !assert.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 !assert.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 !assert.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 any 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 := NewDocumentReader(bytes.NewReader(b)) dr, err := dvr.ReadDocument() noerr(t, err) _, vr, err := dr.ReadElement() noerr(t, err) want := CodeWithScope{ Code: "var hello = 'world';", Scope: D{{"bar", nil}}, } val := reflect.New(tCodeWithScope).Elem() err = codeWithScopeDecodeValue(dc, vr, val) noerr(t, err) got := val.Interface().(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 := &valueReaderWriter{BSONType: TypeString, Return: string("hello, world!")} llvrw.T = t want := errors.New("ubsonv error") valUnmarshaler := &testValueUnmarshaler{err: want} got := valueUnmarshalerDecodeValue(dc, llvrw, reflect.ValueOf(valUnmarshaler)) if !assert.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 := &valueReaderWriter{BSONType: TypeString, Return: string("hello, world!")} llvrw.T = t val := reflect.ValueOf(testValueUnmarshaler{}) want := ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val} got := valueUnmarshalerDecodeValue(dc, llvrw, val) if !assert.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 := NewDocumentReader(bytes.NewReader(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, got 2 elements", val) dc := DecodeContext{Registry: buildDefaultRegistry()} got := arrayDecodeValue(dc, vr, reflect.ValueOf(val)) if !assert.CompareErrors(got, want) { t.Errorf("Errors do not match. got %v; want %v", got, want) } }) t.Run("success path", func(t *testing.T) { oid := NewObjectID() oids := []ObjectID{NewObjectID(), NewObjectID(), NewObjectID()} 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 := ParseDecimal128("1.5e10") if err != nil { t.Errorf("Error parsing decimal128: %v", err) t.FailNow() } testCases := []struct { name string value any 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]ObjectID", map[string]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][]ObjectID", map[string][]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][]Decimal128", map[string][]Decimal128{"Z": {decimal128}}, buildDocumentArray(func(doc []byte) []byte { return bsoncore.AppendDecimal128Element(doc, "0", decimal128.h, decimal128.l) }), nil, }, { "map[mystring]any", map[mystring]any{"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 struct pointer", struct { Foo *struct { A int64 `bson:",minsize"` } `bson:",inline"` Bar *struct { B int64 } `bson:",inline"` }{ Foo: &struct { A int64 `bson:",minsize"` }{ A: 12345, }, Bar: nil, }, buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)), nil, }, { "nested inline struct pointer", struct { Foo *struct { Bar *struct { A int64 `bson:",minsize"` } `bson:",inline"` } `bson:",inline"` }{ Foo: &struct { Bar *struct { A int64 `bson:",minsize"` } `bson:",inline"` }{ Bar: &struct { A int64 `bson:",minsize"` }{ A: 12345, }, }, }, buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)), nil, }, { "inline nil struct pointer", struct { Foo *struct { A int64 `bson:",minsize"` } `bson:",inline"` }{ Foo: nil, }, buildDocument([]byte{}), nil, }, { "inline overwrite", struct { Foo struct { A int32 B string } `bson:",inline"` A int64 }{ Foo: struct { A int32 B string }{ A: 0, B: "foo", }, A: 54321, }, buildDocument(func(doc []byte) []byte { doc = bsoncore.AppendStringElement(doc, "b", "foo") doc = bsoncore.AppendInt64Element(doc, "a", 54321) return doc }(nil)), nil, }, { "inline overwrite with nested structs", struct { Foo struct { A int32 } `bson:",inline"` Bar struct { A int32 } `bson:",inline"` A int64 }{ Foo: struct { A int32 }{}, Bar: struct { A int32 }{}, A: 54321, }, buildDocument(bsoncore.AppendInt64Element(nil, "a", 54321)), 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 ObjectID T []struct{} Y json.Number Z time.Time AA json.Number AB *url.URL AC Decimal128 AD *time.Time AE *testValueUnmarshaler AF *bool AG *bool AH *int32 AI *int64 AJ *ObjectID AK *ObjectID AL testValueUnmarshaler AM any AN any AO any AP D AQ A AR [2]E AS []byte AT map[string]any AU CodeWithScope AV M AW D AX map[string]any AY []E AZ any }{ 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: TypeString, 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: TypeString, val: bsoncore.AppendString(nil, "hello, world!")}, AM: "hello, world", AN: int32(12345), AO: oid, AP: D{{"foo", "bar"}}, AQ: A{"foo", "bar"}, AR: [2]E{{"hello", "world"}, {"pi", 3.14159}}, AS: nil, AT: nil, AU: CodeWithScope{Code: "var hello = 'world';", Scope: D{{"pi", 3.14159}}}, AV: M{"foo": D{{"bar", "baz"}}}, AW: D{{"foo", D{{"bar", "baz"}}}}, AX: map[string]any{"foo": D{{"bar", "baz"}}}, AY: []E{{"foo", D{{"bar", "baz"}}}}, AZ: D{{"foo", 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.h, decimal128.l) 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{[]any}", 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 []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 []Decimal128 AD []*time.Time AE []*testValueUnmarshaler AF []*bool AG []*int32 AH []*int64 AI []*ObjectID AJ []D AK []A AL [][2]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: []Decimal128{decimal128}, AD: []*time.Time{&now, &now}, AE: []*testValueUnmarshaler{ {t: TypeString, val: bsoncore.AppendString(nil, "hello")}, {t: TypeString, val: bsoncore.AppendString(nil, "world")}, }, AF: []*bool{pbool(true), nil}, AG: []*int32{pi32(12345), nil}, AH: []*int64{pi64(1234567890), nil, pi64(9012345678)}, AI: []*ObjectID{&oid, nil}, AJ: []D{{{"foo", "bar"}}, nil}, AK: []A{{"foo", "bar"}, nil}, AL: [][2]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", bsoncore.BuildDocumentElement(nil, "0", bsoncore.AppendStringElement(nil, "foo", "bar"))) doc = appendArrayElement(doc, "i", bsoncore.AppendBinaryElement(nil, "0", 0x00, []byte{0x01, 0x02, 0x03})) doc = appendArrayElement(doc, "k", appendArrayElement(nil, "0", bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")), ) doc = appendArrayElement(doc, "l", bsoncore.BuildDocumentElement(nil, "0", bsoncore.AppendStringElement(nil, "m", "foobar"))) doc = appendArrayElement(doc, "n", appendArrayElement(nil, "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", bsoncore.BuildDocumentElement(nil, "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.h, decimal128.l)) 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( appendArrayElement(nil, "0", bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "foo"), "1", "bar"), ), "1", ), ) doc = appendArrayElement(doc, "al", bsoncore.BuildDocumentElement(nil, "0", bsoncore.AppendDoubleElement(bsoncore.AppendStringElement(nil, "hello", "world"), "pi", 3.14159), ), ) return doc }(nil)), nil, }, } t.Run("Decode", func(t *testing.T) { compareTime := func(t1, t2 time.Time) bool { if t1.Location() != t2.Location() { return false } return t1.Equal(t2) } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { vr := NewDocumentReader(bytes.NewReader(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("error path", func(t *testing.T) { testCases := []struct { name string value any b []byte err error }{ { "duplicate name struct", struct { A int64 B int64 `bson:"a"` }{ A: 0, B: 54321, }, buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)), fmt.Errorf("duplicated key a"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { vr := NewDocumentReader(bytes.NewReader(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) if err == nil || !strings.Contains(err.Error(), tc.err.Error()) { t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err) } }) } }) t.Run("defaultEmptyInterfaceCodec.DecodeValue", func(t *testing.T) { t.Run("DecodeValue", func(t *testing.T) { testCases := []struct { name string val any bsontype Type }{ { "Double - float64", float64(3.14159), TypeDouble, }, { "String - string", "foo bar baz", TypeString, }, { "Array - A", A{3.14159}, TypeArray, }, { "Binary - Binary", Binary{Subtype: 0xFF, Data: []byte{0x01, 0x02, 0x03}}, TypeBinary, }, { "Undefined - Undefined", Undefined{}, TypeUndefined, }, { "ObjectID - ObjectID", ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, TypeObjectID, }, { "Boolean - bool", bool(true), TypeBoolean, }, { "DateTime - DateTime", DateTime(1234567890), TypeDateTime, }, { "Null - Null", nil, TypeNull, }, { "Regex - Regex", Regex{Pattern: "foo", Options: "bar"}, TypeRegex, }, { "DBPointer - DBPointer", DBPointer{ DB: "foobar", Pointer: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, }, TypeDBPointer, }, { "JavaScript - JavaScript", JavaScript("var foo = 'bar';"), TypeJavaScript, }, { "Symbol - Symbol", Symbol("foobarbazlolz"), TypeSymbol, }, { "Int32 - int32", int32(123456), TypeInt32, }, { "Int64 - int64", int64(1234567890), TypeInt64, }, { "Timestamp - Timestamp", Timestamp{T: 12345, I: 67890}, TypeTimestamp, }, { "Decimal128 - decimal.Decimal128", NewDecimal128(12345, 67890), TypeDecimal128, }, { "MinKey - MinKey", MinKey{}, TypeMinKey, }, { "MaxKey - MaxKey", MaxKey{}, TypeMaxKey, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { llvr := &valueReaderWriter{BSONType: tc.bsontype} t.Run("Type Map failure", func(t *testing.T) { if tc.bsontype == TypeNull { t.Skip() } val := reflect.New(tEmpty).Elem() dc := DecodeContext{Registry: newTestRegistry()} want := errNoTypeMapEntry{Type: tc.bsontype} got := (&emptyInterfaceCodec{}).DecodeValue(dc, llvr, val) if !assert.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 == TypeNull { t.Skip() } val := reflect.New(tEmpty).Elem() reg := newTestRegistry() reg.RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)) dc := DecodeContext{ Registry: reg, } want := errNoDecoder{Type: reflect.TypeOf(tc.val)} got := (&emptyInterfaceCodec{}).DecodeValue(dc, llvr, val) if !assert.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 == TypeNull { t.Skip() } want := errors.New("DecodeValue failure error") llc := &llCodec{t: t, err: want} reg := newTestRegistry() reg.RegisterTypeDecoder(reflect.TypeOf(tc.val), llc) reg.RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)) dc := DecodeContext{ Registry: reg, } got := (&emptyInterfaceCodec{}).DecodeValue(dc, llvr, reflect.New(tEmpty).Elem()) if !assert.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} reg := newTestRegistry() reg.RegisterTypeDecoder(reflect.TypeOf(tc.val), llc) reg.RegisterTypeMapEntry(tc.bsontype, reflect.TypeOf(tc.val)) dc := DecodeContext{ Registry: reg, } got := reflect.New(tEmpty).Elem() err := (&emptyInterfaceCodec{}).DecodeValue(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-any", func(t *testing.T) { val := uint64(1234567890) want := ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(val)} got := (&emptyInterfaceCodec{}).DecodeValue(DecodeContext{}, nil, reflect.ValueOf(val)) if !assert.CompareErrors(got, want) { t.Errorf("Errors are not equal. got %v; want %v", got, want) } }) t.Run("nil *any", func(t *testing.T) { var val any want := ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(val)} got := (&emptyInterfaceCodec{}).DecodeValue(DecodeContext{}, nil, reflect.ValueOf(val)) if !assert.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 := &valueReaderWriter{BSONType: TypeDouble} want := errNoTypeMapEntry{Type: TypeDouble} val := reflect.New(tEmpty).Elem() got := (&emptyInterfaceCodec{}).DecodeValue(DecodeContext{Registry: newTestRegistry()}, llvr, val) if !assert.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 := NewDocumentReader(bytes.NewReader(data)) want := D{{"pi", 3.14159}} var got any val := reflect.ValueOf(&got).Elem() err := (&emptyInterfaceCodec{}).DecodeValue(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) } }) t.Run("custom type map entry", func(t *testing.T) { // registering a custom type map entry for both Type(0) anad TypeEmbeddedDocument should cause // the top-level to decode to registered type when unmarshalling to any topLevelReg := &Registry{ typeEncoders: new(typeEncoderCache), typeDecoders: new(typeDecoderCache), kindEncoders: new(kindEncoderCache), kindDecoders: new(kindDecoderCache), } registerDefaultEncoders(topLevelReg) registerDefaultDecoders(topLevelReg) topLevelReg.RegisterTypeMapEntry(Type(0), reflect.TypeOf(M{})) embeddedReg := &Registry{ typeEncoders: new(typeEncoderCache), typeDecoders: new(typeDecoderCache), kindEncoders: new(kindEncoderCache), kindDecoders: new(kindDecoderCache), } registerDefaultEncoders(embeddedReg) registerDefaultDecoders(embeddedReg) embeddedReg.RegisterTypeMapEntry(Type(0), reflect.TypeOf(M{})) // create doc {"nested": {"foo": 1}} innerDoc := bsoncore.BuildDocument( nil, bsoncore.AppendInt32Element(nil, "foo", 1), ) doc := bsoncore.BuildDocument( nil, bsoncore.AppendDocumentElement(nil, "nested", innerDoc), ) want := M{ "nested": D{{"foo", int32(1)}}, } testCases := []struct { name string registry *Registry }{ {"top level", topLevelReg}, {"embedded", embeddedReg}, } for _, tc := range testCases { var got any vr := NewDocumentReader(bytes.NewReader(doc)) val := reflect.ValueOf(&got).Elem() err := (&emptyInterfaceCodec{}).DecodeValue(DecodeContext{Registry: tc.registry}, vr, val) noerr(t, err) if !cmp.Equal(got, want) { t.Fatalf("got %v, want %v", got, want) } } }) t.Run("custom type map entry is used if there is no type information", func(t *testing.T) { // If a type map entry is registered for TypeEmbeddedDocument, the decoder should use it when // type information is not available. reg := &Registry{ typeEncoders: new(typeEncoderCache), typeDecoders: new(typeDecoderCache), kindEncoders: new(kindEncoderCache), kindDecoders: new(kindDecoderCache), } registerDefaultEncoders(reg) registerDefaultDecoders(reg) reg.RegisterTypeMapEntry(TypeEmbeddedDocument, reflect.TypeOf(M{})) // build document {"nested": {"foo": 10}} inner := bsoncore.BuildDocument( nil, bsoncore.AppendInt32Element(nil, "foo", 10), ) doc := bsoncore.BuildDocument( nil, bsoncore.AppendDocumentElement(nil, "nested", inner), ) want := D{ {"nested", M{ "foo": int32(10), }}, } var got D vr := NewDocumentReader(bytes.NewReader(doc)) val := reflect.ValueOf(&got).Elem() err := (&sliceCodec{}).DecodeValue(DecodeContext{Registry: reg}, vr, val) noerr(t, err) if !cmp.Equal(got, want) { t.Fatalf("got %v, want %v", got, want) } }) }) t.Run("decode errors contain key information", func(t *testing.T) { decodeValueError := errors.New("decode value error") emptyInterfaceErrorDecode := func(DecodeContext, ValueReader, reflect.Value) error { return decodeValueError } emptyInterfaceErrorRegistry := newTestRegistry() emptyInterfaceErrorRegistry.RegisterTypeDecoder(tEmpty, ValueDecoderFunc(emptyInterfaceErrorDecode)) // Set up a document {foo: 10} and an error that would happen if the value were decoded into any // using the registry defined above. docBytes := bsoncore.BuildDocumentFromElements( nil, bsoncore.AppendInt32Element(nil, "foo", 10), ) docEmptyInterfaceErr := &DecodeError{ keys: []string{"foo"}, wrapped: decodeValueError, } // Set up struct definitions where Foo maps to any and string. When decoded using the registry defined // above, the any struct will get an error when calling DecodeValue and the string struct will get an // error when looking up a decoder. type emptyInterfaceStruct struct { Foo any } type stringStruct struct { Foo string } emptyInterfaceStructErr := &DecodeError{ keys: []string{"foo"}, wrapped: decodeValueError, } stringStructErr := &DecodeError{ keys: []string{"foo"}, wrapped: errNoDecoder{reflect.TypeOf("")}, } // Test a deeply nested struct mixed with maps and slices. // Build document {"first": {"second": {"randomKey": {"third": [{}, {"fourth": "value"}]}}}} type inner3 struct{ Fourth any } type inner2 struct{ Third []inner3 } type inner1 struct{ Second map[string]inner2 } type outer struct{ First inner1 } inner3EmptyDoc := buildDocument(nil) inner3Doc := buildDocument(bsoncore.AppendStringElement(nil, "fourth", "value")) inner3Array := buildArray( // buildArray takes []byte so we first append() all of the values into a single []byte append( bsoncore.AppendDocumentElement(nil, "0", inner3EmptyDoc), bsoncore.AppendDocumentElement(nil, "1", inner3Doc)..., ), ) inner2Doc := buildDocument(bsoncore.AppendArrayElement(nil, "third", inner3Array)) inner2Map := buildDocument(bsoncore.AppendDocumentElement(nil, "randomKey", inner2Doc)) inner1Doc := buildDocument(bsoncore.AppendDocumentElement(nil, "second", inner2Map)) outerDoc := buildDocument(bsoncore.AppendDocumentElement(nil, "first", inner1Doc)) // Use a registry that has all default decoders with the custom any decoder that always errors. nestedRegistry := &Registry{ typeEncoders: new(typeEncoderCache), typeDecoders: new(typeDecoderCache), kindEncoders: new(kindEncoderCache), kindDecoders: new(kindDecoderCache), } registerDefaultDecoders(nestedRegistry) nestedRegistry.RegisterTypeDecoder(tEmpty, ValueDecoderFunc(emptyInterfaceErrorDecode)) nestedErr := &DecodeError{ keys: []string{"fourth", "1", "third", "randomKey", "second", "first"}, wrapped: decodeValueError, } testCases := []struct { name string val any vr ValueReader registry *Registry // buildDefaultRegistry will be used if this is nil decoder ValueDecoder err error }{ { // DecodeValue error when decoding into a D. "D slice", D{}, NewDocumentReader(bytes.NewReader(docBytes)), emptyInterfaceErrorRegistry, &sliceCodec{}, docEmptyInterfaceErr, }, { // DecodeValue error when decoding into a []string. "string slice", []string{}, &valueReaderWriter{BSONType: TypeArray}, nil, &sliceCodec{}, &DecodeError{ keys: []string{"0"}, wrapped: errors.New("cannot decode array into a string type"), }, }, { // DecodeValue error when decoding into a E array. This should have the same behavior as // the "D slice" test above because both the defaultSliceCodec and ArrayDecodeValue use // the decodeD helper function. "D array", [1]E{}, NewDocumentReader(bytes.NewReader(docBytes)), emptyInterfaceErrorRegistry, ValueDecoderFunc(arrayDecodeValue), docEmptyInterfaceErr, }, { // DecodeValue error when decoding into a string array. This should have the same behavior as // the "D slice" test above because both the defaultSliceCodec and ArrayDecodeValue use // the decodeDefault helper function. "string array", [1]string{}, &valueReaderWriter{BSONType: TypeArray}, nil, ValueDecoderFunc(arrayDecodeValue), &DecodeError{ keys: []string{"0"}, wrapped: errors.New("cannot decode array into a string type"), }, }, { // DecodeValue error when decoding into a map. "map", map[string]any{}, NewDocumentReader(bytes.NewReader(docBytes)), emptyInterfaceErrorRegistry, &mapCodec{}, docEmptyInterfaceErr, }, { // DecodeValue error when decoding into a struct. "struct - DecodeValue error", emptyInterfaceStruct{}, NewDocumentReader(bytes.NewReader(docBytes)), emptyInterfaceErrorRegistry, newStructCodec(nil), emptyInterfaceStructErr, }, { // ErrNoDecoder when decoding into a struct. // This test uses NewRegistryBuilder().Build rather than buildDefaultRegistry to ensure that there is // no decoder for strings. "struct - no decoder found", stringStruct{}, NewDocumentReader(bytes.NewReader(docBytes)), newTestRegistry(), newStructCodec(nil), stringStructErr, }, { "deeply nested struct", outer{}, NewDocumentReader(bytes.NewReader(outerDoc)), nestedRegistry, newStructCodec(nil), nestedErr, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { dc := DecodeContext{Registry: tc.registry} if dc.Registry == nil { dc.Registry = buildDefaultRegistry() } var val reflect.Value if rtype := reflect.TypeOf(tc.val); rtype != nil { val = reflect.New(rtype).Elem() } err := tc.decoder.DecodeValue(dc, tc.vr, val) assert.Equal(t, tc.err, err, "expected error %v, got %v", tc.err, err) }) } t.Run("keys are correctly reversed", func(t *testing.T) { innerBytes := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendInt32Element(nil, "bar", 10)) outerBytes := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendDocumentElement(nil, "foo", innerBytes)) type inner struct{ Bar string } type outer struct{ Foo inner } dc := DecodeContext{Registry: buildDefaultRegistry()} vr := NewDocumentReader(bytes.NewReader(outerBytes)) val := reflect.New(reflect.TypeOf(outer{})).Elem() err := newStructCodec(nil).DecodeValue(dc, vr, val) var decodeErr *DecodeError assert.True(t, errors.As(err, &decodeErr), "expected DecodeError, got %v of type %T", err, err) expectedKeys := []string{"foo", "bar"} assert.Equal(t, expectedKeys, decodeErr.Keys(), "expected keys slice %v, got %v", expectedKeys, decodeErr.Keys()) keyPath := strings.Join(expectedKeys, ".") assert.True(t, strings.Contains(decodeErr.Error(), keyPath), "expected error %v to contain key pattern %s", decodeErr, keyPath) }) }) t.Run("values are converted", func(t *testing.T) { // When decoding into a D or M, values must be converted if they are not being decoded to the default type. t.Run("D", func(t *testing.T) { trueValue := bsoncore.Value{ Type: bsoncore.TypeBoolean, Data: bsoncore.AppendBoolean(nil, true), } docBytes := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendBooleanElement(nil, "bool", true), bsoncore.BuildArrayElement(nil, "boolArray", trueValue), ) reg := &Registry{ typeEncoders: new(typeEncoderCache), typeDecoders: new(typeDecoderCache), kindEncoders: new(kindEncoderCache), kindDecoders: new(kindDecoderCache), } registerDefaultDecoders(reg) reg.RegisterTypeMapEntry(TypeBoolean, reflect.TypeOf(mybool(true))) dc := DecodeContext{Registry: reg} vr := NewDocumentReader(bytes.NewReader(docBytes)) val := reflect.New(tD).Elem() err := dDecodeValue(dc, vr, val) assert.Nil(t, err, "DDecodeValue error: %v", err) want := D{ {"bool", mybool(true)}, {"boolArray", A{mybool(true)}}, } got := val.Interface().(D) assert.Equal(t, want, got, "want document %v, got %v", want, got) }) t.Run("M", func(t *testing.T) { docBytes := bsoncore.BuildDocumentFromElements(nil, bsoncore.AppendBooleanElement(nil, "bool", true), ) type myMap map[string]mybool dc := DecodeContext{Registry: buildDefaultRegistry()} vr := NewDocumentReader(bytes.NewReader(docBytes)) val := reflect.New(reflect.TypeOf(myMap{})).Elem() err := (&mapCodec{}).DecodeValue(dc, vr, val) assert.Nil(t, err, "DecodeValue error: %v", err) want := myMap{ "bool": mybool(true), } got := val.Interface().(myMap) assert.Equal(t, want, got, "expected map %v, got %v", want, got) }) }) } // 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 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 buildDefaultRegistry() *Registry { reg := &Registry{ typeEncoders: new(typeEncoderCache), typeDecoders: new(typeDecoderCache), kindEncoders: new(kindEncoderCache), kindDecoders: new(kindDecoderCache), } registerDefaultEncoders(reg) registerDefaultDecoders(reg) return reg } ================================================ FILE: bson/default_value_encoders.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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/json" "errors" "math" "net/url" "reflect" "sync" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) var bvwPool = sync.Pool{ New: func() any { return new(valueWriter) }, } var errInvalidValue = errors.New("cannot encode invalid element") var sliceWriterPool = sync.Pool{ New: func() any { sw := make(sliceWriter, 0) return &sw }, } func encodeElement(ec EncodeContext, dw DocumentWriter, e 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 } // registerDefaultEncoders will register the encoder methods attached to DefaultValueEncoders with // the provided RegistryBuilder. func registerDefaultEncoders(reg *Registry) { mapEncoder := &mapCodec{} uintCodec := &uintCodec{} reg.RegisterTypeEncoder(tByteSlice, &byteSliceCodec{}) reg.RegisterTypeEncoder(tTime, &timeCodec{}) reg.RegisterTypeEncoder(tEmpty, &emptyInterfaceCodec{}) reg.RegisterTypeEncoder(tCoreArray, &arrayCodec{}) reg.RegisterTypeEncoder(tOID, ValueEncoderFunc(objectIDEncodeValue)) reg.RegisterTypeEncoder(tDecimal, ValueEncoderFunc(decimal128EncodeValue)) reg.RegisterTypeEncoder(tJSONNumber, ValueEncoderFunc(jsonNumberEncodeValue)) reg.RegisterTypeEncoder(tURL, ValueEncoderFunc(urlEncodeValue)) reg.RegisterTypeEncoder(tJavaScript, ValueEncoderFunc(javaScriptEncodeValue)) reg.RegisterTypeEncoder(tSymbol, ValueEncoderFunc(symbolEncodeValue)) reg.RegisterTypeEncoder(tBinary, ValueEncoderFunc(binaryEncodeValue)) reg.RegisterTypeEncoder(tVector, ValueEncoderFunc(vectorEncodeValue)) reg.RegisterTypeEncoder(tUndefined, ValueEncoderFunc(undefinedEncodeValue)) reg.RegisterTypeEncoder(tDateTime, ValueEncoderFunc(dateTimeEncodeValue)) reg.RegisterTypeEncoder(tNull, ValueEncoderFunc(nullEncodeValue)) reg.RegisterTypeEncoder(tRegex, ValueEncoderFunc(regexEncodeValue)) reg.RegisterTypeEncoder(tDBPointer, ValueEncoderFunc(dbPointerEncodeValue)) reg.RegisterTypeEncoder(tTimestamp, ValueEncoderFunc(timestampEncodeValue)) reg.RegisterTypeEncoder(tMinKey, ValueEncoderFunc(minKeyEncodeValue)) reg.RegisterTypeEncoder(tMaxKey, ValueEncoderFunc(maxKeyEncodeValue)) reg.RegisterTypeEncoder(tCoreDocument, ValueEncoderFunc(coreDocumentEncodeValue)) reg.RegisterTypeEncoder(tCodeWithScope, ValueEncoderFunc(codeWithScopeEncodeValue)) reg.RegisterKindEncoder(reflect.Bool, ValueEncoderFunc(booleanEncodeValue)) reg.RegisterKindEncoder(reflect.Int, ValueEncoderFunc(intEncodeValue)) reg.RegisterKindEncoder(reflect.Int8, ValueEncoderFunc(intEncodeValue)) reg.RegisterKindEncoder(reflect.Int16, ValueEncoderFunc(intEncodeValue)) reg.RegisterKindEncoder(reflect.Int32, ValueEncoderFunc(intEncodeValue)) reg.RegisterKindEncoder(reflect.Int64, ValueEncoderFunc(intEncodeValue)) reg.RegisterKindEncoder(reflect.Uint, uintCodec) reg.RegisterKindEncoder(reflect.Uint8, uintCodec) reg.RegisterKindEncoder(reflect.Uint16, uintCodec) reg.RegisterKindEncoder(reflect.Uint32, uintCodec) reg.RegisterKindEncoder(reflect.Uint64, uintCodec) reg.RegisterKindEncoder(reflect.Float32, ValueEncoderFunc(floatEncodeValue)) reg.RegisterKindEncoder(reflect.Float64, ValueEncoderFunc(floatEncodeValue)) reg.RegisterKindEncoder(reflect.Array, ValueEncoderFunc(arrayEncodeValue)) reg.RegisterKindEncoder(reflect.Map, mapEncoder) reg.RegisterKindEncoder(reflect.Slice, &sliceCodec{}) reg.RegisterKindEncoder(reflect.String, &stringCodec{}) reg.RegisterKindEncoder(reflect.Struct, newStructCodec(mapEncoder)) reg.RegisterKindEncoder(reflect.Ptr, &pointerCodec{}) reg.RegisterInterfaceEncoder(tValueMarshaler, ValueEncoderFunc(valueMarshalerEncodeValue)) reg.RegisterInterfaceEncoder(tMarshaler, ValueEncoderFunc(marshalerEncodeValue)) } // booleanEncodeValue is the ValueEncoderFunc for bool types. func booleanEncodeValue(_ EncodeContext, vw 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 intEncodeValue(ec EncodeContext, vw 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, } } // floatEncodeValue is the ValueEncoderFunc for float types. func floatEncodeValue(_ EncodeContext, vw 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} } // objectIDEncodeValue is the ValueEncoderFunc for ObjectID. func objectIDEncodeValue(_ EncodeContext, vw 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().(ObjectID)) } // decimal128EncodeValue is the ValueEncoderFunc for Decimal128. func decimal128EncodeValue(_ EncodeContext, vw 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().(Decimal128)) } // jsonNumberEncodeValue is the ValueEncoderFunc for json.Number. func jsonNumberEncodeValue(ec EncodeContext, vw 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 intEncodeValue(ec, vw, reflect.ValueOf(i64)) } f64, err := jsnum.Float64() if err != nil { return err } return floatEncodeValue(ec, vw, reflect.ValueOf(f64)) } // urlEncodeValue is the ValueEncoderFunc for url.URL. func urlEncodeValue(_ EncodeContext, vw 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()) } // arrayEncodeValue is the ValueEncoderFunc for array types. func arrayEncodeValue(ec EncodeContext, vw 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 []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().(E) err = encodeElement(ec, dw, e) if err != nil { return err } } return dw.WriteDocumentEnd() } // If we have a []byte we want to treat it as a binary instead of as an array. if val.Type().Elem() == tByte { var byteSlice []byte for idx := 0; idx < val.Len(); idx++ { byteSlice = append(byteSlice, val.Index(idx).Interface().(byte)) } return vw.WriteBinary(byteSlice) } aw, err := vw.WriteArray() if err != nil { return err } elemType := val.Type().Elem() encoder, err := ec.LookupEncoder(elemType) if err != nil && elemType.Kind() != reflect.Interface { return err } for idx := 0; idx < val.Len(); idx++ { currEncoder, currVal, lookupErr := lookupElementEncoder(ec, encoder, val.Index(idx)) if lookupErr != nil && !errors.Is(lookupErr, errInvalidValue) { return lookupErr } vw, err := aw.WriteArrayElement() if err != nil { return err } if errors.Is(lookupErr, errInvalidValue) { err = vw.WriteNull() if err != nil { return err } continue } err = currEncoder.EncodeValue(ec, vw, currVal) if err != nil { return err } } return aw.WriteArrayEnd() } func lookupElementEncoder(ec EncodeContext, origEncoder ValueEncoder, currVal reflect.Value) (ValueEncoder, reflect.Value, error) { if origEncoder != nil || (currVal.Kind() != reflect.Interface) { return origEncoder, currVal, nil } currVal = currVal.Elem() if !currVal.IsValid() { return nil, currVal, errInvalidValue } currEncoder, err := ec.LookupEncoder(currVal.Type()) return currEncoder, currVal, err } // valueMarshalerEncodeValue is the ValueEncoderFunc for ValueMarshaler implementations. func valueMarshalerEncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { // Either val or a pointer to val must implement ValueMarshaler switch { case !val.IsValid(): return ValueEncoderError{Name: "ValueMarshalerEncodeValue", Types: []reflect.Type{tValueMarshaler}, Received: val} case val.Type().Implements(tValueMarshaler): // If ValueMarshaler is implemented on a concrete type, make sure that val isn't a nil pointer if isImplementationNil(val, tValueMarshaler) { return vw.WriteNull() } case reflect.PtrTo(val.Type()).Implements(tValueMarshaler) && val.CanAddr(): val = val.Addr() default: return ValueEncoderError{Name: "ValueMarshalerEncodeValue", Types: []reflect.Type{tValueMarshaler}, Received: val} } m, ok := val.Interface().(ValueMarshaler) if !ok { return vw.WriteNull() } t, data, err := m.MarshalBSONValue() if err != nil { return err } return copyValueFromBytes(vw, Type(t), data) } // marshalerEncodeValue is the ValueEncoderFunc for Marshaler implementations. func marshalerEncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { // Either val or a pointer to val must implement Marshaler switch { case !val.IsValid(): return ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: val} case val.Type().Implements(tMarshaler): // If Marshaler is implemented on a concrete type, make sure that val isn't a nil pointer if isImplementationNil(val, tMarshaler) { return vw.WriteNull() } case reflect.PtrTo(val.Type()).Implements(tMarshaler) && val.CanAddr(): val = val.Addr() default: return ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: val} } m, ok := val.Interface().(Marshaler) if !ok { return vw.WriteNull() } data, err := m.MarshalBSON() if err != nil { return err } return copyValueFromBytes(vw, TypeEmbeddedDocument, data) } // javaScriptEncodeValue is the ValueEncoderFunc for the JavaScript type. func javaScriptEncodeValue(_ EncodeContext, vw 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 Symbol type. func symbolEncodeValue(_ EncodeContext, vw 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 binaryEncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tBinary { return ValueEncoderError{Name: "BinaryEncodeValue", Types: []reflect.Type{tBinary}, Received: val} } b := val.Interface().(Binary) return vw.WriteBinaryWithSubtype(b.Data, b.Subtype) } // vectorEncodeValue is the ValueEncoderFunc for Vector. func vectorEncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { t := val.Type() if !val.IsValid() || t != tVector { return ValueEncoderError{ Name: "VectorEncodeValue", Types: []reflect.Type{tVector}, Received: val, } } v := val.Interface().(Vector) b := v.Binary() return vw.WriteBinaryWithSubtype(b.Data, b.Subtype) } // undefinedEncodeValue is the ValueEncoderFunc for Undefined. func undefinedEncodeValue(_ EncodeContext, vw 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 dateTimeEncodeValue(_ EncodeContext, vw 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 nullEncodeValue(_ EncodeContext, vw 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 regexEncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tRegex { return ValueEncoderError{Name: "RegexEncodeValue", Types: []reflect.Type{tRegex}, Received: val} } regex := val.Interface().(Regex) return vw.WriteRegex(regex.Pattern, regex.Options) } // dbPointerEncodeValue is the ValueEncoderFunc for DBPointer. func dbPointerEncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tDBPointer { return ValueEncoderError{Name: "DBPointerEncodeValue", Types: []reflect.Type{tDBPointer}, Received: val} } dbp := val.Interface().(DBPointer) return vw.WriteDBPointer(dbp.DB, dbp.Pointer) } // timestampEncodeValue is the ValueEncoderFunc for Timestamp. func timestampEncodeValue(_ EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tTimestamp { return ValueEncoderError{Name: "TimestampEncodeValue", Types: []reflect.Type{tTimestamp}, Received: val} } ts := val.Interface().(Timestamp) return vw.WriteTimestamp(ts.T, ts.I) } // minKeyEncodeValue is the ValueEncoderFunc for MinKey. func minKeyEncodeValue(_ EncodeContext, vw 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 maxKeyEncodeValue(_ EncodeContext, vw 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 coreDocumentEncodeValue(_ EncodeContext, vw 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 copyDocumentFromBytes(vw, cdoc) } // codeWithScopeEncodeValue is the ValueEncoderFunc for CodeWithScope. func codeWithScopeEncodeValue(ec EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tCodeWithScope { return ValueEncoderError{Name: "CodeWithScopeEncodeValue", Types: []reflect.Type{tCodeWithScope}, Received: val} } cws := val.Interface().(CodeWithScope) dw, err := vw.WriteCodeWithScope(string(cws.Code)) if err != nil { return err } sw := sliceWriterPool.Get().(*sliceWriter) defer sliceWriterPool.Put(sw) *sw = (*sw)[:0] scopeVW := bvwPool.Get().(*valueWriter) scopeVW.reset(scopeVW.buf[:0]) scopeVW.w = 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 = copyBytesToDocumentWriter(dw, *sw) if err != nil { return err } return dw.WriteDocumentEnd() } // isImplementationNil returns if val is a nil pointer and inter is implemented on a concrete type func isImplementationNil(val reflect.Value, inter reflect.Type) bool { vt := val.Type() for vt.Kind() == reflect.Ptr { vt = vt.Elem() } return vt.Implements(inter) && val.Kind() == reflect.Ptr && val.IsNil() } ================================================ FILE: bson/default_value_encoders_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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/json" "errors" "fmt" "math" "net/url" "reflect" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) type myInterface interface { Foo() int } type myStruct struct { Val int } func (ms myStruct) Foo() int { return ms.Val } func TestDefaultValueEncoders(t *testing.T) { 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 now := time.Now().Truncate(time.Millisecond) pjsnum := new(json.Number) *pjsnum = json.Number("3.14159") d128 := NewDecimal128(12345, 67890) var nilValueMarshaler *testValueMarshaler var nilMarshaler *testMarshaler vmStruct := struct{ V testValueMarshalPtr }{testValueMarshalPtr{t: TypeString, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}}} mStruct := struct{ V testMarshalPtr }{testMarshalPtr{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))}} type subtest struct { name string val any ectx *EncodeContext llvrw *valueReaderWriter invoke invoked err error } testCases := []struct { name string ve ValueEncoder subtests []subtest }{ { "BooleanEncodeValue", ValueEncoderFunc(booleanEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "BooleanEncodeValue", Kinds: []reflect.Kind{reflect.Bool}, Received: reflect.ValueOf(wrong)}, }, {"fast path", bool(true), nil, nil, writeBoolean, nil}, {"reflection path", mybool(true), nil, nil, writeBoolean, nil}, }, }, { "IntEncodeValue", ValueEncoderFunc(intEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, 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, writeInt32, nil}, {"int16/fast path", int16(32767), nil, nil, writeInt32, nil}, {"int32/fast path", int32(2147483647), nil, nil, writeInt32, nil}, {"int64/fast path", int64(1234567890987), nil, nil, writeInt64, nil}, {"int64/fast path - minsize", int64(math.MaxInt32), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"int64/fast path - minsize too large", int64(math.MaxInt32 + 1), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"int64/fast path - minsize too small", int64(math.MinInt32 - 1), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"int/fast path - positive int32", int(math.MaxInt32 - 1), nil, nil, writeInt32, nil}, {"int/fast path - negative int32", int(math.MinInt32 + 1), nil, nil, writeInt32, nil}, {"int/fast path - MaxInt32", int(math.MaxInt32), nil, nil, writeInt32, nil}, {"int/fast path - MinInt32", int(math.MinInt32), nil, nil, writeInt32, nil}, {"int8/reflection path", myint8(127), nil, nil, writeInt32, nil}, {"int16/reflection path", myint16(32767), nil, nil, writeInt32, nil}, {"int32/reflection path", myint32(2147483647), nil, nil, writeInt32, nil}, {"int64/reflection path", myint64(1234567890987), nil, nil, writeInt64, nil}, {"int64/reflection path - minsize", myint64(math.MaxInt32), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"int64/reflection path - minsize too large", myint64(math.MaxInt32 + 1), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"int64/reflection path - minsize too small", myint64(math.MinInt32 - 1), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"int/reflection path - positive int32", myint(math.MaxInt32 - 1), nil, nil, writeInt32, nil}, {"int/reflection path - negative int32", myint(math.MinInt32 + 1), nil, nil, writeInt32, nil}, {"int/reflection path - MaxInt32", myint(math.MaxInt32), nil, nil, writeInt32, nil}, {"int/reflection path - MinInt32", myint(math.MinInt32), nil, nil, writeInt32, nil}, }, }, { "UintEncodeValue", &uintCodec{}, []subtest{ { "wrong type", wrong, nil, nil, 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, writeInt32, nil}, {"uint16/fast path", uint16(32767), nil, nil, writeInt32, nil}, {"uint32/fast path", uint32(2147483647), nil, nil, writeInt64, nil}, {"uint64/fast path", uint64(1234567890987), nil, nil, writeInt64, nil}, {"uint/fast path", uint(1234567), nil, nil, writeInt64, nil}, {"uint32/fast path - minsize", uint32(2147483647), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"uint64/fast path - minsize", uint64(2147483647), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"uint/fast path - minsize", uint(2147483647), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"uint32/fast path - minsize too large", uint32(2147483648), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"uint64/fast path - minsize too large", uint64(2147483648), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"uint/fast path - minsize too large", uint(2147483648), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"uint64/fast path - overflow", uint64(1 << 63), nil, nil, nothing, fmt.Errorf("%d overflows int64", uint64(1<<63))}, {"uint8/reflection path", myuint8(127), nil, nil, writeInt32, nil}, {"uint16/reflection path", myuint16(32767), nil, nil, writeInt32, nil}, {"uint32/reflection path", myuint32(2147483647), nil, nil, writeInt64, nil}, {"uint64/reflection path", myuint64(1234567890987), nil, nil, writeInt64, nil}, {"uint32/reflection path - minsize", myuint32(2147483647), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"uint64/reflection path - minsize", myuint64(2147483647), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"uint/reflection path - minsize", myuint(2147483647), &EncodeContext{minSize: true}, nil, writeInt32, nil}, {"uint32/reflection path - minsize too large", myuint(1 << 31), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"uint64/reflection path - minsize too large", myuint64(1 << 31), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"uint/reflection path - minsize too large", myuint(2147483648), &EncodeContext{minSize: true}, nil, writeInt64, nil}, {"uint64/reflection path - overflow", myuint64(1 << 63), nil, nil, nothing, fmt.Errorf("%d overflows int64", uint64(1<<63))}, }, }, { "FloatEncodeValue", ValueEncoderFunc(floatEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{ Name: "FloatEncodeValue", Kinds: []reflect.Kind{reflect.Float32, reflect.Float64}, Received: reflect.ValueOf(wrong), }, }, {"float32/fast path", float32(3.14159), nil, nil, writeDouble, nil}, {"float64/fast path", float64(3.14159), nil, nil, writeDouble, nil}, {"float32/reflection path", myfloat32(3.14159), nil, nil, writeDouble, nil}, {"float64/reflection path", myfloat64(3.14159), nil, nil, writeDouble, nil}, }, }, { "TimeEncodeValue", &timeCodec{}, []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "TimeEncodeValue", Types: []reflect.Type{tTime}, Received: reflect.ValueOf(wrong)}, }, {"time.Time", now, nil, nil, writeDateTime, nil}, }, }, { "MapEncodeValue", &mapCodec{}, []subtest{ { "wrong kind", wrong, nil, nil, nothing, ValueEncoderError{Name: "MapEncodeValue", Kinds: []reflect.Kind{reflect.Map}, Received: reflect.ValueOf(wrong)}, }, { "WriteDocument Error", map[string]any{}, nil, &valueReaderWriter{Err: errors.New("wd error"), ErrAfter: writeDocument}, writeDocument, errors.New("wd error"), }, { "Lookup Error", map[string]int{"foo": 1}, &EncodeContext{Registry: newTestRegistry()}, &valueReaderWriter{}, writeDocument, fmt.Errorf("no encoder found for int"), }, { "WriteDocumentElement Error", map[string]any{"foo": "bar"}, &EncodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("wde error"), ErrAfter: writeDocumentElement}, writeDocumentElement, errors.New("wde error"), }, { "EncodeValue Error", map[string]any{"foo": "bar"}, &EncodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("ev error"), ErrAfter: writeString}, writeString, errors.New("ev error"), }, { "empty map/success", map[string]any{}, &EncodeContext{Registry: newTestRegistry()}, &valueReaderWriter{}, writeDocumentEnd, nil, }, { "with interface/success", map[string]myInterface{"foo": myStruct{1}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "with interface/nil/success", map[string]myInterface{"foo": nil}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "non-string key success", map[int]any{ 1: "foobar", }, &EncodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{}, writeDocumentEnd, nil, }, }, }, { "ArrayEncodeValue", ValueEncoderFunc(arrayEncodeValue), []subtest{ { "wrong kind", wrong, nil, nil, nothing, ValueEncoderError{Name: "ArrayEncodeValue", Kinds: []reflect.Kind{reflect.Array}, Received: reflect.ValueOf(wrong)}, }, { "WriteArray Error", [1]string{}, nil, &valueReaderWriter{Err: errors.New("wa error"), ErrAfter: writeArray}, writeArray, errors.New("wa error"), }, { "Lookup Error", [1]int{1}, &EncodeContext{Registry: newTestRegistry()}, &valueReaderWriter{}, writeArray, fmt.Errorf("no encoder found for int"), }, { "WriteArrayElement Error", [1]string{"foo"}, &EncodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("wae error"), ErrAfter: writeArrayElement}, writeArrayElement, errors.New("wae error"), }, { "EncodeValue Error", [1]string{"foo"}, &EncodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("ev error"), ErrAfter: writeString}, writeString, errors.New("ev error"), }, { "[1]E/success", [1]E{{"hello", "world"}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "[1]E/success", [1]E{{"hello", nil}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "[1]interface/success", [1]myInterface{myStruct{1}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeArrayEnd, nil, }, { "[1]interface/nil/success", [1]myInterface{nil}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeArrayEnd, nil, }, }, }, { "SliceEncodeValue", &sliceCodec{}, []subtest{ { "wrong kind", wrong, nil, nil, nothing, ValueEncoderError{Name: "SliceEncodeValue", Kinds: []reflect.Kind{reflect.Slice}, Received: reflect.ValueOf(wrong)}, }, { "WriteArray Error", []string{}, nil, &valueReaderWriter{Err: errors.New("wa error"), ErrAfter: writeArray}, writeArray, errors.New("wa error"), }, { "Lookup Error", []int{1}, &EncodeContext{Registry: newTestRegistry()}, &valueReaderWriter{}, writeArray, fmt.Errorf("no encoder found for int"), }, { "WriteArrayElement Error", []string{"foo"}, &EncodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("wae error"), ErrAfter: writeArrayElement}, writeArrayElement, errors.New("wae error"), }, { "EncodeValue Error", []string{"foo"}, &EncodeContext{Registry: buildDefaultRegistry()}, &valueReaderWriter{Err: errors.New("ev error"), ErrAfter: writeString}, writeString, errors.New("ev error"), }, { "D/success", D{{"hello", "world"}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "D/success", D{{"hello", nil}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "empty slice/success", []any{}, &EncodeContext{Registry: newTestRegistry()}, &valueReaderWriter{}, writeArrayEnd, nil, }, { "interface/success", []myInterface{myStruct{1}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeArrayEnd, nil, }, { "interface/success", []myInterface{nil}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeArrayEnd, nil, }, }, }, { "ObjectIDEncodeValue", ValueEncoderFunc(objectIDEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "ObjectIDEncodeValue", Types: []reflect.Type{tOID}, Received: reflect.ValueOf(wrong)}, }, { "ObjectID/success", ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, nil, nil, writeObjectID, nil, }, }, }, { "Decimal128EncodeValue", ValueEncoderFunc(decimal128EncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "Decimal128EncodeValue", Types: []reflect.Type{tDecimal}, Received: reflect.ValueOf(wrong)}, }, {"Decimal128/success", d128, nil, nil, writeDecimal128, nil}, }, }, { "JSONNumberEncodeValue", ValueEncoderFunc(jsonNumberEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "JSONNumberEncodeValue", Types: []reflect.Type{tJSONNumber}, Received: reflect.ValueOf(wrong)}, }, { "json.Number/invalid", json.Number("hello world"), nil, nil, nothing, errors.New(`strconv.ParseFloat: parsing "hello world": invalid syntax`), }, { "json.Number/int64/success", json.Number("1234567890"), nil, nil, writeInt64, nil, }, { "json.Number/float64/success", json.Number("3.14159"), nil, nil, writeDouble, nil, }, }, }, { "URLEncodeValue", ValueEncoderFunc(urlEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "URLEncodeValue", Types: []reflect.Type{tURL}, Received: reflect.ValueOf(wrong)}, }, {"url.URL", url.URL{Scheme: "http", Host: "example.com"}, nil, nil, writeString, nil}, }, }, { "ByteSliceEncodeValue", &byteSliceCodec{}, []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "ByteSliceEncodeValue", Types: []reflect.Type{tByteSlice}, Received: reflect.ValueOf(wrong)}, }, {"[]byte", []byte{0x01, 0x02, 0x03}, nil, nil, writeBinary, nil}, {"[]byte/nil", []byte(nil), nil, nil, writeNull, nil}, }, }, { "EmptyInterfaceEncodeValue", &emptyInterfaceCodec{}, []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.ValueOf(wrong)}, }, }, }, { "ValueMarshalerEncodeValue", ValueEncoderFunc(valueMarshalerEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{ Name: "ValueMarshalerEncodeValue", Types: []reflect.Type{tValueMarshaler}, Received: reflect.ValueOf(wrong), }, }, { "MarshalBSONValue error", testValueMarshaler{err: errors.New("mbsonv error")}, nil, nil, nothing, errors.New("mbsonv error"), }, { "Copy error", testValueMarshaler{}, nil, nil, nothing, fmt.Errorf("cannot copy unknown BSON type %s", Type(0)), }, { "success struct implementation", testValueMarshaler{t: TypeString, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}}, nil, nil, writeString, nil, }, { "success ptr to struct implementation", &testValueMarshaler{t: TypeString, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}}, nil, nil, writeString, nil, }, { "success nil ptr to struct implementation", nilValueMarshaler, nil, nil, writeNull, nil, }, { "success ptr to ptr implementation", &testValueMarshalPtr{t: TypeString, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}}, nil, nil, writeString, nil, }, { "unaddressable ptr implementation", testValueMarshalPtr{t: TypeString, buf: []byte{0x04, 0x00, 0x00, 0x00, 'f', 'o', 'o', 0x00}}, nil, nil, nothing, ValueEncoderError{ Name: "ValueMarshalerEncodeValue", Types: []reflect.Type{tValueMarshaler}, Received: reflect.ValueOf(testValueMarshalPtr{}), }, }, }, }, { "MarshalerEncodeValue", ValueEncoderFunc(marshalerEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: reflect.ValueOf(wrong)}, }, { "MarshalBSON error", testMarshaler{err: errors.New("mbson error")}, nil, nil, nothing, errors.New("mbson error"), }, { "success struct implementation", testMarshaler{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))}, nil, nil, writeDocumentEnd, nil, }, { "success ptr to struct implementation", &testMarshaler{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))}, nil, nil, writeDocumentEnd, nil, }, { "success nil ptr to struct implementation", nilMarshaler, nil, nil, writeNull, nil, }, { "success ptr to ptr implementation", &testMarshalPtr{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))}, nil, nil, writeDocumentEnd, nil, }, { "unaddressable ptr implementation", testMarshalPtr{buf: bsoncore.BuildDocument(nil, bsoncore.AppendDoubleElement(nil, "pi", 3.14159))}, nil, nil, nothing, ValueEncoderError{Name: "MarshalerEncodeValue", Types: []reflect.Type{tMarshaler}, Received: reflect.ValueOf(testMarshalPtr{})}, }, }, }, { "PointerCodec.EncodeValue", &pointerCodec{}, []subtest{ { "nil", nil, nil, nil, writeNull, nil, }, { "not pointer", int32(123456), nil, nil, nothing, ValueEncoderError{Name: "PointerCodec.EncodeValue", Kinds: []reflect.Kind{reflect.Ptr}, Received: reflect.ValueOf(int32(123456))}, }, { "typed nil", (*int32)(nil), nil, nil, writeNull, nil, }, { "no encoder", &wrong, &EncodeContext{Registry: buildDefaultRegistry()}, nil, nothing, errNoEncoder{Type: reflect.TypeOf(wrong)}, }, }, }, { "pointer implementation addressable interface", &pointerCodec{}, []subtest{ { "ValueMarshaler", &vmStruct, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "Marshaler", &mStruct, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, }, }, { "JavaScriptEncodeValue", ValueEncoderFunc(javaScriptEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "JavaScriptEncodeValue", Types: []reflect.Type{tJavaScript}, Received: reflect.ValueOf(wrong)}, }, {"JavaScript", JavaScript("foobar"), nil, nil, writeJavascript, nil}, }, }, { "SymbolEncodeValue", ValueEncoderFunc(symbolEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "SymbolEncodeValue", Types: []reflect.Type{tSymbol}, Received: reflect.ValueOf(wrong)}, }, {"Symbol", Symbol("foobar"), nil, nil, writeSymbol, nil}, }, }, { "BinaryEncodeValue", ValueEncoderFunc(binaryEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "BinaryEncodeValue", Types: []reflect.Type{tBinary}, Received: reflect.ValueOf(wrong)}, }, {"Binary/success", Binary{Data: []byte{0x01, 0x02}, Subtype: 0xFF}, nil, nil, writeBinaryWithSubtype, nil}, }, }, { "UndefinedEncodeValue", ValueEncoderFunc(undefinedEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "UndefinedEncodeValue", Types: []reflect.Type{tUndefined}, Received: reflect.ValueOf(wrong)}, }, {"Undefined/success", Undefined{}, nil, nil, writeUndefined, nil}, }, }, { "DateTimeEncodeValue", ValueEncoderFunc(dateTimeEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "DateTimeEncodeValue", Types: []reflect.Type{tDateTime}, Received: reflect.ValueOf(wrong)}, }, {"DateTime/success", DateTime(1234567890), nil, nil, writeDateTime, nil}, }, }, { "NullEncodeValue", ValueEncoderFunc(nullEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "NullEncodeValue", Types: []reflect.Type{tNull}, Received: reflect.ValueOf(wrong)}, }, {"Null/success", Null{}, nil, nil, writeNull, nil}, }, }, { "RegexEncodeValue", ValueEncoderFunc(regexEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "RegexEncodeValue", Types: []reflect.Type{tRegex}, Received: reflect.ValueOf(wrong)}, }, {"Regex/success", Regex{Pattern: "foo", Options: "bar"}, nil, nil, writeRegex, nil}, }, }, { "DBPointerEncodeValue", ValueEncoderFunc(dbPointerEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "DBPointerEncodeValue", Types: []reflect.Type{tDBPointer}, Received: reflect.ValueOf(wrong)}, }, { "DBPointer/success", DBPointer{ DB: "foobar", Pointer: ObjectID{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}, }, nil, nil, writeDBPointer, nil, }, }, }, { "TimestampEncodeValue", ValueEncoderFunc(timestampEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "TimestampEncodeValue", Types: []reflect.Type{tTimestamp}, Received: reflect.ValueOf(wrong)}, }, {"Timestamp/success", Timestamp{T: 12345, I: 67890}, nil, nil, writeTimestamp, nil}, }, }, { "MinKeyEncodeValue", ValueEncoderFunc(minKeyEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "MinKeyEncodeValue", Types: []reflect.Type{tMinKey}, Received: reflect.ValueOf(wrong)}, }, {"MinKey/success", MinKey{}, nil, nil, writeMinKey, nil}, }, }, { "MaxKeyEncodeValue", ValueEncoderFunc(maxKeyEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{Name: "MaxKeyEncodeValue", Types: []reflect.Type{tMaxKey}, Received: reflect.ValueOf(wrong)}, }, {"MaxKey/success", MaxKey{}, nil, nil, writeMaxKey, nil}, }, }, { "CoreDocumentEncodeValue", ValueEncoderFunc(coreDocumentEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{ Name: "CoreDocumentEncodeValue", Types: []reflect.Type{tCoreDocument}, Received: reflect.ValueOf(wrong), }, }, { "WriteDocument Error", bsoncore.Document{}, nil, &valueReaderWriter{Err: errors.New("wd error"), ErrAfter: writeDocument}, writeDocument, errors.New("wd error"), }, { "bsoncore.Document.Elements Error", bsoncore.Document{0xFF, 0x00, 0x00, 0x00, 0x00}, nil, &valueReaderWriter{}, writeDocument, errors.New("length read exceeds number of bytes available. length=5 bytes=255"), }, { "WriteDocumentElement Error", bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))), nil, &valueReaderWriter{Err: errors.New("wde error"), ErrAfter: writeDocumentElement}, writeDocumentElement, errors.New("wde error"), }, { "encodeValue error", bsoncore.Document(buildDocument(bsoncore.AppendNullElement(nil, "foo"))), nil, &valueReaderWriter{Err: errors.New("ev error"), ErrAfter: writeNull}, writeNull, errors.New("ev error"), }, { "iterator error", bsoncore.Document{0x0C, 0x00, 0x00, 0x00, 0x01, 'f', 'o', 'o', 0x00, 0x01, 0x02, 0x03}, nil, &valueReaderWriter{}, writeDocumentElement, errors.New("not enough bytes available to read type. bytes=3 type=double"), }, }, }, { "StructEncodeValue", newStructCodec(&mapCodec{}), []subtest{ { "interface value", struct{ Foo myInterface }{Foo: myStruct{1}}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, { "nil interface value", struct{ Foo myInterface }{Foo: nil}, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, }, }, { "CodeWithScopeEncodeValue", ValueEncoderFunc(codeWithScopeEncodeValue), []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{ Name: "CodeWithScopeEncodeValue", Types: []reflect.Type{tCodeWithScope}, Received: reflect.ValueOf(wrong), }, }, { "WriteCodeWithScope error", CodeWithScope{}, nil, &valueReaderWriter{Err: errors.New("wcws error"), ErrAfter: writeCodeWithScope}, writeCodeWithScope, errors.New("wcws error"), }, { "CodeWithScope/success", CodeWithScope{ Code: "var hello = 'world';", Scope: D{}, }, &EncodeContext{Registry: buildDefaultRegistry()}, nil, writeDocumentEnd, nil, }, }, }, { "CoreArrayEncodeValue", &arrayCodec{}, []subtest{ { "wrong type", wrong, nil, nil, nothing, ValueEncoderError{ Name: "CoreArrayEncodeValue", Types: []reflect.Type{tCoreArray}, Received: reflect.ValueOf(wrong), }, }, { "WriteArray Error", bsoncore.Array{}, nil, &valueReaderWriter{Err: errors.New("wa error"), ErrAfter: writeArray}, writeArray, errors.New("wa error"), }, { "WriteArrayElement Error", bsoncore.Array(buildDocumentArray(func([]byte) []byte { return bsoncore.AppendNullElement(nil, "foo") })), nil, &valueReaderWriter{Err: errors.New("wae error"), ErrAfter: writeArrayElement}, writeArrayElement, errors.New("wae error"), }, { "encodeValue error", bsoncore.Array(buildDocumentArray(func([]byte) []byte { return bsoncore.AppendNullElement(nil, "foo") })), nil, &valueReaderWriter{Err: errors.New("ev error"), ErrAfter: writeNull}, writeNull, errors.New("ev error"), }, }, }, } 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(valueReaderWriter) if subtest.llvrw != nil { llvrw = subtest.llvrw } llvrw.T = t err := tc.ve.EncodeValue(ec, llvrw, reflect.ValueOf(subtest.val)) if !assert.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 := NewObjectID() oids := []ObjectID{NewObjectID(), NewObjectID(), NewObjectID()} 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 := ParseDecimal128("1.5e10") if err != nil { t.Errorf("Error parsing decimal128: %v", err) t.FailNow() } testCases := []struct { name string value any 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]ObjectID", map[string]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][]ObjectID", map[string][]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][]Decimal128", map[string][]Decimal128{"Z": {decimal128}}, buildDocumentArray(func(doc []byte) []byte { return bsoncore.AppendDecimal128Element(doc, "0", decimal128.h, decimal128.l) }), 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 struct pointer", struct { Foo *struct { A int64 `bson:",minsize"` } `bson:",inline"` Bar *struct { B int64 } `bson:",inline"` }{ Foo: &struct { A int64 `bson:",minsize"` }{ A: 12345, }, Bar: nil, }, buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)), nil, }, { "nested inline struct pointer", struct { Foo *struct { Bar *struct { A int64 `bson:",minsize"` } `bson:",inline"` } `bson:",inline"` }{ Foo: &struct { Bar *struct { A int64 `bson:",minsize"` } `bson:",inline"` }{ Bar: &struct { A int64 `bson:",minsize"` }{ A: 12345, }, }, }, buildDocument(bsoncore.AppendInt32Element(nil, "a", 12345)), nil, }, { "inline nil struct pointer", struct { Foo *struct { A int64 `bson:",minsize"` } `bson:",inline"` }{ Foo: nil, }, buildDocument([]byte{}), nil, }, { "inline overwrite", struct { Foo struct { A int32 B string } `bson:",inline"` A int64 }{ Foo: struct { A int32 B string }{ A: 0, B: "foo", }, A: 54321, }, buildDocument(func(doc []byte) []byte { doc = bsoncore.AppendStringElement(doc, "b", "foo") doc = bsoncore.AppendInt64Element(doc, "a", 54321) return doc }(nil)), nil, }, { "inline overwrite respects ordering", struct { A int64 Foo struct { A int32 B string } `bson:",inline"` }{ A: 54321, Foo: struct { A int32 B string }{ A: 0, B: "foo", }, }, buildDocument(func(doc []byte) []byte { doc = bsoncore.AppendInt64Element(doc, "a", 54321) doc = bsoncore.AppendStringElement(doc, "b", "foo") return doc }(nil)), nil, }, { "inline overwrite with nested structs", struct { Foo struct { A int32 } `bson:",inline"` Bar struct { A int32 } `bson:",inline"` A int64 }{ Foo: struct { A int32 }{}, Bar: struct { A int32 }{}, A: 54321, }, buildDocument(bsoncore.AppendInt64Element(nil, "a", 54321)), 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 ObjectID T []struct{} Y json.Number Z time.Time AA json.Number AB *url.URL AC Decimal128 AD *time.Time AE testValueMarshaler AF map[string]any AG 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: TypeString, buf: bsoncore.AppendString(nil, "hello, world")}, AF: nil, AG: CodeWithScope{Code: "var hello = 'world';", Scope: 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.h, decimal128.l) doc = bsoncore.AppendDateTimeElement(doc, "ad", now.UnixNano()/int64(time.Millisecond)) doc = bsoncore.AppendStringElement(doc, "ae", "hello, world") doc = bsoncore.AppendNullElement(doc, "af") doc = bsoncore.AppendCodeWithScopeElement(doc, "ag", "var hello = 'world';", buildDocument(bsoncore.AppendDoubleElement(nil, "pi", 3.14159)), ) return doc }(nil)), nil, }, { "struct{[]any}", 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 []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 []Decimal128 AD []*time.Time AE []testValueMarshaler }{ 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: []Decimal128{decimal128}, AD: []*time.Time{&now, &now}, AE: []testValueMarshaler{ {t: TypeString, buf: bsoncore.AppendString(nil, "hello")}, {t: TypeString, buf: bsoncore.AppendString(nil, "world")}, }, }, 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", bsoncore.BuildDocumentElement(nil, "0", bsoncore.AppendStringElement(nil, "foo", "bar"))) doc = appendArrayElement(doc, "i", bsoncore.AppendBinaryElement(nil, "0", 0x00, []byte{0x01, 0x02, 0x03})) doc = appendArrayElement(doc, "k", appendArrayElement(nil, "0", bsoncore.AppendStringElement(bsoncore.AppendStringElement(nil, "0", "baz"), "1", "qux")), ) doc = appendArrayElement(doc, "l", bsoncore.BuildDocumentElement(nil, "0", bsoncore.AppendStringElement(nil, "m", "foobar"))) doc = appendArrayElement(doc, "n", appendArrayElement(nil, "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", bsoncore.BuildDocumentElement(nil, "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.h, decimal128.l)) 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"), ) return doc }(nil)), nil, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { b := make(sliceWriter, 0, 512) vw := NewDocumentWriter(&b) 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 !errors.Is(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("error path", func(t *testing.T) { testCases := []struct { name string value any err error }{ { "duplicate name struct", struct { A int64 B int64 `bson:"a"` }{ A: 0, B: 54321, }, fmt.Errorf("duplicated key a"), }, { "inline map", struct { Foo map[string]string `bson:",inline"` Baz string }{ Foo: map[string]string{"baz": "bar"}, Baz: "hi", }, fmt.Errorf("Key baz of inlined map conflicts with a struct field name"), }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { b := make(sliceWriter, 0, 512) vw := NewDocumentWriter(&b) 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 == nil || !strings.Contains(err.Error(), tc.err.Error()) { t.Errorf("Did not receive expected error. got %v; want %v", err, tc.err) } }) } }) t.Run("EmptyInterfaceEncodeValue/nil", func(t *testing.T) { val := reflect.New(tEmpty).Elem() llvrw := new(valueReaderWriter) err := (&emptyInterfaceCodec{}).EncodeValue(EncodeContext{Registry: newTestRegistry()}, llvrw, val) noerr(t, err) if llvrw.invoked != writeNull { t.Errorf("Incorrect method called. got %v; want %v", llvrw.invoked, writeNull) } }) t.Run("EmptyInterfaceEncodeValue/LookupEncoder error", func(t *testing.T) { val := reflect.New(tEmpty).Elem() val.Set(reflect.ValueOf(int64(1234567890))) llvrw := new(valueReaderWriter) got := (&emptyInterfaceCodec{}).EncodeValue(EncodeContext{Registry: newTestRegistry()}, llvrw, val) want := errNoEncoder{Type: tInt64} if !assert.CompareErrors(got, want) { t.Errorf("Did not receive expected error. got %v; want %v", got, want) } }) } type testValueMarshalPtr struct { t Type buf []byte err error } func (tvm *testValueMarshalPtr) MarshalBSONValue() (byte, []byte, error) { return byte(tvm.t), tvm.buf, tvm.err } type testMarshalPtr struct { buf []byte err error } func (tvm *testMarshalPtr) MarshalBSON() ([]byte, error) { return tvm.buf, tvm.err } ================================================ FILE: bson/doc.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 // Package bson is a library for reading, writing, and manipulating BSON. BSON is a binary serialization // format used to store documents and make remote procedure calls in MongoDB. For more information about // the Go BSON library, including usage examples, check out the [Work with BSON] page in the Go Driver // docs site. For more information about BSON, see https://bsonspec.org. // // # Native Go Types // // The [D] and [M] types defined in this package can be used to build representations of BSON using native Go types. D is a // slice and M is a map. For more information about the use cases for these types, see the documentation on the type // definitions. // // Note that a D should not be constructed with duplicate key names, as that can cause undefined server behavior. // // Example: // // bson.D{{"foo", "bar"}, {"hello", "world"}, {"pi", 3.14159}} // bson.M{"foo": "bar", "hello": "world", "pi": 3.14159} // // When decoding BSON to a D or M, the following type mappings apply when unmarshaling: // // 1. BSON int32 unmarshals to an int32. // 2. BSON int64 unmarshals to an int64. // 3. BSON double unmarshals to a float64. // 4. BSON string unmarshals to a string. // 5. BSON boolean unmarshals to a bool. // 6. BSON embedded document unmarshals to the parent type (i.e. D for a D, M for an M). // 7. BSON array unmarshals to a bson.A. // 8. BSON ObjectId unmarshals to a bson.ObjectID. // 9. BSON datetime unmarshals to a bson.DateTime. // 10. BSON binary unmarshals to a bson.Binary. // 11. BSON regular expression unmarshals to a bson.Regex. // 12. BSON JavaScript unmarshals to a bson.JavaScript. // 13. BSON code with scope unmarshals to a bson.CodeWithScope. // 14. BSON timestamp unmarshals to an bson.Timestamp. // 15. BSON 128-bit decimal unmarshals to an bson.Decimal128. // 16. BSON min key unmarshals to an bson.MinKey. // 17. BSON max key unmarshals to an bson.MaxKey. // 18. BSON undefined unmarshals to a bson.Undefined. // 19. BSON null unmarshals to nil. // 20. BSON DBPointer unmarshals to a bson.DBPointer. // 21. BSON symbol unmarshals to a bson.Symbol. // // The above mappings also apply when marshaling a D or M to BSON. Some other useful marshaling mappings are: // // 1. time.Time marshals to a BSON datetime. // 2. int8, int16, and int32 marshal to a BSON int32. // 3. int marshals to a BSON int32 if the value is between math.MinInt32 and math.MaxInt32, inclusive, and a BSON int64 // otherwise. // 4. int64 marshals to BSON int64 (unless [Encoder.IntMinSize] is set). // 5. uint8 and uint16 marshal to a BSON int32. // 6. uint, uint32, and uint64 marshal to a BSON int64 (unless [Encoder.IntMinSize] is set). // 7. BSON null and undefined values will unmarshal into the zero value of a field (e.g. unmarshaling a BSON null or // undefined value into a string will yield the empty string.). // // # Structs // // Structs can be marshaled/unmarshaled to/from BSON or Extended JSON. When transforming structs to/from BSON or Extended // JSON, the following rules apply: // // 1. Only exported fields in structs will be marshaled or unmarshaled. // // 2. When marshaling a struct, each field will be lowercased to generate the key for the corresponding BSON element. // For example, a struct field named "Foo" will generate key "foo". This can be overridden via a struct tag (e.g. // `bson:"fooField"` to generate key "fooField" instead). // // 3. An embedded struct field is marshaled as a subdocument. The key will be the lowercased name of the field's type. // // 4. A pointer field is marshaled as the underlying type if the pointer is non-nil. If the pointer is nil, it is // marshaled as a BSON null value. // // 5. When unmarshaling, a field of type any will follow the D/M type mappings listed above. BSON documents // unmarshaled into an any field will be unmarshaled as a D. // // The encoding of each struct field can be customized by the "bson" struct tag. The "bson" tag gives the name of the // field, followed by a comma-separated list of options. The name may be omitted in order to specify options without // overriding the default field name. The following options can be used to configure behavior: // // 1. omitempty: If the "omitempty" struct tag is specified on a field, the field will not be marshaled if it is set to // an "empty" value. Numbers, booleans, and strings are considered empty if their value is equal to the zero value for // the type (i.e. 0 for numbers, false for booleans, and "" for strings). Slices, maps, and arrays are considered // empty if they are of length zero. Interfaces and pointers are considered empty if their value is nil. By default, // structs are only considered empty if the struct type implements [Zeroer] and the "IsZero" // method returns true. Struct types that do not implement [Zeroer] are never considered empty and will be // marshaled as embedded documents. NOTE: It is recommended that this tag be used for all slice and map fields. // // 2. minsize: If the minsize struct tag is specified on a field of type int64, uint, uint32, or uint64 and the value of // the field can fit in a signed int32, the field will be serialized as a BSON int32 rather than a BSON int64. For // other types, this tag is ignored. // // 3. truncate: If the truncate struct tag is specified on a field with a non-float numeric type, BSON doubles // unmarshaled into that field will be truncated at the decimal point. For example, if 3.14 is unmarshaled into a // field of type int, it will be unmarshaled as 3. If this tag is not specified, the decoder will throw an error if // the value cannot be decoded without losing precision. For float64 or non-numeric types, this tag is ignored. // // 4. inline: If the inline struct tag is specified for a struct or map field, the field will be "flattened" when // marshaling and "un-flattened" when unmarshaling. This means that all of the fields in that struct/map will be // pulled up one level and will become top-level fields rather than being fields in a nested document. For example, // if a map field named "Map" with value map[string]any{"foo": "bar"} is inlined, the resulting document will // be {"foo": "bar"} instead of {"map": {"foo": "bar"}}. There can only be one inlined map field in a struct. If // there are duplicated fields in the resulting document when an inlined struct is marshaled, the inlined field will // be overwritten. If there are duplicated fields in the resulting document when an inlined map is marshaled, an // error will be returned. This tag can be used with fields that are pointers to structs. If an inlined pointer field // is nil, it will not be marshaled. For fields that are not maps or structs, this tag is ignored. // // # Raw BSON // // The Raw family of types is used to validate and retrieve elements from a slice of bytes. This // type is most useful when you want do lookups on BSON bytes without unmarshaling it into another // type. // // Example: // // var raw bson.Raw = ... // bytes from somewhere // err := raw.Validate() // if err != nil { return err } // val := raw.Lookup("foo") // i32, ok := val.Int32OK() // // do something with i32... // // # Custom Registry // // The Go BSON library uses a [Registry] to define encoding and decoding behavior for different data types. // The default encoding and decoding behavior can be customized or extended by using a modified Registry. // The custom registry system is composed of two parts: // // 1) [ValueEncoder] and [ValueDecoder] 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. // // To use a custom Registry, use [Encoder.SetRegistry] or [Decoder.SetRegistry]. // // [Work with BSON]: https://www.mongodb.com/docs/drivers/go/current/fundamentals/bson/ package bson ================================================ FILE: bson/empty_interface_codec.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" ) // emptyInterfaceCodec is the Codec used for any values. type emptyInterfaceCodec struct { // decodeBinaryAsSlice causes DecodeValue to unmarshal BSON binary field values that are the // "Generic" or "Old" BSON binary subtype as a Go byte slice instead of a Binary. decodeBinaryAsSlice bool } // Assert that emptyInterfaceCodec satisfies the typeDecoder interface, which allows it // to be used by collection type decoders (e.g. map, slice, etc) to set individual values in a // collection. var _ typeDecoder = &emptyInterfaceCodec{} // EncodeValue is the ValueEncoderFunc for any. func (eic *emptyInterfaceCodec) EncodeValue(ec EncodeContext, vw ValueWriter, val reflect.Value) error { if !val.IsValid() || val.Type() != tEmpty { return ValueEncoderError{Name: "EmptyInterfaceEncodeValue", Types: []reflect.Type{tEmpty}, Received: val} } if val.IsNil() { return vw.WriteNull() } encoder, err := ec.LookupEncoder(val.Elem().Type()) if err != nil { return err } return encoder.EncodeValue(ec, vw, val.Elem()) } func (eic *emptyInterfaceCodec) getEmptyInterfaceDecodeType(dc DecodeContext, valueType Type) (reflect.Type, error) { isDocument := valueType == Type(0) || valueType == TypeEmbeddedDocument if isDocument { if dc.defaultDocumentType != nil { // If the bsontype is an embedded document and the DocumentType is set on the DecodeContext, then return // that type. return dc.defaultDocumentType, nil } } rtype, err := dc.LookupTypeMapEntry(valueType) if err == nil { return rtype, nil } if isDocument { // For documents, fallback to looking up a type map entry for Type(0) or TypeEmbeddedDocument, // depending on the original valueType. var lookupType Type switch valueType { case Type(0): lookupType = TypeEmbeddedDocument case TypeEmbeddedDocument: lookupType = Type(0) } rtype, err = dc.LookupTypeMapEntry(lookupType) if err == nil { return rtype, nil } // fallback to bson.D return tD, nil } return nil, err } func (eic *emptyInterfaceCodec) decodeType(dc DecodeContext, vr ValueReader, t reflect.Type) (reflect.Value, error) { if t != tEmpty { return emptyValue, ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: reflect.Zero(t)} } rtype, err := eic.getEmptyInterfaceDecodeType(dc, vr.Type()) if err != nil { switch vr.Type() { case TypeNull: return reflect.Zero(t), vr.ReadNull() default: return emptyValue, err } } decoder, err := dc.LookupDecoder(rtype) if err != nil { return emptyValue, err } elem, err := decodeTypeOrValueWithInfo(decoder, dc, vr, rtype) if err != nil { return emptyValue, err } if (eic.decodeBinaryAsSlice || dc.binaryAsSlice) && rtype == tBinary { binElem := elem.Interface().(Binary) if binElem.Subtype == TypeBinaryGeneric || binElem.Subtype == TypeBinaryBinaryOld { elem = reflect.ValueOf(binElem.Data) } } return elem, nil } // DecodeValue is the ValueDecoderFunc for any. func (eic *emptyInterfaceCodec) DecodeValue(dc DecodeContext, vr ValueReader, val reflect.Value) error { if !val.CanSet() || val.Type() != tEmpty { return ValueDecoderError{Name: "EmptyInterfaceDecodeValue", Types: []reflect.Type{tEmpty}, Received: val} } elem, err := eic.decodeType(dc, vr, val.Type()) if err != nil { return err } val.Set(elem) return nil } ================================================ FILE: bson/encoder.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "sync" ) // 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() any { return new(Encoder) }, } // An Encoder writes a serialization format to an output stream. It writes to a ValueWriter // as the destination of BSON data. type Encoder struct { ec EncodeContext vw ValueWriter } // NewEncoder returns a new encoder that writes to vw. func NewEncoder(vw ValueWriter) *Encoder { return &Encoder{ ec: EncodeContext{Registry: defaultRegistry}, vw: vw, } } // Encode writes the BSON encoding of val to the stream. // // See [Marshal] for details about BSON marshaling behavior. func (e *Encoder) Encode(val any) 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 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 ValueWriter) { e.vw = vw } // SetRegistry replaces the current registry of the Encoder with r. func (e *Encoder) SetRegistry(r *Registry) { e.ec.Registry = r } // ErrorOnInlineDuplicates causes the Encoder to return an error if there is a duplicate field in // the marshaled BSON when the "inline" struct tag option is set. func (e *Encoder) ErrorOnInlineDuplicates() { e.ec.errorOnInlineDuplicates = true } // IntMinSize causes the Encoder to marshal Go integer values (int, int8, int16, int32, int64, uint, // uint8, uint16, uint32, or uint64) as the minimum BSON int size (either 32 or 64 bits) that can // represent the integer value. func (e *Encoder) IntMinSize() { e.ec.minSize = true } // StringifyMapKeysWithFmt causes the Encoder to convert Go map keys to BSON document field name // strings using fmt.Sprint instead of the default string conversion logic. func (e *Encoder) StringifyMapKeysWithFmt() { e.ec.stringifyMapKeysWithFmt = true } // NilMapAsEmpty causes the Encoder to marshal nil Go maps as empty BSON documents instead of BSON // null. func (e *Encoder) NilMapAsEmpty() { e.ec.nilMapAsEmpty = true } // NilSliceAsEmpty causes the Encoder to marshal nil Go slices as empty BSON arrays instead of BSON // null. func (e *Encoder) NilSliceAsEmpty() { e.ec.nilSliceAsEmpty = true } // NilByteSliceAsEmpty causes the Encoder to marshal nil Go byte slices as empty BSON binary values // instead of BSON null. func (e *Encoder) NilByteSliceAsEmpty() { e.ec.nilByteSliceAsEmpty = true } // TODO(GODRIVER-2820): Update the description to remove the note about only examining exported // TODO struct fields once the logic is updated to also inspect private struct fields. // OmitZeroStruct causes the Encoder to consider the zero value for a struct (e.g. MyStruct{}) // as empty and omit it from the marshaled BSON when the "omitempty" struct tag option is set // or the OmitEmpty() method is called. // // Note that the Encoder only examines exported struct fields when determining if a struct is the // zero value. It considers pointers to a zero struct value (e.g. &MyStruct{}) not empty. func (e *Encoder) OmitZeroStruct() { e.ec.omitZeroStruct = true } // OmitEmpty causes the Encoder to omit empty values from the marshaled BSON as the "omitempty" // struct tag option is set. func (e *Encoder) OmitEmpty() { e.ec.omitEmpty = true } // UseJSONStructTags causes the Encoder to fall back to using the "json" struct tag if a "bson" // struct tag is not specified. func (e *Encoder) UseJSONStructTags() { e.ec.useJSONStructTags = true } ================================================ FILE: bson/encoder_example_test.go ================================================ // Copyright (C) MongoDB, Inc. 2023-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package bson_test import ( "bytes" "errors" "fmt" "io" "go.mongodb.org/mongo-driver/v2/bson" ) func ExampleEncoder() { // Create an Encoder that writes BSON values to a bytes.Buffer. buf := new(bytes.Buffer) vw := bson.NewDocumentWriter(buf) encoder := bson.NewEncoder(vw) type Product struct { Name string `bson:"name"` SKU string `bson:"sku"` Price int64 `bson:"price_cents"` } // Use the Encoder to marshal a BSON document that contains the name, SKU, // and price (in cents) of a product. product := Product{ Name: "Cereal Rounds", SKU: "AB12345", Price: 399, } err := encoder.Encode(product) if err != nil { panic(err) } // Print the BSON document as Extended JSON by converting it to bson.Raw. fmt.Println(bson.Raw(buf.Bytes()).String()) // Output: {"name": "Cereal Rounds","sku": "AB12345","price_cents": {"$numberLong":"399"}} } type CityState struct { City string State string } func (k CityState) String() string { return fmt.Sprintf("%s, %s", k.City, k.State) } func ExampleEncoder_StringifyMapKeysWithFmt() { // Create an Encoder that writes BSON values to a bytes.Buffer. buf := new(bytes.Buffer) vw := bson.NewDocumentWriter(buf) encoder := bson.NewEncoder(vw) // Configure the Encoder to convert Go map keys to BSON document field names // using fmt.Sprintf instead of the default string conversion logic. encoder.StringifyMapKeysWithFmt() // Use the Encoder to marshal a BSON document that contains is a map of // city and state to a list of zip codes in that city. zipCodes := map[CityState][]int{ {City: "New York", State: "NY"}: {10001, 10301, 10451}, } err := encoder.Encode(zipCodes) if err != nil { panic(err) } // Print the BSON document as Extended JSON by converting it to bson.Raw. fmt.Println(bson.Raw(buf.Bytes()).String()) // Output: {"New York, NY": [{"$numberInt":"10001"},{"$numberInt":"10301"},{"$numberInt":"10451"}]} } func ExampleEncoder_UseJSONStructTags() { // Create an Encoder that writes BSON values to a bytes.Buffer. buf := new(bytes.Buffer) vw := bson.NewDocumentWriter(buf) encoder := bson.NewEncoder(vw) type Product struct { Name string `json:"name"` SKU string `json:"sku"` Price int64 `json:"price_cents"` } // Configure the Encoder to use "json" struct tags when decoding if "bson" // struct tags are not present. encoder.UseJSONStructTags() // Use the Encoder to marshal a BSON document that contains the name, SKU, // and price (in cents) of a product. product := Product{ Name: "Cereal Rounds", SKU: "AB12345", Price: 399, } err := encoder.Encode(product) if err != nil { panic(err) } // Print the BSON document as Extended JSON by converting it to bson.Raw. fmt.Println(bson.Raw(buf.Bytes()).String()) // Output: {"name": "Cereal Rounds","sku": "AB12345","price_cents": {"$numberLong":"399"}} } func ExampleEncoder_multipleBSONDocuments() { // Create an Encoder that writes BSON values to a bytes.Buffer. buf := new(bytes.Buffer) vw := bson.NewDocumentWriter(buf) encoder := bson.NewEncoder(vw) type Coordinate struct { X int Y int } // Use the encoder to marshal 5 Coordinate values as a sequence of BSON // documents. for i := 0; i < 5; i++ { err := encoder.Encode(Coordinate{ X: i, Y: i + 1, }) if err != nil { panic(err) } } // Read each marshaled BSON document from the buffer and print them as // Extended JSON by converting them to bson.Raw. for { doc, err := bson.ReadDocument(buf) if errors.Is(err, io.EOF) { return } if err != nil { panic(err) } fmt.Println(doc.String()) } // Output: // {"x": {"$numberInt":"0"},"y": {"$numberInt":"1"}} // {"x": {"$numberInt":"1"},"y": {"$numberInt":"2"}} // {"x": {"$numberInt":"2"},"y": {"$numberInt":"3"}} // {"x": {"$numberInt":"3"},"y": {"$numberInt":"4"}} // {"x": {"$numberInt":"4"},"y": {"$numberInt":"5"}} } func ExampleEncoder_extendedJSON() { // Create an Encoder that writes canonical Extended JSON values to a // bytes.Buffer. buf := new(bytes.Buffer) vw := bson.NewExtJSONValueWriter(buf, true, false) encoder := bson.NewEncoder(vw) type Product struct { Name string `bson:"name"` SKU string `bson:"sku"` Price int64 `bson:"price_cents"` } // Use the Encoder to marshal a BSON document that contains the name, SKU, // and price (in cents) of a product. product := Product{ Name: "Cereal Rounds", SKU: "AB12345", Price: 399, } err := encoder.Encode(product) if err != nil { panic(err) } fmt.Println(buf.String()) // Output: {"name":"Cereal Rounds","sku":"AB12345","price_cents":{"$numberLong":"399"}} } func ExampleEncoder_multipleExtendedJSONDocuments() { // Create an Encoder that writes canonical Extended JSON values to a // bytes.Buffer. buf := new(bytes.Buffer) vw := bson.NewExtJSONValueWriter(buf, true, false) encoder := bson.NewEncoder(vw) type Coordinate struct { X int Y int } // Use the encoder to marshal 5 Coordinate values as a sequence of Extended // JSON documents. for i := 0; i < 5; i++ { err := encoder.Encode(Coordinate{ X: i, Y: i + 1, }) if err != nil { panic(err) } } fmt.Println(buf.String()) // Output: // {"x":{"$numberInt":"0"},"y":{"$numberInt":"1"}} // {"x":{"$numberInt":"1"},"y":{"$numberInt":"2"}} // {"x":{"$numberInt":"2"},"y":{"$numberInt":"3"}} // {"x":{"$numberInt":"3"},"y":{"$numberInt":"4"}} // {"x":{"$numberInt":"4"},"y":{"$numberInt":"5"}} } func ExampleEncoder_IntMinSize() { // Create an encoder that will marshal integers as the minimum BSON int size // (either 32 or 64 bits) that can represent the integer value. type foo struct { Bar uint32 } buf := new(bytes.Buffer) vw := bson.NewDocumentWriter(buf) enc := bson.NewEncoder(vw) enc.IntMinSize() err := enc.Encode(foo{2}) if err != nil { panic(err) } fmt.Println(bson.Raw(buf.Bytes()).String()) // Output: // {"bar": {"$numberInt":"2"}} } ================================================ FILE: bson/encoder_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "time" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" ) func TestBasicEncode(t *testing.T) { for _, tc := range marshalingTestCases { t.Run(tc.name, func(t *testing.T) { got := make(sliceWriter, 0, 1024) vw := NewDocumentWriter(&got) reg := defaultRegistry encoder, err := reg.LookupEncoder(reflect.TypeOf(tc.val)) noerr(t, err) err = encoder.EncodeValue(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(sliceWriter, 0, 1024) vw := NewDocumentWriter(&got) enc := NewEncoder(vw) 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 ValueWriter }{ { "error", nil, errors.New("Marshaler error"), errors.New("Marshaler error"), &valueReaderWriter{}, }, { "copy error", []byte{0x05, 0x00, 0x00, 0x00, 0x00}, nil, errors.New("copy error"), &valueReaderWriter{Err: errors.New("copy error"), ErrAfter: 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 ValueWriter b := make(sliceWriter, 0, 100) compareVW := false if tc.vw != nil { vw = tc.vw } else { compareVW = true vw = NewDocumentWriter(&b) } enc := NewEncoder(vw) got := enc.Encode(marshaler) want := tc.wanterr if !assert.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 any) []byte { b, err := Marshal(d) if err != nil { panic(err) } return b } type stringerTest struct{} func (stringerTest) String() string { return "test key" } func TestEncoderConfiguration(t *testing.T) { type inlineDuplicateInner struct { Duplicate string } type inlineDuplicateOuter struct { Inline inlineDuplicateInner `bson:",inline"` Duplicate string } type zeroStruct struct { MyString string } testCases := []struct { description string configure func(*Encoder) input any want []byte wantErr error }{ // Test that ErrorOnInlineDuplicates causes the Encoder to return an error if there are any // duplicate fields in the marshaled document caused by using the "inline" struct tag. { description: "ErrorOnInlineDuplicates", configure: func(enc *Encoder) { enc.ErrorOnInlineDuplicates() }, input: inlineDuplicateOuter{ Inline: inlineDuplicateInner{Duplicate: "inner"}, Duplicate: "outer", }, wantErr: errors.New("struct bson.inlineDuplicateOuter has duplicated key duplicate"), }, // Test that IntMinSize encodes Go int and int64 values as BSON int32 if the value is small // enough. { description: "IntMinSize", configure: func(enc *Encoder) { enc.IntMinSize() }, input: D{ {Key: "myInt", Value: int(1)}, {Key: "myInt64", Value: int64(1)}, {Key: "myUint", Value: uint(1)}, {Key: "myUint32", Value: uint32(1)}, {Key: "myUint64", Value: uint64(1)}, }, want: bsoncore.NewDocumentBuilder(). AppendInt32("myInt", 1). AppendInt32("myInt64", 1). AppendInt32("myUint", 1). AppendInt32("myUint32", 1). AppendInt32("myUint64", 1). Build(), }, // Test that StringifyMapKeysWithFmt uses fmt.Sprint to convert map keys to BSON field names. { description: "StringifyMapKeysWithFmt", configure: func(enc *Encoder) { enc.StringifyMapKeysWithFmt() }, input: map[stringerTest]string{ {}: "test value", }, want: bsoncore.NewDocumentBuilder(). AppendString("test key", "test value"). Build(), }, // Test that NilMapAsEmpty encodes nil Go maps as empty BSON documents. { description: "NilMapAsEmpty", configure: func(enc *Encoder) { enc.NilMapAsEmpty() }, input: D{{Key: "myMap", Value: map[string]string(nil)}}, want: bsoncore.NewDocumentBuilder(). AppendDocument("myMap", bsoncore.NewDocumentBuilder().Build()). Build(), }, // Test that NilSliceAsEmpty encodes nil Go slices as empty BSON arrays. { description: "NilSliceAsEmpty", configure: func(enc *Encoder) { enc.NilSliceAsEmpty() }, input: D{{Key: "mySlice", Value: []string(nil)}}, want: bsoncore.NewDocumentBuilder(). AppendArray("mySlice", bsoncore.NewArrayBuilder().Build()). Build(), }, // Test that NilByteSliceAsEmpty encodes nil Go byte slices as empty BSON binary elements. { description: "NilByteSliceAsEmpty", configure: func(enc *Encoder) { enc.NilByteSliceAsEmpty() }, input: D{{Key: "myBytes", Value: []byte(nil)}}, want: bsoncore.NewDocumentBuilder(). AppendBinary("myBytes", TypeBinaryGeneric, []byte{}). Build(), }, // Test that OmitZeroStruct omits empty structs from the marshaled document if the // "omitempty" struct tag is used. { description: "OmitZeroStruct", configure: func(enc *Encoder) { enc.OmitZeroStruct() }, input: struct { Zero zeroStruct `bson:",omitempty"` }{}, want: bsoncore.NewDocumentBuilder().Build(), }, // Test that OmitZeroStruct omits empty structs from the marshaled document if // OmitEmpty is also set. { description: "OmitEmpty with non-zeroer struct", configure: func(enc *Encoder) { enc.OmitZeroStruct() enc.OmitEmpty() }, input: struct { Zero zeroStruct }{}, want: bsoncore.NewDocumentBuilder().Build(), }, // Test that OmitEmpty omits empty values from the marshaled document. { description: "OmitEmpty", configure: func(enc *Encoder) { enc.OmitEmpty() }, input: struct { Zero zeroTest I64 int64 F64 float64 String string Boolean bool Slice []int Array [0]int Map map[string]int Bytes []byte Time time.Time Pointer *int }{ Zero: zeroTest{true}, }, want: bsoncore.NewDocumentBuilder().Build(), }, // Test that UseJSONStructTags causes the Encoder to fall back to "json" struct tags if // "bson" struct tags are not available. { description: "UseJSONStructTags", configure: func(enc *Encoder) { enc.UseJSONStructTags() }, input: struct { StructFieldName string `json:"jsonFieldName"` }{ StructFieldName: "test value", }, want: bsoncore.NewDocumentBuilder(). AppendString("jsonFieldName", "test value"). Build(), }, } for _, tc := range testCases { tc := tc // Capture range variable. t.Run(tc.description, func(t *testing.T) { t.Parallel() got := new(bytes.Buffer) vw := NewDocumentWriter(got) enc := NewEncoder(vw) tc.configure(enc) err := enc.Encode(tc.input) if tc.wantErr != nil { assert.Equal(t, tc.wantErr, err, "expected and actual errors do not match") return } require.NoError(t, err, "Encode error") assert.Equal(t, tc.want, got.Bytes(), "expected and actual encoded BSON do not match") // After we compare the raw bytes, also decode the expected and actual BSON as a bson.D // and compare them. The goal is to make assertion failures easier to debug because // binary diffs are very difficult to understand. var wantDoc D err = Unmarshal(tc.want, &wantDoc) require.NoError(t, err, "Unmarshal error") var gotDoc D err = Unmarshal(got.Bytes(), &gotDoc) require.NoError(t, err, "Unmarshal error") assert.Equal(t, wantDoc, gotDoc, "expected and actual decoded documents do not match") }) } } ================================================ FILE: bson/example_test.go ================================================ // Copyright (C) MongoDB, Inc. 2023-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package bson_test import ( "fmt" "time" "go.mongodb.org/mongo-driver/v2/bson" ) // This example uses Raw to skip parsing a nested document in a BSON message. func ExampleRaw_unmarshal() { b, err := bson.Marshal(bson.M{ "Word": "beach", "Synonyms": bson.A{"coast", "shore", "waterfront"}, }) if err != nil { panic(err) } var res struct { Word string Synonyms bson.Raw // Don't parse the whole list, we just want to count the elements. } err = bson.Unmarshal(b, &res) if err != nil { panic(err) } elems, err := res.Synonyms.Elements() if err != nil { panic(err) } fmt.Printf("%s, synonyms count: %d\n", res.Word, len(elems)) // Output: beach, synonyms count: 3 } // This example uses Raw to add a precomputed BSON document during marshal. func ExampleRaw_marshal() { precomputed, err := bson.Marshal(bson.M{"Precomputed": true}) if err != nil { panic(err) } msg := struct { Message string Metadata bson.Raw }{ Message: "Hello World!", Metadata: precomputed, } b, err := bson.Marshal(msg) if err != nil { panic(err) } // Print the Extended JSON by converting BSON to bson.Raw. fmt.Println(bson.Raw(b).String()) // Output: {"message": "Hello World!","metadata": {"Precomputed": true}} } // This example uses RawValue to delay parsing a value in a BSON message. func ExampleRawValue_unmarshal() { b1, err := bson.Marshal(bson.M{ "Format": "UNIX", "Timestamp": 1675282389, }) if err != nil { panic(err) } b2, err := bson.Marshal(bson.M{ "Format": "RFC3339", "Timestamp": time.Unix(1675282389, 0).Format(time.RFC3339), }) if err != nil { panic(err) } for _, b := range [][]byte{b1, b2} { var res struct { Format string Timestamp bson.RawValue // Delay parsing until we know the timestamp format. } err = bson.Unmarshal(b, &res) if err != nil { panic(err) } var t time.Time switch res.Format { case "UNIX": t = time.Unix(res.Timestamp.AsInt64(), 0) case "RFC3339": t, err = time.Parse(time.RFC3339, res.Timestamp.StringValue()) if err != nil { panic(err) } } fmt.Println(res.Format, t.Unix()) } // Output: // UNIX 1675282389 // RFC3339 1675282389 } // This example uses RawValue to add a precomputed BSON string value during marshal. func ExampleRawValue_marshal() { t, val, err := bson.MarshalValue("Precomputed message!") if err != nil { panic(err) } precomputed := bson.RawValue{ Type: t, Value: val, } msg := struct { Message bson.RawValue Time time.Time }{ Message: precomputed, Time: time.Unix(1675282389, 0), } b, err := bson.Marshal(msg) if err != nil { panic(err) } // Print the Extended JSON by converting BSON to bson.Raw. fmt.Println(bson.Raw(b).String()) // Output: {"message": "Precomputed message!","time": {"$date":{"$numberLong":"1675282389000"}}} } ================================================ FILE: bson/extjson_parser.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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/base64" "encoding/hex" "errors" "fmt" "io" "strings" ) 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 Type v any } type extJSONObject struct { keys []string values []*extJSONValue } type extJSONParser struct { js *jsonScanner s jsonParseState m []jsonParseMode k string v *extJSONValue err error canonicalOnly bool depth int maxDepth int emptyObject bool relaxedUUID bool } // newExtJSONParser returns a new extended JSON parser, ready to to begin // parsing from the first character of the argued json input. It will not // perform any read-ahead and will therefore not report any errors about // malformed JSON at this point. func newExtJSONParser(r io.Reader, canonicalOnly bool) *extJSONParser { return &extJSONParser{ js: &jsonScanner{r: r}, s: jpsStartState, m: []jsonParseMode{}, canonicalOnly: canonicalOnly, maxDepth: maxNestingDepth, } } // peekType examines the next value and returns its BSON Type func (ejp *extJSONParser) peekType() (Type, error) { var t Type var err error initialState := ejp.s ejp.advanceState() switch ejp.s { case jpsSawValue: t = ejp.v.t case jpsSawBeginArray: t = TypeArray 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 = TypeEmbeddedDocument ejp.emptyObject = true case jpsInvalidState: err = ejp.err case jpsSawKey: if initialState == jpsStartState { return TypeEmbeddedDocument, nil } t = wrapperKeyBSONType(ejp.k) // if $uuid is encountered, parse as binary subtype 4 if ejp.k == "$uuid" { ejp.relaxedUUID = true t = TypeBinary } switch t { case TypeJavaScript: // just saw $code, need to check for $scope at same level _, err = ejp.readValue(TypeJavaScript) if err != nil { break } switch ejp.s { case jpsSawEndObject: // type is TypeJavaScript case jpsSawComma: ejp.advanceState() if ejp.s == jpsSawKey && ejp.k == "$scope" { t = TypeCodeWithScope } else { err = fmt.Errorf("invalid extended JSON: unexpected key %s in CodeWithScope object", ejp.k) } case jpsInvalidState: err = ejp.err default: err = ErrInvalidJSON } case TypeCodeWithScope: err = errors.New("invalid extended JSON: code with $scope must contain $code before $scope") } } } return t, err } // readKey parses the next key and its type and returns them func (ejp *extJSONParser) readKey() (string, 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 Type) (*extJSONValue, error) { if ejp.s == jpsInvalidState { return nil, ejp.err } var v *extJSONValue switch t { case TypeNull, TypeBoolean, TypeString: if ejp.s != jpsSawValue { return nil, invalidRequestError(t.String()) } v = ejp.v case TypeInt32, TypeInt64, TypeDouble: // relaxed version allows these to be literal number values if ejp.s == jpsSawValue { v = ejp.v break } fallthrough case TypeDecimal128, TypeSymbol, TypeObjectID, TypeMinKey, TypeMaxKey, TypeUndefined: 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 TypeBinary, TypeRegex, TypeTimestamp, TypeDBPointer: 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 == TypeBinary && ejp.s == jpsSawValue { // convert relaxed $uuid format if ejp.relaxedUUID { defer func() { ejp.relaxedUUID = false }() uuid, err := ejp.v.parseSymbol() if err != nil { return nil, err } // RFC 4122 defines the length of a UUID as 36 and the hyphens in a UUID as appearing // in the 8th, 13th, 18th, and 23rd characters. // // See https://tools.ietf.org/html/rfc4122#section-3 valid := len(uuid) == 36 && string(uuid[8]) == "-" && string(uuid[13]) == "-" && string(uuid[18]) == "-" && string(uuid[23]) == "-" if !valid { return nil, fmt.Errorf("$uuid value does not follow RFC 4122 format regarding length and hyphens") } // remove hyphens uuidNoHyphens := strings.ReplaceAll(uuid, "-", "") if len(uuidNoHyphens) != 32 { return nil, fmt.Errorf("$uuid value does not follow RFC 4122 format regarding length and hyphens") } // convert hex to bytes bytes, err := hex.DecodeString(uuidNoHyphens) if err != nil { return nil, fmt.Errorf("$uuid value does not follow RFC 4122 format regarding hex bytes: %w", err) } ejp.advanceState() if ejp.s != jpsSawEndObject { return nil, invalidJSONErrorForType("$uuid and value and then }", TypeBinary) } base64 := &extJSONValue{ t: TypeString, v: base64.StdEncoding.EncodeToString(bytes), } subType := &extJSONValue{ t: TypeString, v: "04", } v = &extJSONValue{ t: TypeEmbeddedDocument, v: &extJSONObject{ keys: []string{"base64", "subType"}, values: []*extJSONValue{base64, subType}, }, } break } // convert legacy $binary format base64 := ejp.v ejp.advanceState() if ejp.s != jpsSawComma { return nil, invalidJSONErrorForType(",", TypeBinary) } ejp.advanceState() key, t, err := ejp.readKey() if err != nil { return nil, err } if key != "$type" { return nil, invalidJSONErrorForType("$type", TypeBinary) } 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 }", TypeBinary) } v = &extJSONValue{ t: TypeEmbeddedDocument, 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: TypeEmbeddedDocument, v: &extJSONObject{keys: keys, values: vals}} case TypeDateTime: 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: TypeEmbeddedDocument, v: &extJSONObject{keys: keys, values: vals}} case jpsSawValue: if ejp.canonicalOnly { return nil, invalidJSONError("{") } v = ejp.v default: if ejp.canonicalOnly { return nil, invalidJSONErrorForType("object", t) } return nil, invalidJSONErrorForType("ISO-8601 Internet Date/Time Format as described in RFC-3339", t) } ejp.advanceState() if ejp.s != jpsSawEndObject { return nil, invalidJSONErrorForType("value and then }", t) } default: return nil, invalidRequestError(t.String()) } case TypeJavaScript: 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 TypeCodeWithScope: 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 TypeEmbeddedDocument, TypeArray: 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 Type) bool { switch t { case TypeMinKey, TypeMaxKey: return ejp.v.t == TypeInt32 case TypeUndefined: return ejp.v.t == TypeBoolean case TypeInt32, TypeInt64, TypeDouble, TypeDecimal128, TypeSymbol, TypeObjectID: return ejp.v.t == TypeString 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 Type switch jt.t { case jttInt32: t = TypeInt32 case jttInt64: t = TypeInt64 case jttDouble: t = TypeDouble case jttString: t = TypeString case jttBool: t = TypeBoolean case jttNull: t = TypeNull 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 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) } ================================================ FILE: bson/extjson_parser_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "strings" "testing" "github.com/google/go-cmp/cmp" ) 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 []Type errFs []expectedErrorFunc } type readKeyValueTestCase struct { desc string input string keys []string typs []Type vals []*extJSONValue keyEFs []expectedErrorFunc valEFs []expectedErrorFunc } func expectNoError(t *testing.T, err error, desc string) { if err != nil { t.Helper() t.Errorf("%s: Unepexted error: %v", desc, err) t.FailNow() } } func expectError(t *testing.T, err error, desc string) { if err == nil { t.Helper() t.Errorf("%s: Expected error", desc) t.FailNow() } } func expectSpecificError(expected error) expectedErrorFunc { return func(t *testing.T, err error, desc string) { if !errors.Is(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 any, desc string) { return func(t *testing.T, expected, actual any, 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 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 Type, desc string) peekTypeTestCase { return peekTypeTestCase{ desc: desc, input: input, typs: []Type{typ}, errFs: []expectedErrorFunc{expectNoError}, } } makeInvalidTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase { return peekTypeTestCase{ desc: desc, input: input, typs: []Type{Type(0)}, errFs: []expectedErrorFunc{lastEF}, } } makeInvalidPeekTypeTestCase := func(desc, input string, lastEF expectedErrorFunc) peekTypeTestCase { return peekTypeTestCase{ desc: desc, input: input, typs: []Type{TypeArray, TypeString, Type(0)}, errFs: []expectedErrorFunc{expectNoError, expectNoError, lastEF}, } } cases := []peekTypeTestCase{ makeValidPeekTypeTestCase(`null`, TypeNull, "Null"), makeValidPeekTypeTestCase(`"string"`, TypeString, "String"), makeValidPeekTypeTestCase(`true`, TypeBoolean, "Boolean--true"), makeValidPeekTypeTestCase(`false`, TypeBoolean, "Boolean--false"), makeValidPeekTypeTestCase(`{"$minKey": 1}`, TypeMinKey, "MinKey"), makeValidPeekTypeTestCase(`{"$maxKey": 1}`, TypeMaxKey, "MaxKey"), makeValidPeekTypeTestCase(`{"$numberInt": "42"}`, TypeInt32, "Int32"), makeValidPeekTypeTestCase(`{"$numberLong": "42"}`, TypeInt64, "Int64"), makeValidPeekTypeTestCase(`{"$symbol": "symbol"}`, TypeSymbol, "Symbol"), makeValidPeekTypeTestCase(`{"$numberDouble": "42.42"}`, TypeDouble, "Double"), makeValidPeekTypeTestCase(`{"$undefined": true}`, TypeUndefined, "Undefined"), makeValidPeekTypeTestCase(`{"$numberDouble": "NaN"}`, TypeDouble, "Double--NaN"), makeValidPeekTypeTestCase(`{"$numberDecimal": "1234"}`, TypeDecimal128, "Decimal"), makeValidPeekTypeTestCase(`{"foo": "bar"}`, TypeEmbeddedDocument, "Toplevel document"), makeValidPeekTypeTestCase(`{"$date": {"$numberLong": "0"}}`, TypeDateTime, "Datetime"), makeValidPeekTypeTestCase(`{"$code": "function() {}"}`, TypeJavaScript, "Code no scope"), makeValidPeekTypeTestCase(`[{"$numberInt": "1"},{"$numberInt": "2"}]`, TypeArray, "Array"), makeValidPeekTypeTestCase(`{"$timestamp": {"t": 42, "i": 1}}`, TypeTimestamp, "Timestamp"), makeValidPeekTypeTestCase(`{"$oid": "57e193d7a9cc81b4027498b5"}`, TypeObjectID, "Object ID"), makeValidPeekTypeTestCase(`{"$binary": {"base64": "AQIDBAU=", "subType": "80"}}`, TypeBinary, "Binary"), makeValidPeekTypeTestCase(`{"$code": "function() {}", "$scope": {}}`, TypeCodeWithScope, "Code With Scope"), makeValidPeekTypeTestCase(`{"$binary": {"base64": "o0w498Or7cijeBSpkquNtg==", "subType": "03"}}`, TypeBinary, "Binary"), makeValidPeekTypeTestCase(`{"$binary": "o0w498Or7cijeBSpkquNtg==", "$type": "03"}`, TypeBinary, "Binary"), makeValidPeekTypeTestCase(`{"$regularExpression": {"pattern": "foo*", "options": "ix"}}`, TypeRegex, "Regular expression"), makeValidPeekTypeTestCase(`{"$dbPointer": {"$ref": "db.collection", "$id": {"$oid": "57e193d7a9cc81b4027498b1"}}}`, TypeDBPointer, "DBPointer"), makeValidPeekTypeTestCase(`{"$ref": "collection", "$id": {"$oid": "57fd71e96e32ab4225b723fb"}, "$db": "database"}`, TypeEmbeddedDocument, "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: []Type{TypeArray, Type(0)}, errFs: []expectedErrorFunc{expectNoError, expectError}, }, makeInvalidTestCase("lone $scope", `{"$scope": {}}`, expectError), makeInvalidTestCase("empty code with unknown extra key", `{"$code":"", "0":""}`, expectError), makeInvalidTestCase("non-empty code with unknown extra key", `{"$code":"foobar", "0":""}`, expectError), } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { ejp := newExtJSONParser(strings.NewReader(tc.input), true) // Manually set the parser's starting state to jpsSawColon so peekType will read ahead to find the extjson // type of the value. If not set, the parser will be in jpsStartState and advance to jpsSawKey, which will // cause it to return without peeking the extjson type. ejp.s = jpsSawColon for i, eTyp := range tc.typs { errF := tc.errFs[i] typ, err := ejp.peekType() errF(t, err, tc.desc) if err != nil { // Don't inspect the type if there was an error return } typDiff(t, eTyp, typ, tc.desc) } }) } } func TestExtJSONParserReadKeyReadValue(t *testing.T) { // several test cases will use the same keys, types, and values, and only differ on input structure keys := []string{"_id", "Symbol", "String", "Int32", "Int64", "Int", "MinKey"} types := []Type{TypeObjectID, TypeSymbol, TypeString, TypeInt32, TypeInt64, TypeInt32, TypeMinKey} values := []*extJSONValue{ {t: TypeString, v: "57e193d7a9cc81b4027498b5"}, {t: TypeString, v: "symbol"}, {t: TypeString, v: "string"}, {t: TypeString, v: "42"}, {t: TypeString, v: "42"}, {t: TypeInt32, v: int32(42)}, {t: TypeInt32, 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: []Type{Type(0)}, vals: []*extJSONValue{nil}, keyEFs: []expectedErrorFunc{expectError}, valEFs: []expectedErrorFunc{expectErrorNOOP}, } } secondKeyError := func(desc, input, firstKey string, firstType Type, firstValue *extJSONValue) readKeyValueTestCase { return readKeyValueTestCase{ desc: desc, input: input, keys: []string{firstKey, ""}, typs: []Type{firstType, 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: []Type{TypeInt32, TypeEmbeddedDocument, TypeEmbeddedDocument, TypeInt32, Type(0), Type(0), TypeInt32, Type(0)}, vals: []*extJSONValue{ {t: TypeInt32, v: int32(1)}, nil, nil, {t: TypeInt32, v: int32(4)}, nil, nil, {t: TypeInt32, 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: []Type{TypeInt32}, 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", TypeInt32, &extJSONValue{t: TypeInt32, v: int32(1)}), secondKeyError("invalid input: missing comma", `{"a": 1 "b"`, "a", TypeInt32, &extJSONValue{t: TypeInt32, v: int32(1)}), secondKeyError("invalid input: extra comma", `{"a": 1,, "b"`, "a", TypeInt32, &extJSONValue{t: TypeInt32, v: int32(1)}), secondKeyError("invalid input: trailing comma in object", `{"a": 1,}`, "a", TypeInt32, &extJSONValue{t: TypeInt32, v: int32(1)}), { desc: "invalid input: lone scope after a complete value", input: `{"a": "", "b": {"$scope: ""}}`, keys: []string{"a"}, typs: []Type{TypeString}, vals: []*extJSONValue{{TypeString, ""}}, keyEFs: []expectedErrorFunc{expectNoError, expectNoError}, valEFs: []expectedErrorFunc{expectNoError, expectError}, }, { desc: "invalid input: lone scope nested", input: `{"a":{"b":{"$scope":{`, keys: []string{}, typs: []Type{}, vals: []*extJSONValue{nil}, keyEFs: []expectedErrorFunc{expectNoError}, valEFs: []expectedErrorFunc{expectError}, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { ejp := newExtJSONParser(strings.NewReader(tc.input), true) for i, eKey := range tc.keys { eTyp := tc.typs[i] eVal := tc.vals[i] keyErrF := tc.keyEFs[i] valErrF := tc.valEFs[i] k, typ, err := ejp.readKey() readKeyDiff(t, eKey, k, eTyp, typ, err, keyErrF, tc.desc) v, err := ejp.readValue(typ) readValueDiff(t, eVal, v, err, valErrF, tc.desc) } }) } } type ejpExpectationTest func(t *testing.T, p *extJSONParser, expectedKey string, expectedType Type, expectedValue any) type ejpTestCase struct { f ejpExpectationTest p *extJSONParser k string t Type v any } // 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 Type, expectedValue any) { 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 Type, expectedValue any) { k, typ, err := p.readKey() readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey) v, err := p.readValue(typ) expectNoError(t, err, "") typDiff(t, TypeEmbeddedDocument, 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 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 Type, expectedValue any) { subdoc := expectedValue.(ejpSubDocumentTestValue) k, typ, err := p.readKey() readKeyDiff(t, expectedKey, k, expectedType, typ, err, expectNoError, expectedKey) if expectedType == TypeCodeWithScope { v, err := p.readValue(typ) readValueDiff(t, &extJSONValue{t: TypeString, 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 == TypeCodeWithScope { // expect scope doc to close k, typ, err = p.readKey() readKeyDiff(t, "", k, Type(0), typ, err, expectErrEOD, expectedKey) } // expect subdoc to close k, typ, err = p.readKey() readKeyDiff(t, "", k, 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, _ Type, expectedValue any) { ktvs := expectedValue.([]ejpKeyTypValTriple) k, typ, err := p.readKey() readKeyDiff(t, expectedKey, k, TypeArray, 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, 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: TypeObjectID, v: &extJSONValue{t: TypeString, v: "57e193d7a9cc81b4027498b5"}, }, { f: expectSingleValue, p: ejp, k: "Symbol", t: TypeSymbol, v: &extJSONValue{t: TypeString, v: "symbol"}, }, { f: expectSingleValue, p: ejp, k: "String", t: TypeString, v: &extJSONValue{t: TypeString, v: "string"}, }, { f: expectSingleValue, p: ejp, k: "Int32", t: TypeInt32, v: &extJSONValue{t: TypeString, v: "42"}, }, { f: expectSingleValue, p: ejp, k: "Int64", t: TypeInt64, v: &extJSONValue{t: TypeString, v: "42"}, }, { f: expectSingleValue, p: ejp, k: "Double", t: TypeDouble, v: &extJSONValue{t: TypeString, v: "42.42"}, }, { f: expectSingleValue, p: ejp, k: "SpecialFloat", t: TypeDouble, v: &extJSONValue{t: TypeString, v: "NaN"}, }, { f: expectSingleValue, p: ejp, k: "Decimal", t: TypeDecimal128, v: &extJSONValue{t: TypeString, v: "1234"}, }, { f: expectMultipleValues, p: ejp, k: "Binary", t: TypeBinary, v: &extJSONObject{ keys: []string{"base64", "subType"}, values: []*extJSONValue{ {t: TypeString, v: "o0w498Or7cijeBSpkquNtg=="}, {t: TypeString, v: "03"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "BinaryLegacy", t: TypeBinary, v: &extJSONObject{ keys: []string{"base64", "subType"}, values: []*extJSONValue{ {t: TypeString, v: "o0w498Or7cijeBSpkquNtg=="}, {t: TypeString, v: "03"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "BinaryUserDefined", t: TypeBinary, v: &extJSONObject{ keys: []string{"base64", "subType"}, values: []*extJSONValue{ {t: TypeString, v: "AQIDBAU="}, {t: TypeString, v: "80"}, }, }, }, { f: expectSingleValue, p: ejp, k: "Code", t: TypeJavaScript, v: &extJSONValue{t: TypeString, v: "function() {}"}, }, { f: expectSubDocument, p: ejp, k: "CodeWithEmptyScope", t: TypeCodeWithScope, v: ejpSubDocumentTestValue{ code: "function() {}", ktvs: []ejpKeyTypValTriple{}, }, }, { f: expectSubDocument, p: ejp, k: "CodeWithScope", t: TypeCodeWithScope, v: ejpSubDocumentTestValue{ code: "function() {}", ktvs: []ejpKeyTypValTriple{ {"x", TypeInt32, &extJSONValue{t: TypeInt32, v: int32(1)}}, }, }, }, { f: expectSubDocument, p: ejp, k: "EmptySubdocument", t: TypeEmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{}, }, }, { f: expectSubDocument, p: ejp, k: "Subdocument", t: TypeEmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{ {"foo", TypeString, &extJSONValue{t: TypeString, v: "bar"}}, {"baz", TypeInt32, &extJSONValue{t: TypeString, v: "42"}}, }, }, }, { f: expectArray, p: ejp, k: "Array", t: TypeArray, v: []ejpKeyTypValTriple{ {typ: TypeInt32, val: &extJSONValue{t: TypeString, v: "1"}}, {typ: TypeInt64, val: &extJSONValue{t: TypeString, v: "2"}}, {typ: TypeDouble, val: &extJSONValue{t: TypeString, v: "3"}}, {typ: TypeInt32, val: &extJSONValue{t: TypeInt32, v: int32(4)}}, {typ: TypeString, val: &extJSONValue{t: TypeString, v: "string"}}, {typ: TypeDouble, val: &extJSONValue{t: TypeDouble, v: 5.0}}, }, }, { f: expectMultipleValues, p: ejp, k: "Timestamp", t: TypeTimestamp, v: &extJSONObject{ keys: []string{"t", "i"}, values: []*extJSONValue{ {t: TypeInt32, v: int32(42)}, {t: TypeInt32, v: int32(1)}, }, }, }, { f: expectMultipleValues, p: ejp, k: "RegularExpression", t: TypeRegex, v: &extJSONObject{ keys: []string{"pattern", "options"}, values: []*extJSONValue{ {t: TypeString, v: "foo*"}, {t: TypeString, v: "ix"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "DatetimeEpoch", t: TypeDateTime, v: &extJSONObject{ keys: []string{"$numberLong"}, values: []*extJSONValue{ {t: TypeString, v: "0"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "DatetimePositive", t: TypeDateTime, v: &extJSONObject{ keys: []string{"$numberLong"}, values: []*extJSONValue{ {t: TypeString, v: "9223372036854775807"}, }, }, }, { f: expectMultipleValues, p: ejp, k: "DatetimeNegative", t: TypeDateTime, v: &extJSONObject{ keys: []string{"$numberLong"}, values: []*extJSONValue{ {t: TypeString, v: "-9223372036854775808"}, }, }, }, { f: expectSingleValue, p: ejp, k: "True", t: TypeBoolean, v: &extJSONValue{t: TypeBoolean, v: true}, }, { f: expectSingleValue, p: ejp, k: "False", t: TypeBoolean, v: &extJSONValue{t: TypeBoolean, v: false}, }, { f: expectMultipleValues, p: ejp, k: "DBPointer", t: TypeDBPointer, v: &extJSONObject{ keys: []string{"$ref", "$id"}, values: []*extJSONValue{ {t: TypeString, v: "db.collection"}, {t: TypeString, v: "57e193d7a9cc81b4027498b1"}, }, }, }, { f: expectSubDocument, p: ejp, k: "DBRef", t: TypeEmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{ {"$ref", TypeString, &extJSONValue{t: TypeString, v: "collection"}}, {"$id", TypeObjectID, &extJSONValue{t: TypeString, v: "57fd71e96e32ab4225b723fb"}}, {"$db", TypeString, &extJSONValue{t: TypeString, v: "database"}}, }, }, }, { f: expectSubDocument, p: ejp, k: "DBRefNoDB", t: TypeEmbeddedDocument, v: ejpSubDocumentTestValue{ ktvs: []ejpKeyTypValTriple{ {"$ref", TypeString, &extJSONValue{t: TypeString, v: "collection"}}, {"$id", TypeObjectID, &extJSONValue{t: TypeString, v: "57fd71e96e32ab4225b723fb"}}, }, }, }, { f: expectSingleValue, p: ejp, k: "MinKey", t: TypeMinKey, v: &extJSONValue{t: TypeInt32, v: int32(1)}, }, { f: expectSingleValue, p: ejp, k: "MaxKey", t: TypeMaxKey, v: &extJSONValue{t: TypeInt32, v: int32(1)}, }, { f: expectSingleValue, p: ejp, k: "Null", t: TypeNull, v: &extJSONValue{t: TypeNull, v: nil}, }, { f: expectSingleValue, p: ejp, k: "Undefined", t: TypeUndefined, v: &extJSONValue{t: TypeBoolean, 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, Type(0), typ, err, expectErrEOD, "") // expect end of whole document: read EOF k, typ, err = ejp.readKey() readKeyDiff(t, "", k, 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: TypeString, 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: TypeString, v: "2019-06-04T14:54:31.416+0000", } _, err := val.parseDateTime() if err != nil { t.Fatalf("error parsing date time: %v", err) } }) } ================================================ FILE: bson/extjson_prose_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package bson import ( "fmt" "testing" "go.mongodb.org/mongo-driver/v2/internal/assert" ) func TestExtJSON(t *testing.T) { timestampNegativeInt32Err := fmt.Errorf("$timestamp i number should be uint32: -1") timestampNegativeInt64Err := fmt.Errorf("$timestamp i number should be uint32: -2147483649") timestampLargeValueErr := fmt.Errorf("$timestamp i number should be uint32: 4294967296") testCases := []struct { name string input string canonical bool err error }{ {"timestamp - negative int32 value", `{"":{"$timestamp":{"t":0,"i":-1}}}`, false, timestampNegativeInt32Err}, {"timestamp - negative int64 value", `{"":{"$timestamp":{"t":0,"i":-2147483649}}}`, false, timestampNegativeInt64Err}, {"timestamp - value overflows uint32", `{"":{"$timestamp":{"t":0,"i":4294967296}}}`, false, timestampLargeValueErr}, {"top level key is not treated as special", `{"$code": "foo"}`, false, nil}, {"escaped single quote errors", `{"f\'oo": "bar"}`, false, ErrInvalidJSON}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var res Raw err := UnmarshalExtJSON([]byte(tc.input), tc.canonical, &res) if tc.err == nil { assert.Nil(t, err, "UnmarshalExtJSON error: %v", err) return } assert.NotNil(t, err, "expected error %v, got nil", tc.err) assert.Equal(t, tc.err.Error(), err.Error(), "expected error %v, got %v", tc.err, err) }) } } ================================================ FILE: bson/extjson_reader.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "io" ) type ejvrState struct { mode mode vType Type depth int } // extJSONValueReader is for reading extended JSON. type extJSONValueReader struct { p *extJSONParser stack []ejvrState frame int } // NewExtJSONValueReader returns a ValueReader that reads Extended JSON values // from r. If canonicalOnly is true, reading values from the ValueReader returns // an error if the Extended JSON was not marshaled in canonical mode. func NewExtJSONValueReader(r io.Reader, canonicalOnly bool) (ValueReader, error) { return newExtJSONValueReader(r, canonicalOnly) } func newExtJSONValueReader(r io.Reader, canonicalOnly bool) (*extJSONValueReader, error) { ejvr := new(extJSONValueReader) return ejvr.reset(r, canonicalOnly) } func (ejvr *extJSONValueReader) reset(r io.Reader, canonicalOnly bool) (*extJSONValueReader, error) { p := newExtJSONParser(r, canonicalOnly) typ, err := p.peekType() if err != nil { return nil, ErrInvalidJSON } var m mode switch typ { case TypeEmbeddedDocument: m = mTopLevel case TypeArray: 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 Type) { ejvr.advanceFrame() ejvr.stack[ejvr.frame].mode = m ejvr.stack[ejvr.frame].vType = t } func (ejvr *extJSONValueReader) pop() { switch ejvr.stack[ejvr.frame].mode { case mElement, mValue: ejvr.frame-- case mDocument, mArray, mCodeWithScope: ejvr.frame -= 2 // we pop twice to jump over the vrElement: vrDocument -> vrElement -> vrDocument/TopLevel/etc... } } func (ejvr *extJSONValueReader) skipObject() { // read entire object until depth returns to 0 (last ending } or ] seen) depth := 1 for depth > 0 { ejvr.p.advanceState() // If object is empty, raise depth and continue. When emptyObject is true, the // parser has already read both the opening and closing brackets of an empty // object ("{}"), so the next valid token will be part of the parent document, // not part of the nested document. // // If there is a comma, there are remaining fields, emptyObject must be set back // to false, and comma must be skipped with advanceState(). if ejvr.p.emptyObject { if ejvr.p.s == jpsSawComma { ejvr.p.emptyObject = false ejvr.p.advanceState() } depth-- continue } switch ejvr.p.s { case jpsSawBeginObject, jpsSawBeginArray: depth++ case jpsSawEndObject, jpsSawEndArray: depth-- } } } func (ejvr *extJSONValueReader) invalidTransitionErr(destination mode, name string, modes []mode) error { te := TransitionError{ name: name, current: ejvr.stack[ejvr.frame].mode, destination: destination, modes: modes, action: "read", } if ejvr.frame != 0 { te.parent = ejvr.stack[ejvr.frame-1].mode } return te } func (ejvr *extJSONValueReader) typeError(t Type) error { return fmt.Errorf("positioned on %s, but attempted to read %s", ejvr.stack[ejvr.frame].vType, t) } func (ejvr *extJSONValueReader) ensureElementValue(t 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() 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 TypeArray, TypeEmbeddedDocument, TypeCodeWithScope: // read entire array, doc or CodeWithScope ejvr.skipObject() default: _, err := ejvr.p.readValue(t) if err != nil { return err } } return nil } func (ejvr *extJSONValueReader) ReadArray() (ArrayReader, error) { switch ejvr.stack[ejvr.frame].mode { case mTopLevel: // allow reading array from top level case mArray: return ejvr, nil default: if err := ejvr.ensureElementValue(TypeArray, 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(TypeBinary, 0, "ReadBinary"); err != nil { return nil, 0, err } v, err := ejvr.p.readValue(TypeBinary) 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(TypeBoolean, 0, "ReadBoolean"); err != nil { return false, err } v, err := ejvr.p.readValue(TypeBoolean) if err != nil { return false, err } if v.t != TypeBoolean { 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 != TypeEmbeddedDocument { return nil, ejvr.typeError(TypeEmbeddedDocument) } 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(TypeCodeWithScope, 0, "ReadCodeWithScope"); err != nil { return "", nil, err } v, err := ejvr.p.readValue(TypeCodeWithScope) if err != nil { return "", nil, err } code, err = v.parseJavascript() ejvr.pushCodeWithScope() return code, ejvr, err } func (ejvr *extJSONValueReader) ReadDBPointer() (ns string, oid ObjectID, err error) { if err = ejvr.ensureElementValue(TypeDBPointer, 0, "ReadDBPointer"); err != nil { return "", NilObjectID, err } v, err := ejvr.p.readValue(TypeDBPointer) if err != nil { return "", NilObjectID, err } ns, oid, err = v.parseDBPointer() ejvr.pop() return ns, oid, err } func (ejvr *extJSONValueReader) ReadDateTime() (int64, error) { if err := ejvr.ensureElementValue(TypeDateTime, 0, "ReadDateTime"); err != nil { return 0, err } v, err := ejvr.p.readValue(TypeDateTime) if err != nil { return 0, err } d, err := v.parseDateTime() ejvr.pop() return d, err } func (ejvr *extJSONValueReader) ReadDecimal128() (Decimal128, error) { if err := ejvr.ensureElementValue(TypeDecimal128, 0, "ReadDecimal128"); err != nil { return Decimal128{}, err } v, err := ejvr.p.readValue(TypeDecimal128) if err != nil { return Decimal128{}, err } d, err := v.parseDecimal128() ejvr.pop() return d, err } func (ejvr *extJSONValueReader) ReadDouble() (float64, error) { if err := ejvr.ensureElementValue(TypeDouble, 0, "ReadDouble"); err != nil { return 0, err } v, err := ejvr.p.readValue(TypeDouble) 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(TypeInt32, 0, "ReadInt32"); err != nil { return 0, err } v, err := ejvr.p.readValue(TypeInt32) 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(TypeInt64, 0, "ReadInt64"); err != nil { return 0, err } v, err := ejvr.p.readValue(TypeInt64) 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(TypeJavaScript, 0, "ReadJavascript"); err != nil { return "", err } v, err := ejvr.p.readValue(TypeJavaScript) if err != nil { return "", err } code, err = v.parseJavascript() ejvr.pop() return code, err } func (ejvr *extJSONValueReader) ReadMaxKey() error { if err := ejvr.ensureElementValue(TypeMaxKey, 0, "ReadMaxKey"); err != nil { return err } v, err := ejvr.p.readValue(TypeMaxKey) if err != nil { return err } err = v.parseMinMaxKey("max") ejvr.pop() return err } func (ejvr *extJSONValueReader) ReadMinKey() error { if err := ejvr.ensureElementValue(TypeMinKey, 0, "ReadMinKey"); err != nil { return err } v, err := ejvr.p.readValue(TypeMinKey) if err != nil { return err } err = v.parseMinMaxKey("min") ejvr.pop() return err } func (ejvr *extJSONValueReader) ReadNull() error { if err := ejvr.ensureElementValue(TypeNull, 0, "ReadNull"); err != nil { return err } v, err := ejvr.p.readValue(TypeNull) if err != nil { return err } if v.t != TypeNull { return fmt.Errorf("expected type null but got type %s", v.t) } ejvr.pop() return nil } func (ejvr *extJSONValueReader) ReadObjectID() (ObjectID, error) { if err := ejvr.ensureElementValue(TypeObjectID, 0, "ReadObjectID"); err != nil { return ObjectID{}, err } v, err := ejvr.p.readValue(TypeObjectID) if err != nil { return 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(TypeRegex, 0, "ReadRegex"); err != nil { return "", "", err } v, err := ejvr.p.readValue(TypeRegex) 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(TypeString, 0, "ReadString"); err != nil { return "", err } v, err := ejvr.p.readValue(TypeString) if err != nil { return "", err } if v.t != TypeString { 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(TypeSymbol, 0, "ReadSymbol"); err != nil { return "", err } v, err := ejvr.p.readValue(TypeSymbol) 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(TypeTimestamp, 0, "ReadTimestamp"); err != nil { return 0, 0, err } v, err := ejvr.p.readValue(TypeTimestamp) 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(TypeUndefined, 0, "ReadUndefined"); err != nil { return err } v, err := ejvr.p.readValue(TypeUndefined) 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 errors.Is(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 errors.Is(err, ErrEOA) { ejvr.pop() } return nil, err } ejvr.push(mValue, t) return ejvr, nil } ================================================ FILE: bson/extjson_reader_test.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file 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" "io" "strings" "testing" "github.com/google/go-cmp/cmp" "go.mongodb.org/mongo-driver/v2/internal/assert" ) 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: TypeBoolean}, }, 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(assert.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) { var actual [][]byte switch vr.Type() { case TypeEmbeddedDocument: for { result, err := copyDocumentToBytes(vr) if err != nil { if errors.Is(err, io.EOF) { break } return nil, err } actual = append(actual, result) } case TypeArray: ar, err := vr.ReadArray() if err != nil { return nil, err } for { evr, err := ar.ReadValue() if err != nil { if errors.Is(err, ErrEOA) { break } return nil, err } result, err := 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 } ================================================ FILE: bson/extjson_tables.go ================================================ // Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this 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 bson 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