Skip to content

Commit d906d6f

Browse files
authored
fix: consider default client when loading states (#309)
fix: consider default client when loading states Signed-off-by: warber <[email protected]>
1 parent 73c0e85 commit d906d6f

File tree

5 files changed

+101
-30
lines changed

5 files changed

+101
-30
lines changed

openfeature/client.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -713,16 +713,19 @@ func (c *Client) evaluate(
713713
c.finallyHooks(ctx, hookCtx, providerInvocationClientApiHooks, options)
714714
}()
715715

716-
// short circuit if provider is in NOT READY state
717-
if c.State() == NotReadyState {
718-
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, ProviderNotReadyError, options)
719-
return evalDetails, ProviderNotReadyError
720-
}
716+
// bypass short-circuit logic for the Noop provider; it is essentially stateless and a "special case"
717+
if _, ok := provider.(NoopProvider); !ok {
718+
// short circuit if provider is in NOT READY state
719+
if c.State() == NotReadyState {
720+
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, ProviderNotReadyError, options)
721+
return evalDetails, ProviderNotReadyError
722+
}
721723

722-
// short circuit if provider is in FATAL state
723-
if c.State() == FatalState {
724-
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, ProviderFatalError, options)
725-
return evalDetails, ProviderFatalError
724+
// short circuit if provider is in FATAL state
725+
if c.State() == FatalState {
726+
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, ProviderFatalError, options)
727+
return evalDetails, ProviderFatalError
728+
}
726729
}
727730

728731
evalCtx, err = c.beforeHooks(ctx, hookCtx, apiClientInvocationProviderHooks, evalCtx, options)

