-
Notifications
You must be signed in to change notification settings - Fork 95
feat: add configuration property to define which key is used if a string without equals is used as a selector #1798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✅ Deploy Preview for polite-licorice-3db33c canceled.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @aepfli, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a significant enhancement to how flag selectors are parsed, particularly for simplified selector strings that do not explicitly define a key-value pair. By adding a configurable fallback key, the system gains increased flexibility, allowing users to customize the interpretation of such selectors beyond the default 'source' key. This change improves the adaptability of the flag evaluation logic without breaking backward compatibility.
Highlights
- Configurable Selector Fallback: Introduced a new configuration property (
selector-fallback-key) that allows defining which key is used when a selector string without an equals sign is provided (e.g., "myValue" instead of "key=myValue"). - Enhanced Selector Parsing: The internal
SelectorandexpressionToMapfunctions now support this configurable fallback, defaulting to the existingsourceIndexif no fallback key is specified. - Store Initialization Refactor: The
store.NewStorefunction now accepts aStoreConfigstruct, encapsulating source and the new selector fallback key, leading to cleaner initialization. - Command-Line Option: A new
--selector-fallback-keyflag has been added to theflagdcommand to allow users to specify this fallback behavior via command-line arguments.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
…ing without equals is used as a selector Signed-off-by: Simon Schrottner <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
bef0dd9 to
62c0b36
Compare
62c0b36 to
484cf74
Compare
Signed-off-by: Simon Schrottner <[email protected]> diff --git c/core/pkg/service/iservice.go i/core/pkg/service/iservice.go index 97d8ec2..3ce9a84 100644 --- c/core/pkg/service/iservice.go +++ i/core/pkg/service/iservice.go @@ -36,6 +36,7 @@ type Configuration struct { ContextValues map[string]any HeaderToContextKeyMappings map[string]string StreamDeadline time.Duration + SelectorFallbackKey string } /* diff --git c/flagd/pkg/runtime/from_config.go i/flagd/pkg/runtime/from_config.go index 6b56ec8..8b3b544 100644 --- c/flagd/pkg/runtime/from_config.go +++ i/flagd/pkg/runtime/from_config.go @@ -103,7 +103,8 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime, logger.WithFields(zap.String("component", "service")), jsonEvaluator, store, - recorder) + recorder, + config.SelectorFallbackKey) // ofrep service ofrepService, err := ofrep.NewOfrepService(jsonEvaluator, config.CORS, ofrep.SvcConfiguration{ @@ -111,6 +112,7 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime, Port: config.OfrepServicePort, ServiceName: svcName, MetricsRecorder: recorder, + SelectorFallbackKey: config.SelectorFallbackKey, }, config.ContextValues, config.HeaderToContextKeyMappings, @@ -131,6 +133,7 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime, SocketPath: config.SyncServiceSocketPath, StreamDeadline: config.StreamDeadline, DisableSyncMetadata: config.DisableSyncMetadata, + SelectorFallbackKey: config.SelectorFallbackKey, }) if err != nil { return nil, fmt.Errorf("error creating sync service: %w", err) @@ -167,6 +170,7 @@ func FromConfig(logger *logger.Logger, version string, config Config) (*Runtime, ContextValues: config.ContextValues, HeaderToContextKeyMappings: config.HeaderToContextKeyMappings, StreamDeadline: config.StreamDeadline, + SelectorFallbackKey: config.SelectorFallbackKey, }, Syncs: iSyncs, }, nil diff --git c/flagd/pkg/service/flag-evaluation/connect_service.go i/flagd/pkg/service/flag-evaluation/connect_service.go index 3fa1b4a..b18be7f 100644 --- c/flagd/pkg/service/flag-evaluation/connect_service.go +++ i/flagd/pkg/service/flag-evaluation/connect_service.go @@ -68,12 +68,12 @@ type ConnectService struct { metricsServerMtx sync.RWMutex readinessEnabled bool + + selectorFallbackKey string } // NewConnectService creates a ConnectService with provided parameters -func NewConnectService( - logger *logger.Logger, evaluator evaluator.IEvaluator, store store.IStore, mRecorder telemetry.IMetricsRecorder, -) *ConnectService { +func NewConnectService(logger *logger.Logger, evaluator evaluator.IEvaluator, store store.IStore, mRecorder telemetry.IMetricsRecorder, selectorFallbackKey string) *ConnectService { cs := &ConnectService{ logger: logger, eval: evaluator, @@ -84,6 +84,7 @@ func NewConnectService( store: store, logger: logger, }, + selectorFallbackKey: selectorFallbackKey, } if mRecorder != nil { cs.metrics = mRecorder @@ -158,6 +159,7 @@ func (s *ConnectService) setupServer(svcConf service.Configuration) (net.Listene s.eventingConfiguration, s.metrics, svcConf.ContextValues, + s.selectorFallbackKey, ) marshalOpts := WithJSON( @@ -177,6 +179,7 @@ func (s *ConnectService) setupServer(svcConf service.Configuration) (net.Listene svcConf.ContextValues, svcConf.HeaderToContextKeyMappings, svcConf.StreamDeadline, + s.selectorFallbackKey, ) _, newHandler := evaluationV1.NewServiceHandler(newFes, append(svcConf.Options, marshalOpts)...) diff --git c/flagd/pkg/service/flag-evaluation/connect_service_test.go i/flagd/pkg/service/flag-evaluation/connect_service_test.go index 8e89d62..5473442 100644 --- c/flagd/pkg/service/flag-evaluation/connect_service_test.go +++ i/flagd/pkg/service/flag-evaluation/connect_service_test.go @@ -84,7 +84,7 @@ func TestConnectService_UnixConnection(t *testing.T) { exp := metric.NewManualReader() rs := resource.NewWithAttributes("testSchema") metricRecorder := telemetry.NewOTelRecorder(exp, rs, tt.name) - svc := NewConnectService(logger.NewLogger(nil, false), eval, nil, metricRecorder) + svc := NewConnectService(logger.NewLogger(nil, false), eval, nil, metricRecorder, "") serveConf := iservice.Configuration{ ReadinessProbe: func() bool { return true @@ -139,7 +139,7 @@ func TestAddMiddleware(t *testing.T) { rs := resource.NewWithAttributes("testSchema") metricRecorder := telemetry.NewOTelRecorder(exp, rs, "my-exporter") - svc := NewConnectService(logger.NewLogger(nil, false), nil, nil, metricRecorder) + svc := NewConnectService(logger.NewLogger(nil, false), nil, nil, metricRecorder, "") serveConf := iservice.Configuration{ ReadinessProbe: func() bool { @@ -187,7 +187,7 @@ func TestConnectServiceNotify(t *testing.T) { rs := resource.NewWithAttributes("testSchema") metricRecorder := telemetry.NewOTelRecorder(exp, rs, "my-exporter") - service := NewConnectService(logger.NewLogger(nil, false), eval, s, metricRecorder) + service := NewConnectService(logger.NewLogger(nil, false), eval, s, metricRecorder, "") sChan := make(chan iservice.Notification, 1) eventing := service.eventingConfiguration @@ -278,7 +278,7 @@ func TestConnectServiceShutdown(t *testing.T) { rs := resource.NewWithAttributes("testSchema") metricRecorder := telemetry.NewOTelRecorder(exp, rs, "my-exporter") - service := NewConnectService(logger.NewLogger(nil, false), eval, s, metricRecorder) + service := NewConnectService(logger.NewLogger(nil, false), eval, s, metricRecorder, "") sChan := make(chan iservice.Notification, 1) eventing := service.eventingConfiguration diff --git c/flagd/pkg/service/flag-evaluation/flag_evaluator.go i/flagd/pkg/service/flag-evaluation/flag_evaluator.go index 58c1c3b..88a169e 100644 --- c/flagd/pkg/service/flag-evaluation/flag_evaluator.go +++ i/flagd/pkg/service/flag-evaluation/flag_evaluator.go @@ -36,6 +36,7 @@ type OldFlagEvaluationService struct { eventingConfiguration IEvents flagEvalTracer trace.Tracer contextValues map[string]any + selectorFallbackKey string } // NewOldFlagEvaluationService creates a OldFlagEvaluationService with provided parameters @@ -45,6 +46,7 @@ func NewOldFlagEvaluationService( eventingCfg IEvents, metricsRecorder telemetry.IMetricsRecorder, contextValues map[string]any, + selectorFallback string, ) *OldFlagEvaluationService { svc := &OldFlagEvaluationService{ logger: log, @@ -53,6 +55,7 @@ func NewOldFlagEvaluationService( eventingConfiguration: eventingCfg, flagEvalTracer: otel.Tracer("flagEvaluationService"), contextValues: contextValues, + selectorFallbackKey: selectorFallback, } if metricsRecorder != nil { @@ -76,7 +79,7 @@ func (s *OldFlagEvaluationService) ResolveAll( } selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) values, _, err := s.eval.ResolveAllValues(ctx, reqID, mergeContexts(req.Msg.GetContext().AsMap(), s.contextValues, req.Header(), make(map[string]string))) @@ -143,7 +146,7 @@ func (s *OldFlagEvaluationService) EventStream( requestNotificationChan := make(chan service.Notification, 1) selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) s.eventingConfiguration.Subscribe(ctx, req, &selector, requestNotificationChan) defer s.eventingConfiguration.Unsubscribe(req) @@ -186,7 +189,7 @@ func (s *OldFlagEvaluationService) ResolveBoolean( defer span.End() res := connect.NewResponse(&schemaV1.ResolveBooleanResponse{}) selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) err := resolve[bool]( @@ -218,7 +221,7 @@ func (s *OldFlagEvaluationService) ResolveString( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&schemaV1.ResolveStringResponse{}) @@ -251,7 +254,7 @@ func (s *OldFlagEvaluationService) ResolveInt( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&schemaV1.ResolveIntResponse{}) @@ -284,7 +287,7 @@ func (s *OldFlagEvaluationService) ResolveFloat( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&schemaV1.ResolveFloatResponse{}) @@ -317,7 +320,7 @@ func (s *OldFlagEvaluationService) ResolveObject( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&schemaV1.ResolveObjectResponse{}) diff --git c/flagd/pkg/service/flag-evaluation/flag_evaluator_v2.go i/flagd/pkg/service/flag-evaluation/flag_evaluator_v2.go index 0831737..eb67bf4 100644 --- c/flagd/pkg/service/flag-evaluation/flag_evaluator_v2.go +++ i/flagd/pkg/service/flag-evaluation/flag_evaluator_v2.go @@ -31,6 +31,7 @@ type FlagEvaluationService struct { contextValues map[string]any headerToContextKeyMappings map[string]string deadline time.Duration + selectorFallbackKey string } // NewFlagEvaluationService creates a FlagEvaluationService with provided parameters @@ -41,6 +42,7 @@ func NewFlagEvaluationService(log *logger.Logger, contextValues map[string]any, headerToContextKeyMappings map[string]string, streamDeadline time.Duration, + selectorFallbackKey string, ) *FlagEvaluationService { svc := &FlagEvaluationService{ logger: log, @@ -51,6 +53,7 @@ func NewFlagEvaluationService(log *logger.Logger, contextValues: contextValues, headerToContextKeyMappings: headerToContextKeyMappings, deadline: streamDeadline, + selectorFallbackKey: selectorFallbackKey, } if metricsRecorder != nil { @@ -76,7 +79,7 @@ func (s *FlagEvaluationService) ResolveAll( } selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) evaluationContext := mergeContexts(req.Msg.GetContext().AsMap(), s.contextValues, req.Header(), s.headerToContextKeyMappings) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) @@ -165,7 +168,7 @@ func (s *FlagEvaluationService) EventStream( s.logger.Debug("starting event stream for request") requestNotificationChan := make(chan service.Notification, 1) selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) s.eventingConfiguration.Subscribe(ctx, req, &selector, requestNotificationChan) defer s.eventingConfiguration.Unsubscribe(req) @@ -211,7 +214,7 @@ func (s *FlagEvaluationService) ResolveBoolean( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&evalV1.ResolveBooleanResponse{}) @@ -243,7 +246,7 @@ func (s *FlagEvaluationService) ResolveString( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&evalV1.ResolveStringResponse{}) @@ -275,7 +278,7 @@ func (s *FlagEvaluationService) ResolveInt( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&evalV1.ResolveIntResponse{}) @@ -307,7 +310,7 @@ func (s *FlagEvaluationService) ResolveFloat( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&evalV1.ResolveFloatResponse{}) @@ -339,7 +342,7 @@ func (s *FlagEvaluationService) ResolveObject( defer span.End() selectorExpression := req.Header().Get(flagdService.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx = context.WithValue(ctx, store.SelectorContextKey{}, selector) res := connect.NewResponse(&evalV1.ResolveObjectResponse{}) diff --git c/flagd/pkg/service/flag-evaluation/ofrep/handler.go i/flagd/pkg/service/flag-evaluation/ofrep/handler.go index cbc8d43..18f0b57 100644 --- c/flagd/pkg/service/flag-evaluation/ofrep/handler.go +++ i/flagd/pkg/service/flag-evaluation/ofrep/handler.go @@ -33,6 +33,7 @@ type handler struct { contextValues map[string]any headerToContextKeyMappings map[string]string tracer trace.Tracer + selectorFallbackKey string } func NewOfrepHandler( @@ -42,6 +43,7 @@ func NewOfrepHandler( headerToContextKeyMappings map[string]string, metricsRecorder telemetry.IMetricsRecorder, serviceName string, + selectorFallbackKey: selectorFallbackKey, ) http.Handler { h := handler{ Logger: logger, @@ -49,6 +51,7 @@ func NewOfrepHandler( contextValues: contextValues, headerToContextKeyMappings: headerToContextKeyMappings, tracer: otel.Tracer("flagd.ofrep.v1"), + selectorFallbackKey: selectorFallbackKey, } router := mux.NewRouter() @@ -93,7 +96,7 @@ func (h *handler) HandleFlagEvaluation(w http.ResponseWriter, r *http.Request) { } evaluationContext := flagdContext(h.Logger, requestID, request, h.contextValues, r.Header, h.headerToContextKeyMappings) selectorExpression := r.Header.Get(service.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, h.selectorFallbackKey) ctx := context.WithValue(r.Context(), store.SelectorContextKey{}, selector) evaluation := h.evaluator.ResolveAsAnyValue(ctx, requestID, flagKey, evaluationContext) @@ -117,7 +120,7 @@ func (h *handler) HandleBulkEvaluation(w http.ResponseWriter, r *http.Request) { evaluationContext := flagdContext(h.Logger, requestID, request, h.contextValues, r.Header, h.headerToContextKeyMappings) selectorExpression := r.Header.Get(service.FLAGD_SELECTOR_HEADER) - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, h.selectorFallbackKey) ctx := context.WithValue(r.Context(), store.SelectorContextKey{}, selector) evaluations, metadata, err := h.evaluator.ResolveAllValues(ctx, requestID, evaluationContext) diff --git c/flagd/pkg/service/flag-evaluation/ofrep/ofrep_service.go i/flagd/pkg/service/flag-evaluation/ofrep/ofrep_service.go index bc5689e..9048112 100644 --- c/flagd/pkg/service/flag-evaluation/ofrep/ofrep_service.go +++ i/flagd/pkg/service/flag-evaluation/ofrep/ofrep_service.go @@ -24,6 +24,7 @@ type SvcConfiguration struct { Port uint16 ServiceName string MetricsRecorder telemetry.IMetricsRecorder + SelectorFallbackKey string } type Service struct { @@ -47,6 +48,7 @@ func NewOfrepService( headerToContextKeyMappings, cfg.MetricsRecorder, cfg.ServiceName, + cfg.SelectorFallbackKey, )) server := http.Server{ diff --git c/flagd/pkg/service/flag-sync/handler.go i/flagd/pkg/service/flag-sync/handler.go index 7bf83e2..3abbda7 100644 --- c/flagd/pkg/service/flag-sync/handler.go +++ i/flagd/pkg/service/flag-sync/handler.go @@ -26,12 +26,14 @@ type syncHandler struct { contextValues map[string]any deadline time.Duration disableSyncMetadata bool + + selectorFallbackKey string } func (s syncHandler) SyncFlags(req *syncv1.SyncFlagsRequest, server syncv1grpc.FlagSyncService_SyncFlagsServer) error { watcher := make(chan store.FlagQueryResult, 1) selectorExpression := req.GetSelector() - selector := store.NewSelector(selectorExpression) + selector := store.NewSelectorWithFallback(selectorExpression, s.selectorFallbackKey) ctx := server.Context() syncContextMap := make(map[string]any) diff --git c/flagd/pkg/service/flag-sync/sync_service.go i/flagd/pkg/service/flag-sync/sync_service.go index 22f6c0c..8b5680b 100644 --- c/flagd/pkg/service/flag-sync/sync_service.go +++ i/flagd/pkg/service/flag-sync/sync_service.go @@ -35,6 +35,7 @@ type SvcConfigurations struct { SocketPath string StreamDeadline time.Duration DisableSyncMetadata bool + SelectorFallbackKey string } type Service struct { @@ -86,6 +87,7 @@ func NewSyncService(cfg SvcConfigurations) (*Service, error) { contextValues: cfg.ContextValues, deadline: cfg.StreamDeadline, disableSyncMetadata: cfg.DisableSyncMetadata, + selectorFallbackKey: cfg.SelectorFallbackKey, }) var lis net.Listener
484cf74 to
6656c15
Compare
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
2ce0312 to
7e3247d
Compare
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
|
/gemini review last review from gemini, rest is for the community |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a configurable fallback key for flag selectors, which is a great enhancement for flexibility. The implementation is solid, with good refactoring of the store initialization and extensive test coverage for the new selector parsing logic. I have a few suggestions to improve robustness and clarity.
Signed-off-by: Simon Schrottner <[email protected]>
| } | ||
|
|
||
| // Handle pure fallback case (no equals anywhere) | ||
| if strings.Index(sExp, "=") == -1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aepfli staticcheck says: S1003: should use !strings.Contains(sExp, "=") instead (staticcheck), which makes sense to me.
| type Selector struct { | ||
| indexMap map[string]string | ||
| indexMap map[string]string | ||
| usingFallback bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this member (per Selector), and we don't need a separate "With" for it either, since this is an immutable global setting (all Selectors will share it).
Instead, I would recommend taking advantage of module initialization and sync.Once to do this, by adding something like this at the top of the file (just below the imports):
var fallbackKey string // our fallback key global
var fallbackKeyOnce sync.Once // sync.Once instance
// public function
func SetFallbackKey(key string) {
fallbackKeyOnce.Do(func() {
fallbackKey = key
})
}Then we just call SetFallbackKey at some point during init, which sets the fallback key ONLY ONCE for the duration of the program; then we can get rid of this per instance and just always refer to this module global.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW for testing, you can manually reset the module private sync.Once.
| b.ResetTimer() | ||
| for i := 0; i < b.N; i++ { | ||
| _ = selector.WithIndex("key", "benchmark-key") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need specific benchmark tests for this.
| } | ||
| } | ||
|
|
||
| func TestSelector_WithFallback(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can remove all these tests (perhaps moving 1 or 2 to the other functions) if we do this.
|
This was mostly to facilitate an internal migration issue, but we've come up with an alternative that will avoid adding this to flagd. |
|
I am not sure I agree here, yes the motivation stems from an internal problem, and we might be able to fix this differently internally, but the problem still exists for other users of flagd, which also do need some kind of migration path. How should they handle this? |
This is not a problem for external users. A selector without an expression will continue to represent a source, there's no plan to change this, and no need to change it, since up to now is been the only possibility. |
|
i am not sure i am agreeing with this comment, because flagset will be in the future the default approach, and as a library/service we should provide a migration path also for other projects. But it is not he right approach to this via this pr implicitly, we should do this explicitly with also defining the migration and deprecation path. In the end this is also something like the experimental flags for the jvm, till a feature reaches GA and afterwards they get removed again. We should make this migration easy for users of flagd to also rely on the flagset as a default, without forcing their sdk users, to update their dependencies. |
Nobody who is using flagd right now is using the selector for a flagSetId because it's not possible, so there's no need for a "migration path". Anyone who wants to select on a flagSetId can use an expression to do that in the future, so I'm not seeing the point. |
This pull request introduces a significant enhancement to how flag selectors are parsed, particularly for simplified selector strings that do not explicitly define a key-value pair. By adding a configurable fallback key, the system gains increased flexibility, allowing users to customize the interpretation of such selectors beyond the default 'source' key. This change improves the adaptability of the flag evaluation logic without breaking backward compatibility.
Simply put: if we have a selector without a
=we are currently assume that this is a "source"-selector and transform it tosource=<selector>. With this changes, we can configure the key of this selector to any arbitrary key. So when selector-fallback-key isfoothis will turn it intofoo=<selector>.Highlights
selector-fallback-key) that allows defining which key is used when a selector string without an equals sign is provided (e.g., "myValue" instead of "key=myValue").SelectorandexpressionToMapfunctions now support this configurable fallback, defaulting to the existingsourceIndexif no fallback key is specified.store.NewStorefunction now accepts aStoreConfigstruct, encapsulating source and the new selector fallback key, leading to cleaner initialization.--selector-fallback-keyflag has been added to theflagdcommand to allow users to specify this fallback behavior via command-line arguments.