feat!: cleanup evaluator interface#1793
Conversation
✅ Deploy Preview for polite-licorice-3db33c canceled.
|
There was a problem hiding this comment.
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 primarily focuses on streamlining the evaluator and store interfaces by simplifying their state management methods. The changes aim to reduce complexity and clarify responsibilities, moving away from direct state retrieval and explicit notification returns at these interface levels. The modifications are propagated throughout the codebase, including tests and mock implementations, to ensure consistency.
Highlights
- Evaluator Interface Simplification: The
IEvaluatorinterface has been refactored to remove theGetState()method and simplify theSetState()method signature.SetState()now returns only anerror, removing previous returns for changed flags and resync requirements. - Store Interface Alignment: The
IStoreinterface and itsStoreimplementation have been updated to align with the evaluator changes. TheString()method has been removed, and theUpdate()method no longer returns notification or resync information. - Test Suite Updates: All relevant test files (
json_test.go,store_test.go) and mock implementations (ievaluator.go,http.go) have been updated to reflect the new interface signatures and removed functionalities, ensuring compatibility and correctness. - New Mock Interface: A new
MockCroninterface and its mock recorder have been added incore/pkg/sync/http/mock/http.goto facilitate testing of cron-related functionalities.
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. ↩
There was a problem hiding this comment.
Code Review
This pull request is a good refactoring that simplifies the IEvaluator and IStore interfaces by removing event and resync notifications from the state update methods. The changes are consistent across the codebase, and the tests have been updated accordingly, with some tests being significantly improved. I have a couple of suggestions to enhance observability and fix a bug in a test.
| je.store.Update(payload.Source, definition.Flags, definition.Metadata) | ||
|
|
||
| events, reSync = je.store.Update(payload.Source, definition.Flags, definition.Metadata) | ||
|
|
||
| // Number of events correlates to the number of flags changed through this sync, record it | ||
| span.SetAttributes(attribute.Int("feature_flag.change_count", len(events))) | ||
|
|
||
| return events, reSync, nil | ||
| return nil |
There was a problem hiding this comment.
This change removes the feature_flag.change_count attribute from the flagSync trace span, which is a regression in observability. This metric is valuable for understanding the impact of flag updates. Consider reintroducing it. This would likely require modifying IStore.Update to return the number of changes.
changeCount := je.store.Update(payload.Source, definition.Flags, definition.Metadata)
// Number of events correlates to the number of flags changed through this sync, record it
span.SetAttributes(attribute.Int("feature_flag.change_count", changeCount))
return nilThere was a problem hiding this comment.
removed on purpose, we need to see how we want to handle this information in the future
77bccf1 to
a4b9efc
Compare
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
diff --git c/core/pkg/evaluator/ievaluator.go i/core/pkg/evaluator/ievaluator.go
index 9f492c2..00b5590 100644
--- c/core/pkg/evaluator/ievaluator.go
+++ i/core/pkg/evaluator/ievaluator.go
@@ -34,8 +34,7 @@ func NewAnyValue(
IEvaluator is an extension of IResolver, allowing storage updates and retrievals
*/
type IEvaluator interface {
- GetState() (string, error)
- SetState(payload sync.DataSync) (map[string]interface{}, bool, error)
+ SetState(payload sync.DataSync) error
IResolver
}
diff --git c/core/pkg/evaluator/json.go i/core/pkg/evaluator/json.go
index 432b4a7..12b862b 100644
--- c/core/pkg/evaluator/json.go
+++ i/core/pkg/evaluator/json.go
@@ -117,15 +117,7 @@ func NewJSON(logger *logger.Logger, s store.IStore, opts ...JSONEvaluatorOption)
return &ev
}
-func (je *JSON) GetState() (string, error) {
- s, err := je.store.String()
- if err != nil {
- return "", fmt.Errorf("unable to fetch evaluator state: %w", err)
- }
- return s, nil
-}
-
-func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, error) {
+func (je *JSON) SetState(payload sync.DataSync) error {
_, span := je.jsonEvalTracer.Start(
context.Background(),
"flagSync",
@@ -138,18 +130,12 @@ func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, e
if err != nil {
span.SetStatus(codes.Error, "flagSync error")
span.RecordError(err)
- return nil, false, err
+ return err
}
- var events map[string]interface{}
- var reSync bool
+ je.store.Update(payload.Source, definition.Flags, definition.Metadata)
- events, reSync = je.store.Update(payload.Source, definition.Flags, definition.Metadata)
-
- // Number of events correlates to the number of flags changed through this sync, record it
- span.SetAttributes(attribute.Int("feature_flag.change_count", len(events)))
-
- return events, reSync, nil
+ return nil
}
// Resolver implementation for flagd flags. This resolver should be kept reusable, hence must interact with interfaces.
diff --git c/core/pkg/evaluator/json_test.go i/core/pkg/evaluator/json_test.go
index 540b299..1e68def 100644
--- c/core/pkg/evaluator/json_test.go
+++ i/core/pkg/evaluator/json_test.go
@@ -1,15 +1,15 @@
//nolint:wrapcheck
-package evaluator
+package evaluator_test
import (
"context"
"encoding/json"
"fmt"
"reflect"
- "strings"
"testing"
"time"
+ "github.com/open-feature/flagd/core/pkg/evaluator"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/store"
@@ -381,48 +381,42 @@ var flagConfig = fmt.Sprintf(`{
VersionOverride)
func TestGetState_Valid_ContainsFlag(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("Expected no error")
}
// get the state
- state, err := evaluator.GetState()
- if err != nil {
+ state := evaluator.ResolveAsAnyValue(context.Background(), "", "validFlag", nil)
+ if state.Error != nil {
t.Fatalf("expected no error")
}
-
- // validate it contains the flag
- wants := "validFlag"
- if !strings.Contains(state, wants) {
- t.Fatalf("expected: %s to contain: %s", state, wants)
- }
}
func TestSetState_Invalid_Error(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
// set state with an invalid flag definition
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("unexpected error")
}
}
func TestSetState_Valid_NoError(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
// set state with a valid flag definition
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
}
func TestResolveAllValues(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -490,8 +484,8 @@ func TestResolveBooleanValue(t *testing.T) {
{DisabledFlag, nil, StaticBoolValue, model.ErrorReason, model.FlagDisabledErrorCode},
}
const reqID = "default"
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -525,8 +519,8 @@ func BenchmarkResolveBooleanValue(b *testing.B) {
{DisabledFlag, nil, StaticBoolValue, model.ErrorReason, model.FlagDisabledErrorCode},
}
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -565,8 +559,8 @@ func TestResolveStringValue(t *testing.T) {
{DisabledFlag, nil, "", model.ErrorReason, model.FlagDisabledErrorCode},
}
const reqID = "default"
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -601,8 +595,8 @@ func BenchmarkResolveStringValue(b *testing.B) {
{DisabledFlag, nil, "", model.ErrorReason, model.FlagDisabledErrorCode},
}
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -641,8 +635,8 @@ func TestResolveFloatValue(t *testing.T) {
{DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode},
}
const reqID = "default"
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -677,8 +671,8 @@ func BenchmarkResolveFloatValue(b *testing.B) {
{DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode},
}
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -717,8 +711,8 @@ func TestResolveIntValue(t *testing.T) {
{DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode},
}
const reqID = "default"
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -753,8 +747,8 @@ func BenchmarkResolveIntValue(b *testing.B) {
{DisabledFlag, nil, 0, model.ErrorReason, model.FlagDisabledErrorCode},
}
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -793,8 +787,8 @@ func TestResolveObjectValue(t *testing.T) {
{DisabledFlag, nil, "{}", model.ErrorReason, model.FlagDisabledErrorCode},
}
const reqID = "default"
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -832,8 +826,8 @@ func BenchmarkResolveObjectValue(b *testing.B) {
{DisabledFlag, nil, "{}", model.ErrorReason, model.FlagDisabledErrorCode},
}
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -877,8 +871,8 @@ func TestResolveAsAnyValue(t *testing.T) {
{DisabledFlag, nil, "{}", model.ErrorReason, model.FlagDisabledErrorCode},
}
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -913,8 +907,8 @@ func TestResolve_DefaultVariant(t *testing.T) {
for _, test := range tests {
t.Run("", func(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: test.flags, Source: "testSource"})
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ err := evaluator.SetState(sync.DataSync{FlagData: test.flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
@@ -979,9 +973,9 @@ func TestSetState_DefaultVariantValidation(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
- jsonEvaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags, Source: "testSource"})
if tt.valid && err != nil {
t.Error(err)
@@ -993,7 +987,7 @@ func TestSetState_DefaultVariantValidation(t *testing.T) {
func TestState_Evaluator(t *testing.T) {
tests := map[string]struct {
inputState string
- expectedOutputState string
+ expectedOutputState map[string]model.Flag
expectedError bool
expectedResync bool
}{
@@ -1010,6 +1004,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1028,20 +1025,19 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
- expectedOutputState: `
- {
- "flags": {
- "fibAlgo": {
- "variants": {
- "recursive": "recursive",
- "memo": "memo",
- "loop": "loop",
- "binet": "binet"
- },
- "defaultVariant": "recursive",
- "state": "ENABLED",
- "source":"testSource",
- "targeting": {
+ expectedOutputState: map[string]model.Flag{
+ "fibAlgo": {
+ Key: "fibAlgo",
+ Variants: map[string]any{
+ "recursive": "recursive",
+ "memo": "memo",
+ "loop": "loop",
+ "binet": "binet",
+ },
+ DefaultVariant: "recursive",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
"if": [
{
"in": ["@faas.com", {
@@ -1049,12 +1045,13 @@ func TestState_Evaluator(t *testing.T) {
}]
}, "binet", null
]
- }
- }
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
},
- "flagSources":null
- }
- `,
+ FlagSetId: "flagSetId",
+ },
+ },
},
"no-indentation": {
inputState: `
@@ -1069,6 +1066,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1087,20 +1087,19 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
- expectedOutputState: `
- {
- "flags": {
- "fibAlgo": {
- "variants": {
- "recursive": "recursive",
- "memo": "memo",
- "loop": "loop",
- "binet": "binet"
- },
- "defaultVariant": "recursive",
- "state": "ENABLED",
- "source":"testSource",
- "targeting": {
+ expectedOutputState: map[string]model.Flag{
+ "fibAlgo": {
+ Key: "fibAlgo",
+ Variants: map[string]any{
+ "recursive": "recursive",
+ "memo": "memo",
+ "loop": "loop",
+ "binet": "binet",
+ },
+ DefaultVariant: "recursive",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
"if": [
{
"in": ["@faas.com", {
@@ -1108,12 +1107,13 @@ func TestState_Evaluator(t *testing.T) {
}]
}, "binet", null
]
- }
- }
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
},
- "flagSources":null
- }
- `,
+ FlagSetId: "flagSetId",
+ },
+ },
},
"invalid evaluator json": {
inputState: `
@@ -1128,6 +1128,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1157,6 +1160,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1175,6 +1181,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "off",
"source":"testSource",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1196,57 +1205,55 @@ func TestState_Evaluator(t *testing.T) {
}
`,
expectedError: false,
- expectedOutputState: `
- {
- "flags": {
- "fibAlgo": {
- "variants": {
+ expectedOutputState: map[string]model.Flag{
+ "fibAlgo": {
+ Key: "fibAlgo",
+ Variants: map[string]any{
"recursive": "recursive",
- "memo": "memo",
- "loop": "loop",
- "binet": "binet"
- },
- "defaultVariant": "recursive",
- "state": "ENABLED",
- "source":"testSource",
- "targeting": {
- "if": [
- {
- "in": ["@faas.com", {
- "var": ["email"]
- }]
- }, "binet", "null", "loop"
- ]
- }
- },
- "isColorYellow": {
- "state": "ENABLED",
- "variants": {
- "on": true,
- "off": false
- },
- "defaultVariant": "off",
- "source":"testSource",
- "targeting": {
+ "memo": "memo",
+ "loop": "loop",
+ "binet": "binet",
+ },
+ DefaultVariant: "recursive",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
+ "if": [
+ {
+ "in": ["@faas.com", {
+ "var": ["email"]
+ }]
+ }, "binet", null
+ ]
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
+ },
+ FlagSetId: "flagSetId",
+ },
+ "isColorYellow": {
+ Key: "isColorYellow",
+ Variants: map[string]any{
+ "on": true,
+ "off": false,
+ },
+ DefaultVariant: "off",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
"if": [
{
"==": [
- {
- "varr": ["color"]
- },
- "yellow"
- ]
- },
- "on",
"off",
"none"
]
- }
- }
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
+ },
+ FlagSetId: "flagSetId",
},
- "flagSources":null
- }
- `,
+ },
},
"empty evaluator": {
inputState: `
@@ -1281,54 +1288,63 @@ func TestState_Evaluator(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
- jsonEvaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, resync, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState, Source: "testSource"})
if err != nil {
if !tt.expectedError {
t.Error(err)
}
- if resync != tt.expectedResync {
- t.Errorf("expected resync %t got %t", tt.expectedResync, resync)
- }
return
} else if tt.expectedError {
t.Error("expected error, got nil")
return
}
- if resync != tt.expectedResync {
- t.Errorf("expected resync %t got %t", tt.expectedResync, resync)
- }
-
- got, err := jsonEvaluator.GetState()
+ got, _, err := jsonEvaluator.store.GetAll(context.Background(), nil)
if err != nil {
t.Error(err)
}
- var expectedOutputJSON map[string]interface{}
- if err := json.Unmarshal([]byte(tt.expectedOutputState), &expectedOutputJSON); err != nil {
- t.Fatal(err)
- }
+ for _, v := range got {
- var gotOutputJSON map[string]interface{}
- if err := json.Unmarshal([]byte(got), &gotOutputJSON); err != nil {
- t.Fatal(err)
- }
+ // json data can be formatted differently, hence we remove it from the object and compare separately
+ parsedTargeting, _ := normalizeJSON(v.Targeting)
+ flag := tt.expectedOutputState[v.Key]
+ flag.Targeting = nil
+ gotTargeting, _ := normalizeJSON(v.Targeting)
+ v.Targeting = nil
- if !reflect.DeepEqual(expectedOutputJSON["flags"], gotOutputJSON) {
- t.Errorf("expected state: %v got state: %v", expectedOutputJSON["flags"], gotOutputJSON)
+ if !reflect.DeepEqual(parsedTargeting, gotTargeting) {
+ t.Errorf("\nexpected targeting: %s\ngot targeting: %s", parsedTargeting, gotTargeting)
+ }
+
+ if !reflect.DeepEqual(flag, v) {
+ t.Errorf("expected state: %v got state: %v", flag, v)
+ }
}
})
}
}
+func normalizeJSON(jsonData []byte) (interface{}, error) {
+ var result interface{}
+ if jsonData == nil {
+ return nil, nil // Handle nil gracefully
+ }
+ err := json.Unmarshal(jsonData, &result)
+ if err != nil {
+ return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
+ }
+ return result, nil
+}
+
func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
tests := map[string]struct {
- flagResolution func(evaluator *JSON) error
+ flagResolution func(evaluator *evaluator.JSON) error
}{
"Add_ResolveAllValues": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, err := evaluator.ResolveAllValues(context.TODO(), "", nil)
if err != nil {
return err
@@ -1337,7 +1353,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
},
},
"Update_ResolveAllValues": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, err := evaluator.ResolveAllValues(context.TODO(), "", nil)
if err != nil {
return err
@@ -1346,7 +1362,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
},
},
"Delete_ResolveAllValues": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, err := evaluator.ResolveAllValues(context.TODO(), "", nil)
if err != nil {
return err
@@ -1355,31 +1371,31 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
},
},
"Add_ResolveBooleanValue": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveBooleanValue(context.TODO(), "", StaticBoolFlag, nil)
return err
},
},
"Update_ResolveStringValue": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveBooleanValue(context.TODO(), "", StaticStringValue, nil)
return err
},
},
"Delete_ResolveIntValue": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveIntValue(context.TODO(), "", StaticIntFlag, nil)
return err
},
},
"Add_ResolveFloatValue": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveFloatValue(context.TODO(), "", StaticFloatFlag, nil)
return err
},
},
"Update_ResolveObjectValue": {
- flagResolution: func(evaluator *JSON) error {
+ flagResolution: func(evaluator *evaluator.JSON) error {
_, _, _, _, err := evaluator.ResolveObjectValue(context.TODO(), "", StaticObjectFlag, nil)
return err
},
@@ -1388,9 +1404,9 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
- jsonEvaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatal(err)
}
@@ -1413,7 +1429,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
errChan <- nil
return
default:
- _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
errChan <- err
return
@@ -1453,9 +1469,9 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
func TestFlagdAmbientProperties(t *testing.T) {
t.Run("flagKeyIsInTheContext", func(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"welcome-banner": {
"state": "ENABLED",
@@ -1493,9 +1509,9 @@ func TestFlagdAmbientProperties(t *testing.T) {
})
t.Run("timestampIsInTheContext", func(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"welcome-banner": {
"state": "ENABLED",
@@ -1527,9 +1543,9 @@ func TestFlagdAmbientProperties(t *testing.T) {
func TestTargetingVariantBehavior(t *testing.T) {
t.Run("missing variant error", func(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"missing-variant": {
"state": "ENABLED",
@@ -1555,9 +1571,9 @@ func TestTargetingVariantBehavior(t *testing.T) {
})
t.Run("null fallback", func(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"null-fallback": {
"state": "ENABLED",
@@ -1587,10 +1603,10 @@ func TestTargetingVariantBehavior(t *testing.T) {
})
t.Run("match booleans", func(t *testing.T) {
- evaluator := NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+ evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
//nolint:dupword
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"match-boolean": {
"state": "ENABLED",
diff --git c/core/pkg/evaluator/mock/ievaluator.go i/core/pkg/evaluator/mock/ievaluator.go
index 98d0367..bf0ce56 100644
--- c/core/pkg/evaluator/mock/ievaluator.go
+++ i/core/pkg/evaluator/mock/ievaluator.go
@@ -42,21 +42,6 @@ func (m *MockIEvaluator) EXPECT() *MockIEvaluatorMockRecorder {
return m.recorder
}
-// GetState mocks base method.
-func (m *MockIEvaluator) GetState() (string, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetState")
- ret0, _ := ret[0].(string)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetState indicates an expected call of GetState.
-func (mr *MockIEvaluatorMockRecorder) GetState() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockIEvaluator)(nil).GetState))
-}
-
// ResolveAllValues mocks base method.
func (m *MockIEvaluator) ResolveAllValues(ctx context.Context, reqID string, context map[string]any) ([]evaluator.AnyValue, model.Metadata, error) {
m.ctrl.T.Helper()
@@ -178,13 +163,11 @@ func (mr *MockIEvaluatorMockRecorder) ResolveStringValue(ctx, reqID, flagKey, co
}
// SetState mocks base method.
-func (m *MockIEvaluator) SetState(payload sync.DataSync) (map[string]any, bool, error) {
+func (m *MockIEvaluator) SetState(payload sync.DataSync) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetState", payload)
- ret0, _ := ret[0].(map[string]any)
- ret1, _ := ret[1].(bool)
- ret2, _ := ret[2].(error)
- return ret0, ret1, ret2
+ ret0, _ := ret[0].(error)
+ return ret0
}
// SetState indicates an expected call of SetState.
diff --git c/core/pkg/store/store.go i/core/pkg/store/store.go
index 2b8b87c..0c6bc16 100644
--- c/core/pkg/store/store.go
+++ i/core/pkg/store/store.go
@@ -2,14 +2,12 @@ package store
import (
"context"
- "encoding/json"
"fmt"
"slices"
"github.com/hashicorp/go-memdb"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
- "github.com/open-feature/flagd/core/pkg/notifications"
)
var noValidatedSources = []string{}
@@ -24,8 +22,7 @@ type IStore interface {
Get(ctx context.Context, key string, selector *Selector) (model.Flag, model.Metadata, error)
GetAll(ctx context.Context, selector *Selector) (map[string]model.Flag, model.Metadata, error)
Watch(ctx context.Context, selector *Selector, watcher chan<- FlagQueryResult)
- Update(source string, flags map[string]model.Flag, metadata model.Metadata) (map[string]interface{}, bool)
- String() (string, error)
+ Update(source string, flags map[string]model.Flag, metadata model.Metadata)
}
var _ IStore = (*Store)(nil)
@@ -194,22 +191,6 @@ func (s *Store) Get(_ context.Context, key string, selector *Selector) (model.Fl
return flag, queryMeta, nil
}
-func (f *Store) String() (string, error) {
- f.logger.Debug("dumping flags to string")
-
- state, _, err := f.GetAll(context.Background(), nil)
- if err != nil {
- return "", fmt.Errorf("unable to get all flags: %w", err)
- }
-
- bytes, err := json.Marshal(state)
- if err != nil {
- return "", fmt.Errorf("unable to marshal flags: %w", err)
- }
-
- return string(bytes), nil
-}
-
// GetAll returns a copy of the store's state (copy in order to be concurrency safe)
func (s *Store) GetAll(ctx context.Context, selector *Selector) (map[string]model.Flag, model.Metadata, error) {
flags := make(map[string]model.Flag)
@@ -229,9 +210,7 @@ func (s *Store) Update(
source string,
flags map[string]model.Flag,
metadata model.Metadata,
-) (map[string]interface{}, bool) {
- resyncRequired := false
-
+) {
if source == "" {
panic("source cannot be empty")
}
@@ -313,7 +292,6 @@ func (s *Store) Update(
}
txn.Commit()
- return notifications.NewFromFlags(oldFlags, flags), resyncRequired
}
// Watch the result-set of a selector for changes, sending updates to the watcher channel.
diff --git c/core/pkg/store/store_test.go i/core/pkg/store/store_test.go
index ced7e76..c6cf2dd 100644
--- c/core/pkg/store/store_test.go
+++ i/core/pkg/store/store_test.go
@@ -25,8 +25,6 @@ func TestUpdateFlags(t *testing.T) {
source string
wantFlags map[string]model.Flag
setMetadata model.Metadata
- wantNotifs map[string]interface{}
- wantResync bool
}{
{
name: "both nil",
@@ -37,10 +35,9 @@ func TestUpdateFlags(t *testing.T) {
}
return s
},
- source: source1,
- newFlags: nil,
- wantFlags: map[string]model.Flag{},
- wantNotifs: map[string]interface{}{},
+ source: source1,
+ newFlags: nil,
+ wantFlags: map[string]model.Flag{},
},
{
name: "both empty flags",
@@ -51,10 +48,9 @@ func TestUpdateFlags(t *testing.T) {
}
return s
},
- source: source1,
- newFlags: map[string]model.Flag{},
- wantFlags: map[string]model.Flag{},
- wantNotifs: map[string]interface{}{},
+ source: source1,
+ newFlags: map[string]model.Flag{},
+ wantFlags: map[string]model.Flag{},
},
{
name: "empty new",
@@ -65,10 +61,9 @@ func TestUpdateFlags(t *testing.T) {
}
return s
},
- source: source1,
- newFlags: nil,
- wantFlags: map[string]model.Flag{},
- wantNotifs: map[string]interface{}{},
+ source: source1,
+ newFlags: nil,
+ wantFlags: map[string]model.Flag{},
},
{
name: "update from source 1 (old flag removed)",
@@ -89,10 +84,6 @@ func TestUpdateFlags(t *testing.T) {
wantFlags: map[string]model.Flag{
"paka": {Key: "paka", DefaultVariant: "on", Source: source1, FlagSetId: nilFlagSetId, Priority: 0},
},
- wantNotifs: map[string]interface{}{
- "paka": map[string]interface{}{"type": "write"},
- "waka": map[string]interface{}{"type": "delete"},
- },
},
{
name: "update from source 1 (new flag added)",
@@ -114,7 +105,6 @@ func TestUpdateFlags(t *testing.T) {
"waka": {Key: "waka", DefaultVariant: "off", Source: source1, FlagSetId: nilFlagSetId, Priority: 0},
"paka": {Key: "paka", DefaultVariant: "on", Source: source2, FlagSetId: nilFlagSetId, Priority: 1},
},
- wantNotifs: map[string]interface{}{"paka": map[string]interface{}{"type": "write"}},
},
{
name: "flag set inheritance",
@@ -138,10 +128,6 @@ func TestUpdateFlags(t *testing.T) {
"waka": {Key: "waka", DefaultVariant: "on", Source: source1, FlagSetId: "topLevelSet", Priority: 0, Metadata: model.Metadata{"flagSetId": "topLevelSet"}},
"paka": {Key: "paka", DefaultVariant: "on", Source: source1, FlagSetId: "flagLevelSet", Priority: 0, Metadata: model.Metadata{"flagSetId": "flagLevelSet"}},
},
- wantNotifs: map[string]interface{}{
- "paka": map[string]interface{}{"type": "write"},
- "waka": map[string]interface{}{"type": "write"},
- },
},
}
@@ -150,12 +136,10 @@ func TestUpdateFlags(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
store := tt.setup(t)
- gotNotifs, resyncRequired := store.Update(tt.source, tt.newFlags, tt.setMetadata)
+ store.Update(tt.source, tt.newFlags, tt.setMetadata)
gotFlags, _, _ := store.GetAll(context.Background(), nil)
require.Equal(t, tt.wantFlags, gotFlags)
- require.Equal(t, tt.wantNotifs, gotNotifs)
- require.Equal(t, tt.wantResync, resyncRequired)
})
}
}
diff --git c/flagd/pkg/runtime/runtime.go i/flagd/pkg/runtime/runtime.go
index 511354b..df03f26 100644
--- c/flagd/pkg/runtime/runtime.go
+++ i/flagd/pkg/runtime/runtime.go
@@ -127,7 +127,7 @@ func (r *Runtime) updateAndEmit(payload sync.DataSync) {
r.mu.Lock()
defer r.mu.Unlock()
- _, _, err := r.Evaluator.SetState(payload)
+ err := r.Evaluator.SetState(payload)
if err != nil {
r.Logger.Error(fmt.Sprintf("error setting state: %v", err))
return
diff --git c/core/pkg/evaluator/ievaluator.go i/core/pkg/evaluator/ievaluator.go
index 9f492c2..00b5590 100644
--- c/core/pkg/evaluator/ievaluator.go
+++ i/core/pkg/evaluator/ievaluator.go
@@ -34,8 +34,7 @@ func NewAnyValue(
IEvaluator is an extension of IResolver, allowing storage updates and retrievals
*/
type IEvaluator interface {
- GetState() (string, error)
- SetState(payload sync.DataSync) (map[string]interface{}, bool, error)
+ SetState(payload sync.DataSync) error
IResolver
}
diff --git c/core/pkg/evaluator/json.go i/core/pkg/evaluator/json.go
index 432b4a7..12b862b 100644
--- c/core/pkg/evaluator/json.go
+++ i/core/pkg/evaluator/json.go
@@ -117,15 +117,7 @@ func NewJSON(logger *logger.Logger, s store.IStore, opts ...JSONEvaluatorOption)
return &ev
}
-func (je *JSON) GetState() (string, error) {
- s, err := je.store.String()
- if err != nil {
- return "", fmt.Errorf("unable to fetch evaluator state: %w", err)
- }
- return s, nil
-}
-
-func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, error) {
+func (je *JSON) SetState(payload sync.DataSync) error {
_, span := je.jsonEvalTracer.Start(
context.Background(),
"flagSync",
@@ -138,18 +130,12 @@ func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, e
if err != nil {
span.SetStatus(codes.Error, "flagSync error")
span.RecordError(err)
- return nil, false, err
+ return err
}
- var events map[string]interface{}
- var reSync bool
+ je.store.Update(payload.Source, definition.Flags, definition.Metadata)
- events, reSync = je.store.Update(payload.Source, definition.Flags, definition.Metadata)
-
- // Number of events correlates to the number of flags changed through this sync, record it
- span.SetAttributes(attribute.Int("feature_flag.change_count", len(events)))
-
- return events, reSync, nil
+ return nil
}
// Resolver implementation for flagd flags. This resolver should be kept reusable, hence must interact with interfaces.
diff --git c/core/pkg/evaluator/json_test.go i/core/pkg/evaluator/json_test.go
index 733b0db..1e68def 100644
--- c/core/pkg/evaluator/json_test.go
+++ i/core/pkg/evaluator/json_test.go
@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"reflect"
- "strings"
"testing"
"time"
@@ -383,29 +382,23 @@ var flagConfig = fmt.Sprintf(`{
func TestGetState_Valid_ContainsFlag(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("Expected no error")
}
// get the state
- state, err := evaluator.GetState()
- if err != nil {
+ state := evaluator.ResolveAsAnyValue(context.Background(), "", "validFlag", nil)
+ if state.Error != nil {
t.Fatalf("expected no error")
}
-
- // validate it contains the flag
- wants := "validFlag"
- if !strings.Contains(state, wants) {
- t.Fatalf("expected: %s to contain: %s", state, wants)
- }
}
func TestSetState_Invalid_Error(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
// set state with an invalid flag definition
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: InvalidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("unexpected error")
}
@@ -415,7 +408,7 @@ func TestSetState_Valid_NoError(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
// set state with a valid flag definition
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: ValidFlags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -423,7 +416,7 @@ func TestSetState_Valid_NoError(t *testing.T) {
func TestResolveAllValues(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -492,7 +485,7 @@ func TestResolveBooleanValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -527,7 +520,7 @@ func BenchmarkResolveBooleanValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -567,7 +560,7 @@ func TestResolveStringValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -603,7 +596,7 @@ func BenchmarkResolveStringValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -643,7 +636,7 @@ func TestResolveFloatValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -679,7 +672,7 @@ func BenchmarkResolveFloatValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -719,7 +712,7 @@ func TestResolveIntValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -755,7 +748,7 @@ func BenchmarkResolveIntValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -795,7 +788,7 @@ func TestResolveObjectValue(t *testing.T) {
}
const reqID = "default"
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -834,7 +827,7 @@ func BenchmarkResolveObjectValue(b *testing.B) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
b.Fatalf("expected no error")
}
@@ -879,7 +872,7 @@ func TestResolveAsAnyValue(t *testing.T) {
}
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
}
@@ -915,7 +908,7 @@ func TestResolve_DefaultVariant(t *testing.T) {
for _, test := range tests {
t.Run("", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{FlagData: test.flags, Source: "testSource"})
+ err := evaluator.SetState(sync.DataSync{FlagData: test.flags, Source: "testSource"})
if err != nil {
t.Fatalf("expected no error")
@@ -982,7 +975,7 @@ func TestSetState_DefaultVariantValidation(t *testing.T) {
t.Run(name, func(t *testing.T) {
jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.jsonFlags, Source: "testSource"})
if tt.valid && err != nil {
t.Error(err)
@@ -994,7 +987,7 @@ func TestSetState_DefaultVariantValidation(t *testing.T) {
func TestState_Evaluator(t *testing.T) {
tests := map[string]struct {
inputState string
- expectedOutputState string
+ expectedOutputState map[string]model.Flag
expectedError bool
expectedResync bool
}{
@@ -1011,6 +1004,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1029,20 +1025,19 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
- expectedOutputState: `
- {
- "flags": {
- "fibAlgo": {
- "variants": {
- "recursive": "recursive",
- "memo": "memo",
- "loop": "loop",
- "binet": "binet"
- },
- "defaultVariant": "recursive",
- "state": "ENABLED",
- "source":"testSource",
- "targeting": {
+ expectedOutputState: map[string]model.Flag{
+ "fibAlgo": {
+ Key: "fibAlgo",
+ Variants: map[string]any{
+ "recursive": "recursive",
+ "memo": "memo",
+ "loop": "loop",
+ "binet": "binet",
+ },
+ DefaultVariant: "recursive",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
"if": [
{
"in": ["@faas.com", {
@@ -1050,12 +1045,13 @@ func TestState_Evaluator(t *testing.T) {
}]
}, "binet", null
]
- }
- }
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
},
- "flagSources":null
- }
- `,
+ FlagSetId: "flagSetId",
+ },
+ },
},
"no-indentation": {
inputState: `
@@ -1070,6 +1066,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1088,20 +1087,19 @@ func TestState_Evaluator(t *testing.T) {
}
}
`,
- expectedOutputState: `
- {
- "flags": {
- "fibAlgo": {
- "variants": {
- "recursive": "recursive",
- "memo": "memo",
- "loop": "loop",
- "binet": "binet"
- },
- "defaultVariant": "recursive",
- "state": "ENABLED",
- "source":"testSource",
- "targeting": {
+ expectedOutputState: map[string]model.Flag{
+ "fibAlgo": {
+ Key: "fibAlgo",
+ Variants: map[string]any{
+ "recursive": "recursive",
+ "memo": "memo",
+ "loop": "loop",
+ "binet": "binet",
+ },
+ DefaultVariant: "recursive",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
"if": [
{
"in": ["@faas.com", {
@@ -1109,12 +1107,13 @@ func TestState_Evaluator(t *testing.T) {
}]
}, "binet", null
]
- }
- }
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
},
- "flagSources":null
- }
- `,
+ FlagSetId: "flagSetId",
+ },
+ },
},
"invalid evaluator json": {
inputState: `
@@ -1129,6 +1128,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1158,6 +1160,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "recursive",
"state": "ENABLED",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1176,6 +1181,9 @@ func TestState_Evaluator(t *testing.T) {
},
"defaultVariant": "off",
"source":"testSource",
+ "metadata": {
+ "flagSetId": "flagSetId"
+ },
"targeting": {
"if": [
{
@@ -1197,57 +1205,55 @@ func TestState_Evaluator(t *testing.T) {
}
`,
expectedError: false,
- expectedOutputState: `
- {
- "flags": {
- "fibAlgo": {
- "variants": {
+ expectedOutputState: map[string]model.Flag{
+ "fibAlgo": {
+ Key: "fibAlgo",
+ Variants: map[string]any{
"recursive": "recursive",
- "memo": "memo",
- "loop": "loop",
- "binet": "binet"
- },
- "defaultVariant": "recursive",
- "state": "ENABLED",
- "source":"testSource",
- "targeting": {
- "if": [
- {
- "in": ["@faas.com", {
- "var": ["email"]
- }]
- }, "binet", "null", "loop"
- ]
- }
- },
- "isColorYellow": {
- "state": "ENABLED",
- "variants": {
- "on": true,
- "off": false
- },
- "defaultVariant": "off",
- "source":"testSource",
- "targeting": {
+ "memo": "memo",
+ "loop": "loop",
+ "binet": "binet",
+ },
+ DefaultVariant: "recursive",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
+ "if": [
+ {
+ "in": ["@faas.com", {
+ "var": ["email"]
+ }]
+ }, "binet", null
+ ]
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
+ },
+ FlagSetId: "flagSetId",
+ },
+ "isColorYellow": {
+ Key: "isColorYellow",
+ Variants: map[string]any{
+ "on": true,
+ "off": false,
+ },
+ DefaultVariant: "off",
+ State: "ENABLED",
+ Source: "testSource",
+ Targeting: json.RawMessage(`{
"if": [
{
"==": [
- {
- "varr": ["color"]
- },
- "yellow"
- ]
- },
- "on",
"off",
"none"
]
- }
- }
+ }`),
+ Metadata: map[string]interface{}{
+ "flagSetId": "flagSetId",
+ },
+ FlagSetId: "flagSetId",
},
- "flagSources":null
- }
- `,
+ },
},
"empty evaluator": {
inputState: `
@@ -1284,46 +1290,55 @@ func TestState_Evaluator(t *testing.T) {
t.Run(name, func(t *testing.T) {
jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, resync, err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: tt.inputState, Source: "testSource"})
if err != nil {
if !tt.expectedError {
t.Error(err)
}
- if resync != tt.expectedResync {
- t.Errorf("expected resync %t got %t", tt.expectedResync, resync)
- }
return
} else if tt.expectedError {
t.Error("expected error, got nil")
return
}
- if resync != tt.expectedResync {
- t.Errorf("expected resync %t got %t", tt.expectedResync, resync)
- }
-
- got, err := jsonEvaluator.GetState()
+ got, _, err := jsonEvaluator.store.GetAll(context.Background(), nil)
if err != nil {
t.Error(err)
}
- var expectedOutputJSON map[string]interface{}
- if err := json.Unmarshal([]byte(tt.expectedOutputState), &expectedOutputJSON); err != nil {
- t.Fatal(err)
- }
+ for _, v := range got {
- var gotOutputJSON map[string]interface{}
- if err := json.Unmarshal([]byte(got), &gotOutputJSON); err != nil {
- t.Fatal(err)
- }
+ // json data can be formatted differently, hence we remove it from the object and compare separately
+ parsedTargeting, _ := normalizeJSON(v.Targeting)
+ flag := tt.expectedOutputState[v.Key]
+ flag.Targeting = nil
+ gotTargeting, _ := normalizeJSON(v.Targeting)
+ v.Targeting = nil
- if !reflect.DeepEqual(expectedOutputJSON["flags"], gotOutputJSON) {
- t.Errorf("expected state: %v got state: %v", expectedOutputJSON["flags"], gotOutputJSON)
+ if !reflect.DeepEqual(parsedTargeting, gotTargeting) {
+ t.Errorf("\nexpected targeting: %s\ngot targeting: %s", parsedTargeting, gotTargeting)
+ }
+
+ if !reflect.DeepEqual(flag, v) {
+ t.Errorf("expected state: %v got state: %v", flag, v)
+ }
}
})
}
}
+func normalizeJSON(jsonData []byte) (interface{}, error) {
+ var result interface{}
+ if jsonData == nil {
+ return nil, nil // Handle nil gracefully
+ }
+ err := json.Unmarshal(jsonData, &result)
+ if err != nil {
+ return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
+ }
+ return result, nil
+}
+
func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
tests := map[string]struct {
flagResolution func(evaluator *evaluator.JSON) error
@@ -1391,7 +1406,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
t.Run(name, func(t *testing.T) {
jsonEvaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
t.Fatal(err)
}
@@ -1414,7 +1429,7 @@ func TestFlagStateSafeForConcurrentReadWrites(t *testing.T) {
errChan <- nil
return
default:
- _, _, err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
+ err := jsonEvaluator.SetState(sync.DataSync{FlagData: flagConfig, Source: "testSource"})
if err != nil {
errChan <- err
return
@@ -1456,7 +1471,7 @@ func TestFlagdAmbientProperties(t *testing.T) {
t.Run("flagKeyIsInTheContext", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"welcome-banner": {
"state": "ENABLED",
@@ -1496,7 +1511,7 @@ func TestFlagdAmbientProperties(t *testing.T) {
t.Run("timestampIsInTheContext", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"welcome-banner": {
"state": "ENABLED",
@@ -1530,7 +1545,7 @@ func TestTargetingVariantBehavior(t *testing.T) {
t.Run("missing variant error", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"missing-variant": {
"state": "ENABLED",
@@ -1558,7 +1573,7 @@ func TestTargetingVariantBehavior(t *testing.T) {
t.Run("null fallback", func(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"null-fallback": {
"state": "ENABLED",
@@ -1591,7 +1606,7 @@ func TestTargetingVariantBehavior(t *testing.T) {
evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
//nolint:dupword
- _, _, err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
+ err := evaluator.SetState(sync.DataSync{Source: "testSource", FlagData: `{
"flags": {
"match-boolean": {
"state": "ENABLED",
diff --git c/core/pkg/evaluator/mock/ievaluator.go i/core/pkg/evaluator/mock/ievaluator.go
index 98d0367..bf0ce56 100644
--- c/core/pkg/evaluator/mock/ievaluator.go
+++ i/core/pkg/evaluator/mock/ievaluator.go
@@ -42,21 +42,6 @@ func (m *MockIEvaluator) EXPECT() *MockIEvaluatorMockRecorder {
return m.recorder
}
-// GetState mocks base method.
-func (m *MockIEvaluator) GetState() (string, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "GetState")
- ret0, _ := ret[0].(string)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// GetState indicates an expected call of GetState.
-func (mr *MockIEvaluatorMockRecorder) GetState() *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockIEvaluator)(nil).GetState))
-}
-
// ResolveAllValues mocks base method.
func (m *MockIEvaluator) ResolveAllValues(ctx context.Context, reqID string, context map[string]any) ([]evaluator.AnyValue, model.Metadata, error) {
m.ctrl.T.Helper()
@@ -178,13 +163,11 @@ func (mr *MockIEvaluatorMockRecorder) ResolveStringValue(ctx, reqID, flagKey, co
}
// SetState mocks base method.
-func (m *MockIEvaluator) SetState(payload sync.DataSync) (map[string]any, bool, error) {
+func (m *MockIEvaluator) SetState(payload sync.DataSync) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetState", payload)
- ret0, _ := ret[0].(map[string]any)
- ret1, _ := ret[1].(bool)
- ret2, _ := ret[2].(error)
- return ret0, ret1, ret2
+ ret0, _ := ret[0].(error)
+ return ret0
}
// SetState indicates an expected call of SetState.
diff --git c/core/pkg/store/store.go i/core/pkg/store/store.go
index 2b8b87c..0c6bc16 100644
--- c/core/pkg/store/store.go
+++ i/core/pkg/store/store.go
@@ -2,14 +2,12 @@ package store
import (
"context"
- "encoding/json"
"fmt"
"slices"
"github.com/hashicorp/go-memdb"
"github.com/open-feature/flagd/core/pkg/logger"
"github.com/open-feature/flagd/core/pkg/model"
- "github.com/open-feature/flagd/core/pkg/notifications"
)
var noValidatedSources = []string{}
@@ -24,8 +22,7 @@ type IStore interface {
Get(ctx context.Context, key string, selector *Selector) (model.Flag, model.Metadata, error)
GetAll(ctx context.Context, selector *Selector) (map[string]model.Flag, model.Metadata, error)
Watch(ctx context.Context, selector *Selector, watcher chan<- FlagQueryResult)
- Update(source string, flags map[string]model.Flag, metadata model.Metadata) (map[string]interface{}, bool)
- String() (string, error)
+ Update(source string, flags map[string]model.Flag, metadata model.Metadata)
}
var _ IStore = (*Store)(nil)
@@ -194,22 +191,6 @@ func (s *Store) Get(_ context.Context, key string, selector *Selector) (model.Fl
return flag, queryMeta, nil
}
-func (f *Store) String() (string, error) {
- f.logger.Debug("dumping flags to string")
-
- state, _, err := f.GetAll(context.Background(), nil)
- if err != nil {
- return "", fmt.Errorf("unable to get all flags: %w", err)
- }
-
- bytes, err := json.Marshal(state)
- if err != nil {
- return "", fmt.Errorf("unable to marshal flags: %w", err)
- }
-
- return string(bytes), nil
-}
-
// GetAll returns a copy of the store's state (copy in order to be concurrency safe)
func (s *Store) GetAll(ctx context.Context, selector *Selector) (map[string]model.Flag, model.Metadata, error) {
flags := make(map[string]model.Flag)
@@ -229,9 +210,7 @@ func (s *Store) Update(
source string,
flags map[string]model.Flag,
metadata model.Metadata,
-) (map[string]interface{}, bool) {
- resyncRequired := false
-
+) {
if source == "" {
panic("source cannot be empty")
}
@@ -313,7 +292,6 @@ func (s *Store) Update(
}
txn.Commit()
- return notifications.NewFromFlags(oldFlags, flags), resyncRequired
}
// Watch the result-set of a selector for changes, sending updates to the watcher channel.
diff --git c/core/pkg/store/store_test.go i/core/pkg/store/store_test.go
index ced7e76..c6cf2dd 100644
--- c/core/pkg/store/store_test.go
+++ i/core/pkg/store/store_test.go
@@ -25,8 +25,6 @@ func TestUpdateFlags(t *testing.T) {
source string
wantFlags map[string]model.Flag
setMetadata model.Metadata
- wantNotifs map[string]interface{}
- wantResync bool
}{
{
name: "both nil",
@@ -37,10 +35,9 @@ func TestUpdateFlags(t *testing.T) {
}
return s
},
- source: source1,
- newFlags: nil,
- wantFlags: map[string]model.Flag{},
- wantNotifs: map[string]interface{}{},
+ source: source1,
+ newFlags: nil,
+ wantFlags: map[string]model.Flag{},
},
{
name: "both empty flags",
@@ -51,10 +48,9 @@ func TestUpdateFlags(t *testing.T) {
}
return s
},
- source: source1,
- newFlags: map[string]model.Flag{},
- wantFlags: map[string]model.Flag{},
- wantNotifs: map[string]interface{}{},
+ source: source1,
+ newFlags: map[string]model.Flag{},
+ wantFlags: map[string]model.Flag{},
},
{
name: "empty new",
@@ -65,10 +61,9 @@ func TestUpdateFlags(t *testing.T) {
}
return s
},
- source: source1,
- newFlags: nil,
- wantFlags: map[string]model.Flag{},
- wantNotifs: map[string]interface{}{},
+ source: source1,
+ newFlags: nil,
+ wantFlags: map[string]model.Flag{},
},
{
name: "update from source 1 (old flag removed)",
@@ -89,10 +84,6 @@ func TestUpdateFlags(t *testing.T) {
wantFlags: map[string]model.Flag{
"paka": {Key: "paka", DefaultVariant: "on", Source: source1, FlagSetId: nilFlagSetId, Priority: 0},
},
- wantNotifs: map[string]interface{}{
- "paka": map[string]interface{}{"type": "write"},
- "waka": map[string]interface{}{"type": "delete"},
- },
},
{
name: "update from source 1 (new flag added)",
@@ -114,7 +105,6 @@ func TestUpdateFlags(t *testing.T) {
"waka": {Key: "waka", DefaultVariant: "off", Source: source1, FlagSetId: nilFlagSetId, Priority: 0},
"paka": {Key: "paka", DefaultVariant: "on", Source: source2, FlagSetId: nilFlagSetId, Priority: 1},
},
- wantNotifs: map[string]interface{}{"paka": map[string]interface{}{"type": "write"}},
},
{
name: "flag set inheritance",
@@ -138,10 +128,6 @@ func TestUpdateFlags(t *testing.T) {
"waka": {Key: "waka", DefaultVariant: "on", Source: source1, FlagSetId: "topLevelSet", Priority: 0, Metadata: model.Metadata{"flagSetId": "topLevelSet"}},
"paka": {Key: "paka", DefaultVariant: "on", Source: source1, FlagSetId: "flagLevelSet", Priority: 0, Metadata: model.Metadata{"flagSetId": "flagLevelSet"}},
},
- wantNotifs: map[string]interface{}{
- "paka": map[string]interface{}{"type": "write"},
- "waka": map[string]interface{}{"type": "write"},
- },
},
}
@@ -150,12 +136,10 @@ func TestUpdateFlags(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
store := tt.setup(t)
- gotNotifs, resyncRequired := store.Update(tt.source, tt.newFlags, tt.setMetadata)
+ store.Update(tt.source, tt.newFlags, tt.setMetadata)
gotFlags, _, _ := store.GetAll(context.Background(), nil)
require.Equal(t, tt.wantFlags, gotFlags)
- require.Equal(t, tt.wantNotifs, gotNotifs)
- require.Equal(t, tt.wantResync, resyncRequired)
})
}
}
diff --git c/flagd/pkg/runtime/runtime.go i/flagd/pkg/runtime/runtime.go
index 511354b..df03f26 100644
--- c/flagd/pkg/runtime/runtime.go
+++ i/flagd/pkg/runtime/runtime.go
@@ -127,7 +127,7 @@ func (r *Runtime) updateAndEmit(payload sync.DataSync) {
r.mu.Lock()
defer r.mu.Unlock()
- _, _, err := r.Evaluator.SetState(payload)
+ err := r.Evaluator.SetState(payload)
if err != nil {
r.Logger.Error(fmt.Sprintf("error setting state: %v", err))
return
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
a4b9efc to
41c8b62
Compare
depends: open-feature#1792 for fixing build issues --------- Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com> Signed-off-by: Giovanni Liva <giovanni.liva@dynatrace.com>
🤖 I have created a release *beep* *boop* --- <details><summary>flagd: 0.13.0</summary> ## [0.13.0](flagd/v0.12.9...flagd/v0.13.0) (2025-12-23) ### 🐛 Bug Fixes * fixing sync return format missing flag layer, adding full e2e suite ([#1827](#1827)) ([570693d](570693d)) * **security:** update module github.com/go-viper/mapstructure/v2 to v2.4.0 [security] ([#1784](#1784)) ([037e30b](037e30b)) * **security:** update module golang.org/x/crypto to v0.45.0 [security] ([#1826](#1826)) ([7e0762b](7e0762b)) ### ✨ New Features * add support for http-based ofrep metrics ([#1803](#1803)) ([fcd19b3](fcd19b3)) * cleanup evaluator interface ([#1793](#1793)) ([aa504f7](aa504f7)) * enable parsing of array flag configurations for flagd ([#1797](#1797)) ([97c6ffa](97c6ffa)) * multi-project support via selectors and flagSetId namespacing ([#1702](#1702)) ([f9ce46f](f9ce46f)) * normalize selector in sync (use header as in OFREP and RPC) ([#1815](#1815)) ([c1f06cb](c1f06cb)) ### 🧹 Chore * **refactor:** use memdb for flag storage ([#1697](#1697)) ([5c5c1cf](5c5c1cf)) ### 🔄 Refactoring * store cleanup ([#1705](#1705)) ([bcff8d7](bcff8d7)) </details> <details><summary>flagd-proxy: 0.8.1</summary> ## [0.8.1](flagd-proxy/v0.8.0...flagd-proxy/v0.8.1) (2025-12-23) ### 🐛 Bug Fixes * **security:** update module github.com/go-viper/mapstructure/v2 to v2.4.0 [security] ([#1784](#1784)) ([037e30b](037e30b)) * **security:** update module golang.org/x/crypto to v0.45.0 [security] ([#1826](#1826)) ([7e0762b](7e0762b)) </details> <details><summary>core: 0.13.0</summary> ## [0.13.0](core/v0.12.1...core/v0.13.0) (2025-12-23) ### ⚠ BREAKING CHANGES * enable parsing of array flag configurations for flagd ([#1797](#1797)) * cleanup evaluator interface ([#1793](#1793)) * removes the `fractionalEvaluation` operator since it has been replaced with `fractional`. ([#1704](#1704)) ### 🐛 Bug Fixes * **security:** update module github.com/go-viper/mapstructure/v2 to v2.4.0 [security] ([#1784](#1784)) ([037e30b](037e30b)) * **security:** update module golang.org/x/crypto to v0.45.0 [security] ([#1825](#1825)) ([44edcc9](44edcc9)) * **security:** update module golang.org/x/crypto to v0.45.0 [security] ([#1826](#1826)) ([7e0762b](7e0762b)) ### ✨ New Features * Add OAuth support for HTTP Sync ([#1791](#1791)) ([268fd75](268fd75)) * Add OTEL default variables ([#1812](#1812)) ([c2e3fc6](c2e3fc6)) * allow null flagSetId Selector, restrict Selector to single key-value-pairs ([#1708](#1708)) ([#1811](#1811)) ([c12a0ae](c12a0ae)) * change jsonschema parser ([#1794](#1794)) ([bf3f722](bf3f722)) * cleanup evaluator interface ([#1793](#1793)) ([aa504f7](aa504f7)) * enable parsing of array flag configurations for flagd ([#1797](#1797)) ([97c6ffa](97c6ffa)) * multi-project support via selectors and flagSetId namespacing ([#1702](#1702)) ([f9ce46f](f9ce46f)) ### 🧹 Chore * **refactor:** use memdb for flag storage ([#1697](#1697)) ([5c5c1cf](5c5c1cf)) * removes the `fractionalEvaluation` operator since it has been replaced with `fractional`. ([#1704](#1704)) ([3228ad8](3228ad8)) ### 🔄 Refactoring * remove deprecated bearerToken option ([#1816](#1816)) ([efda06a](efda06a)) * removed unused Selector from Flag and Store. ([#1747](#1747)) ([1083005](1083005)) * store cleanup ([#1705](#1705)) ([bcff8d7](bcff8d7)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Signed-off-by: OpenFeature Bot <109696520+openfeaturebot@users.noreply.github.com> Signed-off-by: Todd Baert <todd.baert@dynatrace.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
depends: #1792 for fixing build issues