Skip to content

Commit

Permalink
solidify interfaces to match OF API & client
Browse files Browse the repository at this point in the history
Signed-off-by: Kavindu Dodanduwa <kavindudodanduwa@gmail.com>
  • Loading branch information
Kavindu-Dodan committed Apr 30, 2024
1 parent 10e29be commit efeff46
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 75 deletions.
2 changes: 1 addition & 1 deletion e2e/evaluation_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/open-feature/go-sdk/openfeature/memprovider"
)

func setupFuzzClient(f *testing.F) *openfeature.Client {
func setupFuzzClient(f *testing.F) openfeature.IClient {
f.Helper()

memoryProvider := memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{})
Expand Down
24 changes: 0 additions & 24 deletions openfeature/api.go

This file was deleted.

32 changes: 3 additions & 29 deletions openfeature/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,6 @@ import (
"github.com/go-logr/logr"
)

// IClient defines the behaviour required of an openfeature client
type IClient interface {
Metadata() ClientMetadata
AddHooks(hooks ...Hook)
AddHandler(eventType EventType, callback EventCallback)
RemoveHandler(eventType EventType, callback EventCallback)
SetEvaluationContext(evalCtx EvaluationContext)
EvaluationContext() EvaluationContext
BooleanValue(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) (bool, error)
StringValue(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) (string, error)
FloatValue(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) (float64, error)
IntValue(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) (int64, error)
ObjectValue(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) (interface{}, error)
BooleanValueDetails(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) (BooleanEvaluationDetails, error)
StringValueDetails(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) (StringEvaluationDetails, error)
FloatValueDetails(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) (FloatEvaluationDetails, error)
IntValueDetails(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) (IntEvaluationDetails, error)
ObjectValueDetails(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) (InterfaceEvaluationDetails, error)

Boolean(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) bool
String(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) string
Float(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) float64
Int(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) int64
Object(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) interface{}
}

// ClientMetadata provides a client's metadata
type ClientMetadata struct {
name string
Expand All @@ -56,7 +30,7 @@ func (cm ClientMetadata) Name() string {

// Client implements the behaviour required of an openfeature client
type Client struct {
api evalAPI
api evalImpl
clientEventing ClientEvent
metadata ClientMetadata
hooks []Hook
Expand All @@ -69,8 +43,8 @@ type Client struct {
// interface guard to ensure that Client implements IClient
var _ IClient = (*Client)(nil)

// NewClient returns a new Client. Name is a unique identifier for this client
func NewClient(name string) *Client {
// NewClient returns a new IClient. Name is a unique identifier for this client
func NewClient(name string) IClient {
return &Client{
api: api,
clientEventing: eventing,
Expand Down
15 changes: 10 additions & 5 deletions openfeature/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ func TestRequirement_1_2_1(t *testing.T) {
client.AddHooks(mockHook)
client.AddHooks(mockHook, mockHook)

if len(client.hooks) != 3 {
c := client.(*Client)
if len(c.hooks) != 3 {
t.Error("func client.AddHooks didn't append the list of hooks to the client's existing collection of hooks")
}
}
Expand Down Expand Up @@ -360,7 +361,8 @@ func TestRequirement_1_4_7(t *testing.T) {
t.Errorf("error setting up provider %v", err)
}

res, err := client.evaluate(
c := client.(*Client)
res, err := c.evaluate(
context.Background(), "foo", Boolean, true, EvaluationContext{}, EvaluationOptions{},
)
if err == nil {
Expand Down Expand Up @@ -395,7 +397,8 @@ func TestRequirement_1_4_8(t *testing.T) {
t.Errorf("error setting up provider %v", err)
}

res, err := client.evaluate(
c := client.(*Client)
res, err := c.evaluate(
context.Background(), "foo", Boolean, true, EvaluationContext{}, EvaluationOptions{},
)
if err == nil {
Expand Down Expand Up @@ -659,7 +662,8 @@ func TestRequirement_1_4_12(t *testing.T) {
})

client := NewClient("test")
evalDetails, err := client.evaluate(
c := client.(*Client)
evalDetails, err := c.evaluate(
context.Background(), "foo", Boolean, true, EvaluationContext{}, EvaluationOptions{},
)
if err == nil {
Expand Down Expand Up @@ -895,7 +899,8 @@ func TestErrorCodeFromProviderReturnedInEvaluationDetails(t *testing.T) {
})

client := NewClient("test")
evalDetails, err := client.evaluate(
c := client.(*Client)
evalDetails, err := c.evaluate(
context.Background(), "foo", Boolean, true, EvaluationContext{}, EvaluationOptions{},
)
if err == nil {
Expand Down
40 changes: 35 additions & 5 deletions openfeature/evaluation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
"golang.org/x/exp/maps"
)

type evalAPI interface {
API
// evalImpl is an internal reference interface extending IOFApi
type evalImpl interface {
IOFApi
GetProvider() FeatureProvider
GetNamedProviders() map[string]FeatureProvider
GetHooks() []Hook
Expand Down Expand Up @@ -68,12 +69,12 @@ func (api *EvaluationAPI) SetProvider(provider FeatureProvider, async bool) erro
return nil
}

// GetProvider returns the default FeatureProvider
func (api *EvaluationAPI) GetProvider() FeatureProvider {
// GetProviderMetadata returns the default FeatureProvider's metadata
func (api *EvaluationAPI) GetProviderMetadata() Metadata {
api.mu.RLock()
defer api.mu.RUnlock()

return api.defaultProvider
return api.defaultProvider.Metadata()
}

// SetNamedProvider sets a provider with client name. Returns an error if FeatureProvider is nil
Expand Down Expand Up @@ -103,6 +104,19 @@ func (api *EvaluationAPI) SetNamedProvider(clientName string, provider FeaturePr
return nil
}

// GetNamedProviderMetadata returns the default FeatureProvider's metadata
func (api *EvaluationAPI) GetNamedProviderMetadata(name string) Metadata {
api.mu.RLock()
defer api.mu.RUnlock()

Check warning on line 110 in openfeature/evaluation.go

View check run for this annotation

Codecov / codecov/patch

openfeature/evaluation.go#L108-L110

Added lines #L108 - L110 were not covered by tests

provider, ok := api.namedProviders[name]
if !ok {
return ProviderMetadata()

Check warning on line 114 in openfeature/evaluation.go

View check run for this annotation

Codecov / codecov/patch

openfeature/evaluation.go#L112-L114

Added lines #L112 - L114 were not covered by tests
}

return provider.Metadata()

Check warning on line 117 in openfeature/evaluation.go

View check run for this annotation

Codecov / codecov/patch

openfeature/evaluation.go#L117

Added line #L117 was not covered by tests
}

// GetNamedProviders returns named providers map.
func (api *EvaluationAPI) GetNamedProviders() map[string]FeatureProvider {
api.mu.RLock()
Expand All @@ -111,6 +125,14 @@ func (api *EvaluationAPI) GetNamedProviders() map[string]FeatureProvider {
return api.namedProviders
}

func (api *EvaluationAPI) GetClient() IClient {
return NewClient("")

Check warning on line 129 in openfeature/evaluation.go

View check run for this annotation

Codecov / codecov/patch

openfeature/evaluation.go#L128-L129

Added lines #L128 - L129 were not covered by tests
}

func (api *EvaluationAPI) GetNamedClient(clientName string) IClient {
return NewClient(clientName)

Check warning on line 133 in openfeature/evaluation.go

View check run for this annotation

Codecov / codecov/patch

openfeature/evaluation.go#L132-L133

Added lines #L132 - L133 were not covered by tests
}

func (api *EvaluationAPI) SetEvaluationContext(apiCtx EvaluationContext) {
api.mu.Lock()
defer api.mu.Unlock()
Expand Down Expand Up @@ -173,6 +195,14 @@ func (api *EvaluationAPI) ForEvaluation(clientName string) (FeatureProvider, []H
return provider, api.hks, api.apiCtx
}

// GetProvider returns the default FeatureProvider
func (api *EvaluationAPI) GetProvider() FeatureProvider {
api.mu.RLock()
defer api.mu.RUnlock()

return api.defaultProvider
}

// initNewAndShutdownOld is a helper to initialise new FeatureProvider and Shutdown the old FeatureProvider.
func (api *EvaluationAPI) initNewAndShutdownOld(newProvider FeatureProvider, oldProvider FeatureProvider, async bool) error {
if async {
Expand Down
5 changes: 3 additions & 2 deletions openfeature/event_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
"golang.org/x/exp/maps"
)

type eventingAPI interface {
Eventing
// eventingImpl is an internal reference interface extending IEventing
type eventingImpl interface {
IEventing
GetAPIRegistry() map[EventType][]EventCallback
GetClientRegistry(client string) ScopedCallback
}
Expand Down
2 changes: 1 addition & 1 deletion openfeature/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func TestRequirement_4_3_3(t *testing.T) {
flagKey: flagKey,
flagType: String,
defaultValue: defaultValue,
clientMetadata: client.metadata,
clientMetadata: client.Metadata(),
providerMetadata: mockProvider.Metadata(),
evaluationContext: evalCtx,
}
Expand Down
58 changes: 58 additions & 0 deletions openfeature/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package openfeature

import "context"

// IOFApi defines the OpenFeature API contract
type IOFApi interface {
SetProvider(provider FeatureProvider, async bool) error
GetProviderMetadata() Metadata
SetNamedProvider(clientName string, provider FeatureProvider, async bool) error
GetNamedProviderMetadata(name string) Metadata
GetClient() IClient
GetNamedClient(clientName string) IClient
SetEvaluationContext(apiCtx EvaluationContext)
AddHooks(hooks ...Hook)
Shutdown()
}

// IClient defines the behaviour required of an OpenFeature client
type IClient interface {
Metadata() ClientMetadata
AddHooks(hooks ...Hook)
AddHandler(eventType EventType, callback EventCallback)
RemoveHandler(eventType EventType, callback EventCallback)
SetEvaluationContext(evalCtx EvaluationContext)
EvaluationContext() EvaluationContext
BooleanValue(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) (bool, error)
StringValue(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) (string, error)
FloatValue(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) (float64, error)
IntValue(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) (int64, error)
ObjectValue(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) (interface{}, error)
BooleanValueDetails(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) (BooleanEvaluationDetails, error)
StringValueDetails(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) (StringEvaluationDetails, error)
FloatValueDetails(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) (FloatEvaluationDetails, error)
IntValueDetails(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) (IntEvaluationDetails, error)
ObjectValueDetails(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) (InterfaceEvaluationDetails, error)

Boolean(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) bool
String(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) string
Float(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) float64
Int(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) int64
Object(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) interface{}
}

// IEventing defines the OpenFeature eventing contract
type IEventing interface {
APIEvent
ClientEvent
}

type APIEvent interface {
AddHandler(eventType EventType, callback EventCallback)
RemoveHandler(eventType EventType, callback EventCallback)
}

type ClientEvent interface {
RegisterClientHandler(clientName string, t EventType, c EventCallback)
RemoveClientHandler(name string, t EventType, c EventCallback)
}
17 changes: 11 additions & 6 deletions openfeature/openfeature.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"github.com/open-feature/go-sdk/openfeature/internal"
)

// api is the global EvaluationAPI implementation. This is a singleton and there can only be one instance.
var api evalAPI
// api is the global evalImpl implementation. This is a singleton and there can only be one instance.
var api evalImpl

// eventing is the global Eventing implementation. This is a singleton and there can only be one instance.
var eventing eventingAPI
// eventing is the global IEventing implementation. This is a singleton and there can only be one instance.
var eventing eventingImpl

// logger is the global logger instance. This is a singleton and there can only be one instance.
// logger is the global Logger instance. This is a singleton and there can only be one instance.
var logger logr.Logger

// init initializes the OpenFeature evaluation API
Expand Down Expand Up @@ -52,6 +52,11 @@ func SetNamedProviderAndWait(clientName string, provider FeatureProvider) error
return api.SetNamedProvider(clientName, provider, false)
}

// GetClient returns an OpenFeature Client
func GetClient() IClient {
return api.GetClient()

Check warning on line 57 in openfeature/openfeature.go

View check run for this annotation

Codecov / codecov/patch

openfeature/openfeature.go#L56-L57

Added lines #L56 - L57 were not covered by tests
}

// SetEvaluationContext sets the global evaluation context.
func SetEvaluationContext(evalCtx EvaluationContext) {
api.SetEvaluationContext(evalCtx)
Expand All @@ -64,7 +69,7 @@ func SetLogger(l logr.Logger) {

// ProviderMetadata returns the default provider's metadata
func ProviderMetadata() Metadata {
return api.GetProvider().Metadata()
return api.GetProviderMetadata()
}

// AddHooks appends to the collection of any previously added hooks
Expand Down
2 changes: 1 addition & 1 deletion openfeature/openfeature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ func TestRequirement_1_1_6(t *testing.T) {
// The client creation function MUST NOT throw, or otherwise abnormally terminate.
func TestRequirement_1_1_7(t *testing.T) {
defer t.Cleanup(initSingleton)
type clientCreationFunc func(name string) *Client
type clientCreationFunc func(name string) IClient

// asserting that our NewClient method matches this signature is enough to deduce that no error is returned
var f clientCreationFunc = NewClient
Expand Down
2 changes: 1 addition & 1 deletion pkg/openfeature/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Client = openfeature.Client
//
// Deprecated: use github.com/open-feature/go-sdk/openfeature.NewClient,
// instead.
func NewClient(name string) *Client {
func NewClient(name string) IClient {
return openfeature.NewClient(name)
}

Expand Down

0 comments on commit efeff46

Please sign in to comment.