Skip to content

Commit af1e05f

Browse files
committed
feat: make enricher configurable
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 80c4069 commit af1e05f

File tree

9 files changed

+89
-15
lines changed

9 files changed

+89
-15
lines changed

providers/flagd/internal/mock/service_mock.go

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

providers/flagd/pkg/configuration.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package flagd
33
import (
44
"errors"
55
"fmt"
6+
of "github.com/open-feature/go-sdk/openfeature"
67
"os"
78
"strconv"
89
"strings"
@@ -64,6 +65,7 @@ type ProviderConfiguration struct {
6465
CustomSyncProvider sync.ISync
6566
CustomSyncProviderUri string
6667
GrpcDialOptionsOverride []grpc.DialOption
68+
ContextEnricher ContextEnricher
6769

6870
log logr.Logger
6971
}
@@ -77,6 +79,10 @@ func newDefaultConfiguration(log logr.Logger) *ProviderConfiguration {
7779
MaxCacheSize: defaultMaxCacheSize,
7880
Resolver: defaultResolver,
7981
Tls: defaultTLS,
82+
ContextEnricher: func(contextValues map[string]any) *of.EvaluationContext {
83+
evaluationContext := of.NewTargetlessEvaluationContext(contextValues)
84+
return &evaluationContext
85+
},
8086
}
8187

8288
p.updateFromEnvVar()
@@ -397,3 +403,10 @@ func WithGrpcDialOptionsOverride(grpcDialOptionsOverride []grpc.DialOption) Prov
397403
p.GrpcDialOptionsOverride = grpcDialOptionsOverride
398404
}
399405
}
406+
407+
// WithContextEnricher allows to add a custom context enricher (BeforeHook)
408+
func WithContextEnricher(contextEnricher ContextEnricher) ProviderOption {
409+
return func(p *ProviderConfiguration) {
410+
p.ContextEnricher = contextEnricher
411+
}
412+
}

providers/flagd/pkg/iservice.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ type IService interface {
2121
ResolveObject(ctx context.Context, key string, defaultValue interface{},
2222
evalCtx map[string]interface{}) of.InterfaceResolutionDetail
2323
EventChannel() <-chan of.Event
24+
ContextValues() map[string]any
2425
}

