From 17a547fea27ffde05f078e97f86d1f11a59ca889 Mon Sep 17 00:00:00 2001 From: Preston Vasquez Date: Wed, 4 Dec 2024 17:00:46 -0700 Subject: [PATCH] GODRIVER-3434 Revert builder-lister pattern for client options (#1899) Co-authored-by: Qingyang Hu <103950869+qingyang-hu@users.noreply.github.com> --- internal/cmd/testatlas/atlas_test.go | 10 +- .../client_side_encryption_prose_test.go | 8 +- .../client_side_encryption_test.go | 12 +- internal/integration/data_lake_test.go | 2 +- internal/integration/handshake_test.go | 2 +- .../initial_dns_seedlist_discovery_test.go | 2 +- internal/integration/json_helpers_test.go | 4 +- internal/integration/mtest/mongotest.go | 30 +- internal/integration/mtest/options.go | 2 +- internal/integration/mtest/setup.go | 12 +- .../integration/sdam_error_handling_test.go | 2 +- internal/integration/unified/client_entity.go | 14 +- .../integration/unified/server_api_options.go | 4 +- internal/integration/unified_spec_test.go | 32 +- internal/integtest/integtest.go | 2 +- internal/mongoutil/mongoutil.go | 14 +- internal/mongoutil/mongoutil_test.go | 170 +------ internal/test/goleak/goleak_test.go | 2 +- mongo/client.go | 177 +++---- mongo/client_test.go | 18 +- mongo/mongocryptd.go | 21 +- mongo/ocsp_test.go | 13 +- mongo/options/autoencryptionoptions.go | 105 +--- mongo/options/clientoptions.go | 469 ++++++------------ mongo/options/clientoptions_test.go | 422 +++++++--------- mongo/options/example_test.go | 2 +- mongo/options/lister.go | 16 - mongo/options/loggeroptions.go | 46 +- mongo/options/serverapioptions.go | 40 +- mongo/with_transactions_test.go | 4 +- x/mongo/driver/auth/auth_spec_test.go | 9 +- .../drivertest/opmsg_deployment_test.go | 5 +- x/mongo/driver/topology/topology_options.go | 61 +-- x/mongo/driver/topology/topology_test.go | 2 +- 34 files changed, 549 insertions(+), 1185 deletions(-) diff --git a/internal/cmd/testatlas/atlas_test.go b/internal/cmd/testatlas/atlas_test.go index 466b0b4233..cf4a84735f 100644 --- a/internal/cmd/testatlas/atlas_test.go +++ b/internal/cmd/testatlas/atlas_test.go @@ -17,7 +17,6 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/handshake" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) @@ -46,12 +45,7 @@ func TestAtlas(t *testing.T) { t.Fatalf("error running test with TLS at index %d: %v", idx, err) } - args, err := mongoutil.NewOptions[options.ClientOptions](clientOpts) - if err != nil { - panic(fmt.Sprintf("failed to construct args from options: %v", err)) - } - - tlsConfigSkipVerify := args.TLSConfig + tlsConfigSkipVerify := clientOpts.TLSConfig tlsConfigSkipVerify.InsecureSkipVerify = true // Run the connectivity test with InsecureSkipVerify to ensure SNI is done correctly even if verification is @@ -66,7 +60,7 @@ func TestAtlas(t *testing.T) { t.Logf("Finished!") } -func runTest(ctx context.Context, clientOpts *options.ClientOptionsBuilder) error { +func runTest(ctx context.Context, clientOpts *options.ClientOptions) error { client, err := mongo.Connect(clientOpts) if err != nil { return fmt.Errorf("Connect error: %w", err) diff --git a/internal/integration/client_side_encryption_prose_test.go b/internal/integration/client_side_encryption_prose_test.go index a925af2be4..cb9ee2a89d 100644 --- a/internal/integration/client_side_encryption_prose_test.go +++ b/internal/integration/client_side_encryption_prose_test.go @@ -527,7 +527,7 @@ func TestClientSideEncryptionProse(t *testing.T) { tlsConfig["kmip"] = kmipConfig } - getBaseAutoEncryptionOpts := func() *options.AutoEncryptionOptionsBuilder { + getBaseAutoEncryptionOpts := func() *options.AutoEncryptionOptions { return options.AutoEncryption(). SetKmsProviders(fullKmsProvidersMap). SetKeyVaultNamespace(kvNamespace). @@ -537,7 +537,7 @@ func TestClientSideEncryptionProse(t *testing.T) { testCases := []struct { name string - aeo *options.AutoEncryptionOptionsBuilder + aeo *options.AutoEncryptionOptions schema bson.Raw // the schema to create the collection. if nil, the collection won't be explicitly created }{ {"remote schema", getBaseAutoEncryptionOpts(), corpusSchema}, @@ -3014,7 +3014,7 @@ type cseProseTest struct { cseStarted []*event.CommandStartedEvent } -func setup(mt *mtest.T, aeo *options.AutoEncryptionOptionsBuilder, kvClientOpts options.Lister[options.ClientOptions], +func setup(mt *mtest.T, aeo *options.AutoEncryptionOptions, kvClientOpts *options.ClientOptions, ceo options.Lister[options.ClientEncryptionOptions]) *cseProseTest { mt.Helper() var cpt cseProseTest @@ -3093,7 +3093,7 @@ func rawValueToCoreValue(rv bson.RawValue) bsoncore.Value { type deadlockTest struct { clientTest *mongo.Client - clientKeyVaultOpts *options.ClientOptionsBuilder + clientKeyVaultOpts *options.ClientOptions clientKeyVaultEvents []startedEvent clientEncryption *mongo.ClientEncryption ciphertext bson.Binary diff --git a/internal/integration/client_side_encryption_test.go b/internal/integration/client_side_encryption_test.go index 1111a71021..1581d65d04 100644 --- a/internal/integration/client_side_encryption_test.go +++ b/internal/integration/client_side_encryption_test.go @@ -356,11 +356,7 @@ func TestClientSideEncryptionCustomCrypt(t *testing.T) { ApplyURI(mtest.ClusterURI()). SetAutoEncryptionOptions(aeOpts) cc := &customCrypt{} - clientOpts.Opts = append(clientOpts.Opts, func(args *options.ClientOptions) error { - args.Crypt = cc - - return nil - }) + clientOpts.Crypt = cc integtest.AddTestServerAPIVersion(clientOpts) client, err := mongo.Connect(clientOpts) @@ -683,11 +679,7 @@ func TestFLEIndexView(t *testing.T) { SetReadPreference(mtest.PrimaryRp) cc := &customCrypt{} - opts.Opts = append(opts.Opts, func(args *options.ClientOptions) error { - args.Crypt = cc - - return nil - }) + opts.Crypt = cc integtest.AddTestServerAPIVersion(opts) diff --git a/internal/integration/data_lake_test.go b/internal/integration/data_lake_test.go index 6c10f9a9e6..23ef8da033 100644 --- a/internal/integration/data_lake_test.go +++ b/internal/integration/data_lake_test.go @@ -97,7 +97,7 @@ func TestAtlasDataLake(t *testing.T) { }) } -func getBaseClientOptions(mt *mtest.T) *options.ClientOptionsBuilder { +func getBaseClientOptions(mt *mtest.T) *options.ClientOptions { mt.Helper() hosts, err := mongoutil.HostsFromURI(mtest.ClusterURI()) diff --git a/internal/integration/handshake_test.go b/internal/integration/handshake_test.go index 54d83a2d31..f4c449e30e 100644 --- a/internal/integration/handshake_test.go +++ b/internal/integration/handshake_test.go @@ -91,7 +91,7 @@ func TestHandshakeProse(t *testing.T) { for _, test := range []struct { name string env map[string]string - opts *options.ClientOptionsBuilder + opts *options.ClientOptions want bson.D }{ { diff --git a/internal/integration/initial_dns_seedlist_discovery_test.go b/internal/integration/initial_dns_seedlist_discovery_test.go index de9d44a058..24d1d5911e 100644 --- a/internal/integration/initial_dns_seedlist_discovery_test.go +++ b/internal/integration/initial_dns_seedlist_discovery_test.go @@ -74,7 +74,7 @@ func runSeedlistDiscoveryDirectory(mt *mtest.T, subdirectory string) { } // runSeedlistDiscoveryPingTest will create a new connection using the test URI and attempt to "ping" the server. -func runSeedlistDiscoveryPingTest(mt *mtest.T, clientOpts *options.ClientOptionsBuilder) { +func runSeedlistDiscoveryPingTest(mt *mtest.T, clientOpts *options.ClientOptions) { ctx := context.Background() client, err := mongo.Connect(clientOpts) diff --git a/internal/integration/json_helpers_test.go b/internal/integration/json_helpers_test.go index 51c46d546b..e3ccb5254e 100644 --- a/internal/integration/json_helpers_test.go +++ b/internal/integration/json_helpers_test.go @@ -67,7 +67,7 @@ func jsonFilesInDir(t testing.TB, dir string) []string { } // create client options from a map -func createClientOptions(t testing.TB, opts bson.Raw) *options.ClientOptionsBuilder { +func createClientOptions(t testing.TB, opts bson.Raw) *options.ClientOptions { t.Helper() clientOpts := options.Client() @@ -125,7 +125,7 @@ func createClientOptions(t testing.TB, opts bson.Raw) *options.ClientOptionsBuil return clientOpts } -func createAutoEncryptionOptions(t testing.TB, opts bson.Raw) *options.AutoEncryptionOptionsBuilder { +func createAutoEncryptionOptions(t testing.TB, opts bson.Raw) *options.AutoEncryptionOptions { t.Helper() aeo := options.AutoEncryption() diff --git a/internal/integration/mtest/mongotest.go b/internal/integration/mtest/mongotest.go index be1da51aeb..3967bf7f82 100644 --- a/internal/integration/mtest/mongotest.go +++ b/internal/integration/mtest/mongotest.go @@ -81,7 +81,7 @@ type T struct { // options copied to sub-tests clientType ClientType - clientOpts *options.ClientOptionsBuilder + clientOpts *options.ClientOptions collOpts *options.CollectionOptionsBuilder shareClient *bool @@ -359,7 +359,7 @@ func (t *T) ClearEvents() { // If t.Coll is not-nil, it will be reset to use the new client. Should only be called if the existing client is // not nil. This will Disconnect the existing client but will not drop existing collections. To do so, ClearCollections // must be called before calling ResetClient. -func (t *T) ResetClient(opts *options.ClientOptionsBuilder) { +func (t *T) ResetClient(opts *options.ClientOptions) { if opts != nil { t.clientOpts = opts } @@ -592,18 +592,13 @@ func (t *T) createTestClient() { clientOpts = options.Client().SetWriteConcern(MajorityWc).SetReadPreference(PrimaryRp) } - args, err := mongoutil.NewOptions[options.ClientOptions](clientOpts) - if err != nil { - t.Fatalf("failed to construct options from builder: %v", err) - } - // set ServerAPIOptions to latest version if required - if args.Deployment == nil && t.clientType != Mock && args.ServerAPIOptions == nil && testContext.requireAPIVersion { + if clientOpts.Deployment == nil && t.clientType != Mock && clientOpts.ServerAPIOptions == nil && testContext.requireAPIVersion { clientOpts.SetServerAPIOptions(options.ServerAPI(driver.TestServerAPIVersion)) } // Setup command monitor - var customMonitor = args.Monitor + var customMonitor = clientOpts.Monitor clientOpts.SetMonitor(&event.CommandMonitor{ Started: func(ctx context.Context, cse *event.CommandStartedEvent) { if customMonitor != nil && customMonitor.Started != nil { @@ -631,8 +626,8 @@ func (t *T) createTestClient() { }, }) // only specify connection pool monitor if no deployment is given - if args.Deployment == nil { - previousPoolMonitor := args.PoolMonitor + if clientOpts.Deployment == nil { + previousPoolMonitor := clientOpts.PoolMonitor clientOpts.SetPoolMonitor(&event.PoolMonitor{ Event: func(evt *event.PoolEvent) { @@ -650,6 +645,7 @@ func (t *T) createTestClient() { }) } + var err error switch t.clientType { case Pinned: // pin to first mongos @@ -658,15 +654,13 @@ func (t *T) createTestClient() { t.Client, err = mongo.Connect(uriOpts, clientOpts) case Mock: // clear pool monitor to avoid configuration error - args, _ = mongoutil.NewOptions[options.ClientOptions](clientOpts) - args.PoolMonitor = nil + clientOpts.PoolMonitor = nil t.mockDeployment = drivertest.NewMockDeployment() - args.Deployment = t.mockDeployment + clientOpts.Deployment = t.mockDeployment - opts := mongoutil.NewOptionsLister(args, nil) - t.Client, err = mongo.Connect(opts) + t.Client, err = mongo.Connect(clientOpts) case Proxy: t.proxyDialer = newProxyDialer() clientOpts.SetDialer(t.proxyDialer) @@ -676,8 +670,8 @@ func (t *T) createTestClient() { case Default: // Use a different set of options to specify the URI because clientOpts may already have a URI or host seedlist // specified. - var uriOpts *options.ClientOptionsBuilder - if args.Deployment == nil { + var uriOpts *options.ClientOptions + if clientOpts.Deployment == nil { // Only specify URI if the deployment is not set to avoid setting topology/server options along with the // deployment. uriOpts = options.Client().ApplyURI(testContext.connString.Original) diff --git a/internal/integration/mtest/options.go b/internal/integration/mtest/options.go index 4635ec267c..aff188b481 100644 --- a/internal/integration/mtest/options.go +++ b/internal/integration/mtest/options.go @@ -134,7 +134,7 @@ func (op *Options) CollectionOptions(opts *options.CollectionOptionsBuilder) *Op } // ClientOptions sets the options to use when creating a client for a test. -func (op *Options) ClientOptions(opts *options.ClientOptionsBuilder) *Options { +func (op *Options) ClientOptions(opts *options.ClientOptions) *Options { op.optFuncs = append(op.optFuncs, func(t *T) { t.clientOpts = opts }) diff --git a/internal/integration/mtest/setup.go b/internal/integration/mtest/setup.go index 38fe5b84bf..d4f181f8c7 100644 --- a/internal/integration/mtest/setup.go +++ b/internal/integration/mtest/setup.go @@ -18,7 +18,6 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/integtest" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readpref" @@ -58,20 +57,15 @@ var testContext struct { serverless bool } -func setupClient(opts *options.ClientOptionsBuilder) (*mongo.Client, error) { - args, err := mongoutil.NewOptions[options.ClientOptions](opts) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - +func setupClient(opts *options.ClientOptions) (*mongo.Client, error) { wcMajority := writeconcern.Majority() // set ServerAPIOptions to latest version if required - if args.ServerAPIOptions == nil && testContext.requireAPIVersion { + if opts.ServerAPIOptions == nil && testContext.requireAPIVersion { opts.SetServerAPIOptions(options.ServerAPI(driver.TestServerAPIVersion)) } // for sharded clusters, pin to one host. Due to how the cache is implemented on 4.0 and 4.2, behavior // can be inconsistent when multiple mongoses are used - return mongo.Connect(opts.SetWriteConcern(wcMajority).SetHosts(args.Hosts[:1])) + return mongo.Connect(opts.SetWriteConcern(wcMajority).SetHosts(opts.Hosts[:1])) } // Setup initializes the current testing context. diff --git a/internal/integration/sdam_error_handling_test.go b/internal/integration/sdam_error_handling_test.go index ceaa5a59f8..9668f52bb6 100644 --- a/internal/integration/sdam_error_handling_test.go +++ b/internal/integration/sdam_error_handling_test.go @@ -27,7 +27,7 @@ import ( func TestSDAMErrorHandling(t *testing.T) { mt := mtest.New(t, noClientOpts) - baseClientOpts := func() *options.ClientOptionsBuilder { + baseClientOpts := func() *options.ClientOptions { return options.Client(). ApplyURI(mtest.ClusterURI()). SetRetryWrites(false). diff --git a/internal/integration/unified/client_entity.go b/internal/integration/unified/client_entity.go index 5c9dc88554..08b9fd7866 100644 --- a/internal/integration/unified/client_entity.go +++ b/internal/integration/unified/client_entity.go @@ -20,7 +20,6 @@ import ( "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/integtest" "go.mongodb.org/mongo-driver/v2/internal/logger" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readconcern" @@ -184,15 +183,10 @@ func newClientEntity(ctx context.Context, em *EntityMap, entityOptions *entityOp } } if entityOptions.ServerAPIOptions != nil { - args, err := mongoutil.NewOptions[options.ServerAPIOptions](entityOptions.ServerAPIOptions) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - - if err := args.ServerAPIVersion.Validate(); err != nil { + if err := entityOptions.ServerAPIOptions.ServerAPIVersion.Validate(); err != nil { return nil, err } - clientOpts.SetServerAPIOptions(entityOptions.ServerAPIOptions.ServerAPIOptionsBuilder) + clientOpts.SetServerAPIOptions(entityOptions.ServerAPIOptions.ServerAPIOptions) } else { integtest.AddTestServerAPIVersion(clientOpts) } @@ -589,7 +583,7 @@ func (c *clientEntity) getRecordEvents() bool { return c.recordEvents.Load().(bool) } -func setClientOptionsFromURIOptions(clientOpts *options.ClientOptionsBuilder, uriOpts bson.M) error { +func setClientOptionsFromURIOptions(clientOpts *options.ClientOptions, uriOpts bson.M) error { // A write concern can be constructed across multiple URI options (e.g. "w", "j", and "wTimeoutMS") so we declare an // empty writeConcern instance here that can be populated in the loop below. var wc writeConcern @@ -654,7 +648,7 @@ func setClientOptionsFromURIOptions(clientOpts *options.ClientOptionsBuilder, ur return nil } -func evaluateUseMultipleMongoses(clientOpts *options.ClientOptionsBuilder, useMultipleMongoses bool) error { +func evaluateUseMultipleMongoses(clientOpts *options.ClientOptions, useMultipleMongoses bool) error { hosts := mtest.ClusterConnString().Hosts if !useMultipleMongoses { diff --git a/internal/integration/unified/server_api_options.go b/internal/integration/unified/server_api_options.go index 72e24496de..e9379386d2 100644 --- a/internal/integration/unified/server_api_options.go +++ b/internal/integration/unified/server_api_options.go @@ -16,7 +16,7 @@ import ( // serverAPIOptions is a wrapper for *options.ServerAPIOptions. This type implements the bson.Unmarshaler interface // to convert BSON documents to a serverAPIOptions instance. type serverAPIOptions struct { - *options.ServerAPIOptionsBuilder + *options.ServerAPIOptions } type serverAPIVersion = options.ServerAPIVersion @@ -37,7 +37,7 @@ func (s *serverAPIOptions) UnmarshalBSON(data []byte) error { return fmt.Errorf("unrecognized fields for serverAPIOptions: %v", mapKeys(temp.Extra)) } - s.ServerAPIOptionsBuilder = options.ServerAPI(temp.ServerAPIVersion) + s.ServerAPIOptions = options.ServerAPI(temp.ServerAPIVersion) if temp.DeprecationErrors != nil { s.SetDeprecationErrors(*temp.DeprecationErrors) } diff --git a/internal/integration/unified_spec_test.go b/internal/integration/unified_spec_test.go index 0a69e4300b..2406426d9c 100644 --- a/internal/integration/unified_spec_test.go +++ b/internal/integration/unified_spec_test.go @@ -27,8 +27,6 @@ import ( "go.mongodb.org/mongo-driver/v2/internal/failpoint" "go.mongodb.org/mongo-driver/v2/internal/integration/mtest" "go.mongodb.org/mongo-driver/v2/internal/integtest" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" - "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/address" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -287,38 +285,24 @@ func runSpecTestCase(mt *mtest.T, test *testCase, testFile testFile) { // Reset the client using the client options specified in the test. testClientOpts := createClientOptions(mt, test.ClientOptions) - args, err := mongoutil.NewOptions[options.ClientOptions](testClientOpts) - require.NoError(mt, err, "failed to construct options from builder") - // If AutoEncryptionOptions is set and AutoEncryption isn't disabled (neither // bypassAutoEncryption nor bypassQueryAnalysis are true), then add extra options to load // the crypt_shared library. - if args.AutoEncryptionOptions != nil { - aeArgs, err := mongoutil.NewOptions[options.AutoEncryptionOptions](args.AutoEncryptionOptions) - require.NoError(mt, err, "failed to construct options from builder") + if testClientOpts.AutoEncryptionOptions != nil { + aeOpts := testClientOpts.AutoEncryptionOptions - bypassAutoEncryption := aeArgs.BypassAutoEncryption != nil && *aeArgs.BypassAutoEncryption - bypassQueryAnalysis := aeArgs.BypassQueryAnalysis != nil && *aeArgs.BypassQueryAnalysis + bypassAutoEncryption := aeOpts.BypassAutoEncryption != nil && *aeOpts.BypassAutoEncryption + bypassQueryAnalysis := aeOpts.BypassQueryAnalysis != nil && *aeOpts.BypassQueryAnalysis if !bypassAutoEncryption && !bypassQueryAnalysis { - if aeArgs.ExtraOptions == nil { - aeArgs.ExtraOptions = make(map[string]interface{}) + if aeOpts.ExtraOptions == nil { + aeOpts.ExtraOptions = make(map[string]interface{}) } for k, v := range getCryptSharedLibExtraOptions() { - aeArgs.ExtraOptions[k] = v + aeOpts.ExtraOptions[k] = v } } - - args.AutoEncryptionOptions = &options.AutoEncryptionOptionsBuilder{ - Opts: []func(*options.AutoEncryptionOptions) error{ - func(args *options.AutoEncryptionOptions) error { - *args = *aeArgs - - return nil - }, - }, - } } test.monitor = newUnifiedRunnerEventMonitor() @@ -326,7 +310,7 @@ func runSpecTestCase(mt *mtest.T, test *testCase, testFile testFile) { Event: test.monitor.handlePoolEvent, }) testClientOpts.SetServerMonitor(test.monitor.sdamMonitor) - if args.HeartbeatInterval == nil { + if testClientOpts.HeartbeatInterval == nil { // If one isn't specified in the test, use a low heartbeat frequency so the Client will quickly recover when // using failpoints that cause SDAM state changes. testClientOpts.SetHeartbeatInterval(defaultHeartbeatInterval) diff --git a/internal/integtest/integtest.go b/internal/integtest/integtest.go index 2f09c4dc30..37ab6e4d77 100644 --- a/internal/integtest/integtest.go +++ b/internal/integtest/integtest.go @@ -78,7 +78,7 @@ func AddCompressorToURI(uri string) string { } // AddTestServerAPIVersion adds the latest server API version in a ServerAPIOptions to passed-in opts. -func AddTestServerAPIVersion(opts *options.ClientOptionsBuilder) { +func AddTestServerAPIVersion(opts *options.ClientOptions) { if os.Getenv("REQUIRE_API_VERSION") == "true" { opts.SetServerAPIOptions(options.ServerAPI(driver.TestServerAPIVersion)) } diff --git a/internal/mongoutil/mongoutil.go b/internal/mongoutil/mongoutil.go index f42dfd3da4..0345b96e8f 100644 --- a/internal/mongoutil/mongoutil.go +++ b/internal/mongoutil/mongoutil.go @@ -71,21 +71,15 @@ func NewOptionsLister[T any](args *T, callback func(*T) error) *OptionsLister[T] // AuthFromURI will create a Credentials object given the provided URI. func AuthFromURI(uri string) (*options.Credential, error) { - args, err := NewOptions[options.ClientOptions](options.Client().ApplyURI(uri)) - if err != nil { - return nil, err - } + opts := options.Client().ApplyURI(uri) - return args.Auth, nil + return opts.Auth, nil } // HostsFromURI will parse the hosts in the URI and return them as a slice of // strings. func HostsFromURI(uri string) ([]string, error) { - args, err := NewOptions[options.ClientOptions](options.Client().ApplyURI(uri)) - if err != nil { - return nil, err - } + opts := options.Client().ApplyURI(uri) - return args.Hosts, nil + return opts.Hosts, nil } diff --git a/internal/mongoutil/mongoutil_test.go b/internal/mongoutil/mongoutil_test.go index 522d47ce3e..661ee5f5bb 100644 --- a/internal/mongoutil/mongoutil_test.go +++ b/internal/mongoutil/mongoutil_test.go @@ -9,161 +9,13 @@ package mongoutil import ( "strings" "testing" - "time" - "go.mongodb.org/mongo-driver/v2/internal/assert" - "go.mongodb.org/mongo-driver/v2/internal/ptrutil" "go.mongodb.org/mongo-driver/v2/mongo/options" ) -func TestNewOptions(t *testing.T) { - t.Parallel() - - // For simplicity, we just chose one options type to test on. This should be - // WLOG since (1) a user cannot merge mixed options, and (2) exported data in - // the options package cannot be backwards breaking. If - // options-package-specific functionality needs to be tested, it should be - // done in a separate test. - clientTests := []struct { - name string - opts []options.Lister[options.ClientOptions] - want options.ClientOptions - }{ - { - name: "nil options", - opts: nil, - want: options.ClientOptions{}, - }, - { - name: "no options", - opts: []options.Lister[options.ClientOptions]{}, - want: options.ClientOptions{}, - }, - { - name: "one option", - opts: []options.Lister[options.ClientOptions]{ - options.Client().SetAppName("testApp"), - }, - want: options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp")}, - }, - { - name: "one nil option", - opts: []options.Lister[options.ClientOptions]{nil}, - want: options.ClientOptions{}, - }, - { - name: "many same options", - opts: []options.Lister[options.ClientOptions]{ - options.Client().SetAppName("testApp"), - options.Client().SetAppName("testApp"), - }, - want: options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp")}, - }, - { - name: "many different options (last one wins)", - opts: []options.Lister[options.ClientOptions]{ - options.Client().SetAppName("testApp1"), - options.Client().SetAppName("testApp2"), - }, - want: options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp2")}, - }, - { - name: "many nil options", - opts: []options.Lister[options.ClientOptions]{nil, nil}, - want: options.ClientOptions{}, - }, - { - name: "many options where last is nil (non-nil wins)", - opts: []options.Lister[options.ClientOptions]{ - options.Client().SetAppName("testApp"), - nil, - }, - want: options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp")}, - }, - { - name: "many nil options where first is nil (non-nil wins)", - opts: []options.Lister[options.ClientOptions]{ - nil, - options.Client().SetAppName("testApp"), - }, - want: options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp")}, - }, - { - name: "many nil options where middle is non-nil (non-nil wins)", - opts: []options.Lister[options.ClientOptions]{ - nil, - options.Client().SetAppName("testApp"), - nil, - }, - want: options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp")}, - }, - } - - for _, test := range clientTests { - test := test - - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - got, err := NewOptions[options.ClientOptions](test.opts...) - assert.NoError(t, err) - - // WLOG it should be enough to test a small subset of arguments. - assert.Equal(t, test.want.AppName, got.AppName) - }) - } -} - -func TestNewOptionsLister(t *testing.T) { - t.Parallel() - - // For simplicity, we just chose one options type to test on. This should be - // WLOG since (1) a user cannot merge mixed options, and (2) exported data in - // the options package cannot be backwards breaking. If - // options-package-specific functionality needs to be tested, it should be - // done in a separate test. - clientTests := []struct { - name string - args *options.ClientOptions - want options.ClientOptions - }{ - { - name: "nil args", - args: nil, - want: options.ClientOptions{}, - }, - { - name: "no args", - args: &options.ClientOptions{}, - want: options.ClientOptions{}, - }, - { - name: "args", - args: &options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp")}, - want: options.ClientOptions{AppName: ptrutil.Ptr[string]("testApp")}, - }, - } - - for _, test := range clientTests { - test := test - - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - opts := NewOptionsLister(test.args, nil) - - got, err := NewOptions[options.ClientOptions](opts) - assert.NoError(t, err) - - // WLOG it should be enough to test a small subset of arguments. - assert.Equal(t, test.want.AppName, got.AppName) - }) - } -} - func BenchmarkNewOptions(b *testing.B) { b.Run("reflect.ValueOf is always called", func(b *testing.B) { - opts := make([]options.Lister[options.ClientOptions], b.N) + opts := make([]options.Lister[options.FindOptions], b.N) // Create a huge string to see if we can force reflect.ValueOf to use heap // over stack. @@ -171,28 +23,12 @@ func BenchmarkNewOptions(b *testing.B) { str := strings.Repeat("a", size) for i := 0; i < b.N; i++ { - opts[i] = options.Client().ApplyURI("x").SetAppName(str). - SetAuth(options.Credential{}).SetHosts([]string{"x", "y"}). - SetDirect(true).SetTimeout(time.Second) - } - - b.ReportAllocs() - for i := 0; i < b.N; i++ { - _, _ = NewOptions[options.ClientOptions](opts...) - } - }) - - b.Run("reflect.ValuOf is never called", func(b *testing.B) { - opts := make([]options.Lister[options.LoggerOptions], b.N) - - for i := 0; i < b.N; i++ { - var lo *options.LoggerOptionsBuilder - opts[i] = lo + opts[i] = options.Find().SetComment(str).SetHint("y").SetMin(1).SetMax(2) } b.ReportAllocs() for i := 0; i < b.N; i++ { - _, _ = NewOptions[options.LoggerOptions](opts...) + _, _ = NewOptions[options.FindOptions](opts...) } }) } diff --git a/internal/test/goleak/goleak_test.go b/internal/test/goleak/goleak_test.go index 5ff16b750f..033668a338 100644 --- a/internal/test/goleak/goleak_test.go +++ b/internal/test/goleak/goleak_test.go @@ -28,7 +28,7 @@ var dbName = fmt.Sprintf("goleak-%d", time.Now().Unix()) func TestGoroutineLeak(t *testing.T) { testCases := []struct { desc string - opts options.Lister[options.ClientOptions] + opts *options.ClientOptions }{ { desc: "base", diff --git a/mongo/client.go b/mongo/client.go index f3ed5d9097..df190b7450 100644 --- a/mongo/client.go +++ b/mongo/client.go @@ -106,7 +106,7 @@ type Client struct { // // The Client.Ping method can be used to verify that the deployment is successfully connected and the // Client was correctly configured. -func Connect(opts ...options.Lister[options.ClientOptions]) (*Client, error) { +func Connect(opts ...*options.ClientOptions) (*Client, error) { c, err := newClient(opts...) if err != nil { return nil, err @@ -131,11 +131,8 @@ func Connect(opts ...options.Lister[options.ClientOptions]) (*Client, error) { // option fields of previous options, there is no partial overwriting. For example, if Username is // set in the Auth field for the first option, and Password is set for the second but with no // Username, after the merge the Username field will be empty. -func newClient(opts ...options.Lister[options.ClientOptions]) (*Client, error) { - args, err := mongoutil.NewOptions(opts...) - if err != nil { - return nil, err - } +func newClient(opts ...*options.ClientOptions) (*Client, error) { + clientOpts := options.MergeClientOptions(opts...) id, err := uuid.New() if err != nil { @@ -148,91 +145,91 @@ func newClient(opts ...options.Lister[options.ClientOptions]) (*Client, error) { // LocalThreshold client.localThreshold = defaultLocalThreshold - if args.LocalThreshold != nil { - client.localThreshold = *args.LocalThreshold + if clientOpts.LocalThreshold != nil { + client.localThreshold = *clientOpts.LocalThreshold } // Monitor - if args.Monitor != nil { - client.monitor = args.Monitor + if clientOpts.Monitor != nil { + client.monitor = clientOpts.Monitor } // ServerMonitor - if args.ServerMonitor != nil { - client.serverMonitor = args.ServerMonitor + if clientOpts.ServerMonitor != nil { + client.serverMonitor = clientOpts.ServerMonitor } // ReadConcern client.readConcern = &readconcern.ReadConcern{} - if args.ReadConcern != nil { - client.readConcern = args.ReadConcern + if clientOpts.ReadConcern != nil { + client.readConcern = clientOpts.ReadConcern } // ReadPreference client.readPreference = readpref.Primary() - if args.ReadPreference != nil { - client.readPreference = args.ReadPreference + if clientOpts.ReadPreference != nil { + client.readPreference = clientOpts.ReadPreference } // BSONOptions - if args.BSONOptions != nil { - client.bsonOpts = args.BSONOptions + if clientOpts.BSONOptions != nil { + client.bsonOpts = clientOpts.BSONOptions } // Registry client.registry = defaultRegistry - if args.Registry != nil { - client.registry = args.Registry + if clientOpts.Registry != nil { + client.registry = clientOpts.Registry } // RetryWrites client.retryWrites = true // retry writes on by default - if args.RetryWrites != nil { - client.retryWrites = *args.RetryWrites + if clientOpts.RetryWrites != nil { + client.retryWrites = *clientOpts.RetryWrites } client.retryReads = true - if args.RetryReads != nil { - client.retryReads = *args.RetryReads + if clientOpts.RetryReads != nil { + client.retryReads = *clientOpts.RetryReads } // Timeout - client.timeout = args.Timeout - client.httpClient = args.HTTPClient + client.timeout = clientOpts.Timeout + client.httpClient = clientOpts.HTTPClient // WriteConcern - if args.WriteConcern != nil { - client.writeConcern = args.WriteConcern + if clientOpts.WriteConcern != nil { + client.writeConcern = clientOpts.WriteConcern } // AutoEncryptionOptions - if args.AutoEncryptionOptions != nil { - if err := client.configureAutoEncryption(args); err != nil { + if clientOpts.AutoEncryptionOptions != nil { + if err := client.configureAutoEncryption(clientOpts); err != nil { return nil, err } } else { - client.cryptFLE = args.Crypt + client.cryptFLE = clientOpts.Crypt } // Deployment - if args.Deployment != nil { - client.deployment = args.Deployment + if clientOpts.Deployment != nil { + client.deployment = clientOpts.Deployment } // Set default options - if args.MaxPoolSize == nil { + if clientOpts.MaxPoolSize == nil { defaultMaxPoolSize := uint64(defaultMaxPoolSize) - args.MaxPoolSize = &defaultMaxPoolSize + clientOpts.MaxPoolSize = &defaultMaxPoolSize } - if args.Auth != nil { + if clientOpts.Auth != nil { client.authenticator, err = auth.CreateAuthenticator( - args.Auth.AuthMechanism, - topology.ConvertCreds(args.Auth), - args.HTTPClient, + clientOpts.Auth.AuthMechanism, + topology.ConvertCreds(clientOpts.Auth), + clientOpts.HTTPClient, ) if err != nil { return nil, fmt.Errorf("error creating authenticator: %w", err) } } - cfg, err := topology.NewConfigFromOptionsWithAuthenticator(args, client.clock, client.authenticator) + cfg, err := topology.NewConfigFromOptionsWithAuthenticator(clientOpts, client.clock, client.authenticator) if err != nil { return nil, err } var connectTimeout time.Duration - if args.ConnectTimeout != nil { - connectTimeout = *args.ConnectTimeout + if clientOpts.ConnectTimeout != nil { + connectTimeout = *clientOpts.ConnectTimeout } client.serverAPI = topology.ServerAPIFromServerOptions(connectTimeout, cfg.ServerOpts) @@ -245,7 +242,7 @@ func newClient(opts ...options.Lister[options.ClientOptions]) (*Client, error) { } // Create a logger for the client. - client.logger, err = newLogger(args.LoggerOptions) + client.logger, err = newLogger(clientOpts.LoggerOptions) if err != nil { return nil, fmt.Errorf("invalid logger options: %w", err) } @@ -474,15 +471,11 @@ func (c *Client) endSessions(ctx context.Context) { } func (c *Client) configureAutoEncryption(args *options.ClientOptions) error { - aeArgs, err := mongoutil.NewOptions[options.AutoEncryptionOptions](args.AutoEncryptionOptions) - if err != nil { - return fmt.Errorf("failed to construct options from builder: %w", err) - } - - c.encryptedFieldsMap = aeArgs.EncryptedFieldsMap + c.encryptedFieldsMap = args.AutoEncryptionOptions.EncryptedFieldsMap if err := c.configureKeyVaultClientFLE(args); err != nil { return err } + if err := c.configureMetadataClientFLE(args); err != nil { return err } @@ -515,68 +508,57 @@ func (c *Client) getOrCreateInternalClient(args *options.ClientOptions) (*Client argsCopy.AutoEncryptionOptions = nil argsCopy.MinPoolSize = ptrutil.Ptr[uint64](0) - opts := mongoutil.NewOptionsLister(&argsCopy, nil) - var err error - c.internalClientFLE, err = newClient(opts) + c.internalClientFLE, err = newClient(&argsCopy) return c.internalClientFLE, err } -func (c *Client) configureKeyVaultClientFLE(clientArgs *options.ClientOptions) error { - // parse key vault options and create new key vault client - aeArgs, err := mongoutil.NewOptions[options.AutoEncryptionOptions](clientArgs.AutoEncryptionOptions) - if err != nil { - return fmt.Errorf("failed to construct options from builder: %w", err) - } +func (c *Client) configureKeyVaultClientFLE(clientOpts *options.ClientOptions) error { + aeOpts := clientOpts.AutoEncryptionOptions + + var err error switch { - case aeArgs.KeyVaultClientOptions != nil: - c.keyVaultClientFLE, err = newClient(aeArgs.KeyVaultClientOptions) - case clientArgs.MaxPoolSize != nil && *clientArgs.MaxPoolSize == 0: + case aeOpts.KeyVaultClientOptions != nil: + c.keyVaultClientFLE, err = newClient(aeOpts.KeyVaultClientOptions) + case clientOpts.MaxPoolSize != nil && *clientOpts.MaxPoolSize == 0: c.keyVaultClientFLE = c default: - c.keyVaultClientFLE, err = c.getOrCreateInternalClient(clientArgs) + c.keyVaultClientFLE, err = c.getOrCreateInternalClient(clientOpts) } if err != nil { return err } - dbName, collName := splitNamespace(aeArgs.KeyVaultNamespace) + dbName, collName := splitNamespace(aeOpts.KeyVaultNamespace) c.keyVaultCollFLE = c.keyVaultClientFLE.Database(dbName).Collection(collName, keyVaultCollOpts) return nil } -func (c *Client) configureMetadataClientFLE(clientArgs *options.ClientOptions) error { - // parse key vault options and create new key vault client - aeArgs, err := mongoutil.NewOptions[options.AutoEncryptionOptions](clientArgs.AutoEncryptionOptions) - if err != nil { - return fmt.Errorf("failed to construct options from builder: %w", err) - } +func (c *Client) configureMetadataClientFLE(clientOpts *options.ClientOptions) error { + aeOpts := clientOpts.AutoEncryptionOptions - if aeArgs.BypassAutoEncryption != nil && *aeArgs.BypassAutoEncryption { + if aeOpts.BypassAutoEncryption != nil && *aeOpts.BypassAutoEncryption { // no need for a metadata client. return nil } - if clientArgs.MaxPoolSize != nil && *clientArgs.MaxPoolSize == 0 { + if clientOpts.MaxPoolSize != nil && *clientOpts.MaxPoolSize == 0 { c.metadataClientFLE = c return nil } - c.metadataClientFLE, err = c.getOrCreateInternalClient(clientArgs) + var err error + c.metadataClientFLE, err = c.getOrCreateInternalClient(clientOpts) + return err } -func (c *Client) newMongoCrypt(opts options.Lister[options.AutoEncryptionOptions]) (*mongocrypt.MongoCrypt, error) { - args, err := mongoutil.NewOptions[options.AutoEncryptionOptions](opts) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - +func (c *Client) newMongoCrypt(opts *options.AutoEncryptionOptions) (*mongocrypt.MongoCrypt, error) { // convert schemas in SchemaMap to bsoncore documents cryptSchemaMap := make(map[string]bsoncore.Document) - for k, v := range args.SchemaMap { + for k, v := range opts.SchemaMap { schema, err := marshal(v, c.bsonOpts, c.registry) if err != nil { return nil, err @@ -586,7 +568,7 @@ func (c *Client) newMongoCrypt(opts options.Lister[options.AutoEncryptionOptions // convert schemas in EncryptedFieldsMap to bsoncore documents cryptEncryptedFieldsMap := make(map[string]bsoncore.Document) - for k, v := range args.EncryptedFieldsMap { + for k, v := range opts.EncryptedFieldsMap { encryptedFields, err := marshal(v, c.bsonOpts, c.registry) if err != nil { return nil, err @@ -594,7 +576,7 @@ func (c *Client) newMongoCrypt(opts options.Lister[options.AutoEncryptionOptions cryptEncryptedFieldsMap[k] = encryptedFields } - kmsProviders, err := marshal(args.KmsProviders, c.bsonOpts, c.registry) + kmsProviders, err := marshal(opts.KmsProviders, c.bsonOpts, c.registry) if err != nil { return nil, fmt.Errorf("error creating KMS providers document: %w", err) } @@ -602,7 +584,7 @@ func (c *Client) newMongoCrypt(opts options.Lister[options.AutoEncryptionOptions // Set the crypt_shared library override path from the "cryptSharedLibPath" extra option if one // was set. cryptSharedLibPath := "" - if val, ok := args.ExtraOptions["cryptSharedLibPath"]; ok { + if val, ok := opts.ExtraOptions["cryptSharedLibPath"]; ok { str, ok := val.(string) if !ok { return nil, fmt.Errorf( @@ -615,12 +597,12 @@ func (c *Client) newMongoCrypt(opts options.Lister[options.AutoEncryptionOptions // intended for use from tests; there is no supported public API for explicitly disabling // loading the crypt_shared library. cryptSharedLibDisabled := false - if v, ok := args.ExtraOptions["__cryptSharedLibDisabledForTestOnly"]; ok { + if v, ok := opts.ExtraOptions["__cryptSharedLibDisabledForTestOnly"]; ok { cryptSharedLibDisabled = v.(bool) } - bypassAutoEncryption := args.BypassAutoEncryption != nil && *args.BypassAutoEncryption - bypassQueryAnalysis := args.BypassQueryAnalysis != nil && *args.BypassQueryAnalysis + bypassAutoEncryption := opts.BypassAutoEncryption != nil && *opts.BypassAutoEncryption + bypassQueryAnalysis := opts.BypassQueryAnalysis != nil && *opts.BypassQueryAnalysis mc, err := mongocrypt.NewMongoCrypt(mcopts.MongoCrypt(). SetKmsProviders(kmsProviders). @@ -629,13 +611,13 @@ func (c *Client) newMongoCrypt(opts options.Lister[options.AutoEncryptionOptions SetEncryptedFieldsMap(cryptEncryptedFieldsMap). SetCryptSharedLibDisabled(cryptSharedLibDisabled || bypassAutoEncryption). SetCryptSharedLibOverridePath(cryptSharedLibPath). - SetHTTPClient(args.HTTPClient)) + SetHTTPClient(opts.HTTPClient)) if err != nil { return nil, err } var cryptSharedLibRequired bool - if val, ok := args.ExtraOptions["cryptSharedLibRequired"]; ok { + if val, ok := opts.ExtraOptions["cryptSharedLibRequired"]; ok { b, ok := val.(bool) if !ok { return nil, fmt.Errorf( @@ -656,10 +638,8 @@ func (c *Client) newMongoCrypt(opts options.Lister[options.AutoEncryptionOptions } //nolint:unused // the unused linter thinks that this function is unreachable because "c.newMongoCrypt" always panics without the "cse" build tag set. -func (c *Client) configureCryptFLE(mc *mongocrypt.MongoCrypt, opts options.Lister[options.AutoEncryptionOptions]) { - args, _ := mongoutil.NewOptions[options.AutoEncryptionOptions](opts) - - bypass := args.BypassAutoEncryption != nil && *args.BypassAutoEncryption +func (c *Client) configureCryptFLE(mc *mongocrypt.MongoCrypt, opts *options.AutoEncryptionOptions) { + bypass := opts.BypassAutoEncryption != nil && *opts.BypassAutoEncryption kr := keyRetriever{coll: c.keyVaultCollFLE} var cir collInfoRetriever // If bypass is true, c.metadataClientFLE is nil and the collInfoRetriever @@ -674,7 +654,7 @@ func (c *Client) configureCryptFLE(mc *mongocrypt.MongoCrypt, opts options.Liste CollInfoFn: cir.cryptCollInfo, KeyFn: kr.cryptKeys, MarkFn: c.mongocryptdFLE.markCommand, - TLSConfig: args.TLSConfig, + TLSConfig: opts.TLSConfig, BypassAutoEncryption: bypass, }) } @@ -896,28 +876,23 @@ func (c *Client) createBaseCursorOptions() driver.CursorOptions { // newLogger will use the LoggerOptions to create an internal logger and publish // messages using a LogSink. -func newLogger(opts options.Lister[options.LoggerOptions]) (*logger.Logger, error) { +func newLogger(opts *options.LoggerOptions) (*logger.Logger, error) { // If there are no logger options, then create a default logger. if opts == nil { opts = options.Logger() } - args, err := mongoutil.NewOptions[options.LoggerOptions](opts) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - // If there are no component-level options and the environment does not // contain component variables, then do nothing. - if len(args.ComponentLevels) == 0 && !logger.EnvHasComponentVariables() { + if len(opts.ComponentLevels) == 0 && !logger.EnvHasComponentVariables() { return nil, nil } // Otherwise, collect the component-level options and create a logger. componentLevels := make(map[logger.Component]logger.Level) - for component, level := range args.ComponentLevels { + for component, level := range opts.ComponentLevels { componentLevels[logger.Component(component)] = logger.Level(level) } - return logger.New(args.Sink, args.MaxDocumentLength, componentLevels) + return logger.New(opts.Sink, opts.MaxDocumentLength, componentLevels) } diff --git a/mongo/client_test.go b/mongo/client_test.go index 70e2075124..e4c79c26a2 100644 --- a/mongo/client_test.go +++ b/mongo/client_test.go @@ -18,7 +18,6 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/integtest" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readconcern" @@ -31,7 +30,7 @@ import ( var bgCtx = context.Background() -func setupClient(opts ...options.Lister[options.ClientOptions]) *Client { +func setupClient(opts ...*options.ClientOptions) *Client { if len(opts) == 0 { clientOpts := options.Client().ApplyURI("mongodb://localhost:27017") integtest.AddTestServerAPIVersion(clientOpts) @@ -128,7 +127,7 @@ func TestClient(t *testing.T) { t.Run("localThreshold", func(t *testing.T) { testCases := []struct { name string - opts *options.ClientOptionsBuilder + opts *options.ClientOptions expectedThreshold time.Duration }{ {"default", options.Client(), defaultLocalThreshold}, @@ -150,7 +149,7 @@ func TestClient(t *testing.T) { t.Run("min pool size from Set*PoolSize()", func(t *testing.T) { testCases := []struct { name string - opts *options.ClientOptionsBuilder + opts *options.ClientOptions err error }{ { @@ -194,7 +193,7 @@ func TestClient(t *testing.T) { t.Run("min pool size from ApplyURI()", func(t *testing.T) { testCases := []struct { name string - opts *options.ClientOptionsBuilder + opts *options.ClientOptions err error }{ { @@ -241,7 +240,7 @@ func TestClient(t *testing.T) { testCases := []struct { name string - opts *options.ClientOptionsBuilder + opts *options.ClientOptions expectErr bool expectedRetry bool }{ @@ -269,7 +268,7 @@ func TestClient(t *testing.T) { testCases := []struct { name string - opts *options.ClientOptionsBuilder + opts *options.ClientOptions expectErr bool expectedRetry bool }{ @@ -317,8 +316,7 @@ func TestClient(t *testing.T) { uri := "mongodb://localhost:27017/foobar" opts := options.Client().ApplyURI(uri) - args, _ := mongoutil.NewOptions[options.ClientOptions](opts) - got := args.GetURI() + got := opts.GetURI() assert.Equal(t, uri, got, "expected GetURI to return %v, got %v", uri, got) }) @@ -415,7 +413,7 @@ func TestClient(t *testing.T) { } }) t.Run("serverAPI version", func(t *testing.T) { - getServerAPIOptions := func() *options.ServerAPIOptionsBuilder { + getServerAPIOptions := func() *options.ServerAPIOptions { return options.ServerAPI(options.ServerAPIVersion1). SetStrict(false).SetDeprecationErrors(false) } diff --git a/mongo/mongocryptd.go b/mongo/mongocryptd.go index cedc48381c..9e6e9daf11 100644 --- a/mongo/mongocryptd.go +++ b/mongo/mongocryptd.go @@ -8,12 +8,10 @@ package mongo import ( "context" - "fmt" "os/exec" "strings" "time" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readconcern" "go.mongodb.org/mongo-driver/v2/mongo/readpref" @@ -40,24 +38,19 @@ type mongocryptdClient struct { // newMongocryptdClient creates a client to mongocryptd. // newMongocryptdClient is expected to not be called if the crypt shared library is available. // The crypt shared library replaces all mongocryptd functionality. -func newMongocryptdClient(opts options.Lister[options.AutoEncryptionOptions]) (*mongocryptdClient, error) { +func newMongocryptdClient(opts *options.AutoEncryptionOptions) (*mongocryptdClient, error) { // create mcryptClient instance and spawn process if necessary var bypassSpawn bool var bypassAutoEncryption bool - args, err := mongoutil.NewOptions[options.AutoEncryptionOptions](opts) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - - if bypass, ok := args.ExtraOptions["mongocryptdBypassSpawn"]; ok { + if bypass, ok := opts.ExtraOptions["mongocryptdBypassSpawn"]; ok { bypassSpawn = bypass.(bool) } - if args.BypassAutoEncryption != nil { - bypassAutoEncryption = *args.BypassAutoEncryption + if opts.BypassAutoEncryption != nil { + bypassAutoEncryption = *opts.BypassAutoEncryption } - bypassQueryAnalysis := args.BypassQueryAnalysis != nil && *args.BypassQueryAnalysis + bypassQueryAnalysis := opts.BypassQueryAnalysis != nil && *opts.BypassQueryAnalysis mc := &mongocryptdClient{ // mongocryptd should not be spawned if any of these conditions are true: @@ -68,7 +61,7 @@ func newMongocryptdClient(opts options.Lister[options.AutoEncryptionOptions]) (* } if !mc.bypassSpawn { - mc.path, mc.spawnArgs = createSpawnArgs(args.ExtraOptions) + mc.path, mc.spawnArgs = createSpawnArgs(opts.ExtraOptions) if err := mc.spawnProcess(); err != nil { return nil, err } @@ -76,7 +69,7 @@ func newMongocryptdClient(opts options.Lister[options.AutoEncryptionOptions]) (* // get connection string uri := defaultURI - if u, ok := args.ExtraOptions["mongocryptdURI"]; ok { + if u, ok := opts.ExtraOptions["mongocryptdURI"]; ok { uri = u.(string) } diff --git a/mongo/ocsp_test.go b/mongo/ocsp_test.go index b180caa2a5..d231b21b6d 100644 --- a/mongo/ocsp_test.go +++ b/mongo/ocsp_test.go @@ -16,7 +16,6 @@ import ( "go.mongodb.org/mongo-driver/v2/internal/assert" "go.mongodb.org/mongo-driver/v2/internal/integtest" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readpref" ) @@ -57,7 +56,7 @@ func TestOCSP(t *testing.T) { }) } -func createOCSPClientOptions(uri string) *options.ClientOptionsBuilder { +func createOCSPClientOptions(uri string) *options.ClientOptions { opts := options.Client().ApplyURI(uri) timeout := 500 * time.Millisecond @@ -69,14 +68,12 @@ func createOCSPClientOptions(uri string) *options.ClientOptionsBuilder { return opts } -func createInsecureOCSPClientOptions(uri string) *options.ClientOptionsBuilder { +func createInsecureOCSPClientOptions(uri string) *options.ClientOptions { opts := createOCSPClientOptions(uri) - args, _ := mongoutil.NewOptions[options.ClientOptions](opts) - - if args.TLSConfig != nil { - args.TLSConfig.InsecureSkipVerify = true - opts.SetTLSConfig(args.TLSConfig) + if opts.TLSConfig != nil { + opts.TLSConfig.InsecureSkipVerify = true + opts.SetTLSConfig(opts.TLSConfig) return opts } diff --git a/mongo/options/autoencryptionoptions.go b/mongo/options/autoencryptionoptions.go index 81f16cf028..c630659c5a 100644 --- a/mongo/options/autoencryptionoptions.go +++ b/mongo/options/autoencryptionoptions.go @@ -9,6 +9,8 @@ package options import ( "crypto/tls" "net/http" + + "go.mongodb.org/mongo-driver/v2/internal/httputil" ) // AutoEncryptionOptions represents arguments used to configure auto encryption/decryption behavior for a mongo.Client @@ -28,7 +30,7 @@ import ( // // See corresponding setter methods for documentation. type AutoEncryptionOptions struct { - KeyVaultClientOptions Lister[ClientOptions] + KeyVaultClientOptions *ClientOptions KeyVaultNamespace string KmsProviders map[string]map[string]interface{} SchemaMap map[string]interface{} @@ -40,29 +42,11 @@ type AutoEncryptionOptions struct { BypassQueryAnalysis *bool } -// AutoEncryptionOptionsBuilder contains options to configure automatic -// encryption for operations. Each option can be set through setter functions. -// See documentation for each setter function for an explanation of the option. -type AutoEncryptionOptionsBuilder struct { - Opts []func(*AutoEncryptionOptions) error -} - // AutoEncryption creates a new AutoEncryptionOptions configured with default values. -func AutoEncryption() *AutoEncryptionOptionsBuilder { - opts := &AutoEncryptionOptionsBuilder{} - - opts.Opts = append(opts.Opts, func(args *AutoEncryptionOptions) error { - args.HTTPClient = http.DefaultClient - - return nil - }) - - return opts -} - -// List returns a list of AutoEncryptionOptions setter functions. -func (a *AutoEncryptionOptionsBuilder) List() []func(*AutoEncryptionOptions) error { - return a.Opts +func AutoEncryption() *AutoEncryptionOptions { + return &AutoEncryptionOptions{ + HTTPClient: httputil.DefaultHTTPClient, + } } // SetKeyVaultClientOptions specifies options for the client used to communicate with the key vault collection. @@ -74,34 +58,22 @@ func (a *AutoEncryptionOptionsBuilder) List() []func(*AutoEncryptionOptions) err // (and created if necessary). The internal mongo.Client may be shared during automatic encryption (if // BypassAutomaticEncryption is false). The internal mongo.Client is configured with the same options as the target // mongo.Client except minPoolSize is set to 0 and AutoEncryptionOptions is omitted. -func (a *AutoEncryptionOptionsBuilder) SetKeyVaultClientOptions(opts Lister[ClientOptions]) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.KeyVaultClientOptions = opts - - return nil - }) +func (a *AutoEncryptionOptions) SetKeyVaultClientOptions(opts *ClientOptions) *AutoEncryptionOptions { + a.KeyVaultClientOptions = opts return a } // SetKeyVaultNamespace specifies the namespace of the key vault collection. This is required. -func (a *AutoEncryptionOptionsBuilder) SetKeyVaultNamespace(ns string) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.KeyVaultNamespace = ns - - return nil - }) +func (a *AutoEncryptionOptions) SetKeyVaultNamespace(ns string) *AutoEncryptionOptions { + a.KeyVaultNamespace = ns return a } // SetKmsProviders specifies options for KMS providers. This is required. -func (a *AutoEncryptionOptionsBuilder) SetKmsProviders(providers map[string]map[string]interface{}) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.KmsProviders = providers - - return nil - }) +func (a *AutoEncryptionOptions) SetKmsProviders(providers map[string]map[string]interface{}) *AutoEncryptionOptions { + a.KmsProviders = providers return a } @@ -113,12 +85,8 @@ func (a *AutoEncryptionOptionsBuilder) SetKmsProviders(providers map[string]map[ // Supplying a schemaMap provides more security than relying on JSON Schemas obtained from the server. It protects // against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted // data that should be encrypted. -func (a *AutoEncryptionOptionsBuilder) SetSchemaMap(schemaMap map[string]interface{}) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.SchemaMap = schemaMap - - return nil - }) +func (a *AutoEncryptionOptions) SetSchemaMap(schemaMap map[string]interface{}) *AutoEncryptionOptions { + a.SchemaMap = schemaMap return a } @@ -131,12 +99,8 @@ func (a *AutoEncryptionOptionsBuilder) SetSchemaMap(schemaMap map[string]interfa // (and created if necessary). The internal mongo.Client may be shared for key vault operations (if KeyVaultClient is // unset). The internal mongo.Client is configured with the same options as the target mongo.Client except minPoolSize // is set to 0 and AutoEncryptionOptions is omitted. -func (a *AutoEncryptionOptionsBuilder) SetBypassAutoEncryption(bypass bool) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.BypassAutoEncryption = &bypass - - return nil - }) +func (a *AutoEncryptionOptions) SetBypassAutoEncryption(bypass bool) *AutoEncryptionOptions { + a.BypassAutoEncryption = &bypass return a } @@ -170,50 +134,33 @@ func (a *AutoEncryptionOptionsBuilder) SetBypassAutoEncryption(bypass bool) *Aut // absolute path to the directory containing the linked libmongocrypt library. Setting an override // path disables the default system library search path. If an override path is specified but the // crypt_shared library cannot be loaded, Client creation will return an error. Must be a string. -func (a *AutoEncryptionOptionsBuilder) SetExtraOptions(extraOpts map[string]interface{}) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.ExtraOptions = extraOpts - - return nil - }) +func (a *AutoEncryptionOptions) SetExtraOptions(extraOpts map[string]interface{}) *AutoEncryptionOptions { + a.ExtraOptions = extraOpts return a } // SetTLSConfig specifies tls.Config instances for each KMS provider to use to configure TLS on all connections created // to the KMS provider. -// -// This should only be used to set custom TLS configurations. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12. -func (a *AutoEncryptionOptionsBuilder) SetTLSConfig(cfg map[string]*tls.Config) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.TLSConfig = cfg - - return nil - }) +func (a *AutoEncryptionOptions) SetTLSConfig(cfg map[string]*tls.Config) *AutoEncryptionOptions { + // This should only be used to set custom TLS configurations. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12. + a.TLSConfig = cfg return a } // SetEncryptedFieldsMap specifies a map from namespace to local EncryptedFieldsMap document. // EncryptedFieldsMap is used for Queryable Encryption. -func (a *AutoEncryptionOptionsBuilder) SetEncryptedFieldsMap(ef map[string]interface{}) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.EncryptedFieldsMap = ef - - return nil - }) +func (a *AutoEncryptionOptions) SetEncryptedFieldsMap(ef map[string]interface{}) *AutoEncryptionOptions { + a.EncryptedFieldsMap = ef return a } // SetBypassQueryAnalysis specifies whether or not query analysis should be used for automatic encryption. // Use this option when using explicit encryption with Queryable Encryption. -func (a *AutoEncryptionOptionsBuilder) SetBypassQueryAnalysis(bypass bool) *AutoEncryptionOptionsBuilder { - a.Opts = append(a.Opts, func(args *AutoEncryptionOptions) error { - args.BypassQueryAnalysis = &bypass - - return nil - }) +func (a *AutoEncryptionOptions) SetBypassQueryAnalysis(bypass bool) *AutoEncryptionOptions { + a.BypassQueryAnalysis = &bypass return a } diff --git a/mongo/options/clientoptions.go b/mongo/options/clientoptions.go index 474ff5d829..007d235390 100644 --- a/mongo/options/clientoptions.go +++ b/mongo/options/clientoptions.go @@ -18,6 +18,7 @@ import ( "math" "net" "net/http" + "reflect" "strings" "time" @@ -240,7 +241,7 @@ type DriverInfo struct { type ClientOptions struct { AppName *string Auth *Credential - AutoEncryptionOptions Lister[AutoEncryptionOptions] + AutoEncryptionOptions *AutoEncryptionOptions ConnectTimeout *time.Duration Compressors []string Dialer ContextDialer @@ -252,7 +253,7 @@ type ClientOptions struct { HTTPClient *http.Client LoadBalanced *bool LocalThreshold *time.Duration - LoggerOptions Lister[LoggerOptions] + LoggerOptions *LoggerOptions MaxConnIdleTime *time.Duration MaxPoolSize *uint64 MinPoolSize *uint64 @@ -267,7 +268,7 @@ type ClientOptions struct { ReplicaSet *string RetryReads *bool RetryWrites *bool - ServerAPIOptions Lister[ServerAPIOptions] + ServerAPIOptions *ServerAPIOptions ServerMonitoringMode *string ServerSelectionTimeout *time.Duration SRVMaxHosts *int @@ -292,38 +293,17 @@ type ClientOptions struct { Deployment driver.Deployment connString *connstring.ConnString -} - -// ClientOptionsBuilder contains options to configure a Client instance. Each -// option can be set through setter functions. See documentation for each setter -// function for an explanation of the option. -type ClientOptionsBuilder struct { - Opts []func(*ClientOptions) error + err error } // Client creates a new ClientOptions instance. -func Client() *ClientOptionsBuilder { - opts := &ClientOptionsBuilder{} +func Client() *ClientOptions { + opts := &ClientOptions{} opts = opts.SetHTTPClient(httputil.DefaultHTTPClient) return opts } -// List returns a list of ClientOptions setter functions. -func (c *ClientOptionsBuilder) List() []func(*ClientOptions) error { - return c.Opts -} - -// GetURI returns the original URI used to configure the ClientOptions instance. -// If ApplyURI was not called during construction, this returns "". -func (opts *ClientOptions) GetURI() string { - if opts.connString == nil { - return "" - } - - return opts.connString.Original -} - func setURIOpts(uri string, opts *ClientOptions) error { connString, err := connstring.ParseAndValidate(uri) if err != nil { @@ -529,11 +509,9 @@ func setURIOpts(uri string, opts *ClientOptions) error { // GetURI returns the original URI used to configure the ClientOptions instance. // If ApplyURI was not called during construction, this returns "". -func (c *ClientOptionsBuilder) GetURI() string { - args, _ := getOptions[ClientOptions](c) - - if args != nil && args.connString != nil { - return args.connString.Original +func (c *ClientOptions) GetURI() string { + if c != nil && c.connString != nil { + return c.connString.Original } return "" @@ -541,102 +519,96 @@ func (c *ClientOptionsBuilder) GetURI() string { // Validate validates the client options. This method will return the first // error found. -func (c *ClientOptionsBuilder) Validate() error { - args, err := getOptions[ClientOptions](c) - if err != nil { - return err +func (c *ClientOptions) Validate() error { + if c.err != nil { + return c.err } // Direct connections cannot be made if multiple hosts are specified or an SRV // URI is used. - if args.Direct != nil && *args.Direct { - if len(args.Hosts) > 1 { + if c.Direct != nil && *c.Direct { + if len(c.Hosts) > 1 { return errors.New("a direct connection cannot be made if multiple hosts are specified") } - if args.connString != nil && args.connString.Scheme == connstring.SchemeMongoDBSRV { + if c.connString != nil && c.connString.Scheme == connstring.SchemeMongoDBSRV { return errors.New("a direct connection cannot be made if an SRV URI is used") } } - if args.HeartbeatInterval != nil && *args.HeartbeatInterval < (500*time.Millisecond) { + if c.HeartbeatInterval != nil && *c.HeartbeatInterval < (500*time.Millisecond) { return fmt.Errorf("heartbeatFrequencyMS must exceed the minimum heartbeat interval of 500ms, got heartbeatFrequencyMS=%q", - *args.HeartbeatInterval) + *c.HeartbeatInterval) } - if args.MaxPoolSize != nil && args.MinPoolSize != nil && *args.MaxPoolSize != 0 && - *args.MinPoolSize > *args.MaxPoolSize { + if c.MaxPoolSize != nil && c.MinPoolSize != nil && *c.MaxPoolSize != 0 && + *c.MinPoolSize > *c.MaxPoolSize { return fmt.Errorf("minPoolSize must be less than or equal to maxPoolSize, got minPoolSize=%d maxPoolSize=%d", - *args.MinPoolSize, *args.MaxPoolSize) + *c.MinPoolSize, *c.MaxPoolSize) } // verify server API version if ServerAPIOptions are passed in. - if args.ServerAPIOptions != nil { - serverAPIopts, err := getOptions[ServerAPIOptions](args.ServerAPIOptions) - if err != nil { - return fmt.Errorf("failed to construct options from builder: %w", err) - } - - if err := serverAPIopts.ServerAPIVersion.Validate(); err != nil { + if c.ServerAPIOptions != nil { + if err := c.ServerAPIOptions.ServerAPIVersion.Validate(); err != nil { return err } } // Validation for load-balanced mode. - if args.LoadBalanced != nil && *args.LoadBalanced { - if len(args.Hosts) > 1 { + if c.LoadBalanced != nil && *c.LoadBalanced { + if len(c.Hosts) > 1 { return connstring.ErrLoadBalancedWithMultipleHosts } - if args.ReplicaSet != nil { + if c.ReplicaSet != nil { return connstring.ErrLoadBalancedWithReplicaSet } - if args.Direct != nil && *args.Direct { + if c.Direct != nil && *c.Direct { return connstring.ErrLoadBalancedWithDirectConnection } } // Validation for srvMaxHosts. - if args.SRVMaxHosts != nil && *args.SRVMaxHosts > 0 { - if args.ReplicaSet != nil { + if c.SRVMaxHosts != nil && *c.SRVMaxHosts > 0 { + if c.ReplicaSet != nil { return connstring.ErrSRVMaxHostsWithReplicaSet } - if args.LoadBalanced != nil && *args.LoadBalanced { + if c.LoadBalanced != nil && *c.LoadBalanced { return connstring.ErrSRVMaxHostsWithLoadBalanced } } - if mode := args.ServerMonitoringMode; mode != nil && !connstring.IsValidServerMonitoringMode(*mode) { + if mode := c.ServerMonitoringMode; mode != nil && !connstring.IsValidServerMonitoringMode(*mode) { return fmt.Errorf("invalid server monitoring mode: %q", *mode) } - if to := args.Timeout; to != nil && *to < 0 { + if to := c.Timeout; to != nil && *to < 0 { return fmt.Errorf(`invalid value %q for "Timeout": value must be positive`, *to) } // OIDC Validation - if args.Auth != nil && args.Auth.AuthMechanism == auth.MongoDBOIDC { - if args.Auth.Password != "" { + if c.Auth != nil && c.Auth.AuthMechanism == auth.MongoDBOIDC { + if c.Auth.Password != "" { return fmt.Errorf("password must not be set for the %s auth mechanism", auth.MongoDBOIDC) } - if args.Auth.OIDCMachineCallback != nil && args.Auth.OIDCHumanCallback != nil { + if c.Auth.OIDCMachineCallback != nil && c.Auth.OIDCHumanCallback != nil { return fmt.Errorf("cannot set both OIDCMachineCallback and OIDCHumanCallback, only one may be specified") } - if args.Auth.OIDCHumanCallback == nil && args.Auth.AuthMechanismProperties[auth.AllowedHostsProp] != "" { + if c.Auth.OIDCHumanCallback == nil && c.Auth.AuthMechanismProperties[auth.AllowedHostsProp] != "" { return fmt.Errorf("Cannot specify ALLOWED_HOSTS without an OIDCHumanCallback") } - if env, ok := args.Auth.AuthMechanismProperties[auth.EnvironmentProp]; ok { + if env, ok := c.Auth.AuthMechanismProperties[auth.EnvironmentProp]; ok { switch env { case auth.GCPEnvironmentValue, auth.AzureEnvironmentValue: - if args.Auth.OIDCMachineCallback != nil { + if c.Auth.OIDCMachineCallback != nil { return fmt.Errorf("OIDCMachineCallback cannot be specified with the %s %q", env, auth.EnvironmentProp) } - if args.Auth.OIDCHumanCallback != nil { + if c.Auth.OIDCHumanCallback != nil { return fmt.Errorf("OIDCHumanCallback cannot be specified with the %s %q", env, auth.EnvironmentProp) } - if args.Auth.AuthMechanismProperties[auth.ResourceProp] == "" { + if c.Auth.AuthMechanismProperties[auth.ResourceProp] == "" { return fmt.Errorf("%q must be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp) } default: - if args.Auth.AuthMechanismProperties[auth.ResourceProp] != "" { + if c.Auth.AuthMechanismProperties[auth.ResourceProp] != "" { return fmt.Errorf("%q must not be set for the %s %q", auth.ResourceProp, env, auth.EnvironmentProp) } } @@ -660,10 +632,12 @@ func (c *ClientOptionsBuilder) Validate() error { // // For more information about the URI format, see https://www.mongodb.com/docs/manual/reference/connection-string/. See // mongo.Connect documentation for examples of using URIs for different Client configurations. -func (c *ClientOptionsBuilder) ApplyURI(uri string) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - return setURIOpts(uri, opts) - }) +func (c *ClientOptions) ApplyURI(uri string) *ClientOptions { + if c.err != nil { + return c + } + + c.err = setURIOpts(uri, c) return c } @@ -671,12 +645,8 @@ func (c *ClientOptionsBuilder) ApplyURI(uri string) *ClientOptionsBuilder { // SetAppName specifies an application name that is sent to the server when creating new connections. It is used by the // server to log connection and profiling information (e.g. slow query logs). This can also be set through the "appName" // URI option (e.g "appName=example_application"). The default is empty, meaning no app name will be sent. -func (c *ClientOptionsBuilder) SetAppName(s string) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.AppName = &s - - return nil - }) +func (c *ClientOptions) SetAppName(s string) *ClientOptions { + c.AppName = &s return c } @@ -684,12 +654,8 @@ func (c *ClientOptionsBuilder) SetAppName(s string) *ClientOptionsBuilder { // SetAuth specifies a Credential containing options for configuring authentication. See the options.Credential // documentation for more information about Credential fields. The default is an empty Credential, meaning no // authentication will be configured. -func (c *ClientOptionsBuilder) SetAuth(auth Credential) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Auth = &auth - - return nil - }) +func (c *ClientOptions) SetAuth(auth Credential) *ClientOptions { + c.Auth = &auth return c } @@ -710,12 +676,8 @@ func (c *ClientOptionsBuilder) SetAuth(auth Credential) *ClientOptionsBuilder { // // This can also be set through the "compressors" URI option (e.g. "compressors=zstd,zlib,snappy"). The default is // an empty slice, meaning no compression will be enabled. -func (c *ClientOptionsBuilder) SetCompressors(comps []string) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Compressors = comps - - return nil - }) +func (c *ClientOptions) SetCompressors(comps []string) *ClientOptions { + c.Compressors = comps return c } @@ -723,12 +685,8 @@ func (c *ClientOptionsBuilder) SetCompressors(comps []string) *ClientOptionsBuil // SetConnectTimeout specifies a timeout that is used for creating connections to the server. This can be set through // ApplyURI with the "connectTimeoutMS" (e.g "connectTimeoutMS=30") option. If set to 0, no timeout will be used. The // default is 30 seconds. -func (c *ClientOptionsBuilder) SetConnectTimeout(d time.Duration) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ConnectTimeout = &d - - return nil - }) +func (c *ClientOptions) SetConnectTimeout(d time.Duration) *ClientOptions { + c.ConnectTimeout = &d return c } @@ -736,12 +694,8 @@ func (c *ClientOptionsBuilder) SetConnectTimeout(d time.Duration) *ClientOptions // SetDialer specifies a custom ContextDialer to be used to create new connections to the server. This method overrides // the default net.Dialer, so dialer options such as Timeout, KeepAlive, Resolver, etc can be set. // See https://golang.org/pkg/net/#Dialer for more information about the net.Dialer type. -func (c *ClientOptionsBuilder) SetDialer(d ContextDialer) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Dialer = d - - return nil - }) +func (c *ClientOptions) SetDialer(d ContextDialer) *ClientOptions { + c.Dialer = d return c } @@ -761,12 +715,8 @@ func (c *ClientOptionsBuilder) SetDialer(d ContextDialer) *ClientOptionsBuilder // If the "connect" and "directConnection" URI options are both specified in the connection string, their values must // not conflict. Direct connections are not valid if multiple hosts are specified or an SRV URI is used. The default // value for this option is false. -func (c *ClientOptionsBuilder) SetDirect(b bool) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Direct = &b - - return nil - }) +func (c *ClientOptions) SetDirect(b bool) *ClientOptions { + c.Direct = &b return c } @@ -774,12 +724,8 @@ func (c *ClientOptionsBuilder) SetDirect(b bool) *ClientOptionsBuilder { // SetHeartbeatInterval specifies the amount of time to wait between periodic background server checks. This can also be // set through the "heartbeatFrequencyMS" URI option (e.g. "heartbeatFrequencyMS=10000"). The default is 10 seconds. // The minimum is 500ms. -func (c *ClientOptionsBuilder) SetHeartbeatInterval(d time.Duration) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.HeartbeatInterval = &d - - return nil - }) +func (c *ClientOptions) SetHeartbeatInterval(d time.Duration) *ClientOptions { + c.HeartbeatInterval = &d return c } @@ -789,12 +735,8 @@ func (c *ClientOptionsBuilder) SetHeartbeatInterval(d time.Duration) *ClientOpti // // Hosts can also be specified as a comma-separated list in a URI. For example, to include "localhost:27017" and // "localhost:27018", a URI could be "mongodb://localhost:27017,localhost:27018". The default is ["localhost:27017"] -func (c *ClientOptionsBuilder) SetHosts(s []string) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Hosts = s - - return nil - }) +func (c *ClientOptions) SetHosts(s []string) *ClientOptions { + c.Hosts = s return c } @@ -809,12 +751,8 @@ func (c *ClientOptionsBuilder) SetHosts(s []string) *ClientOptionsBuilder { // 3. The options specify whether or not a direct connection should be made, either via the URI or the SetDirect method. // // The default value is false. -func (c *ClientOptionsBuilder) SetLoadBalanced(lb bool) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.LoadBalanced = &lb - - return nil - }) +func (c *ClientOptions) SetLoadBalanced(lb bool) *ClientOptions { + c.LoadBalanced = &lb return c } @@ -823,24 +761,16 @@ func (c *ClientOptionsBuilder) SetLoadBalanced(lb bool) *ClientOptionsBuilder { // operation, this is the acceptable non-negative delta between shortest and longest average round-trip times. A server // within the latency window is selected randomly. This can also be set through the "localThresholdMS" URI option (e.g. // "localThresholdMS=15000"). The default is 15 milliseconds. -func (c *ClientOptionsBuilder) SetLocalThreshold(d time.Duration) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.LocalThreshold = &d - - return nil - }) +func (c *ClientOptions) SetLocalThreshold(d time.Duration) *ClientOptions { + c.LocalThreshold = &d return c } // SetLoggerOptions specifies a LoggerOptions containing options for // configuring a logger. -func (c *ClientOptionsBuilder) SetLoggerOptions(lopts Lister[LoggerOptions]) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.LoggerOptions = lopts - - return nil - }) +func (c *ClientOptions) SetLoggerOptions(lopts *LoggerOptions) *ClientOptions { + c.LoggerOptions = lopts return c } @@ -848,12 +778,8 @@ func (c *ClientOptionsBuilder) SetLoggerOptions(lopts Lister[LoggerOptions]) *Cl // SetMaxConnIdleTime specifies the maximum amount of time that a connection will remain idle in a connection pool // before it is removed from the pool and closed. This can also be set through the "maxIdleTimeMS" URI option (e.g. // "maxIdleTimeMS=10000"). The default is 0, meaning a connection can remain unused indefinitely. -func (c *ClientOptionsBuilder) SetMaxConnIdleTime(d time.Duration) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.MaxConnIdleTime = &d - - return nil - }) +func (c *ClientOptions) SetMaxConnIdleTime(d time.Duration) *ClientOptions { + c.MaxConnIdleTime = &d return c } @@ -861,12 +787,8 @@ func (c *ClientOptionsBuilder) SetMaxConnIdleTime(d time.Duration) *ClientOption // SetMaxPoolSize specifies that maximum number of connections allowed in the driver's connection pool to each server. // Requests to a server will block if this maximum is reached. This can also be set through the "maxPoolSize" URI option // (e.g. "maxPoolSize=100"). If this is 0, maximum connection pool size is not limited. The default is 100. -func (c *ClientOptionsBuilder) SetMaxPoolSize(u uint64) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.MaxPoolSize = &u - - return nil - }) +func (c *ClientOptions) SetMaxPoolSize(u uint64) *ClientOptions { + c.MaxPoolSize = &u return c } @@ -874,12 +796,8 @@ func (c *ClientOptionsBuilder) SetMaxPoolSize(u uint64) *ClientOptionsBuilder { // SetMinPoolSize specifies the minimum number of connections allowed in the driver's connection pool to each server. If // this is non-zero, each server's pool will be maintained in the background to ensure that the size does not fall below // the minimum. This can also be set through the "minPoolSize" URI option (e.g. "minPoolSize=100"). The default is 0. -func (c *ClientOptionsBuilder) SetMinPoolSize(u uint64) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.MinPoolSize = &u - - return nil - }) +func (c *ClientOptions) SetMinPoolSize(u uint64) *ClientOptions { + c.MinPoolSize = &u return c } @@ -887,47 +805,31 @@ func (c *ClientOptionsBuilder) SetMinPoolSize(u uint64) *ClientOptionsBuilder { // SetMaxConnecting specifies the maximum number of connections a connection pool may establish simultaneously. This can // also be set through the "maxConnecting" URI option (e.g. "maxConnecting=2"). If this is 0, the default is used. The // default is 2. Values greater than 100 are not recommended. -func (c *ClientOptionsBuilder) SetMaxConnecting(u uint64) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.MaxConnecting = &u - - return nil - }) +func (c *ClientOptions) SetMaxConnecting(u uint64) *ClientOptions { + c.MaxConnecting = &u return c } // SetPoolMonitor specifies a PoolMonitor to receive connection pool events. See the event.PoolMonitor documentation // for more information about the structure of the monitor and events that can be received. -func (c *ClientOptionsBuilder) SetPoolMonitor(m *event.PoolMonitor) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.PoolMonitor = m - - return nil - }) +func (c *ClientOptions) SetPoolMonitor(m *event.PoolMonitor) *ClientOptions { + c.PoolMonitor = m return c } // SetMonitor specifies a CommandMonitor to receive command events. See the event.CommandMonitor documentation for more // information about the structure of the monitor and events that can be received. -func (c *ClientOptionsBuilder) SetMonitor(m *event.CommandMonitor) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Monitor = m - - return nil - }) +func (c *ClientOptions) SetMonitor(m *event.CommandMonitor) *ClientOptions { + c.Monitor = m return c } // SetServerMonitor specifies an SDAM monitor used to monitor SDAM events. -func (c *ClientOptionsBuilder) SetServerMonitor(m *event.ServerMonitor) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ServerMonitor = m - - return nil - }) +func (c *ClientOptions) SetServerMonitor(m *event.ServerMonitor) *ClientOptions { + c.ServerMonitor = m return c } @@ -935,12 +837,8 @@ func (c *ClientOptionsBuilder) SetServerMonitor(m *event.ServerMonitor) *ClientO // SetReadConcern specifies the read concern to use for read operations. A read concern level can also be set through // the "readConcernLevel" URI option (e.g. "readConcernLevel=majority"). The default is nil, meaning the server will use // its configured default. -func (c *ClientOptionsBuilder) SetReadConcern(rc *readconcern.ReadConcern) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ReadConcern = rc - - return nil - }) +func (c *ClientOptions) SetReadConcern(rc *readconcern.ReadConcern) *ClientOptions { + c.ReadConcern = rc return c } @@ -958,35 +856,24 @@ func (c *ClientOptionsBuilder) SetReadConcern(rc *readconcern.ReadConcern) *Clie // // The default is readpref.Primary(). See https://www.mongodb.com/docs/manual/core/read-preference/#read-preference for // more information about read preferences. -func (c *ClientOptionsBuilder) SetReadPreference(rp *readpref.ReadPref) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ReadPreference = rp - - return nil - }) +func (c *ClientOptions) SetReadPreference(rp *readpref.ReadPref) *ClientOptions { + c.ReadPreference = rp return c } // SetBSONOptions configures optional BSON marshaling and unmarshaling behavior. -func (c *ClientOptionsBuilder) SetBSONOptions(bopts *BSONOptions) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.BSONOptions = bopts - - return nil - }) +func (c *ClientOptions) SetBSONOptions(bopts *BSONOptions) *ClientOptions { + c.BSONOptions = bopts return c } // SetRegistry specifies the BSON registry to use for BSON marshalling/unmarshalling operations. The default is // bson.NewRegistry(). -func (c *ClientOptionsBuilder) SetRegistry(registry *bson.Registry) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Registry = registry +func (c *ClientOptions) SetRegistry(registry *bson.Registry) *ClientOptions { + c.Registry = registry - return nil - }) return c } @@ -995,12 +882,8 @@ func (c *ClientOptionsBuilder) SetRegistry(registry *bson.Registry) *ClientOptio // ApplyURI or SetHosts. All nodes in the replica set must have the same replica set name, or they will not be // considered as part of the set by the Client. This can also be set through the "replicaSet" URI option (e.g. // "replicaSet=replset"). The default is empty. -func (c *ClientOptionsBuilder) SetReplicaSet(s string) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ReplicaSet = &s - - return nil - }) +func (c *ClientOptions) SetReplicaSet(s string) *ClientOptions { + c.ReplicaSet = &s return c } @@ -1016,12 +899,8 @@ func (c *ClientOptionsBuilder) SetReplicaSet(s string) *ClientOptionsBuilder { // This option requires server version >= 3.6 and a replica set or sharded cluster and will be ignored for any other // cluster type. This can also be set through the "retryWrites" URI option (e.g. "retryWrites=true"). The default is // true. -func (c *ClientOptionsBuilder) SetRetryWrites(b bool) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.RetryWrites = &b - - return nil - }) +func (c *ClientOptions) SetRetryWrites(b bool) *ClientOptions { + c.RetryWrites = &b return c } @@ -1034,12 +913,8 @@ func (c *ClientOptionsBuilder) SetRetryWrites(b bool) *ClientOptionsBuilder { // operations run through RunCommand are not retried. // // This option requires server version >= 3.6 and driver version >= 1.1.0. The default is true. -func (c *ClientOptionsBuilder) SetRetryReads(b bool) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.RetryReads = &b - - return nil - }) +func (c *ClientOptions) SetRetryReads(b bool) *ClientOptions { + c.RetryReads = &b return c } @@ -1047,12 +922,8 @@ func (c *ClientOptionsBuilder) SetRetryReads(b bool) *ClientOptionsBuilder { // SetServerSelectionTimeout specifies how long the driver will wait to find an available, suitable server to execute an // operation. This can also be set through the "serverSelectionTimeoutMS" URI option (e.g. // "serverSelectionTimeoutMS=30000"). The default value is 30 seconds. -func (c *ClientOptionsBuilder) SetServerSelectionTimeout(d time.Duration) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ServerSelectionTimeout = &d - - return nil - }) +func (c *ClientOptions) SetServerSelectionTimeout(d time.Duration) *ClientOptions { + c.ServerSelectionTimeout = &d return c } @@ -1068,12 +939,8 @@ func (c *ClientOptionsBuilder) SetServerSelectionTimeout(d time.Duration) *Clien // If any Timeout is set (even 0) on the Client, the values of MaxTime on // operation options, TransactionOptions.MaxCommitTime and // SessionOptions.DefaultMaxCommitTime will be ignored. -func (c *ClientOptionsBuilder) SetTimeout(d time.Duration) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.Timeout = &d - - return nil - }) +func (c *ClientOptions) SetTimeout(d time.Duration) *ClientOptions { + c.Timeout = &d return c } @@ -1102,12 +969,8 @@ func (c *ClientOptionsBuilder) SetTimeout(d time.Duration) *ClientOptionsBuilder // man-in-the-middle attacks and should only be done for testing. // // The default is nil, meaning no TLS will be enabled. -func (c *ClientOptionsBuilder) SetTLSConfig(cfg *tls.Config) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.TLSConfig = cfg - - return nil - }) +func (c *ClientOptions) SetTLSConfig(cfg *tls.Config) *ClientOptions { + c.TLSConfig = cfg return c } @@ -1115,12 +978,8 @@ func (c *ClientOptionsBuilder) SetTLSConfig(cfg *tls.Config) *ClientOptionsBuild // SetHTTPClient specifies the http.Client to be used for any HTTP requests. // // This should only be used to set custom HTTP client configurations. By default, the connection will use an httputil.DefaultHTTPClient. -func (c *ClientOptionsBuilder) SetHTTPClient(client *http.Client) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.HTTPClient = client - - return nil - }) +func (c *ClientOptions) SetHTTPClient(client *http.Client) *ClientOptions { + c.HTTPClient = client return c } @@ -1139,12 +998,8 @@ func (c *ClientOptionsBuilder) SetHTTPClient(client *http.Client) *ClientOptions // returning (e.g. "journal=true"). // // The default is nil, meaning the server will use its configured default. -func (c *ClientOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.WriteConcern = wc - - return nil - }) +func (c *ClientOptions) SetWriteConcern(wc *writeconcern.WriteConcern) *ClientOptions { + c.WriteConcern = wc return c } @@ -1153,12 +1008,8 @@ func (c *ClientOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *C // compressor through ApplyURI or SetCompressors. Supported values are -1 through 9, inclusive. -1 tells the zlib // library to use its default, 0 means no compression, 1 means best speed, and 9 means best compression. // This can also be set through the "zlibCompressionLevel" URI option (e.g. "zlibCompressionLevel=-1"). Defaults to -1. -func (c *ClientOptionsBuilder) SetZlibLevel(level int) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ZlibLevel = &level - - return nil - }) +func (c *ClientOptions) SetZlibLevel(level int) *ClientOptions { + c.ZlibLevel = &level return c } @@ -1166,12 +1017,8 @@ func (c *ClientOptionsBuilder) SetZlibLevel(level int) *ClientOptionsBuilder { // SetZstdLevel sets the level for the zstd compressor. This option is ignored if zstd is not specified as a compressor // through ApplyURI or SetCompressors. Supported values are 1 through 20, inclusive. 1 means best speed and 20 means // best compression. This can also be set through the "zstdCompressionLevel" URI option. Defaults to 6. -func (c *ClientOptionsBuilder) SetZstdLevel(level int) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ZstdLevel = &level - - return nil - }) +func (c *ClientOptions) SetZstdLevel(level int) *ClientOptions { + c.ZstdLevel = &level return c } @@ -1179,12 +1026,8 @@ func (c *ClientOptionsBuilder) SetZstdLevel(level int) *ClientOptionsBuilder { // SetAutoEncryptionOptions specifies an AutoEncryptionOptions instance to automatically encrypt and decrypt commands // and their results. See the options.AutoEncryptionOptions documentation for more information about the supported // options. -func (c *ClientOptionsBuilder) SetAutoEncryptionOptions(aeopts Lister[AutoEncryptionOptions]) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.AutoEncryptionOptions = aeopts - - return nil - }) +func (c *ClientOptions) SetAutoEncryptionOptions(aeopts *AutoEncryptionOptions) *ClientOptions { + c.AutoEncryptionOptions = aeopts return c } @@ -1198,12 +1041,8 @@ func (c *ClientOptionsBuilder) SetAutoEncryptionOptions(aeopts Lister[AutoEncryp // // This can also be set through the tlsDisableOCSPEndpointCheck URI option. Both this URI option and tlsInsecure must // not be set at the same time and will error if they are. The default value is false. -func (c *ClientOptionsBuilder) SetDisableOCSPEndpointCheck(disableCheck bool) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.DisableOCSPEndpointCheck = &disableCheck - - return nil - }) +func (c *ClientOptions) SetDisableOCSPEndpointCheck(disableCheck bool) *ClientOptions { + c.DisableOCSPEndpointCheck = &disableCheck return c } @@ -1211,12 +1050,8 @@ func (c *ClientOptionsBuilder) SetDisableOCSPEndpointCheck(disableCheck bool) *C // SetServerAPIOptions specifies a ServerAPIOptions instance used to configure the API version sent to the server // when running commands. See the options.ServerAPIOptions documentation for more information about the supported // options. -func (c *ClientOptionsBuilder) SetServerAPIOptions(sopts Lister[ServerAPIOptions]) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ServerAPIOptions = sopts - - return nil - }) +func (c *ClientOptions) SetServerAPIOptions(sopts *ServerAPIOptions) *ClientOptions { + c.ServerAPIOptions = sopts return c } @@ -1225,12 +1060,8 @@ func (c *ClientOptionsBuilder) SetServerAPIOptions(sopts Lister[ServerAPIOptions // the helper constants ServerMonitoringModeAuto, ServerMonitoringModePoll, and // ServerMonitoringModeStream for more information about valid server // monitoring modes. -func (c *ClientOptionsBuilder) SetServerMonitoringMode(mode string) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.ServerMonitoringMode = &mode - - return nil - }) +func (c *ClientOptions) SetServerMonitoringMode(mode string) *ClientOptions { + c.ServerMonitoringMode = &mode return c } @@ -1238,12 +1069,8 @@ func (c *ClientOptionsBuilder) SetServerMonitoringMode(mode string) *ClientOptio // SetSRVMaxHosts specifies the maximum number of SRV results to randomly select during polling. To limit the number // of hosts selected in SRV discovery, this function must be called before ApplyURI. This can also be set through // the "srvMaxHosts" URI option. -func (c *ClientOptionsBuilder) SetSRVMaxHosts(srvMaxHosts int) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.SRVMaxHosts = &srvMaxHosts - - return nil - }) +func (c *ClientOptions) SetSRVMaxHosts(srvMaxHosts int) *ClientOptions { + c.SRVMaxHosts = &srvMaxHosts return c } @@ -1251,12 +1078,8 @@ func (c *ClientOptionsBuilder) SetSRVMaxHosts(srvMaxHosts int) *ClientOptionsBui // SetSRVServiceName specifies a custom SRV service name to use in SRV polling. To use a custom SRV service name // in SRV discovery, this function must be called before ApplyURI. This can also be set through the "srvServiceName" // URI option. -func (c *ClientOptionsBuilder) SetSRVServiceName(srvName string) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.SRVServiceName = &srvName - - return nil - }) +func (c *ClientOptions) SetSRVServiceName(srvName string) *ClientOptions { + c.SRVServiceName = &srvName return c } @@ -1264,12 +1087,8 @@ func (c *ClientOptionsBuilder) SetSRVServiceName(srvName string) *ClientOptionsB // SetDriverInfo configures optional data to include in the handshake's client // metadata, delimited by "|" with the driver-generated data. This should be // used by libraries wrapping the driver, e.g. ODMs. -func (c *ClientOptionsBuilder) SetDriverInfo(info *DriverInfo) *ClientOptionsBuilder { - c.Opts = append(c.Opts, func(opts *ClientOptions) error { - opts.DriverInfo = info - - return nil - }) +func (c *ClientOptions) SetDriverInfo(info *DriverInfo) *ClientOptions { + c.DriverInfo = info return c } @@ -1439,3 +1258,41 @@ func extractX509UsernameFromSubject(subject string) string { return strings.Join(pairs, ",") } + +// MergeClientOptions combines the given *ClientOptions into a single +// *ClientOptions in a last one wins fashion. The specified options are merged +// with the existing options on the client, with the specified options taking +// precedence. +func MergeClientOptions(opts ...*ClientOptions) *ClientOptions { + if len(opts) == 1 { + return opts[0] + } + + c := Client() + for _, opt := range opts { + if opt == nil { + continue + } + optValue := reflect.ValueOf(opt).Elem() + cValue := reflect.ValueOf(c).Elem() + for i := 0; i < optValue.NumField(); i++ { + field := optValue.Field(i) + fieldType := optValue.Type().Field(i) + // Check if the field is exported and can be set + if field.CanSet() && fieldType.PkgPath == "" && !field.IsZero() { + cValue.Field(i).Set(field) + } + } + + // Manually handle unexported fields + if opt.err != nil { + c.err = opt.err + } + + if opt.connString != nil { + c.connString = opt.connString + } + } + + return c +} diff --git a/mongo/options/clientoptions_test.go b/mongo/options/clientoptions_test.go index 3dc5ca7c45..61bfa7dd0a 100644 --- a/mongo/options/clientoptions_test.go +++ b/mongo/options/clientoptions_test.go @@ -34,8 +34,7 @@ import ( "go.mongodb.org/mongo-driver/v2/x/mongo/driver/connstring" ) -var tClientOptions = reflect.TypeOf(&ClientOptionsBuilder{}) -var tClientopts = reflect.TypeOf(&ClientOptions{}) +var tClientOptions = reflect.TypeOf(&ClientOptions{}) func TestClientOptions(t *testing.T) { t.Run("ApplyURI/doesn't overwrite previous errors", func(t *testing.T) { @@ -57,32 +56,32 @@ func TestClientOptions(t *testing.T) { field string // field to be set dereference bool // Should we compare a pointer or the field }{ - {"AppName", (*ClientOptionsBuilder).SetAppName, "example-application", "AppName", true}, - {"Auth", (*ClientOptionsBuilder).SetAuth, Credential{Username: "foo", Password: "bar"}, "Auth", true}, - {"Compressors", (*ClientOptionsBuilder).SetCompressors, []string{"zstd", "snappy", "zlib"}, "Compressors", true}, - {"ConnectTimeout", (*ClientOptionsBuilder).SetConnectTimeout, 5 * time.Second, "ConnectTimeout", true}, - {"Dialer", (*ClientOptionsBuilder).SetDialer, testDialer{Num: 12345}, "Dialer", true}, - {"HeartbeatInterval", (*ClientOptionsBuilder).SetHeartbeatInterval, 5 * time.Second, "HeartbeatInterval", true}, - {"Hosts", (*ClientOptionsBuilder).SetHosts, []string{"localhost:27017", "localhost:27018", "localhost:27019"}, "Hosts", true}, - {"LocalThreshold", (*ClientOptionsBuilder).SetLocalThreshold, 5 * time.Second, "LocalThreshold", true}, - {"MaxConnIdleTime", (*ClientOptionsBuilder).SetMaxConnIdleTime, 5 * time.Second, "MaxConnIdleTime", true}, - {"MaxPoolSize", (*ClientOptionsBuilder).SetMaxPoolSize, uint64(250), "MaxPoolSize", true}, - {"MinPoolSize", (*ClientOptionsBuilder).SetMinPoolSize, uint64(10), "MinPoolSize", true}, - {"MaxConnecting", (*ClientOptionsBuilder).SetMaxConnecting, uint64(10), "MaxConnecting", true}, - {"PoolMonitor", (*ClientOptionsBuilder).SetPoolMonitor, &event.PoolMonitor{}, "PoolMonitor", false}, - {"Monitor", (*ClientOptionsBuilder).SetMonitor, &event.CommandMonitor{}, "Monitor", false}, - {"ReadConcern", (*ClientOptionsBuilder).SetReadConcern, readconcern.Majority(), "ReadConcern", false}, - {"ReadPreference", (*ClientOptionsBuilder).SetReadPreference, readpref.SecondaryPreferred(), "ReadPreference", false}, - {"Registry", (*ClientOptionsBuilder).SetRegistry, bson.NewRegistry(), "Registry", false}, - {"ReplicaSet", (*ClientOptionsBuilder).SetReplicaSet, "example-replicaset", "ReplicaSet", true}, - {"RetryWrites", (*ClientOptionsBuilder).SetRetryWrites, true, "RetryWrites", true}, - {"ServerSelectionTimeout", (*ClientOptionsBuilder).SetServerSelectionTimeout, 5 * time.Second, "ServerSelectionTimeout", true}, - {"Direct", (*ClientOptionsBuilder).SetDirect, true, "Direct", true}, - {"TLSConfig", (*ClientOptionsBuilder).SetTLSConfig, &tls.Config{}, "TLSConfig", false}, - {"WriteConcern", (*ClientOptionsBuilder).SetWriteConcern, writeconcern.Majority(), "WriteConcern", false}, - {"ZlibLevel", (*ClientOptionsBuilder).SetZlibLevel, 6, "ZlibLevel", true}, - {"DisableOCSPEndpointCheck", (*ClientOptionsBuilder).SetDisableOCSPEndpointCheck, true, "DisableOCSPEndpointCheck", true}, - {"LoadBalanced", (*ClientOptionsBuilder).SetLoadBalanced, true, "LoadBalanced", true}, + {"AppName", (*ClientOptions).SetAppName, "example-application", "AppName", true}, + {"Auth", (*ClientOptions).SetAuth, Credential{Username: "foo", Password: "bar"}, "Auth", true}, + {"Compressors", (*ClientOptions).SetCompressors, []string{"zstd", "snappy", "zlib"}, "Compressors", true}, + {"ConnectTimeout", (*ClientOptions).SetConnectTimeout, 5 * time.Second, "ConnectTimeout", true}, + {"Dialer", (*ClientOptions).SetDialer, testDialer{Num: 12345}, "Dialer", true}, + {"HeartbeatInterval", (*ClientOptions).SetHeartbeatInterval, 5 * time.Second, "HeartbeatInterval", true}, + {"Hosts", (*ClientOptions).SetHosts, []string{"localhost:27017", "localhost:27018", "localhost:27019"}, "Hosts", true}, + {"LocalThreshold", (*ClientOptions).SetLocalThreshold, 5 * time.Second, "LocalThreshold", true}, + {"MaxConnIdleTime", (*ClientOptions).SetMaxConnIdleTime, 5 * time.Second, "MaxConnIdleTime", true}, + {"MaxPoolSize", (*ClientOptions).SetMaxPoolSize, uint64(250), "MaxPoolSize", true}, + {"MinPoolSize", (*ClientOptions).SetMinPoolSize, uint64(10), "MinPoolSize", true}, + {"MaxConnecting", (*ClientOptions).SetMaxConnecting, uint64(10), "MaxConnecting", true}, + {"PoolMonitor", (*ClientOptions).SetPoolMonitor, &event.PoolMonitor{}, "PoolMonitor", false}, + {"Monitor", (*ClientOptions).SetMonitor, &event.CommandMonitor{}, "Monitor", false}, + {"ReadConcern", (*ClientOptions).SetReadConcern, readconcern.Majority(), "ReadConcern", false}, + {"ReadPreference", (*ClientOptions).SetReadPreference, readpref.SecondaryPreferred(), "ReadPreference", false}, + {"Registry", (*ClientOptions).SetRegistry, bson.NewRegistry(), "Registry", false}, + {"ReplicaSet", (*ClientOptions).SetReplicaSet, "example-replicaset", "ReplicaSet", true}, + {"RetryWrites", (*ClientOptions).SetRetryWrites, true, "RetryWrites", true}, + {"ServerSelectionTimeout", (*ClientOptions).SetServerSelectionTimeout, 5 * time.Second, "ServerSelectionTimeout", true}, + {"Direct", (*ClientOptions).SetDirect, true, "Direct", true}, + {"TLSConfig", (*ClientOptions).SetTLSConfig, &tls.Config{}, "TLSConfig", false}, + {"WriteConcern", (*ClientOptions).SetWriteConcern, writeconcern.Majority(), "WriteConcern", false}, + {"ZlibLevel", (*ClientOptions).SetZlibLevel, 6, "ZlibLevel", true}, + {"DisableOCSPEndpointCheck", (*ClientOptions).SetDisableOCSPEndpointCheck, true, "DisableOCSPEndpointCheck", true}, + {"LoadBalanced", (*ClientOptions).SetLoadBalanced, true, "LoadBalanced", true}, } opt1, opt2, optResult := Client(), Client(), Client() @@ -95,19 +94,20 @@ func TestClientOptions(t *testing.T) { if fn.Type().NumIn() < 2 || fn.Type().In(0) != tClientOptions { t.Fatal("fn argument must have a *ClientOptions as the first argument and one other argument") } - if _, exists := tClientopts.Elem().FieldByName(tc.field); !exists { + if _, exists := tClientOptions.Elem().FieldByName(tc.field); !exists { t.Fatalf("field (%s) does not exist in ClientOptions", tc.field) } - opts := make([]reflect.Value, 2) - opts[0] = reflect.New(tClientOptions.Elem()) + args := make([]reflect.Value, 2) + client := reflect.New(tClientOptions.Elem()) + args[0] = client want := reflect.ValueOf(tc.arg) - opts[1] = want + args[1] = want if !want.IsValid() || !want.CanInterface() { t.Fatal("arg property of test case must be valid") } - _ = fn.Call(opts) + _ = fn.Call(args) // To avoid duplication we're piggybacking on the Set* tests to make the // MergeClientOptions test simpler and more thorough. @@ -116,42 +116,17 @@ func TestClientOptions(t *testing.T) { // the result option. This gives us coverage of options set by the first option, by // the second, and by both. if idx%2 != 0 { - opts[0] = reflect.ValueOf(opt1) - _ = fn.Call(opts) + args[0] = reflect.ValueOf(opt1) + _ = fn.Call(args) } if idx%2 == 0 || idx%3 == 0 { - opts[0] = reflect.ValueOf(opt2) - _ = fn.Call(opts) + args[0] = reflect.ValueOf(opt2) + _ = fn.Call(args) } - opts[0] = reflect.ValueOf(optResult) - _ = fn.Call(opts) + args[0] = reflect.ValueOf(optResult) + _ = fn.Call(args) - optsValue := opts[0].Elem().FieldByName("Opts") - - // Ensure the value is a slice - if optsValue.Kind() != reflect.Slice { - t.Fatalf("expected the options to be a slice") - } - - setters := make([]func(*ClientOptions) error, optsValue.Len()) - - // Iterate over the reflect.Value and extract each function - for i := 0; i < optsValue.Len(); i++ { - elem := optsValue.Index(i) - if elem.Kind() != reflect.Func { - t.Fatalf("expected all elements of opts to be functions") - } - - setters[i] = elem.Interface().(func(*ClientOptions) error) - } - - clientopts := &ClientOptions{} - for _, set := range setters { - err := set(clientopts) - assert.NoError(t, err) - } - - got := reflect.ValueOf(clientopts).Elem().FieldByName(tc.field) + got := client.Elem().FieldByName(tc.field) if !got.IsValid() || !got.CanInterface() { t.Fatal("cannot create concrete instance from retrieved field") } @@ -171,6 +146,35 @@ func TestClientOptions(t *testing.T) { } }) } + + t.Run("MergeClientOptions/all set", func(t *testing.T) { + want := optResult + got := MergeClientOptions(nil, opt1, opt2) + if diff := cmp.Diff( + got, want, + cmp.AllowUnexported(readconcern.ReadConcern{}, writeconcern.WriteConcern{}, readpref.ReadPref{}), + cmp.Comparer(func(r1, r2 *bson.Registry) bool { return r1 == r2 }), + cmp.Comparer(func(cfg1, cfg2 *tls.Config) bool { return cfg1 == cfg2 }), + cmp.Comparer(func(fp1, fp2 *event.PoolMonitor) bool { return fp1 == fp2 }), + cmp.AllowUnexported(ClientOptions{}), + cmpopts.IgnoreFields(http.Client{}, "Transport"), + ); diff != "" { + t.Errorf("diff:\n%s", diff) + t.Errorf("Merged client options do not match. got %v; want %v", got, want) + } + }) + + // go-cmp dont support error comparisons (https://github.com/google/go-cmp/issues/24) + // Use specifique test for this + t.Run("MergeClientOptions/err", func(t *testing.T) { + opt1, opt2 := Client(), Client() + opt1.err = errors.New("Test error") + + got := MergeClientOptions(nil, opt1, opt2) + if got.err.Error() != "Test error" { + t.Errorf("Merged client options do not match. got %v; want %v", got.err.Error(), opt1.err.Error()) + } + }) }) t.Run("direct connection validation", func(t *testing.T) { t.Run("multiple hosts", func(t *testing.T) { @@ -178,7 +182,7 @@ func TestClientOptions(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions }{ {"hosts in URI", Client().ApplyURI("mongodb://localhost,localhost2")}, {"hosts in options", Client().SetHosts([]string{"localhost", "localhost2"})}, @@ -194,21 +198,11 @@ func TestClientOptions(t *testing.T) { t.Run("srv", func(t *testing.T) { expectedErr := errors.New("a direct connection cannot be made if an SRV URI is used") // Use a non-SRV URI and manually set the scheme because using an SRV URI would force an SRV lookup. - optsBldr := Client().ApplyURI("mongodb://localhost:27017") - - args, err := getOptions[ClientOptions](optsBldr) - assert.NoError(t, err) + opts := Client().ApplyURI("mongodb://localhost:27017") - args.connString.Scheme = connstring.SchemeMongoDBSRV + opts.connString.Scheme = connstring.SchemeMongoDBSRV - newOpts := &ClientOptionsBuilder{} - newOpts.Opts = append(newOpts.Opts, func(ca *ClientOptions) error { - *ca = *args - - return nil - }) - - err = newOpts.SetDirect(true).Validate() + err := opts.SetDirect(true).Validate() assert.NotNil(t, err, "expected error, got nil") assert.Equal(t, expectedErr.Error(), err.Error(), "expected error %v, got %v", expectedErr, err) }) @@ -216,7 +210,7 @@ func TestClientOptions(t *testing.T) { t.Run("loadBalanced validation", func(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ {"multiple hosts in URI", Client().ApplyURI("mongodb://foo,bar"), connstring.ErrLoadBalancedWithMultipleHosts}, @@ -243,7 +237,7 @@ func TestClientOptions(t *testing.T) { t.Run("heartbeatFrequencyMS validation", func(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ { @@ -297,7 +291,7 @@ func TestClientOptions(t *testing.T) { t.Run("minPoolSize validation", func(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ { @@ -331,7 +325,7 @@ func TestClientOptions(t *testing.T) { t.Run("srvMaxHosts validation", func(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ {"replica set name", Client().SetReplicaSet("foo"), connstring.ErrSRVMaxHostsWithReplicaSet}, @@ -358,7 +352,7 @@ func TestClientOptions(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ { @@ -393,7 +387,7 @@ func TestClientOptions(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ { @@ -443,7 +437,7 @@ func TestClientOptions(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ { @@ -536,7 +530,7 @@ func TestClientOptions(t *testing.T) { testCases := []struct { name string - opts *ClientOptionsBuilder + opts *ClientOptions err error }{ { @@ -715,24 +709,19 @@ func compareErrors(err1, err2 error) bool { return true } -func TestSetURIopts(t *testing.T) { +func TestApplyURI(t *testing.T) { t.Parallel() testCases := []struct { name string uri string wantopts *ClientOptions - - // A list of possible errors that can be returned, required to account for - // OS-specific errors. - wantErrs []error }{ { - name: "ParseError", - uri: "not-mongo-db-uri://", - wantopts: &ClientOptions{}, - wantErrs: []error{ - fmt.Errorf( + name: "ParseError", + uri: "not-mongo-db-uri://", + wantopts: &ClientOptions{ + err: fmt.Errorf( "error parsing uri: %w", errors.New(`scheme must be "mongodb" or "mongodb+srv"`)), }, @@ -742,9 +731,7 @@ func TestSetURIopts(t *testing.T) { uri: "mongodb://localhost/?maxStaleness=200", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - }, - wantErrs: []error{ - fmt.Errorf("unknown read preference %v", ""), + err: fmt.Errorf("unknown read preference %v", ""), }, }, { @@ -752,9 +739,7 @@ func TestSetURIopts(t *testing.T) { uri: "mongodb://localhost/?readPreference=Primary&maxStaleness=200", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - }, - wantErrs: []error{ - errors.New("can not specify tags, max staleness, or hedge with mode primary"), + err: errors.New("can not specify tags, max staleness, or hedge with mode primary"), }, }, { @@ -762,19 +747,7 @@ func TestSetURIopts(t *testing.T) { uri: "mongodb://localhost/?ssl=true&sslCertificateAuthorityFile=testdata/doesntexist", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - }, - wantErrs: []error{ - &os.PathError{ - Op: "open", - Path: "testdata/doesntexist", - Err: errors.New("no such file or directory"), - }, - &os.PathError{ - Op: "open", - Path: "testdata/doesntexist", - // Windows error - Err: errors.New("The system cannot find the file specified."), //nolint:revive - }, + err: &os.PathError{Op: "open", Path: "testdata/doesntexist"}, }, }, { @@ -782,19 +755,7 @@ func TestSetURIopts(t *testing.T) { uri: "mongodb://localhost/?ssl=true&sslClientCertificateKeyFile=testdata/doesntexist", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - }, - wantErrs: []error{ - &os.PathError{ - Op: "open", - Path: "testdata/doesntexist", - Err: errors.New("no such file or directory"), - }, - &os.PathError{ - Op: "open", - Path: "testdata/doesntexist", - // Windows error - Err: errors.New("The system cannot find the file specified."), //nolint:revive - }, + err: &os.PathError{Op: "open", Path: "testdata/doesntexist"}, }, }, { @@ -803,8 +764,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, AppName: ptrutil.Ptr[string]("awesome-example-application"), + err: nil, }, - wantErrs: nil, }, { name: "AuthMechanism", @@ -812,8 +773,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, Auth: &Credential{AuthSource: "$external", AuthMechanism: "mongodb-x509"}, + err: nil, }, - wantErrs: nil, }, { name: "AuthMechanismProperties", @@ -826,8 +787,8 @@ func TestSetURIopts(t *testing.T) { AuthMechanismProperties: map[string]string{"SERVICE_NAME": "mongodb-fake"}, Username: "foo", }, + err: nil, }, - wantErrs: nil, }, { name: "AuthSource", @@ -835,8 +796,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, Auth: &Credential{AuthSource: "random-database-example", Username: "foo"}, + err: nil, }, - wantErrs: nil, }, { name: "Username", @@ -844,15 +805,14 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, Auth: &Credential{AuthSource: "admin", Username: "foo"}, + err: nil, }, - wantErrs: nil, }, { - name: "Unescaped slash in username", - uri: "mongodb:///:pwd@localhost", - wantopts: &ClientOptions{}, - wantErrs: []error{ - fmt.Errorf("error parsing uri: %w", errors.New("unescaped slash in username")), + name: "Unescaped slash in username", + uri: "mongodb:///:pwd@localhost", + wantopts: &ClientOptions{ + err: fmt.Errorf("error parsing uri: %w", errors.New("unescaped slash in username")), }, }, { @@ -864,8 +824,8 @@ func TestSetURIopts(t *testing.T) { AuthSource: "admin", Username: "foo", Password: "bar", PasswordSet: true, }, + err: nil, }, - wantErrs: nil, }, { name: "Single character username and password", @@ -876,8 +836,8 @@ func TestSetURIopts(t *testing.T) { AuthSource: "admin", Username: "f", Password: "b", PasswordSet: true, }, + err: nil, }, - wantErrs: nil, }, { name: "Connect", @@ -885,8 +845,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, Direct: ptrutil.Ptr[bool](true), + err: nil, }, - wantErrs: nil, }, { name: "ConnectTimeout", @@ -894,8 +854,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ConnectTimeout: ptrutil.Ptr[time.Duration](5 * time.Second), + err: nil, }, - wantErrs: nil, }, { name: "Compressors", @@ -904,16 +864,16 @@ func TestSetURIopts(t *testing.T) { Hosts: []string{"localhost"}, Compressors: []string{"zlib", "snappy"}, ZlibLevel: ptrutil.Ptr[int](6), + err: nil, }, - wantErrs: nil, }, { name: "DatabaseNoAuth", uri: "mongodb://localhost/example-database", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, + err: nil, }, - wantErrs: nil, }, { name: "DatabaseAsDefault", @@ -921,8 +881,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, Auth: &Credential{AuthSource: "example-database", Username: "foo"}, + err: nil, }, - wantErrs: nil, }, { name: "HeartbeatInterval", @@ -930,16 +890,16 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, HeartbeatInterval: ptrutil.Ptr[time.Duration](12 * time.Second), + err: nil, }, - wantErrs: nil, }, { name: "Hosts", uri: "mongodb://localhost:27017,localhost:27018,localhost:27019/", wantopts: &ClientOptions{ Hosts: []string{"localhost:27017", "localhost:27018", "localhost:27019"}, + err: nil, }, - wantErrs: nil, }, { name: "LocalThreshold", @@ -947,8 +907,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, LocalThreshold: ptrutil.Ptr[time.Duration](200 * time.Millisecond), + err: nil, }, - wantErrs: nil, }, { name: "MaxConnIdleTime", @@ -956,8 +916,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, MaxConnIdleTime: ptrutil.Ptr[time.Duration](5 * time.Minute), + err: nil, }, - wantErrs: nil, }, { name: "MaxPoolSize", @@ -965,8 +925,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, MaxPoolSize: ptrutil.Ptr[uint64](256), + err: nil, }, - wantErrs: nil, }, { name: "MinPoolSize", @@ -974,8 +934,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, MinPoolSize: ptrutil.Ptr[uint64](256), + err: nil, }, - wantErrs: nil, }, { name: "MaxConnecting", @@ -983,8 +943,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, MaxConnecting: ptrutil.Ptr[uint64](10), + err: nil, }, - wantErrs: nil, }, { name: "ReadConcern", @@ -992,8 +952,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ReadConcern: readconcern.Linearizable(), + err: nil, }, - wantErrs: nil, }, { name: "ReadPreference", @@ -1001,8 +961,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ReadPreference: readpref.SecondaryPreferred(), + err: nil, }, - wantErrs: nil, }, { name: "ReadPreferenceTagSets", @@ -1010,8 +970,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ReadPreference: readpref.SecondaryPreferred(readpref.WithTags("foo", "bar")), + err: nil, }, - wantErrs: nil, }, { name: "MaxStaleness", @@ -1019,8 +979,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ReadPreference: readpref.SecondaryPreferred(readpref.WithMaxStaleness(250 * time.Second)), + err: nil, }, - wantErrs: nil, }, { name: "RetryWrites", @@ -1028,8 +988,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, RetryWrites: ptrutil.Ptr[bool](true), + err: nil, }, - wantErrs: nil, }, { name: "ReplicaSet", @@ -1037,8 +997,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ReplicaSet: ptrutil.Ptr[string]("rs01"), + err: nil, }, - wantErrs: nil, }, { name: "ServerSelectionTimeout", @@ -1046,16 +1006,16 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ServerSelectionTimeout: ptrutil.Ptr[time.Duration](45 * time.Second), + err: nil, }, - wantErrs: nil, }, { name: "SocketTimeout", uri: "mongodb://localhost/?socketTimeoutMS=15000", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, + err: nil, }, - wantErrs: nil, }, { name: "TLS CACertificate", @@ -1065,8 +1025,8 @@ func TestSetURIopts(t *testing.T) { TLSConfig: &tls.Config{ RootCAs: createCertPool(t, "testdata/ca.pem"), }, + err: nil, }, - wantErrs: nil, }, { name: "TLS Insecure", @@ -1076,8 +1036,8 @@ func TestSetURIopts(t *testing.T) { TLSConfig: &tls.Config{ InsecureSkipVerify: true, }, + err: nil, }, - wantErrs: nil, }, { name: "TLS ClientCertificateKey", @@ -1087,8 +1047,8 @@ func TestSetURIopts(t *testing.T) { TLSConfig: &tls.Config{ Certificates: make([]tls.Certificate, 1), }, + err: nil, }, - wantErrs: nil, }, { name: "TLS ClientCertificateKey with password", @@ -1098,8 +1058,8 @@ func TestSetURIopts(t *testing.T) { TLSConfig: &tls.Config{ Certificates: make([]tls.Certificate, 1), }, + err: nil, }, - wantErrs: nil, }, { name: "TLS Username", @@ -1110,8 +1070,8 @@ func TestSetURIopts(t *testing.T) { AuthMechanism: "mongodb-x509", AuthSource: "$external", Username: `C=US,ST=New York,L=New York City, Inc,O=MongoDB\,OU=WWW`, }, + err: nil, }, - wantErrs: nil, }, { name: "WriteConcern J", @@ -1119,8 +1079,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, WriteConcern: writeconcern.Journaled(), + err: nil, }, - wantErrs: nil, }, { name: "WriteConcern WString", @@ -1128,8 +1088,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, WriteConcern: writeconcern.Majority(), + err: nil, }, - wantErrs: nil, }, { name: "WriteConcern W", @@ -1137,16 +1097,16 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, WriteConcern: &writeconcern.WriteConcern{W: 3}, + err: nil, }, - wantErrs: nil, }, { name: "WriteConcern WTimeout", uri: "mongodb://localhost/?wTimeoutMS=45000", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, + err: nil, }, - wantErrs: nil, }, { name: "ZLibLevel", @@ -1154,8 +1114,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, ZlibLevel: ptrutil.Ptr[int](4), + err: nil, }, - wantErrs: nil, }, { name: "TLS tlsCertificateFile and tlsPrivateKeyFile", @@ -1165,35 +1125,32 @@ func TestSetURIopts(t *testing.T) { TLSConfig: &tls.Config{ Certificates: make([]tls.Certificate, 1), }, + err: nil, }, - wantErrs: nil, }, { - name: "TLS only tlsCertificateFile", - uri: "mongodb://localhost/?tlsCertificateFile=testdata/nopass/cert.pem", - wantopts: &ClientOptions{}, - wantErrs: []error{ - fmt.Errorf( + name: "TLS only tlsCertificateFile", + uri: "mongodb://localhost/?tlsCertificateFile=testdata/nopass/cert.pem", + wantopts: &ClientOptions{ + err: fmt.Errorf( "error validating uri: %w", errors.New("the tlsPrivateKeyFile URI option must be provided if the tlsCertificateFile option is specified")), }, }, { - name: "TLS only tlsPrivateKeyFile", - uri: "mongodb://localhost/?tlsPrivateKeyFile=testdata/nopass/key.pem", - wantopts: &ClientOptions{}, - wantErrs: []error{ - fmt.Errorf( + name: "TLS only tlsPrivateKeyFile", + uri: "mongodb://localhost/?tlsPrivateKeyFile=testdata/nopass/key.pem", + wantopts: &ClientOptions{ + err: fmt.Errorf( "error validating uri: %w", errors.New("the tlsCertificateFile URI option must be provided if the tlsPrivateKeyFile option is specified")), }, }, { - name: "TLS tlsCertificateFile and tlsPrivateKeyFile and tlsCertificateKeyFile", - uri: "mongodb://localhost/?tlsCertificateFile=testdata/nopass/cert.pem&tlsPrivateKeyFile=testdata/nopass/key.pem&tlsCertificateKeyFile=testdata/nopass/certificate.pem", - wantopts: &ClientOptions{}, - wantErrs: []error{ - fmt.Errorf( + name: "TLS tlsCertificateFile and tlsPrivateKeyFile and tlsCertificateKeyFile", + uri: "mongodb://localhost/?tlsCertificateFile=testdata/nopass/cert.pem&tlsPrivateKeyFile=testdata/nopass/key.pem&tlsCertificateKeyFile=testdata/nopass/certificate.pem", + wantopts: &ClientOptions{ + err: fmt.Errorf( "error validating uri: %w", errors.New("the sslClientCertificateKeyFile/tlsCertificateKeyFile URI option cannot be provided "+ "along with tlsCertificateFile or tlsPrivateKeyFile")), @@ -1205,8 +1162,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, DisableOCSPEndpointCheck: ptrutil.Ptr[bool](true), + err: nil, }, - wantErrs: nil, }, { name: "directConnection", @@ -1214,8 +1171,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, Direct: ptrutil.Ptr[bool](true), + err: nil, }, - wantErrs: nil, }, { name: "TLS CA file with multiple certificiates", @@ -1226,17 +1183,15 @@ func TestSetURIopts(t *testing.T) { RootCAs: createCertPool(t, "testdata/ca-with-intermediates-first.pem", "testdata/ca-with-intermediates-second.pem", "testdata/ca-with-intermediates-third.pem"), }, + err: nil, }, - wantErrs: nil, }, { name: "TLS empty CA file", uri: "mongodb://localhost/?tlsCAFile=testdata/empty-ca.pem", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - }, - wantErrs: []error{ - errors.New("the specified CA file does not contain any valid certificates"), + err: errors.New("the specified CA file does not contain any valid certificates"), }, }, { @@ -1244,9 +1199,7 @@ func TestSetURIopts(t *testing.T) { uri: "mongodb://localhost/?tlsCAFile=testdata/ca-key.pem", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - }, - wantErrs: []error{ - errors.New("the specified CA file does not contain any valid certificates"), + err: errors.New("the specified CA file does not contain any valid certificates"), }, }, { @@ -1254,9 +1207,7 @@ func TestSetURIopts(t *testing.T) { uri: "mongodb://localhost/?tlsCAFile=testdata/malformed-ca.pem", wantopts: &ClientOptions{ Hosts: []string{"localhost"}, - }, - wantErrs: []error{ - errors.New("the specified CA file does not contain any valid certificates"), + err: errors.New("the specified CA file does not contain any valid certificates"), }, }, { @@ -1265,8 +1216,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, LoadBalanced: ptrutil.Ptr[bool](true), + err: nil, }, - wantErrs: nil, }, { name: "loadBalanced=false", @@ -1274,8 +1225,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, LoadBalanced: ptrutil.Ptr[bool](false), + err: nil, }, - wantErrs: nil, }, { name: "srvServiceName", @@ -1283,8 +1234,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost.test.build.10gen.cc:27017", "localhost.test.build.10gen.cc:27018"}, SRVServiceName: ptrutil.Ptr[string]("customname"), + err: nil, }, - wantErrs: nil, }, { name: "srvMaxHosts", @@ -1292,8 +1243,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost.test.build.10gen.cc:27017", "localhost.test.build.10gen.cc:27018"}, SRVMaxHosts: ptrutil.Ptr[int](2), + err: nil, }, - wantErrs: nil, }, { name: "GODRIVER-2263 regression test", @@ -1301,8 +1252,8 @@ func TestSetURIopts(t *testing.T) { wantopts: &ClientOptions{ Hosts: []string{"localhost"}, TLSConfig: &tls.Config{Certificates: make([]tls.Certificate, 1)}, + err: nil, }, - wantErrs: nil, }, { name: "GODRIVER-2650 X509 certificate", @@ -1315,17 +1266,15 @@ func TestSetURIopts(t *testing.T) { Username: `C=US,ST=New York,L=New York City,O=MongoDB,OU=Drivers,CN=localhost`, }, TLSConfig: &tls.Config{Certificates: make([]tls.Certificate, 1)}, + err: nil, }, - wantErrs: nil, }, { name: "ALLOWED_HOSTS cannot be specified in URI connection", uri: "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ALLOWED_HOSTS:example.com", wantopts: &ClientOptions{ HTTPClient: httputil.DefaultHTTPClient, - }, - wantErrs: []error{ - errors.New(`error validating uri: ALLOWED_HOSTS cannot be specified in the URI connection string for the "MONGODB-OIDC" auth mechanism, it must be specified through the ClientOptions directly`), + err: errors.New(`error validating uri: ALLOWED_HOSTS cannot be specified in the URI connection string for the "MONGODB-OIDC" auth mechanism, it must be specified through the ClientOptions directly`), }, }, { @@ -1335,8 +1284,8 @@ func TestSetURIopts(t *testing.T) { Hosts: []string{"example.com"}, Auth: &Credential{AuthMechanism: "MONGODB-OIDC", AuthSource: "$external", AuthMechanismProperties: map[string]string{"TOKEN_RESOURCE": "mongodb://test-cluster"}}, HTTPClient: httputil.DefaultHTTPClient, + err: nil, }, - wantErrs: nil, }, { name: "oidc azure", @@ -1347,8 +1296,8 @@ func TestSetURIopts(t *testing.T) { "ENVIRONMENT": "azureManagedIdentities", "TOKEN_RESOURCE": "mongodb://test-cluster"}}, HTTPClient: httputil.DefaultHTTPClient, + err: nil, }, - wantErrs: nil, }, { name: "oidc gcp", @@ -1359,71 +1308,40 @@ func TestSetURIopts(t *testing.T) { "ENVIRONMENT": "gcp", "TOKEN_RESOURCE": "mongodb://test-cluster"}}, HTTPClient: httputil.DefaultHTTPClient, + err: nil, }, - wantErrs: nil, }, { name: "comma in key:value pair causes error", uri: "mongodb://example.com/?authMechanismProperties=TOKEN_RESOURCE:mongodb://host1%2Chost2", wantopts: &ClientOptions{ HTTPClient: httputil.DefaultHTTPClient, - }, - wantErrs: []error{ - errors.New(`error parsing uri: invalid authMechanism property`), + err: errors.New(`error parsing uri: invalid authMechanism property`), }, }, } for _, test := range testCases { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() + result := Client().ApplyURI(test.uri) - // Manually add the URI and ConnString to the test expectations to avoid - // adding them in each test definition. The ConnString should only be - // recorded if there was no error while parsing. - connString, err := connstring.ParseAndValidate(test.uri) + // Manually add the URI and ConnString to the test expectations to avoid adding them in each test + // definition. The ConnString should only be recorded if there was no error while parsing. + cs, err := connstring.ParseAndValidate(test.uri) if err == nil { - test.wantopts.connString = connString + test.wantopts.connString = cs } - // Also manually add the default HTTP client if one does not exist. if test.wantopts.HTTPClient == nil { - test.wantopts.HTTPClient = http.DefaultClient - } - - // Use the setURIopts to just test that a correct error is returned. - if gotErr := setURIOpts(test.uri, &ClientOptions{}); test.wantErrs != nil { - var foundError bool - - for _, err := range test.wantErrs { - if err.Error() == gotErr.Error() { - foundError = true - - break - } - } - - assert.True(t, foundError, "expected error to be one of %v, got: %v", test.wantErrs, gotErr) - } - - // Run this test through the client.ApplyURI method to ensure that it - // remains a naive wrapper. - opts := Client().ApplyURI(test.uri) - - gotopts := &ClientOptions{} - for _, setter := range opts.Opts { - _ = setter(gotopts) + test.wantopts.HTTPClient = httputil.DefaultHTTPClient } - // We have to sort string slices in comparison, as Hosts resolved from SRV - // URIs do not have a set order. + // We have to sort string slices in comparison, as Hosts resolved from SRV URIs do not have a set order. stringLess := func(a, b string) bool { return a < b } if diff := cmp.Diff( - test.wantopts, gotopts, + test.wantopts, result, cmp.AllowUnexported(ClientOptions{}, readconcern.ReadConcern{}, writeconcern.WriteConcern{}, readpref.ReadPref{}), - // cmp.Comparer(func(r1, r2 *bsoncodec.Registry) bool { return r1 == r2 }), + cmp.Comparer(func(r1, r2 *bson.Registry) bool { return r1 == r2 }), cmp.Comparer(compareTLSConfig), cmp.Comparer(compareErrors), cmpopts.SortSlices(stringLess), diff --git a/mongo/options/example_test.go b/mongo/options/example_test.go index 49b403498f..a4cb92ee11 100644 --- a/mongo/options/example_test.go +++ b/mongo/options/example_test.go @@ -37,7 +37,7 @@ func (logger *CustomLogger) Error(err error, msg string, _ ...interface{}) { fmt.Fprintf(logger, "err=%v msg=%s\n", err, msg) } -func ExampleClientOptionsBuilder_SetLoggerOptions_customLogger() { +func ExampleClientOptions_SetLoggerOptions_customLogger() { buf := bytes.NewBuffer(nil) sink := &CustomLogger{Writer: buf} diff --git a/mongo/options/lister.go b/mongo/options/lister.go index d5c76f580d..3b3783b4b4 100644 --- a/mongo/options/lister.go +++ b/mongo/options/lister.go @@ -11,19 +11,3 @@ package options type Lister[T any] interface { List() []func(*T) error } - -func getOptions[T any](mopts Lister[T]) (*T, error) { - opts := new(T) - - for _, setterFn := range mopts.List() { - if setterFn == nil { - continue - } - - if err := setterFn(opts); err != nil { - return nil, err - } - } - - return opts, nil -} diff --git a/mongo/options/loggeroptions.go b/mongo/options/loggeroptions.go index 081721391d..3a5c0806ad 100644 --- a/mongo/options/loggeroptions.go +++ b/mongo/options/loggeroptions.go @@ -78,36 +78,20 @@ type LoggerOptions struct { MaxDocumentLength uint } -// LoggerOptionsBuilder contains options to configure a logger. Each option can -// be set through setter functions. See documentation for each setter function -// for an explanation of the option. -type LoggerOptionsBuilder struct { - Opts []func(*LoggerOptions) error -} - // Logger creates a new LoggerOptions instance. -func Logger() *LoggerOptionsBuilder { - return &LoggerOptionsBuilder{} -} - -// List returns a list of LoggerOptions setter functions. -func (opts *LoggerOptionsBuilder) List() []func(*LoggerOptions) error { - return opts.Opts +func Logger() *LoggerOptions { + return &LoggerOptions{} } // SetComponentLevel sets the LogLevel value for a LogComponent. ComponentLevels is a map of // LogComponent to LogLevel. The LogLevel for a given LogComponent will be used to determine // if a log message should be logged. -func (opts *LoggerOptionsBuilder) SetComponentLevel(component LogComponent, level LogLevel) *LoggerOptionsBuilder { - opts.Opts = append(opts.Opts, func(opts *LoggerOptions) error { - if opts.ComponentLevels == nil { - opts.ComponentLevels = map[LogComponent]LogLevel{} - } +func (opts *LoggerOptions) SetComponentLevel(component LogComponent, level LogLevel) *LoggerOptions { + if opts.ComponentLevels == nil { + opts.ComponentLevels = map[LogComponent]LogLevel{} + } - opts.ComponentLevels[component] = level - - return nil - }) + opts.ComponentLevels[component] = level return opts } @@ -115,12 +99,8 @@ func (opts *LoggerOptionsBuilder) SetComponentLevel(component LogComponent, leve // SetMaxDocumentLength sets the maximum length of a document to be logged. Sink is the // LogSink that will be used to log messages. If this is nil, the driver will use the // standard logging library. -func (opts *LoggerOptionsBuilder) SetMaxDocumentLength(maxDocumentLength uint) *LoggerOptionsBuilder { - opts.Opts = append(opts.Opts, func(opts *LoggerOptions) error { - opts.MaxDocumentLength = maxDocumentLength - - return nil - }) +func (opts *LoggerOptions) SetMaxDocumentLength(maxDocumentLength uint) *LoggerOptions { + opts.MaxDocumentLength = maxDocumentLength return opts } @@ -128,12 +108,8 @@ func (opts *LoggerOptionsBuilder) SetMaxDocumentLength(maxDocumentLength uint) * // SetSink sets the LogSink to use for logging. MaxDocumentLength is the maximum length // of a document to be logged. If the underlying document is larger than this value, it // will be truncated and appended with an ellipses "...". -func (opts *LoggerOptionsBuilder) SetSink(sink LogSink) *LoggerOptionsBuilder { - opts.Opts = append(opts.Opts, func(opts *LoggerOptions) error { - opts.Sink = sink - - return nil - }) +func (opts *LoggerOptions) SetSink(sink LogSink) *LoggerOptions { + opts.Sink = sink return opts } diff --git a/mongo/options/serverapioptions.go b/mongo/options/serverapioptions.go index 116041a3b3..8f38dbc9e7 100644 --- a/mongo/options/serverapioptions.go +++ b/mongo/options/serverapioptions.go @@ -29,50 +29,22 @@ type ServerAPIOptions struct { DeprecationErrors *bool } -// ServerAPIOptionsBuilder contains options to configure serverAPI operations. -// Each option can be set through setter functions. See documentation for each -// setter function for an explanation of the option. -type ServerAPIOptionsBuilder struct { - Opts []func(*ServerAPIOptions) error -} - // ServerAPI creates a new ServerAPIOptions configured with the provided // serverAPIversion. -func ServerAPI(serverAPIVersion ServerAPIVersion) *ServerAPIOptionsBuilder { - opts := &ServerAPIOptionsBuilder{} - - opts.Opts = append(opts.Opts, func(opts *ServerAPIOptions) error { - opts.ServerAPIVersion = serverAPIVersion - - return nil - }) - - return opts -} - -// List returns a list of ServerAPIOptions setter functions. -func (s *ServerAPIOptionsBuilder) List() []func(*ServerAPIOptions) error { - return s.Opts +func ServerAPI(serverAPIVersion ServerAPIVersion) *ServerAPIOptions { + return &ServerAPIOptions{ServerAPIVersion: serverAPIVersion} } // SetStrict specifies whether the server should return errors for features that are not part of the API version. -func (s *ServerAPIOptionsBuilder) SetStrict(strict bool) *ServerAPIOptionsBuilder { - s.Opts = append(s.Opts, func(opts *ServerAPIOptions) error { - opts.Strict = &strict - - return nil - }) +func (s *ServerAPIOptions) SetStrict(strict bool) *ServerAPIOptions { + s.Strict = &strict return s } // SetDeprecationErrors specifies whether the server should return errors for deprecated features. -func (s *ServerAPIOptionsBuilder) SetDeprecationErrors(deprecationErrors bool) *ServerAPIOptionsBuilder { - s.Opts = append(s.Opts, func(opts *ServerAPIOptions) error { - opts.DeprecationErrors = &deprecationErrors - - return nil - }) +func (s *ServerAPIOptions) SetDeprecationErrors(deprecationErrors bool) *ServerAPIOptions { + s.DeprecationErrors = &deprecationErrors return s } diff --git a/mongo/with_transactions_test.go b/mongo/with_transactions_test.go index 3b4d927754..0ffb3b1182 100644 --- a/mongo/with_transactions_test.go +++ b/mongo/with_transactions_test.go @@ -578,7 +578,7 @@ func TestConvenientTransactions(t *testing.T) { }) } -func setupConvenientTransactions(t *testing.T, extraClientOpts ...options.Lister[options.ClientOptions]) *Client { +func setupConvenientTransactions(t *testing.T, extraClientOpts ...*options.ClientOptions) *Client { cs := integtest.ConnString(t) poolMonitor := &event.PoolMonitor{ Event: func(evt *event.PoolEvent) { @@ -597,7 +597,7 @@ func setupConvenientTransactions(t *testing.T, extraClientOpts ...options.Lister SetWriteConcern(writeconcern.Majority()). SetPoolMonitor(poolMonitor) integtest.AddTestServerAPIVersion(baseClientOpts) - fullClientOpts := []options.Lister[options.ClientOptions]{baseClientOpts} + fullClientOpts := []*options.ClientOptions{baseClientOpts} fullClientOpts = append(fullClientOpts, extraClientOpts...) client, err := Connect(fullClientOpts...) diff --git a/x/mongo/driver/auth/auth_spec_test.go b/x/mongo/driver/auth/auth_spec_test.go index 911c583e39..1c0701014d 100644 --- a/x/mongo/driver/auth/auth_spec_test.go +++ b/x/mongo/driver/auth/auth_spec_test.go @@ -13,7 +13,6 @@ import ( "path" "testing" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/internal/require" "go.mongodb.org/mongo-driver/v2/internal/spectest" "go.mongodb.org/mongo-driver/v2/mongo/options" @@ -59,14 +58,12 @@ func runTestsInFile(t *testing.T, dirname string, filename string) { func runTest(t *testing.T, filename string, test testCase) { t.Run(filename+":"+test.Description, func(t *testing.T) { - clientOptsBldr := options.Client().ApplyURI(test.URI) - - opts, _ := mongoutil.NewOptions[options.ClientOptions](clientOptsBldr) + opts := options.Client().ApplyURI(test.URI) if test.Valid { - require.NoError(t, clientOptsBldr.Validate()) + require.NoError(t, opts.Validate()) } else { - require.Error(t, clientOptsBldr.Validate()) + require.Error(t, opts.Validate()) return } diff --git a/x/mongo/driver/drivertest/opmsg_deployment_test.go b/x/mongo/driver/drivertest/opmsg_deployment_test.go index 24e5294e99..3dc5e86265 100644 --- a/x/mongo/driver/drivertest/opmsg_deployment_test.go +++ b/x/mongo/driver/drivertest/opmsg_deployment_test.go @@ -21,11 +21,8 @@ func TestOPMSGMockDeployment(t *testing.T) { md := NewMockDeployment() opts := options.Client() - opts.Opts = append(opts.Opts, func(co *options.ClientOptions) error { - co.Deployment = md + opts.Deployment = md - return nil - }) client, err := mongo.Connect(opts) t.Run("NewMockDeployment connect to client", func(t *testing.T) { diff --git a/x/mongo/driver/topology/topology_options.go b/x/mongo/driver/topology/topology_options.go index 66bbdb74ff..aefa74c56a 100644 --- a/x/mongo/driver/topology/topology_options.go +++ b/x/mongo/driver/topology/topology_options.go @@ -15,7 +15,6 @@ import ( "go.mongodb.org/mongo-driver/v2/event" "go.mongodb.org/mongo-driver/v2/internal/logger" - "go.mongodb.org/mongo-driver/v2/internal/mongoutil" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/x/mongo/driver" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/auth" @@ -44,36 +43,30 @@ type Config struct { logger *logger.Logger } -// ConvertToDriverAPIOptions converts a options.ServerAPIOptions instance to a driver.ServerAPIOptions. -func ConvertToDriverAPIOptions(opts options.Lister[options.ServerAPIOptions]) *driver.ServerAPIOptions { - args, _ := mongoutil.NewOptions[options.ServerAPIOptions](opts) - - driverOpts := driver.NewServerAPIOptions(string(args.ServerAPIVersion)) - if args.Strict != nil { - driverOpts.SetStrict(*args.Strict) +// ConvertToDriverAPIOptions converts a given ServerAPIOptions object from the +// options package to a ServerAPIOptions object from the driver package. +func ConvertToDriverAPIOptions(opts *options.ServerAPIOptions) *driver.ServerAPIOptions { + driverOpts := driver.NewServerAPIOptions(string(opts.ServerAPIVersion)) + if opts.Strict != nil { + driverOpts.SetStrict(*opts.Strict) } - if args.DeprecationErrors != nil { - driverOpts.SetDeprecationErrors(*args.DeprecationErrors) + if opts.DeprecationErrors != nil { + driverOpts.SetDeprecationErrors(*opts.DeprecationErrors) } return driverOpts } -func newLogger(opts options.Lister[options.LoggerOptions]) (*logger.Logger, error) { +func newLogger(opts *options.LoggerOptions) (*logger.Logger, error) { if opts == nil { opts = options.Logger() } - args, err := mongoutil.NewOptions[options.LoggerOptions](opts) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - componentLevels := make(map[logger.Component]logger.Level) - for component, level := range args.ComponentLevels { + for component, level := range opts.ComponentLevels { componentLevels[logger.Component(component)] = logger.Level(level) } - log, err := logger.New(args.Sink, args.MaxDocumentLength, componentLevels) + log, err := logger.New(opts.Sink, opts.MaxDocumentLength, componentLevels) if err != nil { return nil, fmt.Errorf("error creating logger: %w", err) } @@ -128,21 +121,10 @@ func ConvertCreds(cred *options.Credential) *driver.Cred { } } -// NewConfig behaves like NewConfigFromOptions by extracting arguments from a -// list of ClientOptions setters. -func NewConfig(opts *options.ClientOptionsBuilder, clock *session.ClusterClock) (*Config, error) { - args, err := mongoutil.NewOptions[options.ClientOptions](opts) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - - return NewConfigFromOptions(args, clock) -} - -// NewConfigFromOptions will translate data from client options into a topology -// config for building non-default deployments. Server and topology options are -// not honored if a custom deployment is used. -func NewConfigFromOptions(opts *options.ClientOptions, clock *session.ClusterClock) (*Config, error) { +// NewConfig will translate data from client options into a topology config for +// building non-default deployments. Server and topology options are not honored +// if a custom deployment is used. +func NewConfig(opts *options.ClientOptions, clock *session.ClusterClock) (*Config, error) { var authenticator driver.Authenticator var err error if opts.Auth != nil { @@ -163,20 +145,9 @@ func NewConfigFromOptions(opts *options.ClientOptions, clock *session.ClusterClo // options are not honored if a custom deployment is used. It uses a passed in // authenticator to authenticate the connection. func NewConfigFromOptionsWithAuthenticator(opts *options.ClientOptions, clock *session.ClusterClock, authenticator driver.Authenticator) (*Config, error) { - var serverAPI *driver.ServerAPIOptions - clientOptsBldr := options.ClientOptionsBuilder{ - Opts: []func(*options.ClientOptions) error{ - func(copts *options.ClientOptions) error { - *copts = *opts - - return nil - }, - }, - } - - if err := clientOptsBldr.Validate(); err != nil { + if err := opts.Validate(); err != nil { return nil, err } diff --git a/x/mongo/driver/topology/topology_test.go b/x/mongo/driver/topology/topology_test.go index 5aa856c443..b4af40b920 100644 --- a/x/mongo/driver/topology/topology_test.go +++ b/x/mongo/driver/topology/topology_test.go @@ -552,7 +552,7 @@ func TestTopologyConstructionLogging(t *testing.T) { documentDBMsg = `You appear to be connected to a DocumentDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb` ) - newLoggerOptionsBldr := func(sink options.LogSink) *options.LoggerOptionsBuilder { + newLoggerOptionsBldr := func(sink options.LogSink) *options.LoggerOptions { return options. Logger(). SetSink(sink).