Skip to content

Commit fc569ec

Browse files
authored
feat: go context in hook calls (#163)
go context in hook calls Include the go context in hook methods, to maintain the request context through the hooks. Go context often includes instrumentation and request-scoped information that is useful for processing events in the hooks. Plumbing the go context through hook methods enables richer processing of hook events. Signed-off-by: Chad Kunde <[email protected]>
1 parent 96d3169 commit fc569ec

File tree

7 files changed

+210
-213
lines changed

7 files changed

+210
-213
lines changed

pkg/openfeature/client.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -598,18 +598,18 @@ func (c *Client) evaluate(
598598
}
599599

600600
defer func() {
601-
c.finallyHooks(hookCtx, providerInvocationClientApiHooks, options)
601+
c.finallyHooks(ctx, hookCtx, providerInvocationClientApiHooks, options)
602602
}()
603603

604-
evalCtx, err = c.beforeHooks(hookCtx, apiClientInvocationProviderHooks, evalCtx, options)
604+
evalCtx, err = c.beforeHooks(ctx, hookCtx, apiClientInvocationProviderHooks, evalCtx, options)
605605
hookCtx.evaluationContext = evalCtx
606606
if err != nil {
607607
c.logger().Error(
608608
err, "before hook", "flag", flag, "defaultValue", defaultValue,
609609
"evaluationContext", evalCtx, "evaluationOptions", options, "type", flagType.String(),
610610
)
611611
err = fmt.Errorf("before hook: %w", err)
612-
c.errorHooks(hookCtx, providerInvocationClientApiHooks, err, options)
612+
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options)
613613
return evalDetails, err
614614
}
615615

@@ -648,21 +648,21 @@ func (c *Client) evaluate(
648648
"errMessage", resolution.ResolutionError.message,
649649
)
650650
err = fmt.Errorf("error code: %w", err)
651-
c.errorHooks(hookCtx, providerInvocationClientApiHooks, err, options)
651+
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options)
652652
evalDetails.ResolutionDetail = resolution.ResolutionDetail()
653653
evalDetails.Reason = ErrorReason
654654
return evalDetails, err
655655
}
656656
evalDetails.Value = resolution.Value
657657
evalDetails.ResolutionDetail = resolution.ResolutionDetail()
658658

