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 May 1, 2024
1 parent 45be4ce commit 1af6cf0
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 111 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,33 @@ func (h MyHook) Error(context context.Context, hookContext openfeature.HookConte

> Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=hook&projects=&template=document-hook.yaml&title=%5BHook%5D%3A+) so we can add it to the docs!
## Testing

To test interactions with OpenFeature API and Client, you can rely on `openfeature.IOFApi` & `openfeature.IClient` interfaces.

While you may use global methods to interact with the API, it is recommended to obtain the singleton API instance so that you can use appropriate mocks for your testing needs,

```go
// global helper
openfeature.SetProvider(myProvider)

// singleton instance - preferred
apiInstance := openfeature.GetApiInstance()
apiInstance.SetProvider(myProvider)
```

Similarly, while you have options (due to historical reasons) to create a client with `openfeature.NewClient()` helper, it is recommended to use API to generate the client which returns an `IClient` instance.

```go
// global helper
openfeature.NewClient("myClient")

// using API instance - preferred
apiInstance := openfeature.GetApiInstance()
apiInstance.GetClient()
apiInstance.GetNamedClient("myClient")
```

<!-- x-hide-in-docs-start -->
## ⭐️ Support the project

Expand Down
24 changes: 0 additions & 24 deletions openfeature/api.go

This file was deleted.

43 changes: 10 additions & 33 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,8 +30,8 @@ func (cm ClientMetadata) Name() string {

// Client implements the behaviour required of an openfeature client
type Client struct {
api evalAPI
clientEventing ClientEvent
api ofApiImpl
clientEventing clientEvent
metadata ClientMetadata
hooks []Hook
evaluationContext EvaluationContext
Expand All @@ -69,13 +43,16 @@ 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
// NewClient returns a new IClient. Name is a unique identifier for this client
func NewClient(name string) *Client {
return &Client{
api: api,
clientEventing: eventing,
logger: logger,
return newClient(name, api, eventing, logger)
}

func newClient(name string, apiRef ofApiImpl, eventRef clientEvent, log logr.Logger) *Client {
return &Client{
api: apiRef,
clientEventing: eventRef,
logger: log,
metadata: ClientMetadata{name: name},
hooks: []Hook{},
evaluationContext: EvaluationContext{},
Expand Down
13 changes: 11 additions & 2 deletions openfeature/event_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ 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

clientEvent
}

// clientEvent is an internal reference for OpenFeature Client events
type clientEvent interface {
RegisterClientHandler(clientName string, t EventType, c EventCallback)
RemoveClientHandler(name string, t EventType, c EventCallback)
}

// event executor is a registry to connect API and Client event handlers to Providers
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
50 changes: 50 additions & 0 deletions openfeature/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package openfeature

import "context"

// IOFApi defines the OpenFeature API contract
type IOFApi interface {
SetProvider(provider FeatureProvider) error
SetProviderAndWait(provider FeatureProvider) 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()
IEventing
}

// IClient defines the behaviour required of an OpenFeature client
type IClient interface {
Metadata() ClientMetadata
AddHooks(hooks ...Hook)
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
}

// IEventing defines the OpenFeature eventing contract
type IEventing interface {
AddHandler(eventType EventType, callback EventCallback)
RemoveHandler(eventType EventType, callback EventCallback)
}
38 changes: 22 additions & 16 deletions openfeature/openfeature.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@ 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

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

// logger is the global logger instance. This is a singleton and there can only be one instance.
// api is the global ofApiImpl implementation. This is a singleton and there can only be one instance.
var api ofApiImpl
var eventing eventingImpl
var logger logr.Logger

// init initializes the OpenFeature evaluation API
Expand All @@ -28,16 +24,26 @@ func initSingleton() {
api = NewEvaluationAPI(exec, logger)
}

// GetApiInstance returns the current singleton IOFApi instance
func GetApiInstance() IOFApi {
return api
}

// SetProvider sets the default provider. Provider initialization is asynchronous and status can be checked from
// provider status
func SetProvider(provider FeatureProvider) error {
return api.SetProvider(provider, true)
return api.SetProvider(provider)
}

// SetProviderAndWait sets the default provider and waits for its initialization.
// Returns an error if initialization cause error
func SetProviderAndWait(provider FeatureProvider) error {
return api.SetProvider(provider, false)
return api.SetProviderAndWait(provider)
}

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

// SetNamedProvider sets a provider mapped to the given Client name. Provider initialization is asynchronous and
Expand All @@ -52,6 +58,11 @@ func SetNamedProviderAndWait(clientName string, provider FeatureProvider) error
return api.SetNamedProvider(clientName, provider, false)
}

// NamedProviderMetadata returns the named provider's Metadata
func NamedProviderMetadata(name string) Metadata {
return api.GetNamedProviderMetadata(name)
}

// SetEvaluationContext sets the global evaluation context.
func SetEvaluationContext(evalCtx EvaluationContext) {
api.SetEvaluationContext(evalCtx)
Expand All @@ -62,24 +73,19 @@ func SetLogger(l logr.Logger) {
api.SetLogger(l)
}

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

// AddHooks appends to the collection of any previously added hooks
func AddHooks(hooks ...Hook) {
api.AddHooks(hooks...)
}

// AddHandler allows to add API level event handler
func AddHandler(eventType EventType, callback EventCallback) {
eventing.AddHandler(eventType, callback)
api.AddHandler(eventType, callback)
}

// RemoveHandler allows to remove API level event handler
func RemoveHandler(eventType EventType, callback EventCallback) {
eventing.RemoveHandler(eventType, callback)
api.RemoveHandler(eventType, callback)
}

// Shutdown active providers
Expand Down

0 comments on commit 1af6cf0

Please sign in to comment.