openfeature/client_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package openfeature
33
import (
44
"context"
55
"errors"
6+
"math"
67
"reflect"
78
"testing"
89
"time"
@@ -1385,7 +1386,24 @@ func TestRequirement_1_7_6(t *testing.T) {
13851386
mockHook.EXPECT().Error(gomock.Any(), gomock.Any(), ProviderNotReadyError, gomock.Any())
13861387
mockHook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
13871388

1388-
client := GetApiInstance().GetNamedClient(t.Name())
1389+
notReadyEventingProvider := struct {
1390+
FeatureProvider
1391+
StateHandler
1392+
EventHandler
1393+
}{
1394+
NoopProvider{},
1395+
&stateHandlerForTests{
1396+
initF: func(e EvaluationContext) error {
1397+
<-time.After(math.MaxInt)
1398+
return nil
1399+
},
1400+
},
1401+
&ProviderEventing{},
1402+
}
1403+
1404+
_ = GetApiInstance().SetProvider(notReadyEventingProvider)
1405+
1406+
client := GetApiInstance().GetNamedClient("somOtherClient")
13891407
client.AddHooks(mockHook)
13901408

13911409
if client.State() != NotReadyState {
@@ -1401,7 +1419,6 @@ func TestRequirement_1_7_6(t *testing.T) {
14011419
if res != defaultVal {
14021420
t.Fatalf("expected resolved boolean value to default to %t, got %t", defaultVal, res)
14031421
}
1404-
14051422
}
14061423

14071424
// The client MUST default, run error hooks, and indicate an error if flag resolution is attempted while the provider
@@ -1422,7 +1439,10 @@ func TestRequirement_1_7_7(t *testing.T) {
14221439
&ProviderEventing{},
14231440
}
14241441

1425-
_ = SetNamedProviderAndWait(t.Name(), provider)
1442+
err := SetNamedProviderAndWait(t.Name(), provider)
1443+
if err == nil {
1444+
t.Errorf("provider registration was expected to fail but succeeded unexpectedly")
1445+
}
14261446

14271447
ctrl := gomock.NewController(t)
14281448
mockHook := NewMockHook(ctrl)

openfeature/event_executor.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type clientEvent interface {
2727
State(domain string) State
2828
}
2929

30-
const defaultClient = ""
30+
const defaultDomain = ""
3131

3232
// event executor is a registry to connect API and Client event handlers to Providers
3333

@@ -102,7 +102,7 @@ func (e *eventExecutor) AddHandler(t EventType, c EventCallback) {
102102
e.apiRegistry[t] = append(e.apiRegistry[t], c)
103103
}
104104

105-
e.emitOnRegistration(defaultClient, e.defaultProviderReference, t, c)
105+
e.emitOnRegistration(defaultDomain, e.defaultProviderReference, t, c)
106106
}
107107

108108
// RemoveHandler removes an API(global) level handler
@@ -189,7 +189,7 @@ func (e *eventExecutor) GetClientRegistry(client string) scopedCallback {
189189
// emitOnRegistration fulfils the spec requirement to fire events if the
190190
// event type and the state of the associated provider are compatible.
191191
func (e *eventExecutor) emitOnRegistration(domain string, providerReference providerReference, eventType EventType, callback EventCallback) {
192-
state, ok := e.states.Load(domain)
192+
state, ok := e.loadState(domain)
193193
if !ok {
194194
return
195195
}
@@ -213,12 +213,19 @@ func (e *eventExecutor) emitOnRegistration(domain string, providerReference prov
213213
}
214214
}
215215

216-
func (e *eventExecutor) State(domain string) State {
217-
s, ok := e.states.Load(domain)
216+
func (e *eventExecutor) loadState(domain string) (State, bool) {
217+
state, ok := e.states.Load(domain)
218218
if !ok {
219-
return NotReadyState
219+
if state, ok = e.states.Load(defaultDomain); !ok {
220+
return NotReadyState, false
221+
}
220222
}
221-
return s.(State)
223+
return state.(State), true
224+
}
225+
226+
func (e *eventExecutor) State(domain string) State {
227+
state, _ := e.loadState(domain)
228+
return state
222229
}
223230

224231
// registerDefaultProvider registers the default FeatureProvider and remove the old default provider if available
@@ -357,7 +364,7 @@ func (e *eventExecutor) triggerEvent(event Event, handler FeatureProvider) {
357364
}
358365

359366
// handling the default provider
360-
e.states.Store(defaultClient, stateFromEvent(event))
367+
e.states.Store(defaultDomain, stateFromEvent(event))
361368
// invoke default provider bound (no provider associated) handlers by filtering
362369
for domain, registry := range e.scopedRegistry {
363370
if _, ok := e.namedProviderReference[domain]; ok {

openfeature/event_executor_test.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
638638
defer t.Cleanup(initSingleton)
639639

640640
eventingImpl := &ProviderEventing{
641-
c: make(chan Event, 1),
641+
c: make(chan Event),
642642
}
643643

644644
readyEventingProvider := struct {
@@ -673,7 +673,7 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
673673
defer t.Cleanup(initSingleton)
674674

675675
eventingImpl := &ProviderEventing{
676-
c: make(chan Event, 1),
676+
c: make(chan Event),
677677
}
678678

679679
readyEventingProvider := struct {
@@ -685,7 +685,7 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
685685
}
686686

687687
clientAssociation := "clientA"
688-
err := SetNamedProvider(clientAssociation, readyEventingProvider)
688+
err := SetNamedProviderAndWait(clientAssociation, readyEventingProvider)
689689
if err != nil {
690690
t.Fatal(err)
691691
}
@@ -695,7 +695,7 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
695695
rsp <- e
696696
}
697697

698-
client := NewClient(clientAssociation)
698+
client := api.GetNamedClient(clientAssociation)
699699
client.AddHandler(ProviderReady, &callback)
700700

701701
select {
@@ -710,9 +710,8 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
710710
defer t.Cleanup(initSingleton)
711711

712712
eventingImpl := &ProviderEventing{
713-
c: make(chan Event, 1),
713+
c: make(chan Event),
714714
}
715-
eventingImpl.Invoke(Event{EventType: ProviderConfigChange})
716715

717716
readyEventingProvider := struct {
718717
FeatureProvider
@@ -722,7 +721,7 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
722721
eventingImpl,
723722
}
724723

725-
err := SetProvider(readyEventingProvider)
724+
err := SetProviderAndWait(readyEventingProvider)
726725
if err != nil {
727726
t.Fatal(err)
728727
}
@@ -732,7 +731,7 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
732731
rsp <- e
733732
}
734733

735-
client := NewClient("someClient")
734+
client := api.GetNamedClient("someClient")
736735
client.AddHandler(ProviderReady, &callback)
737736

738737
select {
@@ -770,7 +769,7 @@ func TestEventHandler_ProviderReadiness(t *testing.T) {
770769
rsp <- e
771770
}
772771

773-
client := NewClient("someClient")
772+
client := api.GetNamedClient("someClient")
774773
client.AddHandler(ProviderReady, &callback)
775774

776775
select {
@@ -1357,7 +1356,6 @@ func TestEventHandler_1ToNMapping(t *testing.T) {
13571356
}
13581357

13591358
// Contract tests - registration & removal
1360-
13611359
func TestEventHandler_Registration(t *testing.T) {
13621360
t.Run("API handlers", func(t *testing.T) {
13631361
executor := newEventExecutor()

openfeature/openfeature_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package openfeature
22

33
import (
4+
"context"
45
"errors"
6+
"fmt"
57
"reflect"
68
"testing"
79
"time"
@@ -718,6 +720,47 @@ func TestDefaultClientUsage(t *testing.T) {
718720
}
719721
}
720722

723+
func TestLateBindingOfDefaultProvider(t *testing.T) {
724+
// we are expecting
725+
expectedResultUnboundProvider := "default-value-from-unbound-provider"
726+
expectedResultFromLateDefaultProvider := "value-from-late-default-provider"
727+
728+
ctrl := gomock.NewController(t)
729+
defaultProvider := NewMockFeatureProvider(ctrl)
730+
defaultProvider.EXPECT().Metadata().Return(Metadata{Name: "defaultClientReplacement"}).AnyTimes()
731+
defaultProvider.EXPECT().Hooks().AnyTimes().Return([]Hook{})
732+
defaultProvider.EXPECT().StringEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(StringResolutionDetail{Value: expectedResultFromLateDefaultProvider})
733+
734+
client := NewClient("app")
735+
strResult, err := client.StringValue(context.TODO(), "flag", expectedResultUnboundProvider, EvaluationContext{})
736+
if err != nil {
737+
if err != nil {
738+
t.Errorf("flag evaluation failed %v", err)
739+
}
740+
}
741+
742+
if strResult != expectedResultUnboundProvider {
743+
t.Errorf("expected %s, but got %s", expectedResultUnboundProvider, strResult)
744+
}
745+
746+
err = SetProviderAndWait(defaultProvider)
747+
if err != nil {
748+
t.Errorf("provider registration failed %v", err)
749+
}
750+
751+
strResult, err = client.StringValue(context.TODO(), "flag", "default", EvaluationContext{})
752+
if err != nil {
753+
if err != nil {
754+
t.Errorf("flag evaluation failed %v", err)
755+
}
756+
}
757+
758+
if strResult != expectedResultFromLateDefaultProvider {
759+
t.Errorf("expected %s, but got %s", expectedResultFromLateDefaultProvider, strResult)
760+
}
761+
fmt.Println(strResult)
762+
}
763+
721764
// Nil providers are not accepted for default and named providers
722765
func TestForNilProviders(t *testing.T) {
723766
defer t.Cleanup(initSingleton)

0 commit comments

Comments
 (0)