659-
if err := c.afterHooks(hookCtx, providerInvocationClientApiHooks, evalDetails, options); err != nil {
659+
if err := c.afterHooks(ctx, hookCtx, providerInvocationClientApiHooks, evalDetails, options); err != nil {
660660
c.logger().Error(
661661
err, "after hook", "flag", flag, "defaultValue", defaultValue,
662662
"evaluationContext", evalCtx, "evaluationOptions", options, "type", flagType.String(),
663663
)
664664
err = fmt.Errorf("after hook: %w", err)
665-
c.errorHooks(hookCtx, providerInvocationClientApiHooks, err, options)
665+
c.errorHooks(ctx, hookCtx, providerInvocationClientApiHooks, err, options)
666666
return evalDetails, err
667667
}
668668

@@ -682,13 +682,13 @@ func flattenContext(evalCtx EvaluationContext) FlattenedContext {
682682
}
683683

684684
func (c *Client) beforeHooks(
685-
hookCtx HookContext, hooks []Hook, evalCtx EvaluationContext, options EvaluationOptions,
685+
ctx context.Context, hookCtx HookContext, hooks []Hook, evalCtx EvaluationContext, options EvaluationOptions,
686686
) (EvaluationContext, error) {
687687
c.logger().V(debug).Info("executing before hooks")
688688
defer c.logger().V(debug).Info("executed before hooks")
689689

690690
for _, hook := range hooks {
691-
resultEvalCtx, err := hook.Before(hookCtx, options.hookHints)
691+
resultEvalCtx, err := hook.Before(ctx, hookCtx, options.hookHints)
692692
if resultEvalCtx != nil {
693693
hookCtx.evaluationContext = *resultEvalCtx
694694
}
@@ -701,35 +701,35 @@ func (c *Client) beforeHooks(
701701
}
702702

703703
func (c *Client) afterHooks(
704-
hookCtx HookContext, hooks []Hook, evalDetails InterfaceEvaluationDetails, options EvaluationOptions,
704+
ctx context.Context, hookCtx HookContext, hooks []Hook, evalDetails InterfaceEvaluationDetails, options EvaluationOptions,
705705
) error {
706706
c.logger().V(debug).Info("executing after hooks")
707707
defer c.logger().V(debug).Info("executed after hooks")
708708

709709
for _, hook := range hooks {
710-
if err := hook.After(hookCtx, evalDetails, options.hookHints); err != nil {
710+
if err := hook.After(ctx, hookCtx, evalDetails, options.hookHints); err != nil {
711711
return err
712712
}
713713
}
714714

715715
return nil
716716
}
717717

718-
func (c *Client) errorHooks(hookCtx HookContext, hooks []Hook, err error, options EvaluationOptions) {
718+
func (c *Client) errorHooks(ctx context.Context, hookCtx HookContext, hooks []Hook, err error, options EvaluationOptions) {
719719
c.logger().V(debug).Info("executing error hooks")
720720
defer c.logger().V(debug).Info("executed error hooks")
721721

722722
for _, hook := range hooks {
723-
hook.Error(hookCtx, err, options.hookHints)
723+
hook.Error(ctx, hookCtx, err, options.hookHints)
724724
}
725725
}
726726

727-
func (c *Client) finallyHooks(hookCtx HookContext, hooks []Hook, options EvaluationOptions) {
727+
func (c *Client) finallyHooks(ctx context.Context, hookCtx HookContext, hooks []Hook, options EvaluationOptions) {
728728
c.logger().V(debug).Info("executing finally hooks")
729729
defer c.logger().V(debug).Info("executed finally hooks")
730730

731731
for _, hook := range hooks {
732-
hook.Finally(hookCtx, options.hookHints)
732+
hook.Finally(ctx, hookCtx, options.hookHints)
733733
}
734734
}
735735

pkg/openfeature/client_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func TestRequirement_1_4_2__1_4_5__1_4_6(t *testing.T) {
128128
incorrectVariant = "Incorrect variant returned!"
129129
incorrectReason = "Incorrect reason returned!"
130130
)
131-
var objectValue = map[string]interface{}{"foo": 1, "bar": true, "baz": "buz"}
131+
objectValue := map[string]interface{}{"foo": 1, "bar": true, "baz": "buz"}
132132

133133
ctrl := gomock.NewController(t)
134134
mockProvider := NewMockFeatureProvider(ctrl)
@@ -168,7 +168,6 @@ func TestRequirement_1_4_2__1_4_5__1_4_6(t *testing.T) {
168168
})
169169

170170
evDetails, err := client.StringValueDetails(context.Background(), flagKey, "", EvaluationContext{})
171-
172171
if err != nil {
173172
t.Error(err)
174173
}
@@ -819,17 +818,17 @@ func TestSwitchingProvidersMidEvaluationCausesNoImpactToEvaluation(t *testing.T)
819818
mockProvider1.EXPECT().Hooks().Return([]Hook{mockProvider1Hook}).AnyTimes()
820819

821820
// set new provider during initial provider's Before hook
822-
mockProvider1Hook.EXPECT().Before(gomock.Any(), gomock.Any()).
823-
DoAndReturn(func(_ HookContext, _ HookHints) (*EvaluationContext, error) {
821+
mockProvider1Hook.EXPECT().Before(gomock.Any(), gomock.Any(), gomock.Any()).
822+
DoAndReturn(func(_ context.Context, _ HookContext, _ HookHints) (*EvaluationContext, error) {
824823
SetProvider(mockProvider2)
825824
return nil, nil
826825
})
827826
SetProvider(mockProvider1)
828827

829828
mockProvider1.EXPECT().BooleanEvaluation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
830829
// ensure that the first provider's hooks are still fired
831-
mockProvider1Hook.EXPECT().After(gomock.Any(), gomock.Any(), gomock.Any())
832-
mockProvider1Hook.EXPECT().Finally(gomock.Any(), gomock.Any())
830+
mockProvider1Hook.EXPECT().After(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
831+
mockProvider1Hook.EXPECT().Finally(gomock.Any(), gomock.Any(), gomock.Any())
833832

834833
client := NewClient("test")
835834
_, err := client.BooleanValue(context.Background(), "foo", true, EvaluationContext{})

pkg/openfeature/hooks.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package openfeature
22

3+
import "context"
4+
35
// Hook allows application developers to add arbitrary behavior to the flag evaluation lifecycle.
46
// They operate similarly to middleware in many web frameworks.
57
// https://github.com/open-feature/spec/blob/main/specification/hooks.md
68
type Hook interface {
7-
Before(hookContext HookContext, hookHints HookHints) (*EvaluationContext, error)
8-
After(hookContext HookContext, flagEvaluationDetails InterfaceEvaluationDetails, hookHints HookHints) error
9-
Error(hookContext HookContext, err error, hookHints HookHints)
10-
Finally(hookContext HookContext, hookHints HookHints)
9+
Before(ctx context.Context, hookContext HookContext, hookHints HookHints) (*EvaluationContext, error)
10+
After(ctx context.Context, hookContext HookContext, flagEvaluationDetails InterfaceEvaluationDetails, hookHints HookHints) error
11+
Error(ctx context.Context, hookContext HookContext, err error, hookHints HookHints)
12+
Finally(ctx context.Context, hookContext HookContext, hookHints HookHints)
1113
}
1214

1315
// HookHints contains a map of hints for hooks
@@ -98,14 +100,12 @@ var _ Hook = UnimplementedHook{}
98100
// }
99101
type UnimplementedHook struct{}
100102

101-
func (u UnimplementedHook) Before(hookContext HookContext, hookHints HookHints) (*EvaluationContext, error) {
103+
func (UnimplementedHook) Before(context.Context, HookContext, HookHints) (*EvaluationContext, error) {
102104
return nil, nil
103105
}
104106

105-
func (u UnimplementedHook) After(hookContext HookContext, flagEvaluationDetails InterfaceEvaluationDetails, hookHints HookHints) error {
107+
func (UnimplementedHook) After(context.Context, HookContext, InterfaceEvaluationDetails, HookHints) error {
106108
return nil
107109
}
108-
109-
func (u UnimplementedHook) Error(hookContext HookContext, err error, hookHints HookHints) {}
110-
111-
func (u UnimplementedHook) Finally(hookContext HookContext, hookHints HookHints) {}
110+
func (UnimplementedHook) Error(context.Context, HookContext, error, HookHints) {}
111+
func (UnimplementedHook) Finally(context.Context, HookContext, HookHints) {}

pkg/openfeature/hooks_mock_test.go

Lines changed: 35 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)