Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions mongo/options/internaloptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (C) MongoDB, Inc. 2025-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package options

import (
"fmt"

"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
)

// SetInternalClientOptions sets internal options for ClientOptions.
//
// Deprecated: This function is for internal use only. It may be changed or removed in any release.
func SetInternalClientOptions(opts *ClientOptions, custom map[string]any) (*ClientOptions, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this function live in the experimental API? What is the pattern for future custom data? Should ClientOptions be extended with a Custom field of type map[string]any?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the following pattern:

type ClientOptions struct {
	custom map[string]any
}

func WithCustomValue(co ClientOptions, key string, val any) ClientOptions {}
func CustomValue(co ClientOptions key string) any {}

Then in x/mongo/driver:

const someExperimentKey = "x/mongo/driver:myExperiment"

func WithSomeExperimentValue(co options.ClientOptions, on bool) options.ClientOptions {
	return options.WithCustomValue(co, someExperimentKey, on)
}

func SomeExperimentValue(co options.ClientOptions) bool {
	val := options.CustomValue(co, someExperimentKey)
	if b, ok := val.(bool); ok {
		return b
	}

	return false
}

The usage would look something like this:

	clientOpts, _ := options.Client()
	clientOpts = driver.WithSomeExperimentValue(clientOpts, true)

	// ...

And we would check it internally like this:

	expVal := driver.SomeExperimentValue(clientOpts)
	// ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of an expressive interface, I'd prefer a map-style interface without any hint other than a user-provided option name to add obfuscation to discourage general users from using it. However, it makes sense to wrap the map[string]any in a struct and move it to "x/mongo/driver" to emphasize it is "experimental".

I can stack another PR for GODRIVER-3429 (internal-only "AuthenticateToAnything") on top of this one to demonstrate future custom data.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this solution is fine for now, we can update if needed later.

const typeErr = "unexpected type for %s"
for k, v := range custom {
switch k {
case "crypt":
c, ok := v.(driver.Crypt)
if !ok {
return nil, fmt.Errorf(typeErr, k)
}
opts.Crypt = c
case "deployment":
d, ok := v.(driver.Deployment)
if !ok {
return nil, fmt.Errorf(typeErr, k)
}
opts.Deployment = d
default:
return nil, fmt.Errorf("unsupported option: %s", k)
}
}
return opts, nil
}
73 changes: 73 additions & 0 deletions mongo/options/internaloptions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (C) MongoDB, Inc. 2025-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package options

import (
"testing"

"go.mongodb.org/mongo-driver/v2/internal/require"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/drivertest"
)

func TestSetClientOptions(t *testing.T) {
t.Parallel()

t.Run("set Crypt with driver.Crypt", func(t *testing.T) {
t.Parallel()

opts := &ClientOptions{}
c := driver.NewCrypt(&driver.CryptOptions{})
opts, err := SetInternalClientOptions(opts, map[string]any{
"crypt": c,
})
require.NoError(t, err)
require.Equal(t, c, opts.Crypt)
})

t.Run("set Crypt with driver.Deployment", func(t *testing.T) {
t.Parallel()

opts := &ClientOptions{}
_, err := SetInternalClientOptions(opts, map[string]any{
"crypt": &drivertest.MockDeployment{},
})
require.EqualError(t, err, "unexpected type for crypt")
})

t.Run("set Deployment with driver.Deployment", func(t *testing.T) {
t.Parallel()

opts := &ClientOptions{}
d := &drivertest.MockDeployment{}
opts, err := SetInternalClientOptions(opts, map[string]any{
"deployment": d,
})
require.NoError(t, err)
require.Equal(t, d, opts.Deployment)
})

t.Run("set Deployment with driver.Crypt", func(t *testing.T) {
t.Parallel()

opts := &ClientOptions{}
_, err := SetInternalClientOptions(opts, map[string]any{
"deployment": driver.NewCrypt(&driver.CryptOptions{}),
})
require.EqualError(t, err, "unexpected type for deployment")
})

t.Run("set unsupported option", func(t *testing.T) {
t.Parallel()

opts := &ClientOptions{}
_, err := SetInternalClientOptions(opts, map[string]any{
"unsupported": "unsupported",
})
require.EqualError(t, err, "unsupported option: unsupported")
})
}
Loading