providers/flagd/pkg/provider.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func NewProvider(opts ...ProviderOption) (*Provider, error) {
6161
provider.providerConfiguration.log,
6262
provider.providerConfiguration.EventStreamConnectionMaxAttempts)
6363
} else if provider.providerConfiguration.Resolver == inProcess {
64-
inprocess_service := process.NewInProcessService(process.Configuration{
64+
service = process.NewInProcessService(process.Configuration{
6565
Host: provider.providerConfiguration.Host,
6666
Port: provider.providerConfiguration.Port,
6767
ProviderID: provider.providerConfiguration.ProviderID,
@@ -73,19 +73,16 @@ func NewProvider(opts ...ProviderOption) (*Provider, error) {
7373
CustomSyncProviderUri: provider.providerConfiguration.CustomSyncProviderUri,
7474
GrpcDialOptionsOverride: provider.providerConfiguration.GrpcDialOptionsOverride,
7575
})
76-
provider.hooks = append(provider.hooks, NewSyncContextHook(
77-
func() *of.EvaluationContext {
78-
evaluationContext := of.NewTargetlessEvaluationContext(inprocess_service.ContextValues)
79-
return &evaluationContext
80-
}))
81-
service = inprocess_service
8276

8377
} else {
8478
service = process.NewInProcessService(process.Configuration{
8579
OfflineFlagSource: provider.providerConfiguration.OfflineFlagSourcePath,
8680
})
8781
}
8882

83+
provider.hooks = append(provider.hooks, NewSyncContextHook(func() *of.EvaluationContext {
84+
return provider.providerConfiguration.ContextEnricher(service.ContextValues())
85+
}))
8986
provider.service = service
9087

9188
return provider, nil

providers/flagd/pkg/provider_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ func TestNewProvider(t *testing.T) {
2222
grpc.WithAuthority("test-authority"),
2323
}
2424

25+
enrichment := make(map[string]any)
26+
enrichment["test"] = "test"
27+
28+
expectedEnrichment := make(map[string]any)
29+
expectedEnrichment["test"] = "test"
30+
expectedEnrichment["test2"] = "test2"
2531
tests := []struct {
2632
name string
2733
expectedResolver ResolverType
@@ -41,6 +47,7 @@ func TestNewProvider(t *testing.T) {
4147
expectCustomSyncProviderUri string
4248
expectOfflineFlagSourcePath string
4349
expectGrpcDialOptionsOverride []grpc.DialOption
50+
expectContextEnrichment map[string]any
4451
options []ProviderOption
4552
}{
4653
{
@@ -269,6 +276,41 @@ func TestNewProvider(t *testing.T) {
269276
WithOfflineFilePath("offlineFilePath"),
270277
},
271278
},
279+
{
280+
name: "with ContextEnricher inprocess resolver",
281+
expectedResolver: inProcess,
282+
expectHost: defaultHost,
283+
expectPort: defaultInProcessPort,
284+
expectCacheType: defaultCache,
285+
expectCacheSize: defaultMaxCacheSize,
286+
expectMaxRetries: defaultMaxEventStreamRetries,
287+
expectContextEnrichment: enrichment,
288+
options: []ProviderOption{
289+
WithInProcessResolver(),
290+
WithContextEnricher(func(m map[string]any) *of.EvaluationContext {
291+
context := of.NewTargetlessEvaluationContext(m)
292+
return &context
293+
}),
294+
},
295+
},
296+
{
297+
name: "with ContextEnricher inprocess resolver adding values",
298+
expectedResolver: inProcess,
299+
expectHost: defaultHost,
300+
expectPort: defaultInProcessPort,
301+
expectCacheType: defaultCache,
302+
expectCacheSize: defaultMaxCacheSize,
303+
expectMaxRetries: defaultMaxEventStreamRetries,
304+
expectContextEnrichment: expectedEnrichment,
305+
options: []ProviderOption{
306+
WithInProcessResolver(),
307+
WithContextEnricher(func(m map[string]any) *of.EvaluationContext {
308+
m["test2"] = "test2"
309+
context := of.NewTargetlessEvaluationContext(m)
310+
return &context
311+
}),
312+
},
313+
},
272314
}
273315

274316
for _, test := range tests {
@@ -369,6 +411,15 @@ func TestNewProvider(t *testing.T) {
369411
}
370412
}
371413

414+
if test.expectContextEnrichment != nil {
415+
enriched := config.ContextEnricher(enrichment).Attributes()
416+
for k, v := range test.expectContextEnrichment {
417+
if enriched[k] != v {
418+
t.Errorf("incorrect context_enrichment attribute, expected %v, got %v", v, enriched[k])
419+
}
420+
}
421+
}
422+
372423
// this line will fail linting if this provider is no longer compatible with the openfeature sdk
373424
var _ of.FeatureProvider = flagdProvider
374425
})

providers/flagd/pkg/service/in_process/service.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type InProcess struct {
3232
sync sync.ISync
3333
syncEnd context.CancelFunc
3434
wg parallel.WaitGroup
35-
ContextValues map[string]any
35+
contextValues map[string]any
3636
}
3737

3838
type Configuration struct {
@@ -113,7 +113,7 @@ func (i *InProcess) Init() error {
113113
// re-syncs are ignored as we only support single flag sync source
114114
changes, _, err := i.evaluator.SetState(data)
115115
if data.SyncContext != nil {
116-
i.ContextValues = data.SyncContext.AsMap()
116+
i.contextValues = data.SyncContext.AsMap()
117117
}
118118

119119
if err != nil {
@@ -297,6 +297,10 @@ func (i *InProcess) appendMetadata(evalMetadata model.Metadata) {
297297
}
298298
}
299299

300+
func (i *InProcess) ContextValues() map[string]any {
301+
return i.contextValues
302+
}
303+
300304
// makeSyncProvider is a helper to create sync.ISync and return the underlying uri used by it to the caller
301305
func makeSyncProvider(cfg Configuration, log *logger.Logger) (sync.ISync, string) {
302306
if cfg.CustomSyncProvider != nil {

providers/flagd/pkg/service/in_process/service_grpc_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func TestInProcessProviderEvaluation(t *testing.T) {
123123
t.Fatalf("Wrong scope value. Expected %s, but got %s", scope, detail.FlagMetadata["scope"])
124124
}
125125

126-
if len(inProcessService.ContextValues) == 0 {
126+
if len(inProcessService.contextValues) == 0 {
127127
t.Fatal("Expected context_values to be present, but got none")
128128
}
129129
}

providers/flagd/pkg/service/rpc/service.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,10 @@ func handleError(err error) openfeature.ResolutionError {
435435
return openfeature.NewGeneralResolutionError(err.Error())
436436
}
437437

438+
func (s *Service) ContextValues() map[string]any {
439+
return nil
440+
}
441+
438442
func (s *Service) EventChannel() <-chan of.Event {
439443
return s.events
440444
}

providers/flagd/pkg/sync_context_hook.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ import (
55
"github.com/open-feature/go-sdk/openfeature"
66
)
77

8-
type Supplier func() *openfeature.EvaluationContext
8+
type ContextEnricher func(map[string]any) *openfeature.EvaluationContext
99

1010
type SyncContextHook struct {
1111
openfeature.UnimplementedHook
12-
contextSupplier Supplier
12+
contextEnricher func() *openfeature.EvaluationContext
1313
}
1414

15-
func NewSyncContextHook(contextSupplier Supplier) SyncContextHook {
16-
return SyncContextHook{contextSupplier: contextSupplier}
15+
func NewSyncContextHook(contextEnricher func() *openfeature.EvaluationContext) SyncContextHook {
16+
return SyncContextHook{contextEnricher: contextEnricher}
1717
}
1818

1919
func (hook SyncContextHook) Before(ctx context.Context, hookContext openfeature.HookContext, hookHints openfeature.HookHints) (*openfeature.EvaluationContext, error) {
20-
return hook.contextSupplier(), nil
20+
return hook.contextEnricher(), nil
2121
}

0 commit comments

Comments
 (0)