Repository: open-feature/go-sdk
Pull request #180 (2023-06-09 21:21:20)

feat: named client support
## This PR

Is related to https://github.com/open-feature/go-sdk/issues/177  and completes the named client support [1] fixing https://github.com/open-feature/go-sdk/issues/183

Recommends to review commit by commit as this contains a refactoring to isolate API (OpenFeature API), OpenFeature accessors (based on global API singleton). Refactoring will help us with upcoming spec change adoptions (inti/shutdown & events). 

### Changes 

- `SetNamedProvider(clientName string, provider FeatureProvider)` was added to support named client support
- `SetProvider(provider FeatureProvider)` now retunrs an `error`. This is non-breaking but Linters will compalint about not checking the error


### Testing 

Newly added unit test covers the newly added spec change

[1] implement named client support (https://github.com/openfeature/spec/commit/4cf8229d23a88feeaadf8b066e5d3662b94ff262)



codecov[bot]: ## [Codecov](https://app.codecov.io/gh/open-feature/go-sdk/pull/180?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) Report
> Merging [#180](https://app.codecov.io/gh/open-feature/go-sdk/pull/180?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) (b0a4df1) into [main](https://app.codecov.io/gh/open-feature/go-sdk/commit/98228f52911e105404041091f3690684283a04f3?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) (98228f5) will **increase** coverage by `0.26%`.
> The diff coverage is `90.65%`.

```diff
@@            Coverage Diff             @@
##             main     #180      +/-   ##
==========================================
+ Coverage   72.47%   72.73%   +0.26%     
==========================================
  Files           8        8              
  Lines         723      763      +40     
==========================================
+ Hits          524      555      +31     
- Misses        180      187       +7     
- Partials       19       21       +2     
```


| [Impacted Files](https://app.codecov.io/gh/open-feature/go-sdk/pull/180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature) | Coverage Δ | |
|---|---|---|
| [pkg/openfeature/client.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL2NsaWVudC5nbw==) | `71.48% <76.47%> (-1.76%)` | :arrow_down: |
| [pkg/openfeature/api.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL2FwaS5nbw==) | `92.00% <92.00%> (ø)` | |
| [pkg/openfeature/openfeature.go](https://app.codecov.io/gh/open-feature/go-sdk/pull/180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature#diff-cGtnL29wZW5mZWF0dXJlL29wZW5mZWF0dXJlLmdv) | `100.00% <100.00%> (+7.40%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-feature)


Patch:
diff --git a/Makefile b/Makefile
index 0d652fe..dbc0ee7 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,6 @@
 mockgen:
 	mockgen -source=pkg/openfeature/provider.go -destination=pkg/openfeature/provider_mock_test.go -package=openfeature
 	mockgen -source=pkg/openfeature/hooks.go -destination=pkg/openfeature/hooks_mock_test.go -package=openfeature
-	mockgen -source=pkg/openfeature/mutex.go -destination=pkg/openfeature/mutex_mock_test.go -package=openfeature
 test:
 	go test --short -cover ./...
 e2e-test: # dependent on: docker run -p 8013:8013 -v $PWD/test-harness/testing-flags.json:/testing-flags.json ghcr.io/open-feature/flagd-testbed:latest
diff --git a/pkg/openfeature/api.go b/pkg/openfeature/api.go
new file mode 100644
index 0000000..e56bf35
--- /dev/null
+++ b/pkg/openfeature/api.go
@@ -0,0 +1,124 @@
+package openfeature
+
+import (
+	"errors"
+	"sync"
+
+	"github.com/go-logr/logr"
+	"github.com/open-feature/go-sdk/pkg/openfeature/internal"
+)
+
+// evaluationAPI wraps OpenFeature evaluation API functionalities
+type evaluationAPI struct {
+	defaultProvider FeatureProvider
+	namedProviders  map[string]FeatureProvider
+	hks             []Hook
+	evalCtx         EvaluationContext
+	logger          logr.Logger
+	mu              sync.RWMutex
+}
+
+// newEvaluationAPI is a helper to generate an API. Used internally
+func newEvaluationAPI() evaluationAPI {
+	return evaluationAPI{
+		defaultProvider: NoopProvider{},
+		namedProviders:  map[string]FeatureProvider{},
+		hks:             []Hook{},
+		evalCtx:         EvaluationContext{},
+		logger:          logr.New(internal.Logger{}),
+		mu:              sync.RWMutex{},
+	}
+}
+
+// setProvider sets the default FeatureProvider of the evaluationAPI. Returns an error if FeatureProvider is nil
+func (api *evaluationAPI) setProvider(provider FeatureProvider) error {
+	api.mu.Lock()
+	defer api.mu.Unlock()
+
+	if provider == nil {
+		return errors.New("default provider cannot be set to nil")
+	}
+
+	api.defaultProvider = provider
+	return nil
+}
+
+// getProvider returns the default FeatureProvider
+func (api *evaluationAPI) getProvider() FeatureProvider {
+	api.mu.RLock()
+	defer api.mu.RUnlock()
+
+	return api.defaultProvider
+}
+
+// setProvider sets a provider with client name. Returns an error if FeatureProvider is nil
+func (api *evaluationAPI) setNamedProvider(clientName string, provider FeatureProvider) error {
+	api.mu.Lock()
+	defer api.mu.Unlock()
+
+	if provider == nil {
+		return errors.New("provider cannot be set to nil")
+	}
+
+	api.namedProviders[clientName] = provider
+	return nil
+}
+
+// getNamedProviders return default providers
+func (api *evaluationAPI) getNamedProviders() map[string]FeatureProvider {
+	api.mu.RLock()
+	defer api.mu.RUnlock()
+
+	return api.namedProviders
+}
+
+func (api *evaluationAPI) setEvaluationContext(evalCtx EvaluationContext) {
+	api.mu.Lock()
+	defer api.mu.Unlock()
+
+	api.evalCtx = evalCtx
+}
+
+func (api *evaluationAPI) setLogger(l logr.Logger) {
+	api.mu.Lock()
+	defer api.mu.Unlock()
+
+	api.logger = l
+}
+
+func (api *evaluationAPI) getLogger() logr.Logger {
+	api.mu.RLock()
+	defer api.mu.RUnlock()
+
+	return api.logger
+}
+
+func (api *evaluationAPI) addHooks(hooks ...Hook) {
+	api.mu.Lock()
+	defer api.mu.Unlock()
+
+	api.hks = append(api.hks, hooks...)
+}
+
+func (api *evaluationAPI) getHooks() []Hook {
+	api.mu.RLock()
+	defer api.mu.RUnlock()
+
+	return api.hks
+}
+
+// forTransaction is a helper to retrieve transaction(flag evaluation) scoped operators.
+// Returns the default FeatureProvider if no provider mapping exist for the given client name.
+func (api *evaluationAPI) forTransaction(clientName string) (FeatureProvider, []Hook, EvaluationContext) {
+	api.mu.RLock()
+	defer api.mu.RUnlock()
+
+	var provider FeatureProvider
+
+	provider = api.namedProviders[clientName]
+	if provider == nil {
+		provider = api.defaultProvider
+	}
+
+	return provider, api.hks, api.evalCtx
+}
diff --git a/pkg/openfeature/client.go b/pkg/openfeature/client.go
index 28530b9..91e0b24 100644
--- a/pkg/openfeature/client.go
+++ b/pkg/openfeature/client.go
@@ -85,7 +85,6 @@ func (c *Client) AddHooks(hooks ...Hook) {
 	c.mx.Lock()
 	defer c.mx.Unlock()
 	c.hooks = append(c.hooks, hooks...)
-	c.logger().V(info).Info("appended hooks to client", "client", c.metadata.name, "hooks", hooks)
 }
 
 // SetEvaluationContext sets the client's evaluation context
@@ -93,9 +92,6 @@ func (c *Client) SetEvaluationContext(evalCtx EvaluationContext) {
 	c.mx.Lock()
 	defer c.mx.Unlock()
 	c.evaluationContext = evalCtx
-	c.logger().V(info).Info(
-		"set client evaluation context", "client", c.metadata.name, "evaluationContext", evalCtx,
-	)
 }
 
 // EvaluationContext returns the client's evaluation context
@@ -167,12 +163,13 @@ type ResolutionDetail struct {
 	FlagMetadata FlagMetadata
 }
 
-// A structure which supports definition of arbitrary properties, with keys of type string, and values of type boolean, string, int64 or float64.
-//
-// This structure is populated by a provider for use by an Application Author (via the Evaluation API) or an Application Integrator (via hooks).
+// FlagMetadata is a structure which supports definition of arbitrary properties, with keys of type string, and values
+// of type boolean, string, int64 or float64. This structure is populated by a provider for use by an Application
+// Author (via the Evaluation API) or an Application Integrator (via hooks).
 type FlagMetadata map[string]interface{}
 
-// Fetch string value from FlagMetadata, returns an error if the key does not exist, or, the value is of the wrong type
+// GetString fetch string value from FlagMetadata.
+// Returns an error if the key does not exist, or, the value is of the wrong type
 func (f FlagMetadata) GetString(key string) (string, error) {
 	v, ok := f[key]
 	if !ok {
@@ -186,7 +183,8 @@ func (f FlagMetadata) GetString(key string) (string, error) {
 	}
 }
 
-// Fetch bool value from FlagMetadata, returns an error if the key does not exist, or, the value is of the wrong type
+// GetBool fetch bool value from FlagMetadata.
+// Returns an error if the key does not exist, or, the value is of the wrong type
 func (f FlagMetadata) GetBool(key string) (bool, error) {
 	v, ok := f[key]
 	if !ok {
@@ -200,7 +198,8 @@ func (f FlagMetadata) GetBool(key string) (bool, error) {
 	}
 }
 
-// Fetch int64 value from FlagMetadata, returns an error if the key does not exist, or, the value is of the wrong type
+// GetInt fetch int64 value from FlagMetadata.
+// Returns an error if the key does not exist, or, the value is of the wrong type
 func (f FlagMetadata) GetInt(key string) (int64, error) {
 	v, ok := f[key]
 	if !ok {
@@ -222,7 +221,8 @@ func (f FlagMetadata) GetInt(key string) (int64, error) {
 	}
 }
 
-// Fetch float64 value from FlagMetadata, returns an error if the key does not exist, or, the value is of the wrong type
+// GetFloat fetch float64 value from FlagMetadata.
+// Returns an error if the key does not exist, or, the value is of the wrong type
 func (f FlagMetadata) GetFloat(key string) (float64, error) {
 	v, ok := f[key]
 	if !ok {
@@ -241,6 +241,22 @@ func (f FlagMetadata) GetFloat(key string) (float64, error) {
 // Option applies a change to EvaluationOptions
 type Option func(*EvaluationOptions)
 
+// EvaluationOptions should contain a list of hooks to be executed for a flag evaluation
+type EvaluationOptions struct {
+	hooks     []Hook
+	hookHints HookHints
+}
+
+// HookHints returns evaluation options' hook hints
+func (e EvaluationOptions) HookHints() HookHints {
+	return e.hookHints
+}
+
+// Hooks returns evaluation options' hooks
+func (e EvaluationOptions) Hooks() []Hook {
+	return e.hooks
+}
+
 // WithHooks applies provided hooks.
 func WithHooks(hooks ...Hook) Option {
 	return func(options *EvaluationOptions) {
@@ -631,11 +647,6 @@ func (c *Client) ObjectValueDetails(ctx context.Context, flag string, defaultVal
 func (c *Client) evaluate(
 	ctx context.Context, flag string, flagType Type, defaultValue interface{}, evalCtx EvaluationContext, options EvaluationOptions,
 ) (InterfaceEvaluationDetails, error) {
-	c.logger().V(debug).Info(
-		"evaluating flag", "flag", flag, "type", flagType.String(), "defaultValue", defaultValue,
-		"evaluationContext", evalCtx, "evaluationOptions", options,
-	)
-
 	evalDetails := InterfaceEvaluationDetails{
 		Value: defaultValue,
 		EvaluationDetails: EvaluationDetails{
@@ -649,11 +660,7 @@ func (c *Client) evaluate(
 	}
 
 	// ensure that the same provider & hooks are used across this transaction to avoid unexpected behaviour
-	api.RLock()
-	provider := api.prvder
-	globalHooks := api.hks
-	globalCtx := api.evalCtx
-	api.RUnlock()
+	provider, globalHooks, globalCtx := forTransaction(c.metadata.name)
 
 	evalCtx = mergeContexts(evalCtx, c.evaluationContext, globalCtx)                                                           // API (global) -> client -> invocation
 	apiClientInvocationProviderHooks := append(append(append(globalHooks, c.hooks...), options.hooks...), provider.Hooks()...) // API, Client, Invocation, Provider
@@ -737,8 +744,6 @@ func (c *Client) evaluate(
 		c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options)
 		return evalDetails, err
 	}
-
-	c.logger().V(debug).Info("evaluated flag", "flag", flag, "details", evalDetails, "type", flagType)
 	return evalDetails, nil
 }
 
@@ -756,9 +761,6 @@ func flattenContext(evalCtx EvaluationContext) FlattenedContext {
 func (c *Client) beforeHooks(
 	ctx context.Context, hookCtx HookContext, hooks []Hook, evalCtx EvaluationContext, options EvaluationOptions,
 ) (EvaluationContext, error) {
-	c.logger().V(debug).Info("executing before hooks")
-	defer c.logger().V(debug).Info("executed before hooks")
-
 	for _, hook := range hooks {
 		resultEvalCtx, err := hook.Before(ctx, hookCtx, options.hookHints)
 		if resultEvalCtx != nil {
@@ -775,9 +777,6 @@ func (c *Client) beforeHooks(
 func (c *Client) afterHooks(
 	ctx context.Context, hookCtx HookContext, hooks []Hook, evalDetails InterfaceEvaluationDetails, options EvaluationOptions,
 ) error {
-	c.logger().V(debug).Info("executing after hooks")
-	defer c.logger().V(debug).Info("executed after hooks")
-
 	for _, hook := range hooks {
 		if err := hook.After(ctx, hookCtx, evalDetails, options.hookHints); err != nil {
 			return err
@@ -788,18 +787,12 @@ func (c *Client) afterHooks(
 }
 
 func (c *Client) errorHooks(ctx context.Context, hookCtx HookContext, hooks []Hook, err error, options EvaluationOptions) {
-	c.logger().V(debug).Info("executing error hooks")
-	defer c.logger().V(debug).Info("executed error hooks")
-
 	for _, hook := range hooks {
 		hook.Error(ctx, hookCtx, err, options.hookHints)
 	}
 }
 
 func (c *Client) finallyHooks(ctx context.Context, hookCtx HookContext, hooks []Hook, options EvaluationOptions) {
-	c.logger().V(debug).Info("executing finally hooks")
-	defer c.logger().V(debug).Info("executed finally hooks")
-
 	for _, hook := range hooks {
 		hook.Finally(ctx, hookCtx, options.hookHints)
 	}
diff --git a/pkg/openfeature/internal/logger.go b/pkg/openfeature/internal/logger.go
new file mode 100644
index 0000000..1b355f9
--- /dev/null
+++ b/pkg/openfeature/internal/logger.go
@@ -0,0 +1,25 @@
+package internal
+
+import (
+	"log"
+
+	"github.com/go-logr/logr"
+)
+
+// Logger is the sdk's default logr.LogSink implementation.
+// Logs using this logger logs only on error, all other logs are no-ops
+type Logger struct{}
+
+func (l Logger) Init(info logr.RuntimeInfo) {}
+
+func (l Logger) Enabled(level int) bool { return true }
+
+func (l Logger) Info(level int, msg string, keysAndValues ...interface{}) {}
+
+func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) {
+	log.Println("openfeature:", err)
+}
+
+func (l Logger) WithValues(keysAndValues ...interface{}) logr.LogSink { return l }
+
+func (l Logger) WithName(name string) logr.LogSink { return l }
diff --git a/pkg/openfeature/logger.go b/pkg/openfeature/logger.go
deleted file mode 100644
index c8fec05..0000000
--- a/pkg/openfeature/logger.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package openfeature
-
-import (
-	"log"
-
-	"github.com/go-logr/logr"
-)
-
-const (
-	info  = 0
-	debug = 1
-)
-
-// logger is the sdk's default logger
-// logs using the standard log package on error, all other logs are no-ops
-type logger struct{}
-
-func (l logger) Init(info logr.RuntimeInfo) {}
-
-func (l logger) Enabled(level int) bool { return true }
-
-func (l logger) Info(level int, msg string, keysAndValues ...interface{}) {}
-
-func (l logger) Error(err error, msg string, keysAndValues ...interface{}) {
-	log.Println("openfeature:", err)
-}
-
-func (l logger) WithValues(keysAndValues ...interface{}) logr.LogSink { return l }
-
-func (l logger) WithName(name string) logr.LogSink { return l }
diff --git a/pkg/openfeature/mutex.go b/pkg/openfeature/mutex.go
deleted file mode 100644
index 5234bee..0000000
--- a/pkg/openfeature/mutex.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package openfeature
-
-// for the sake of testing
-type mutex interface {
-	Lock()
-	Unlock()
-	RLock()
-	RUnlock()
-}
diff --git a/pkg/openfeature/openfeature.go b/pkg/openfeature/openfeature.go
index b4721f2..7f996de 100644
--- a/pkg/openfeature/openfeature.go
+++ b/pkg/openfeature/openfeature.go
@@ -1,89 +1,30 @@
 package openfeature
 
 import (
-	"sync"
-
 	"github.com/go-logr/logr"
 )
 
-type evaluationAPI struct {
-	prvder  FeatureProvider
-	hks     []Hook
-	evalCtx EvaluationContext
-	logger  logr.Logger
-	mutex
-}
-
-// api is the global evaluationAPI.  This is a singleton and there can only be one instance.
+// api is the global evaluationAPI. This is a singleton and there can only be one instance.
+// Avoid direct access.
 var api evaluationAPI
 
-// init initializes the openfeature evaluation API
+// init initializes the OpenFeature evaluation API
 func init() {
 	initSingleton()
 }
 
 func initSingleton() {
-	api = evaluationAPI{
-		prvder:  NoopProvider{},
-		hks:     []Hook{},
-		evalCtx: EvaluationContext{},
-		logger:  logr.New(logger{}),
-		mutex:   &sync.RWMutex{},
-	}
-}
-
-// EvaluationOptions should contain a list of hooks to be executed for a flag evaluation
-type EvaluationOptions struct {
-	hooks     []Hook
-	hookHints HookHints
-}
-
-// HookHints returns evaluation options' hook hints
-func (e EvaluationOptions) HookHints() HookHints {
-	return e.hookHints
-}
-
-// Hooks returns evaluation options' hooks
-func (e EvaluationOptions) Hooks() []Hook {
-	return e.hooks
-}
-
-func (api *evaluationAPI) provider() FeatureProvider {
-	api.RLock()
-	defer api.RUnlock()
-	return api.prvder
-}
-
-func (api *evaluationAPI) setProvider(provider FeatureProvider) {
-	api.Lock()
-	defer api.Unlock()
-	api.prvder = provider
-	api.logger.V(info).Info("set global provider", "name", provider.Metadata().Name)
+	api = newEvaluationAPI()
 }
 
-func (api *evaluationAPI) setEvaluationContext(evalCtx EvaluationContext) {
-	api.Lock()
-	defer api.Unlock()
-	api.evalCtx = evalCtx
-	api.logger.V(info).Info("set global evaluation context", "evaluationContext", evalCtx)
+// SetProvider sets the default provider.
+func SetProvider(provider FeatureProvider) error {
+	return api.setProvider(provider)
 }
 
-func (api *evaluationAPI) setLogger(l logr.Logger) {
-	api.Lock()
-	defer api.Unlock()
-	api.logger = l
-	api.logger.V(info).Info("set global logger")
-}
-
-func (api *evaluationAPI) hooks() []Hook {
-	api.RLock()
-	defer api.RUnlock()
-	return api.hks
-}
-
-// SetProvider sets the global provider.
-func SetProvider(provider FeatureProvider) {
-	api.setProvider(provider)
+// SetNamedProvider sets a provider mapped to the given Client name.
+func SetNamedProvider(clientName string, provider FeatureProvider) error {
+	return api.setNamedProvider(clientName, provider)
 }
 
 // SetEvaluationContext sets the global evaluation context.
@@ -91,26 +32,43 @@ func SetEvaluationContext(evalCtx EvaluationContext) {
 	api.setEvaluationContext(evalCtx)
 }
 
-// SetLogger sets the global logger.
+// SetLogger sets the global Logger.
 func SetLogger(l logr.Logger) {
 	api.setLogger(l)
 }
 
-// ProviderMetadata returns the global provider's metadata
+// ProviderMetadata returns the default provider's metadata
 func ProviderMetadata() Metadata {
-	return api.provider().Metadata()
+	return api.getProvider().Metadata()
 }
 
 // AddHooks appends to the collection of any previously added hooks
 func AddHooks(hooks ...Hook) {
-	api.Lock()
-	defer api.Unlock()
-	api.hks = append(api.hks, hooks...)
-	api.logger.V(info).Info("appended hooks to the global singleton", "hooks", hooks)
+	api.addHooks(hooks...)
+}
+
+// getProvider returns the default provider of the API. Intended to be used by tests
+func getProvider() FeatureProvider {
+	return api.getProvider()
 }
 
+// getNamedProviders returns the default provider of the API. Intended to be used by tests
+func getNamedProviders() map[string]FeatureProvider {
+	return api.getNamedProviders()
+}
+
+// getHooks returns hooks of the API. Intended to be used by tests
+func getHooks() []Hook {
+	return api.getHooks()
+}
+
+// globalLogger return the global logger set at the API
 func globalLogger() logr.Logger {
-	api.RLock()
-	defer api.RUnlock()
-	return api.logger
+	return api.getLogger()
+}
+
+// forTransaction is a helper to retrieve transaction scoped operators by Client.
+// Here, transaction means a flag evaluation.
+func forTransaction(clientName string) (FeatureProvider, []Hook, EvaluationContext) {
+	return api.forTransaction(clientName)
 }


Test patch:
diff --git a/e2e/evaluation_fuzz_test.go b/e2e/evaluation_fuzz_test.go
index 6dcea8a..d7f3c91 100644
--- a/e2e/evaluation_fuzz_test.go
+++ b/e2e/evaluation_fuzz_test.go
@@ -14,7 +14,10 @@ func setupFuzzClient(f *testing.F) *openfeature.Client {
 	f.Helper()
 
 	provider := flagd.NewProvider(flagd.WithPort(8013), flagd.WithoutCache())
-	openfeature.SetProvider(provider)
+	err := openfeature.SetProvider(provider)
+	if err != nil {
+		f.Errorf("error setting up provider %v", err)
+	}
 
 	select {
 	case <-provider.IsReady():
diff --git a/pkg/openfeature/client_test.go b/pkg/openfeature/client_test.go
index 1b27cc0..254f954 100644
--- a/pkg/openfeature/client_test.go
+++ b/pkg/openfeature/client_test.go
@@ -5,8 +5,6 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/go-logr/logr"
-
 	"github.com/golang/mock/gomock"
 )
 
@@ -134,7 +132,11 @@ func TestRequirement_1_4_2__1_4_5__1_4_6(t *testing.T) {
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
 	mockProvider.EXPECT().Hooks().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	flagKey := "foo"
 
@@ -351,7 +353,11 @@ func TestRequirement_1_4_7(t *testing.T) {
 				ResolutionError: NewGeneralResolutionError("test"),
 			},
 		})
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	res, err := client.evaluate(
 		context.Background(), "foo", Boolean, true, EvaluationContext{}, EvaluationOptions{},
@@ -383,7 +389,10 @@ func TestRequirement_1_4_8(t *testing.T) {
 				ResolutionError: NewGeneralResolutionError("test"),
 			},
 		})
-	SetProvider(mockProvider)
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	res, err := client.evaluate(
 		context.Background(), "foo", Boolean, true, EvaluationContext{}, EvaluationOptions{},
@@ -426,7 +435,11 @@ func TestRequirement_1_4_9(t *testing.T) {
 					ResolutionError: NewGeneralResolutionError("test"),
 				},
 			}).Times(2)
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		value, err := client.BooleanValue(context.Background(), flagKey, defaultValue, evalCtx)
 		if err == nil {
@@ -460,7 +473,11 @@ func TestRequirement_1_4_9(t *testing.T) {
 					ResolutionError: NewGeneralResolutionError("test"),
 				},
 			}).Times(2)
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		value, err := client.StringValue(context.Background(), flagKey, defaultValue, evalCtx)
 		if err == nil {
@@ -494,7 +511,11 @@ func TestRequirement_1_4_9(t *testing.T) {
 					ResolutionError: NewGeneralResolutionError("test"),
 				},
 			}).Times(2)
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		value, err := client.FloatValue(context.Background(), flagKey, defaultValue, evalCtx)
 		if err == nil {
@@ -528,7 +549,11 @@ func TestRequirement_1_4_9(t *testing.T) {
 					ResolutionError: NewGeneralResolutionError("test"),
 				},
 			}).Times(2)
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		value, err := client.IntValue(context.Background(), flagKey, defaultValue, evalCtx)
 		if err == nil {
@@ -564,7 +589,11 @@ func TestRequirement_1_4_9(t *testing.T) {
 					ResolutionError: NewGeneralResolutionError("test"),
 				},
 			}).Times(2)
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		value, err := client.ObjectValue(context.Background(), flagKey, defaultValue, evalCtx)
 		if err == nil {
@@ -605,7 +634,12 @@ func TestRequirement_1_4_12(t *testing.T) {
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
 	mockProvider.EXPECT().Hooks().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
 	mockProvider.EXPECT().BooleanEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
 		Return(BoolResolutionDetail{
 			Value: true,
@@ -654,7 +688,11 @@ func TestRequirement_1_4_13(t *testing.T) {
 					FlagMetadata: nil,
 				},
 			}).Times(1)
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		evDetails, err := client.BooleanValueDetails(context.Background(), flagKey, defaultValue, EvaluationContext{})
 		if err != nil {
@@ -684,7 +722,11 @@ func TestRequirement_1_4_13(t *testing.T) {
 					FlagMetadata: metadata,
 				},
 			}).Times(1)
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		evDetails, err := client.BooleanValueDetails(context.Background(), flagKey, defaultValue, EvaluationContext{})
 		if err != nil {
@@ -795,7 +837,11 @@ func TestBeforeHookNilContext(t *testing.T) {
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
 	mockProvider.EXPECT().Hooks().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	hookNilContext := UnimplementedHook{}
 
@@ -804,7 +850,7 @@ func TestBeforeHookNilContext(t *testing.T) {
 	evalCtx := EvaluationContext{attributes: attributes}
 	mockProvider.EXPECT().BooleanEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), attributes)
 
-	_, err := client.BooleanValue(
+	_, err = client.BooleanValue(
 		context.Background(), "foo", false, evalCtx, WithHooks(hookNilContext),
 	)
 	if err != nil {
@@ -812,35 +858,6 @@ func TestBeforeHookNilContext(t *testing.T) {
 	}
 }
 
-type lr struct {
-	callback func()
-	logger
-}
-
-func (l lr) Info(level int, msg string, keysAndValues ...interface{}) {
-	l.callback()
-}
-
-func TestClientLoggerUsesLatestGlobalLogger(t *testing.T) {
-	defer t.Cleanup(initSingleton)
-
-	called := false
-	l := lr{callback: func() {
-		called = true
-	}}
-
-	client := NewClient("test")
-	SetLogger(logr.New(l))
-	_, err := client.BooleanValue(context.Background(), "foo", false, EvaluationContext{})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if !called {
-		t.Error("client didn't use the updated global logger")
-	}
-}
-
 func TestErrorCodeFromProviderReturnedInEvaluationDetails(t *testing.T) {
 	defer t.Cleanup(initSingleton)
 	ctrl := gomock.NewController(t)
@@ -850,7 +867,12 @@ func TestErrorCodeFromProviderReturnedInEvaluationDetails(t *testing.T) {
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
 	mockProvider.EXPECT().Hooks().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
 	mockProvider.EXPECT().BooleanEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
 		Return(BoolResolutionDetail{
 			Value: true,
@@ -889,10 +911,17 @@ func TestSwitchingProvidersMidEvaluationCausesNoImpactToEvaluation(t *testing.T)
 	// set new provider during initial provider's Before hook
 	mockProvider1Hook.EXPECT().Before(gomock.Any(), gomock.Any(), gomock.Any()).
 		DoAndReturn(func(_ context.Context, _ HookContext, _ HookHints) (*EvaluationContext, error) {
-			SetProvider(mockProvider2)
+			err := SetProvider(mockProvider1)
+			if err != nil {
+				return nil, err
+			}
 			return nil, nil
 		})
-	SetProvider(mockProvider1)
+
+	err := SetProvider(mockProvider1)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	mockProvider1.EXPECT().BooleanEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
 	// ensure that the first provider's hooks are still fired
@@ -900,36 +929,7 @@ func TestSwitchingProvidersMidEvaluationCausesNoImpactToEvaluation(t *testing.T)
 	mockProvider1Hook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
 	client := NewClient("test")
-	_, err := client.BooleanValue(context.Background(), "foo", true, EvaluationContext{})
-	if err != nil {
-		t.Error(err)
-	}
-}
-
-func TestClientProviderLock(t *testing.T) {
-	ctrl := gomock.NewController(t)
-	defer t.Cleanup(initSingleton)
-
-	mockMutex := NewMockmutex(ctrl)
-	mockProvider1 := NewMockFeatureProvider(ctrl)
-	mockProvider2 := NewMockFeatureProvider(ctrl)
-
-	mockProvider1.EXPECT().BooleanEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
-	mockProvider1.EXPECT().Hooks().AnyTimes()
-	mockProvider1.EXPECT().Metadata().AnyTimes()
-
-	mockMutex.EXPECT().RLock().DoAndReturn(func() {
-		api.prvder = mockProvider1
-	})
-
-	// test that any provider change after RUnlock has no impact on this transaction
-	mockMutex.EXPECT().RUnlock().DoAndReturn(func() {
-		api.prvder = mockProvider2
-	})
-	api.mutex = mockMutex
-
-	client := NewClient("test").WithLogger(logr.New(logger{}))
-	_, err := client.BooleanValue(context.Background(), "foo", false, EvaluationContext{})
+	_, err = client.BooleanValue(context.Background(), "foo", true, EvaluationContext{})
 	if err != nil {
 		t.Error(err)
 	}
@@ -946,7 +946,12 @@ func TestObjectEvaluationShouldSupportNilValue(t *testing.T) {
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
 	mockProvider.EXPECT().Hooks().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
 	mockProvider.EXPECT().ObjectEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
 		Return(InterfaceResolutionDetail{
 			Value: value,
diff --git a/pkg/openfeature/evaluation_context_test.go b/pkg/openfeature/evaluation_context_test.go
index 03bbcbd..9ad4059 100644
--- a/pkg/openfeature/evaluation_context_test.go
+++ b/pkg/openfeature/evaluation_context_test.go
@@ -101,7 +101,11 @@ func TestRequirement_3_2_2(t *testing.T) {
 
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	client := NewClient("test")
 	clientEvalCtx := EvaluationContext{
@@ -136,7 +140,7 @@ func TestRequirement_3_2_2(t *testing.T) {
 	flatCtx := flattenContext(expectedMergedEvalCtx)
 	mockProvider.EXPECT().StringEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), flatCtx)
 
-	_, err := client.StringValue(context.Background(), "foo", "bar", invocationEvalCtx)
+	_, err = client.StringValue(context.Background(), "foo", "bar", invocationEvalCtx)
 	if err != nil {
 		t.Error(err)
 	}
diff --git a/pkg/openfeature/hooks_test.go b/pkg/openfeature/hooks_test.go
index fcc78c7..e7044d5 100644
--- a/pkg/openfeature/hooks_test.go
+++ b/pkg/openfeature/hooks_test.go
@@ -157,7 +157,11 @@ func TestRequirement_4_3_2(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		flagKey := "foo"
 		defaultValue := "bar"
@@ -172,7 +176,7 @@ func TestRequirement_4_3_2(t *testing.T) {
 		mockHook.EXPECT().After(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
 		mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
 		if err != nil {
 			t.Errorf("unexpected err: %v", err)
 		}
@@ -199,7 +203,12 @@ func TestRequirement_4_3_3(t *testing.T) {
 
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
 	mockHook1 := NewMockHook(ctrl)
 	mockHook2 := NewMockHook(ctrl)
 	client := NewClient("test")
@@ -239,7 +248,7 @@ func TestRequirement_4_3_3(t *testing.T) {
 	mockHook2.EXPECT().After(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
 	mockHook2.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-	_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook1, mockHook2))
+	_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook1, mockHook2))
 	if err != nil {
 		t.Errorf("unexpected err: %v", err)
 	}
@@ -254,7 +263,12 @@ func TestRequirement_4_3_4(t *testing.T) {
 
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
 	mockHook := NewMockHook(ctrl)
 	client := NewClient("test")
 
@@ -309,7 +323,7 @@ func TestRequirement_4_3_4(t *testing.T) {
 	mockHook.EXPECT().After(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
 	mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-	_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, invEvalCtx, WithHooks(mockHook))
+	_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, invEvalCtx, WithHooks(mockHook))
 	if err != nil {
 		t.Errorf("unexpected err: %v", err)
 	}
@@ -326,7 +340,11 @@ func TestRequirement_4_3_5(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		flagKey := "foo"
 		defaultValue := "bar"
@@ -341,7 +359,7 @@ func TestRequirement_4_3_5(t *testing.T) {
 			After(mockProvider.EXPECT().StringEvaluation(gomock.Any(), flagKey, defaultValue, flatCtx))
 		mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
 		if err != nil {
 			t.Errorf("unexpected err: %v", err)
 		}
@@ -378,7 +396,11 @@ func TestRequirement_4_3_6(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -387,7 +409,7 @@ func TestRequirement_4_3_6(t *testing.T) {
 			After(mockHook.EXPECT().Before(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("forced")))
 		mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
 		if err == nil {
 			t.Error("expected error, got nil")
 		}
@@ -398,7 +420,11 @@ func TestRequirement_4_3_6(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -415,7 +441,7 @@ func TestRequirement_4_3_6(t *testing.T) {
 			)
 		mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
 		if err == nil {
 			t.Error("expected error, got nil")
 		}
@@ -426,7 +452,11 @@ func TestRequirement_4_3_6(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -437,7 +467,7 @@ func TestRequirement_4_3_6(t *testing.T) {
 			After(mockHook.EXPECT().After(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(errors.New("forced")))
 		mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
 		if err == nil {
 			t.Error("expected error, got nil")
 		}
@@ -473,7 +503,11 @@ func TestRequirement_4_3_7(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -483,7 +517,7 @@ func TestRequirement_4_3_7(t *testing.T) {
 			After(mockHook.EXPECT().Before(gomock.Any(), gomock.Any(), gomock.Any()))
 		mockProvider.EXPECT().StringEvaluation(context.Background(), flagKey, defaultValue, flatCtx)
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
 		if err != nil {
 			t.Errorf("unexpected err: %v", err)
 		}
@@ -494,7 +528,11 @@ func TestRequirement_4_3_7(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
 
 		mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -503,7 +541,7 @@ func TestRequirement_4_3_7(t *testing.T) {
 		mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any()).
 			After(mockHook.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()))
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook))
 		if err == nil {
 			t.Error("expected error, got nil")
 		}
@@ -609,7 +647,12 @@ func TestRequirement_4_4_2(t *testing.T) {
 
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
+
 		mockProvider.EXPECT().Hooks().Return([]Hook{mockProviderHook}).Times(2)
 
 		// before: API, Client, Invocation, Provider
@@ -632,7 +675,7 @@ func TestRequirement_4_4_2(t *testing.T) {
 
 		mockProvider.EXPECT().StringEvaluation(context.Background(), flagKey, defaultValue, flatCtx)
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockInvocationHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockInvocationHook))
 		if err != nil {
 			t.Errorf("unexpected err: %v", err)
 		}
@@ -651,7 +694,12 @@ func TestRequirement_4_4_2(t *testing.T) {
 
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
+
 		mockProvider.EXPECT().Hooks().Return([]Hook{mockProviderHook}).Times(2)
 
 		mockAPIHook.EXPECT().Before(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("forced"))
@@ -667,7 +715,7 @@ func TestRequirement_4_4_2(t *testing.T) {
 		mockClientHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 		mockAPIHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockInvocationHook))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockInvocationHook))
 		if err == nil {
 			t.Error("expected error, got nil")
 		}
@@ -710,7 +758,11 @@ func TestRequirement_4_4_6(t *testing.T) {
 			client := NewClient("test")
 			mockProvider := NewMockFeatureProvider(ctrl)
 			mockProvider.EXPECT().Metadata().AnyTimes()
-			SetProvider(mockProvider)
+
+			err := SetProvider(mockProvider)
+			if err != nil {
+				t.Errorf("error setting up provider %v", err)
+			}
 
 			mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -721,7 +773,7 @@ func TestRequirement_4_4_6(t *testing.T) {
 			mockHook2.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
 			mockHook2.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-			_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook1, mockHook2))
+			_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook1, mockHook2))
 			if err == nil {
 				t.Error("expected error, got nil")
 			}
@@ -736,7 +788,11 @@ func TestRequirement_4_4_6(t *testing.T) {
 			client := NewClient("test")
 			mockProvider := NewMockFeatureProvider(ctrl)
 			mockProvider.EXPECT().Metadata().AnyTimes()
-			SetProvider(mockProvider)
+
+			err := SetProvider(mockProvider)
+			if err != nil {
+				t.Errorf("error setting up provider %v", err)
+			}
 
 			mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -751,7 +807,7 @@ func TestRequirement_4_4_6(t *testing.T) {
 
 			mockProvider.EXPECT().StringEvaluation(context.Background(), flagKey, defaultValue, flatCtx)
 
-			_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook1, mockHook2))
+			_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook1, mockHook2))
 			if err == nil {
 				t.Error("expected error, got nil")
 			}
@@ -772,7 +828,11 @@ func TestRequirement_4_4_7(t *testing.T) {
 	client := NewClient("test")
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	mockProvider.EXPECT().Hooks().AnyTimes()
 
@@ -815,7 +875,12 @@ func TestRequirement_4_5_2(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
+
 		mockProvider.EXPECT().Hooks().AnyTimes()
 
 		hookHints := NewHookHints(map[string]interface{}{"foo": "bar"})
@@ -826,7 +891,7 @@ func TestRequirement_4_5_2(t *testing.T) {
 
 		mockProvider.EXPECT().StringEvaluation(context.Background(), flagKey, defaultValue, flatCtx)
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook), WithHookHints(hookHints))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook), WithHookHints(hookHints))
 		if err != nil {
 			t.Errorf("unexpected error: %v", err)
 		}
@@ -838,7 +903,12 @@ func TestRequirement_4_5_2(t *testing.T) {
 		client := NewClient("test")
 		mockProvider := NewMockFeatureProvider(ctrl)
 		mockProvider.EXPECT().Metadata().AnyTimes()
-		SetProvider(mockProvider)
+
+		err := SetProvider(mockProvider)
+		if err != nil {
+			t.Errorf("error setting up provider %v", err)
+		}
+
 		mockProvider.EXPECT().Hooks().AnyTimes()
 
 		hookHints := NewHookHints(map[string]interface{}{"foo": "bar"})
@@ -847,7 +917,7 @@ func TestRequirement_4_5_2(t *testing.T) {
 		mockHook.EXPECT().Error(gomock.Any(), gomock.Any(), gomock.Any(), hookHints)
 		mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
 
-		_, err := client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook), WithHookHints(hookHints))
+		_, err = client.StringValueDetails(context.Background(), flagKey, defaultValue, evalCtx, WithHooks(mockHook), WithHookHints(hookHints))
 		if err == nil {
 			t.Error("expected error, got nil")
 		}
diff --git a/pkg/openfeature/mutex_mock_test.go b/pkg/openfeature/mutex_mock_test.go
deleted file mode 100644
index cc80b1a..0000000
--- a/pkg/openfeature/mutex_mock_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: pkg/openfeature/mutex.go
-
-// Package openfeature is a generated GoMock package.
-package openfeature
-
-import (
-	gomock "github.com/golang/mock/gomock"
-	reflect "reflect"
-)
-
-// Mockmutex is a mock of mutex interface
-type Mockmutex struct {
-	ctrl     *gomock.Controller
-	recorder *MockmutexMockRecorder
-}
-
-// MockmutexMockRecorder is the mock recorder for Mockmutex
-type MockmutexMockRecorder struct {
-	mock *Mockmutex
-}
-
-// NewMockmutex creates a new mock instance
-func NewMockmutex(ctrl *gomock.Controller) *Mockmutex {
-	mock := &Mockmutex{ctrl: ctrl}
-	mock.recorder = &MockmutexMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *Mockmutex) EXPECT() *MockmutexMockRecorder {
-	return m.recorder
-}
-
-// Lock mocks base method
-func (m *Mockmutex) Lock() {
-	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "Lock")
-}
-
-// Lock indicates an expected call of Lock
-func (mr *MockmutexMockRecorder) Lock() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*Mockmutex)(nil).Lock))
-}
-
-// Unlock mocks base method
-func (m *Mockmutex) Unlock() {
-	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "Unlock")
-}
-
-// Unlock indicates an expected call of Unlock
-func (mr *MockmutexMockRecorder) Unlock() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*Mockmutex)(nil).Unlock))
-}
-
-// RLock mocks base method
-func (m *Mockmutex) RLock() {
-	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "RLock")
-}
-
-// RLock indicates an expected call of RLock
-func (mr *MockmutexMockRecorder) RLock() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*Mockmutex)(nil).RLock))
-}
-
-// RUnlock mocks base method
-func (m *Mockmutex) RUnlock() {
-	m.ctrl.T.Helper()
-	m.ctrl.Call(m, "RUnlock")
-}
-
-// RUnlock indicates an expected call of RUnlock
-func (mr *MockmutexMockRecorder) RUnlock() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*Mockmutex)(nil).RUnlock))
-}
diff --git a/pkg/openfeature/openfeature_test.go b/pkg/openfeature/openfeature_test.go
index f3a7676..3199d1b 100644
--- a/pkg/openfeature/openfeature_test.go
+++ b/pkg/openfeature/openfeature_test.go
@@ -1,9 +1,12 @@
 package openfeature
 
 import (
+	"reflect"
 	"testing"
 
+	"github.com/go-logr/logr"
 	"github.com/golang/mock/gomock"
+	"github.com/open-feature/go-sdk/pkg/openfeature/internal"
 )
 
 // The `API`, and any state it maintains SHOULD exist as a global singleton,
@@ -15,14 +18,18 @@ func TestRequirement_1_1_1(t *testing.T) {
 
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProvider.EXPECT().Metadata().AnyTimes()
-	SetProvider(mockProvider)
 
-	if api.provider() != mockProvider {
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
+	if getProvider() != mockProvider {
 		t.Error("func SetProvider hasn't set the provider to the singleton")
 	}
 }
 
-// The `API` MUST provide a function to set the global `provider` singleton,
+// The `API` MUST provide a function to set the default `provider`,
 // which accepts an API-conformant `provider` implementation.
 func TestRequirement_1_1_2(t *testing.T) {
 	defer t.Cleanup(initSingleton)
@@ -31,17 +38,96 @@ func TestRequirement_1_1_2(t *testing.T) {
 	mockProvider := NewMockFeatureProvider(ctrl)
 	mockProviderName := "mock-provider"
 	mockProvider.EXPECT().Metadata().Return(Metadata{Name: mockProviderName}).AnyTimes()
-	SetProvider(mockProvider)
+
+	err := SetProvider(mockProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
 
 	if ProviderMetadata() != mockProvider.Metadata() {
 		t.Error("globally set provider's metadata doesn't match the mock provider's metadata")
 	}
 }
 
+// The `API` MUST provide a function to bind a given `provider` to one or more client `name`s.
+// If the client-name already has a bound provider, it is overwritten with the new mapping.
+func TestRequirement_1_1_3(t *testing.T) {
+	defer t.Cleanup(initSingleton)
+
+	// Setup
+
+	ctrl := gomock.NewController(t)
+	providerA := NewMockFeatureProvider(ctrl)
+	providerA.EXPECT().Metadata().Return(Metadata{Name: "providerA"}).AnyTimes()
+
+	providerB := NewMockFeatureProvider(ctrl)
+	providerB.EXPECT().Metadata().Return(Metadata{Name: "providerB"}).AnyTimes()
+
+	err := SetNamedProvider("clientA", providerA)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
+	err = SetNamedProvider("clientB", providerB)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
+	namedProviders := getNamedProviders()
+
+	// Validate binding
+
+	if len(namedProviders) != 2 {
+		t.Errorf("expected %d providers, but got %d", 2, len(namedProviders))
+	}
+
+	if namedProviders["clientA"] != providerA {
+		t.Errorf("invalid provider binding")
+	}
+
+	if namedProviders["clientB"] != providerB {
+		t.Errorf("invalid provider binding")
+	}
+
+	// Validate provider retrieval by client evaluation. This uses forTransaction("clientName")
+
+	provider, _, _ := forTransaction("clientA")
+	if provider.Metadata().Name != "providerA" {
+		t.Errorf("expected %s, but got %s", "providerA", providerA.Metadata().Name)
+	}
+
+	provider, _, _ = forTransaction("clientB")
+	if provider.Metadata().Name != "providerB" {
+		t.Errorf("expected %s, but got %s", "providerB", providerA.Metadata().Name)
+	}
+
+	// Validate overriding: If the client-name already has a bound provider, it is overwritten with the new mapping.
+
+	providerB2 := NewMockFeatureProvider(ctrl)
+	providerB2.EXPECT().Metadata().Return(Metadata{Name: "providerB2"}).AnyTimes()
+
+	err = SetNamedProvider("clientB", providerB2)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
+	namedProviders = getNamedProviders()
+	if namedProviders["clientB"] != providerB2 {
+		t.Errorf("named provider overriding failed")
+	}
+
+	// Validate provider retrieval by client evaluation. This uses forTransaction("clientName")
+
+	provider, _, _ = forTransaction("clientB")
+	if provider.Metadata().Name != "providerB2" {
+		t.Errorf("expected %s, but got %s", "providerB2", providerA.Metadata().Name)
+	}
+}
+
 // The `API` MUST provide a function to add `hooks` which accepts one or more API-conformant `hooks`,
 // and appends them to the collection of any previously added hooks. When new hooks are added,
 // previously added hooks are not removed.
-func TestRequirement_1_1_3(t *testing.T) {
+func TestRequirement_1_1_4(t *testing.T) {
 	defer t.Cleanup(initSingleton)
 	ctrl := gomock.NewController(t)
 
@@ -50,13 +136,13 @@ func TestRequirement_1_1_3(t *testing.T) {
 	AddHooks(mockHook)
 	AddHooks(mockHook, mockHook)
 
-	if len(api.hooks()) != 3 {
+	if len(getHooks()) != 3 {
 		t.Error("func AddHooks didn't append the list of hooks to the existing collection of hooks")
 	}
 }
 
 // The API MUST provide a function for retrieving the metadata field of the configured `provider`.
-func TestRequirement_1_1_4(t *testing.T) {
+func TestRequirement_1_1_5(t *testing.T) {
 	defer t.Cleanup(initSingleton)
 	defaultProvider := NoopProvider{}
 	if ProviderMetadata() != defaultProvider.Metadata() {
@@ -66,13 +152,13 @@ func TestRequirement_1_1_4(t *testing.T) {
 
 // The `API` MUST provide a function for creating a `client` which accepts the following options:
 // - name (optional): A logical string identifier for the client.
-func TestRequirement_1_1_5(t *testing.T) {
+func TestRequirement_1_1_6(t *testing.T) {
 	defer t.Cleanup(initSingleton)
 	NewClient("test-client")
 }
 
 // The client creation function MUST NOT throw, or otherwise abnormally terminate.
-func TestRequirement_1_1_6(t *testing.T) {
+func TestRequirement_1_1_7(t *testing.T) {
 	defer t.Cleanup(initSingleton)
 	type clientCreationFunc func(name string) *Client
 
@@ -82,6 +168,56 @@ func TestRequirement_1_1_6(t *testing.T) {
 	use(f) // to avoid the declared and not used error
 }
 
+// Non-spec bound validations
+
+// If there is no client name bound provider, then return the default provider
+func TestDefaultClientUsage(t *testing.T) {
+	defer t.Cleanup(initSingleton)
+
+	ctrl := gomock.NewController(t)
+	defaultProvider := NewMockFeatureProvider(ctrl)
+	defaultProvider.EXPECT().Metadata().Return(Metadata{Name: "defaultClientReplacement"}).AnyTimes()
+
+	err := SetProvider(defaultProvider)
+	if err != nil {
+		t.Errorf("error setting up provider %v", err)
+	}
+
+	// Validate provider retrieval by client evaluation
+	provider, _, _ := forTransaction("ClientName")
+
+	if provider.Metadata().Name != "defaultClientReplacement" {
+		t.Errorf("expected %s, but got %s", "defaultClientReplacement", provider.Metadata().Name)
+	}
+}
+
+// Ability to override default logger
+func TestLoggerOverride(t *testing.T) {
+	defer t.Cleanup(initSingleton)
+
+	newOverride := internal.Logger{}
+	SetLogger(logr.New(newOverride))
+
+	if !reflect.DeepEqual(globalLogger().GetSink(), newOverride) {
+		t.Error("logger overriding failed")
+	}
+}
+
+// Nil providers are not accepted for default and named providers
+func TestForNilProviders(t *testing.T) {
+	defer t.Cleanup(initSingleton)
+
+	err := SetProvider(nil)
+	if err == nil {
+		t.Errorf("setting nil provider must result in an error")
+	}
+
+	err = SetNamedProvider("client", nil)
+	if err == nil {
+		t.Errorf("setting nil named provider must result in an error")
+	}
+}
+
 func use(vals ...interface{}) {
 	for _, val := range vals {
 		_ = val