Skip to content

Commit

Permalink
feat: make provider interface "stateless" as per spec 0.8.0 (#299)
Browse files Browse the repository at this point in the history
* make provider stateless (wip)

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>

* chore: changed naming from "client name" to domain

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>

* test: simplified test assertions

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>

* feat: added code to handle short circuit

When triggering an evaluation while the provider is in "not ready" or "fatal state" the sdk shall
return an appropriate error.

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>

* chore: moved sentinel errors into appropriate file

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>

* feat: call error hooks on short circuits

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>

* test: fix tests for asserting wrong handler callbacks

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>

* Delete openfeature/error.go

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>

---------

Signed-off-by: Bernd Warmuth <bernd.warmuth@dynatrace.com>
Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
  • Loading branch information
warber and toddbaert authored Nov 20, 2024
1 parent efab565 commit 510b2a6
Show file tree
Hide file tree
Showing 16 changed files with 678 additions and 279 deletions.
9 changes: 4 additions & 5 deletions e2e/evaluation_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@ package e2e_test

import (
"context"
"strings"
"testing"

"github.com/open-feature/go-sdk/openfeature"
"github.com/open-feature/go-sdk/openfeature/memprovider"
"strings"
"testing"
)

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

memoryProvider := memprovider.NewInMemoryProvider(map[string]memprovider.InMemoryFlag{})
err := openfeature.SetProvider(memoryProvider)
err := openfeature.SetNamedProviderAndWait(f.Name(), memoryProvider)
if err != nil {
f.Errorf("error setting up provider %v", err)
}

return openfeature.NewClient("fuzzing")
return openfeature.GetApiInstance().GetNamedClient(f.Name()).(*openfeature.Client)
}

func FuzzBooleanEvaluation(f *testing.F) {
Expand Down
32 changes: 15 additions & 17 deletions e2e/evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
"github.com/open-feature/go-sdk/openfeature/memprovider"
)

var client = openfeature.NewClient("evaluation tests")

// ctxStorageKey is the key used to pass test data across context.Context
type ctxStorageKey struct{}

Expand Down Expand Up @@ -95,7 +93,7 @@ func initializeEvaluationScenario(ctx *godog.ScenarioContext) {
func aProviderIsRegisteredWithCacheDisabled(ctx context.Context) error {
memoryProvider := memprovider.NewInMemoryProvider(memoryFlags)

err := openfeature.SetProvider(memoryProvider)
err := openfeature.SetNamedProvider("evaluation-test", memoryProvider)
if err != nil {
return err
}
Expand All @@ -111,7 +109,7 @@ func aBooleanFlagWithKeyIsEvaluatedWithDefaultValue(
return ctx, errors.New("default value must be of type bool")
}

got, err := client.BooleanValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").BooleanValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -140,7 +138,7 @@ func theResolvedBooleanValueShouldBe(ctx context.Context, expectedValueStr strin
func aStringFlagWithKeyIsEvaluatedWithDefaultValue(
ctx context.Context, flagKey, defaultValue string,
) (context.Context, error) {
got, err := client.StringValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").StringValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand All @@ -164,7 +162,7 @@ func theResolvedStringValueShouldBe(ctx context.Context, expectedValue string) e
func anIntegerFlagWithKeyIsEvaluatedWithDefaultValue(
ctx context.Context, flagKey string, defaultValue int64,
) (context.Context, error) {
got, err := client.IntValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").IntValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand All @@ -188,7 +186,7 @@ func theResolvedIntegerValueShouldBe(ctx context.Context, expectedValue int64) e
func aFloatFlagWithKeyIsEvaluatedWithDefaultValue(
ctx context.Context, flagKey string, defaultValue float64,
) (context.Context, error) {
got, err := client.FloatValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").FloatValue(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand All @@ -210,7 +208,7 @@ func theResolvedFloatValueShouldBe(ctx context.Context, expectedValue float64) e
}

func anObjectFlagWithKeyIsEvaluatedWithANullDefaultValue(ctx context.Context, flagKey string) (context.Context, error) {
got, err := client.ObjectValue(ctx, flagKey, nil, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").ObjectValue(ctx, flagKey, nil, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -272,7 +270,7 @@ func aBooleanFlagWithKeyIsEvaluatedWithDetailsAndDefaultValue(
return ctx, errors.New("default value must be of type bool")
}

got, err := client.BooleanValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").BooleanValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -316,7 +314,7 @@ func theResolvedBooleanDetailsValueShouldBeTheVariantShouldBeAndTheReasonShouldB
func aStringFlagWithKeyIsEvaluatedWithDetailsAndDefaultValue(
ctx context.Context, flagKey, defaultValue string,
) (context.Context, error) {
got, err := client.StringValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").StringValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -355,7 +353,7 @@ func theResolvedStringDetailsValueShouldBeTheVariantShouldBeAndTheReasonShouldBe
func anIntegerFlagWithKeyIsEvaluatedWithDetailsAndDefaultValue(
ctx context.Context, flagKey string, defaultValue int64,
) (context.Context, error) {
got, err := client.IntValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").IntValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -394,7 +392,7 @@ func theResolvedIntegerDetailsValueShouldBeTheVariantShouldBeAndTheReasonShouldB
func aFloatFlagWithKeyIsEvaluatedWithDetailsAndDefaultValue(
ctx context.Context, flagKey string, defaultValue float64,
) (context.Context, error) {
got, err := client.FloatValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").FloatValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -433,7 +431,7 @@ func theResolvedFloatDetailsValueShouldBeTheVariantShouldBeAndTheReasonShouldBe(
func anObjectFlagWithKeyIsEvaluatedWithDetailsAndANullDefaultValue(
ctx context.Context, flagKey string,
) (context.Context, error) {
got, err := client.ObjectValueDetails(ctx, flagKey, nil, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").ObjectValueDetails(ctx, flagKey, nil, openfeature.EvaluationContext{})
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -540,7 +538,7 @@ func aFlagWithKeyIsEvaluatedWithDefaultValue(
return ctx, errors.New("no contextAwareEvaluationData found")
}

got, err := client.StringValue(ctx, flagKey, defaultValue, ctxAwareEvalData.evaluationContext)
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").StringValue(ctx, flagKey, defaultValue, ctxAwareEvalData.evaluationContext)
if err != nil {
return ctx, fmt.Errorf("openfeature client: %w", err)
}
Expand Down Expand Up @@ -570,7 +568,7 @@ func theResolvedFlagValueIsWhenTheContextIsEmpty(ctx context.Context, expectedRe
return errors.New("no contextAwareEvaluationData found")
}

got, err := client.StringValue(
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").StringValue(
ctx, ctxAwareEvalData.flagKey, ctxAwareEvalData.defaultValue, openfeature.EvaluationContext{},
)
if err != nil {
Expand All @@ -587,7 +585,7 @@ func theResolvedFlagValueIsWhenTheContextIsEmpty(ctx context.Context, expectedRe
func aNonexistentStringFlagWithKeyIsEvaluatedWithDetailsAndADefaultValue(
ctx context.Context, flagKey, defaultValue string,
) (context.Context, error) {
got, err := client.StringValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").StringValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})

return context.WithValue(ctx, ctxStorageKey{}, stringFlagNotFoundData{
evalDetails: got,
Expand Down Expand Up @@ -644,7 +642,7 @@ func theReasonShouldIndicateAnErrorAndTheErrorCodeShouldIndicateAMissingFlagWith
func aStringFlagWithKeyIsEvaluatedAsAnIntegerWithDetailsAndADefaultValue(
ctx context.Context, flagKey string, defaultValue int64,
) (context.Context, error) {
got, err := client.IntValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})
got, err := openfeature.GetApiInstance().GetNamedClient("evaluation-test").IntValueDetails(ctx, flagKey, defaultValue, openfeature.EvaluationContext{})

return context.WithValue(ctx, ctxStorageKey{}, typeErrorData{
evalDetails: got,
Expand Down
19 changes: 19 additions & 0 deletions openfeature/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Client struct {
metadata ClientMetadata
hooks []Hook
evaluationContext EvaluationContext
domain string

mx sync.RWMutex
}
Expand All @@ -56,6 +57,7 @@ func NewClient(name string) *Client {

func newClient(name string, apiRef evaluationImpl, eventRef clientEvent) *Client {
return &Client{
domain: name,
api: apiRef,
clientEventing: eventRef,
metadata: ClientMetadata{name: name},
Expand All @@ -64,6 +66,11 @@ func newClient(name string, apiRef evaluationImpl, eventRef clientEvent) *Client
}
}

// State returns the state of the associated provider
func (c *Client) State() State {
return c.clientEventing.State(c.domain)
}

// Deprecated
// WithLogger sets the logger of the client
func (c *Client) WithLogger(l logr.Logger) *Client {
Expand Down Expand Up @@ -706,6 +713,18 @@ func (c *Client) evaluate(
c.finallyHooks(ctx, hookCtx, providerInvocationClientApiHooks, options)
}()

// short circuit if provider is in NOT READY state
if c.State() == NotReadyState {
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, ProviderNotReadyError, options)
return evalDetails, ProviderNotReadyError
}

// short circuit if provider is in FATAL state
if c.State() == FatalState {
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, ProviderFatalError, options)
return evalDetails, ProviderFatalError
}

evalCtx, err = c.beforeHooks(ctx, hookCtx, apiClientInvocationProviderHooks, evalCtx, options)
hookCtx.evaluationContext = evalCtx
if err != nil {
Expand Down
30 changes: 30 additions & 0 deletions openfeature/client_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ func ExampleNewClient() {
}

func ExampleClient_BooleanValue() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
client := openfeature.NewClient("example-client")
value, err := client.BooleanValue(
context.Background(), "test-flag", true, openfeature.EvaluationContext{},
Expand All @@ -29,6 +32,9 @@ func ExampleClient_BooleanValue() {
}

func ExampleClient_StringValue() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
client := openfeature.NewClient("example-client")
value, err := client.StringValue(
context.Background(), "test-flag", "openfeature", openfeature.EvaluationContext{},
Expand All @@ -42,6 +48,9 @@ func ExampleClient_StringValue() {
}

func ExampleClient_FloatValue() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
client := openfeature.NewClient("example-client")
value, err := client.FloatValue(
context.Background(), "test-flag", 0.55, openfeature.EvaluationContext{},
Expand All @@ -55,6 +64,9 @@ func ExampleClient_FloatValue() {
}

func ExampleClient_IntValue() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
client := openfeature.NewClient("example-client")
value, err := client.IntValue(
context.Background(), "test-flag", 3, openfeature.EvaluationContext{},
Expand All @@ -68,6 +80,9 @@ func ExampleClient_IntValue() {
}

func ExampleClient_ObjectValue() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
client := openfeature.NewClient("example-client")
value, err := client.ObjectValue(
context.Background(), "test-flag", map[string]string{"foo": "bar"}, openfeature.EvaluationContext{},
Expand All @@ -82,6 +97,9 @@ func ExampleClient_ObjectValue() {
}

func ExampleClient_Boolean() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
ctx := context.Background()
client := openfeature.NewClient("example-client")

Expand All @@ -95,6 +113,9 @@ func ExampleClient_Boolean() {
}

func ExampleClient_String() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
ctx := context.Background()
client := openfeature.NewClient("example-client")

Expand All @@ -104,6 +125,9 @@ func ExampleClient_String() {
}

func ExampleClient_Float() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
ctx := context.Background()
client := openfeature.NewClient("example-client")

Expand All @@ -113,6 +137,9 @@ func ExampleClient_Float() {
}

func ExampleClient_Int() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
ctx := context.Background()
client := openfeature.NewClient("example-client")

Expand All @@ -122,6 +149,9 @@ func ExampleClient_Int() {
}

func ExampleClient_Object() {
if err := openfeature.SetNamedProviderAndWait("example-client", openfeature.NoopProvider{}); err != nil {
log.Fatalf("error setting up provider %v", err)
}
ctx := context.Background()
client := openfeature.NewClient("example-client")

Expand Down
Loading

0 comments on commit 510b2a6

Please sign in to comment.