diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 6e83e4850c0..4af9db75b60 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -4,19 +4,19 @@ import ( "encoding/json" "errors" "fmt" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "gopkg.in/yaml.v3" "strings" "sync" "github.com/pelletier/go-toml" - - "github.com/thomaspoignant/go-feature-flag/internal/model" ) type Cache interface { UpdateCache(loadedFlags []byte, fileFormat string) error Close() - GetFlag(key string) (model.Flag, error) + GetFlag(key string) (flag.Flag, error) AllFlags() (FlagsCache, error) } @@ -28,7 +28,7 @@ type cacheImpl struct { func New(notificationService Service) Cache { return &cacheImpl{ - flagsCache: make(map[string]model.FlagData), + flagsCache: make(map[string]flagv1.FlagData), mutex: sync.RWMutex{}, notificationService: notificationService, } @@ -72,20 +72,20 @@ func (c *cacheImpl) Close() { } } -func (c *cacheImpl) GetFlag(key string) (model.Flag, error) { +func (c *cacheImpl) GetFlag(key string) (flag.Flag, error) { c.mutex.RLock() defer c.mutex.RUnlock() if c.flagsCache == nil { - return &model.FlagData{}, errors.New("impossible to read the flag before the initialisation") + return &flagv1.FlagData{}, errors.New("impossible to read the flag before the initialisation") } - flag, ok := c.flagsCache[key] + f, ok := c.flagsCache[key] if !ok { - return &model.FlagData{}, fmt.Errorf("flag [%v] does not exists", key) + return &flagv1.FlagData{}, fmt.Errorf("flag [%v] does not exists", key) } - return &flag, nil + return &f, nil } func (c *cacheImpl) AllFlags() (FlagsCache, error) { diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go index c1e8e7fb493..064250ac8c6 100644 --- a/internal/cache/cache_test.go +++ b/internal/cache/cache_test.go @@ -3,10 +3,10 @@ package cache_test import ( "github.com/stretchr/testify/assert" "github.com/thomaspoignant/go-feature-flag/internal/cache" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" "testing" - "github.com/thomaspoignant/go-feature-flag/internal/model" "github.com/thomaspoignant/go-feature-flag/internal/notifier" ) @@ -58,7 +58,7 @@ disable = false`) tests := []struct { name string args args - expected map[string]model.FlagData + expected map[string]flagv1.FlagData wantErr bool flagFormat string }{ @@ -68,7 +68,7 @@ disable = false`) args: args{ loadedFlags: yamlFile, }, - expected: map[string]model.FlagData{ + expected: map[string]flagv1.FlagData{ "test-flag": { Disable: nil, Rule: testconvert.String("key eq \"random-key\""), @@ -101,7 +101,7 @@ disable = false`) loadedFlags: jsonFile, }, flagFormat: "json", - expected: map[string]model.FlagData{ + expected: map[string]flagv1.FlagData{ "test-flag": { Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), @@ -134,13 +134,14 @@ disable = false`) loadedFlags: tomlFile, }, flagFormat: "toml", - expected: map[string]model.FlagData{ + expected: map[string]flagv1.FlagData{ "test-flag": { Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), + Disable: testconvert.Bool(false), }, }, wantErr: false, @@ -173,14 +174,7 @@ disable = false`), // If no error we compare with expected for key, expected := range tt.expected { got, _ := fCache.GetFlag(key) - assert.Equal(t, expected.GetRule(), got.GetRule()) - assert.Equal(t, expected.GetPercentage(), got.GetPercentage()) - assert.Equal(t, expected.GetTrue(), got.GetTrue()) - assert.Equal(t, expected.GetFalse(), got.GetFalse()) - assert.Equal(t, expected.GetDefault(), got.GetDefault()) - assert.Equal(t, expected.GetTrackEvents(), got.GetTrackEvents()) - assert.Equal(t, expected.GetDisable(), got.GetDisable()) - assert.Equal(t, expected.GetRollout(), got.GetRollout()) + assert.Equal(t, &expected, got) // nolint } fCache.Close() }) @@ -203,7 +197,7 @@ func Test_AllFlags(t *testing.T) { tests := []struct { name string args args - expected map[string]model.FlagData + expected map[string]flagv1.FlagData wantErr bool flagFormat string }{ @@ -213,7 +207,7 @@ func Test_AllFlags(t *testing.T) { args: args{ loadedFlags: yamlFile, }, - expected: map[string]model.FlagData{ + expected: map[string]flagv1.FlagData{ "test-flag": { Disable: nil, Rule: testconvert.String("key eq \"random-key\""), @@ -246,7 +240,7 @@ test-flag2: trackEvents: false `), }, - expected: map[string]model.FlagData{ + expected: map[string]flagv1.FlagData{ "test-flag": { Disable: nil, Rule: testconvert.String("key eq \"random-key\""), @@ -273,7 +267,7 @@ test-flag2: args: args{ loadedFlags: []byte(``), }, - expected: map[string]model.FlagData{}, + expected: map[string]flagv1.FlagData{}, wantErr: true, }, } @@ -293,14 +287,7 @@ test-flag2: // If no error we compare with expected for key, expected := range tt.expected { got := allFlags[key] - assert.Equal(t, expected.GetRule(), got.GetRule()) - assert.Equal(t, expected.GetPercentage(), got.GetPercentage()) - assert.Equal(t, expected.GetTrue(), got.GetTrue()) - assert.Equal(t, expected.GetFalse(), got.GetFalse()) - assert.Equal(t, expected.GetDefault(), got.GetDefault()) - assert.Equal(t, expected.GetTrackEvents(), got.GetTrackEvents()) - assert.Equal(t, expected.GetDisable(), got.GetDisable()) - assert.Equal(t, expected.GetRollout(), got.GetRollout()) + assert.Equal(t, expected, got) } fCache.Close() }) diff --git a/internal/cache/flag_cache.go b/internal/cache/flag_cache.go index 34e6df5110b..799f967b073 100644 --- a/internal/cache/flag_cache.go +++ b/internal/cache/flag_cache.go @@ -1,8 +1,8 @@ package cache -import model "github.com/thomaspoignant/go-feature-flag/internal/model" +import flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" -type FlagsCache map[string]model.FlagData +type FlagsCache map[string]flagv1.FlagData func (fc FlagsCache) Copy() FlagsCache { copyCache := make(FlagsCache) diff --git a/internal/cache/flag_cache_test.go b/internal/cache/flag_cache_test.go index 69968f3cd71..0130d07c4b5 100644 --- a/internal/cache/flag_cache_test.go +++ b/internal/cache/flag_cache_test.go @@ -2,9 +2,9 @@ package cache_test import ( "github.com/stretchr/testify/assert" - "github.com/thomaspoignant/go-feature-flag/internal/model" - "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" "github.com/thomaspoignant/go-feature-flag/internal/cache" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" + "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" "testing" ) @@ -16,7 +16,7 @@ func TestFlagsCache_Copy(t *testing.T) { { name: "Copy with values", fc: cache.FlagsCache{ - "test": model.FlagData{ + "test": flagv1.FlagData{ Disable: testconvert.Bool(false), Rule: testconvert.String("key eq \"toto\""), Percentage: testconvert.Float64(20), diff --git a/internal/cache/notification_service.go b/internal/cache/notification_service.go index 6da3382fef7..e9224c38951 100644 --- a/internal/cache/notification_service.go +++ b/internal/cache/notification_service.go @@ -2,6 +2,7 @@ package cache import ( "github.com/google/go-cmp/cmp" + "github.com/thomaspoignant/go-feature-flag/internal/flag" "sync" "github.com/thomaspoignant/go-feature-flag/internal/model" @@ -43,8 +44,8 @@ func (c *notificationService) Close() { func (c *notificationService) getDifferences( oldCache FlagsCache, newCache FlagsCache) model.DiffCache { diff := model.DiffCache{ - Deleted: map[string]model.Flag{}, - Added: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, + Added: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, } for key := range oldCache { @@ -65,8 +66,8 @@ func (c *notificationService) getDifferences( for key := range newCache { if _, inOldCache := oldCache[key]; !inOldCache { - flag := newCache[key] - diff.Added[key] = &flag + f := newCache[key] + diff.Added[key] = &f } } return diff diff --git a/internal/cache/notification_service_test.go b/internal/cache/notification_service_test.go index fc2d7ff5a34..1f4af9efccb 100644 --- a/internal/cache/notification_service_test.go +++ b/internal/cache/notification_service_test.go @@ -2,6 +2,8 @@ package cache import ( "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" "sync" "testing" @@ -28,13 +30,13 @@ func Test_notificationService_getDifferences(t *testing.T) { name: "Delete flag", args: args{ oldCache: FlagsCache{ - "test-flag": model.FlagData{ + "test-flag": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, - "test-flag2": model.FlagData{ + "test-flag2": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -42,7 +44,7 @@ func Test_notificationService_getDifferences(t *testing.T) { }, }, newCache: FlagsCache{ - "test-flag": model.FlagData{ + "test-flag": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -51,15 +53,15 @@ func Test_notificationService_getDifferences(t *testing.T) { }, }, want: model.DiffCache{ - Deleted: map[string]model.Flag{ - "test-flag2": &model.FlagData{ + Deleted: map[string]flag.Flag{ + "test-flag2": &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, }, - Added: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, }, }, @@ -67,7 +69,7 @@ func Test_notificationService_getDifferences(t *testing.T) { name: "Added flag", args: args{ oldCache: FlagsCache{ - "test-flag": model.FlagData{ + "test-flag": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -75,13 +77,13 @@ func Test_notificationService_getDifferences(t *testing.T) { }, }, newCache: FlagsCache{ - "test-flag": model.FlagData{ + "test-flag": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, - "test-flag2": model.FlagData{ + "test-flag2": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -90,15 +92,15 @@ func Test_notificationService_getDifferences(t *testing.T) { }, }, want: model.DiffCache{ - Added: map[string]model.Flag{ - "test-flag2": &model.FlagData{ + Added: map[string]flag.Flag{ + "test-flag2": &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, }, - Deleted: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, }, }, @@ -106,7 +108,7 @@ func Test_notificationService_getDifferences(t *testing.T) { name: "Updated flag", args: args{ oldCache: FlagsCache{ - "test-flag": model.FlagData{ + "test-flag": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -114,7 +116,7 @@ func Test_notificationService_getDifferences(t *testing.T) { }, }, newCache: FlagsCache{ - "test-flag": model.FlagData{ + "test-flag": flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -123,17 +125,17 @@ func Test_notificationService_getDifferences(t *testing.T) { }, }, want: model.DiffCache{ - Added: map[string]model.Flag{}, - Deleted: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{ "test-flag": { - Before: &model.FlagData{ + Before: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, - After: &model.FlagData{ + After: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), diff --git a/internal/exporter/data_exporter_test.go b/internal/exporter/data_exporter_test.go index 095771dab10..b3ddf1cf8fd 100644 --- a/internal/exporter/data_exporter_test.go +++ b/internal/exporter/data_exporter_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "io/ioutil" "log" "os" @@ -12,7 +13,6 @@ import ( "github.com/thomaspoignant/go-feature-flag/ffuser" "github.com/thomaspoignant/go-feature-flag/internal/exporter" - "github.com/thomaspoignant/go-feature-flag/internal/model" "github.com/thomaspoignant/go-feature-flag/testutils" ) @@ -25,7 +25,7 @@ func TestDataExporterScheduler_flushWithTime(t *testing.T) { inputEvents := []exporter.FeatureEvent{ exporter.NewFeatureEvent(ffuser.NewAnonymousUser("ABCD"), "random-key", - "YO", model.VariationDefault, false, 0), + "YO", flagv1.VariationDefault, false, 0), } for _, event := range inputEvents { @@ -46,7 +46,7 @@ func TestDataExporterScheduler_flushWithNumberOfEvents(t *testing.T) { var inputEvents []exporter.FeatureEvent for i := 0; i <= 100; i++ { inputEvents = append(inputEvents, exporter.NewFeatureEvent(ffuser.NewAnonymousUser("ABCD"), - "random-key", "YO", model.VariationDefault, false, 0)) + "random-key", "YO", flagv1.VariationDefault, false, 0)) } for _, event := range inputEvents { dc.AddEvent(event) @@ -64,7 +64,7 @@ func TestDataExporterScheduler_defaultFlush(t *testing.T) { var inputEvents []exporter.FeatureEvent for i := 0; i <= 100000; i++ { inputEvents = append(inputEvents, exporter.NewFeatureEvent(ffuser.NewAnonymousUser("ABCD"), - "random-key", "YO", model.VariationDefault, false, 0)) + "random-key", "YO", flagv1.VariationDefault, false, 0)) } for _, event := range inputEvents { dc.AddEvent(event) @@ -88,7 +88,7 @@ func TestDataExporterScheduler_exporterReturnError(t *testing.T) { var inputEvents []exporter.FeatureEvent for i := 0; i <= 200; i++ { inputEvents = append(inputEvents, exporter.NewFeatureEvent(ffuser.NewAnonymousUser("ABCD"), - "random-key", "YO", model.VariationDefault, false, 0)) + "random-key", "YO", flagv1.VariationDefault, false, 0)) } for _, event := range inputEvents { dc.AddEvent(event) @@ -109,7 +109,7 @@ func TestDataExporterScheduler_nonBulkExporter(t *testing.T) { var inputEvents []exporter.FeatureEvent for i := 0; i < 100; i++ { inputEvents = append(inputEvents, exporter.NewFeatureEvent(ffuser.NewAnonymousUser("ABCD"), - "random-key", "YO", model.VariationDefault, false, 0)) + "random-key", "YO", flagv1.VariationDefault, false, 0)) } for _, event := range inputEvents { dc.AddEvent(event) diff --git a/internal/exporter/feature_event.go b/internal/exporter/feature_event.go index 6c5a41fc512..f620ed24343 100644 --- a/internal/exporter/feature_event.go +++ b/internal/exporter/feature_event.go @@ -4,14 +4,13 @@ import ( "time" "github.com/thomaspoignant/go-feature-flag/ffuser" - "github.com/thomaspoignant/go-feature-flag/internal/model" ) func NewFeatureEvent( user ffuser.User, flagKey string, value interface{}, - variation model.VariationType, + variation string, failed bool, version float64) FeatureEvent { contextKind := "user" @@ -54,7 +53,7 @@ type FeatureEvent struct { // Variation of the flag requested. Flag variation values can be "True", "False", "Default" or "SdkDefault" // depending on which value was taken during flag evaluation. "SdkDefault" is used when an error is detected and the // default value passed during the call to your variation is used. - Variation model.VariationType `json:"variation"` + Variation string `json:"variation"` // Value of the feature flag returned by feature flag evaluation. Value interface{} `json:"value"` diff --git a/internal/flag/flag.go b/internal/flag/flag.go new file mode 100644 index 00000000000..10f077ec71d --- /dev/null +++ b/internal/flag/flag.go @@ -0,0 +1,37 @@ +package flag + +import ( + "github.com/thomaspoignant/go-feature-flag/ffuser" +) + +type Flag interface { + // Value is returning the Value associate to the flag (True / False / Default ) based + // if the flag apply to the user or not. + Value(flagName string, user ffuser.User) (interface{}, string) + + // String display correctly a flag with the right formatting + String() string + + // GetVersion is the getter for the field Version + // Default: 0.0 + GetVersion() float64 + + // GetTrackEvents is the getter of the field TrackEvents + // Default: true + GetTrackEvents() bool + + // GetDisable is the getter for the field Disable + // Default: false + GetDisable() bool + + // GetDefaultVariation return the name of the default variation (if something goes wrong) + GetDefaultVariation() string + + // GetVariationValue return the value of variation from his name + GetVariationValue(variationName string) interface{} + + // GetRawValues is returning a raw value of the Flag used by the notifiers + // We should not have any logic based on these values, this is only to + // display the information. + GetRawValues() map[string]string +} diff --git a/internal/flagstate/flag_state.go b/internal/flagstate/flag_state.go index d9c98dc1620..64c6c0c41b5 100644 --- a/internal/flagstate/flag_state.go +++ b/internal/flagstate/flag_state.go @@ -1,7 +1,6 @@ package flagstate import ( - "github.com/thomaspoignant/go-feature-flag/internal/model" "time" ) @@ -9,7 +8,7 @@ import ( func NewFlagState( trackEvents bool, value interface{}, - variationType model.VariationType, + variationType string, failed bool) FlagState { return FlagState{ Value: value, @@ -22,9 +21,9 @@ func NewFlagState( // FlagState represents the state of an individual feature flag, with regard to a specific user, when it was called. type FlagState struct { - Value interface{} `json:"value"` - Timestamp int64 `json:"timestamp"` - VariationType model.VariationType `json:"variationType"` - TrackEvents bool `json:"trackEvents"` - Failed bool `json:"-"` + Value interface{} `json:"value"` + Timestamp int64 `json:"timestamp"` + VariationType string `json:"variationType"` + TrackEvents bool `json:"trackEvents"` + Failed bool `json:"-"` } diff --git a/internal/model/flag.go b/internal/flagv1/flag_data.go similarity index 70% rename from internal/model/flag.go rename to internal/flagv1/flag_data.go index bd06714d05d..2e449a71141 100644 --- a/internal/model/flag.go +++ b/internal/flagv1/flag_data.go @@ -1,88 +1,25 @@ -package model +package flagv1 import ( "fmt" "github.com/nikunjy/rules/parser" - "hash/fnv" + "github.com/thomaspoignant/go-feature-flag/ffuser" + "github.com/thomaspoignant/go-feature-flag/internal/utils" "math" "strconv" "strings" "time" - - "github.com/thomaspoignant/go-feature-flag/ffuser" -) - -// VariationType enum which describe the decision taken -type VariationType string - -const ( - // VariationTrue is a constant to explain that we are using the "True" variation - VariationTrue VariationType = "True" - - // VariationFalse is a constant to explain that we are using the "False" variation - VariationFalse VariationType = "False" - - // VariationDefault is a constant to explain that we are using the "Default" variation - VariationDefault VariationType = "Default" - - // VariationSDKDefault is a constant to explain that we are using the default from the SDK variation - VariationSDKDefault VariationType = "SdkDefault" ) // percentageMultiplier is the multiplier used to have a bigger range of possibility. -const percentageMultiplier = 1000 - -type Flag interface { - // Value is returning the Value associate to the flag (True / False / Default ) based - // if the flag apply to the user or not. - Value(flagName string, user ffuser.User) (interface{}, VariationType) - - // String display correctly a flag with the right formatting - String() string - - // GetRule is the getter of the field Rule - // Default: empty string - GetRule() string - - // GetPercentage is the getter of the field Rule - // Default: 0.0 - GetPercentage() float64 - - // GetTrue is the getter of the field True - // Default: nil - GetTrue() interface{} - - // GetFalse is the getter of the field False - // Default: nil - GetFalse() interface{} - - // GetDefault is the getter of the field Default - // Default: nil - GetDefault() interface{} - - // GetTrackEvents is the getter of the field TrackEvents - // Default: true - GetTrackEvents() bool - - // GetDisable is the getter for the field Disable - // Default: false - GetDisable() bool - - // GetRollout is the getter for the field Rollout - // Default: nil - GetRollout() *Rollout - - // GetVersion is the getter for the field Version - // Default: 0.0 - GetVersion() float64 -} +const percentageMultiplier = float64(1000) // FlagData describe the fields of a flag. type FlagData struct { // Rule is the query use to select on which user the flag should apply. // Rule format is based on the nikunjy/rules module. // If no rule set, the flag apply to all users (percentage still apply). - Rule *string `json:"rule,omitempty" yaml:"rule,omitempty" toml:"rule,omitempty" slack_short:"false"` + Rule *string `json:"rule,omitempty" yaml:"rule,omitempty" toml:"rule,omitempty"` // Percentage of the users affect by the flag. // Default value is 0 @@ -108,12 +45,12 @@ type FlagData struct { // Rollout is the object to configure how the flag is rollout. // You have different rollout strategy available but only one is used at a time. - Rollout *Rollout `json:"rollout,omitempty" yaml:"rollout,omitempty" toml:"rollout,omitempty" slack_short:"false"` + Rollout *Rollout `json:"rollout,omitempty" yaml:"rollout,omitempty" toml:"rollout,omitempty"` // Version (optional) This field contains the version of the flag. // The version is manually managed when you configure your flags and it is used to display the information // in the notifications and data collection. - Version *float64 `json:"version,omitempty" yaml:"version,omitempty" toml:"version,omitempty" slack_short:"false"` + Version *float64 `json:"version,omitempty" yaml:"version,omitempty" toml:"version,omitempty"` } // Value is returning the Value associate to the flag (True / False / Default ) based @@ -122,20 +59,20 @@ func (f *FlagData) Value(flagName string, user ffuser.User) (interface{}, Variat f.updateFlagStage() if f.isExperimentationOver() { // if we have an experimentation that has not started or that is finished we use the default value. - return f.GetDefault(), VariationDefault + return f.getDefault(), VariationDefault } if f.evaluateRule(user) { if f.isInPercentage(flagName, user) { // Rule applied and user in the cohort. - return f.GetTrue(), VariationTrue + return f.getTrue(), VariationTrue } // Rule applied and user not in the cohort. - return f.GetFalse(), VariationFalse + return f.getFalse(), VariationFalse } // Default value is used if the rule does not applied to the user. - return f.GetDefault(), VariationDefault + return f.getDefault(), VariationDefault } func (f *FlagData) isExperimentationOver() bool { @@ -159,7 +96,7 @@ func (f *FlagData) isInPercentage(flagName string, user ffuser.User) bool { return true } - hashID := Hash(flagName+user.GetKey()) % maxPercentage + hashID := utils.Hash(flagName+user.GetKey()) % maxPercentage return hashID < uint32(percentage) } @@ -171,24 +108,24 @@ func (f *FlagData) evaluateRule(user ffuser.User) bool { } // No rule means that all user can be impacted. - if f.GetRule() == "" { + if f.getRule() == "" { return true } // Evaluate the rule on the user. - return parser.Evaluate(f.GetRule(), userToMap(user)) + return parser.Evaluate(f.getRule(), utils.UserToMap(user)) } // string display correctly a flag func (f FlagData) String() string { toString := []string{} - toString = append(toString, fmt.Sprintf("percentage=%d%%", int64(math.Round(f.GetPercentage())))) - if f.GetRule() != "" { - toString = append(toString, fmt.Sprintf("rule=\"%s\"", f.GetRule())) + toString = append(toString, fmt.Sprintf("percentage=%d%%", int64(math.Round(f.getPercentage())))) + if f.getRule() != "" { + toString = append(toString, fmt.Sprintf("rule=\"%s\"", f.getRule())) } - toString = append(toString, fmt.Sprintf("true=\"%v\"", f.GetTrue())) - toString = append(toString, fmt.Sprintf("false=\"%v\"", f.GetFalse())) - toString = append(toString, fmt.Sprintf("default=\"%v\"", f.GetDefault())) + toString = append(toString, fmt.Sprintf("true=\"%v\"", f.getTrue())) + toString = append(toString, fmt.Sprintf("false=\"%v\"", f.getFalse())) + toString = append(toString, fmt.Sprintf("default=\"%v\"", f.getDefault())) toString = append(toString, fmt.Sprintf("disable=\"%v\"", f.GetDisable())) if f.TrackEvents != nil { @@ -202,35 +139,10 @@ func (f FlagData) String() string { return strings.Join(toString, ", ") } -// Hash is taking a string and convert. -func Hash(s string) uint32 { - h := fnv.New32a() - _, err := h.Write([]byte(s)) - // if we have a problem to get the hash we return 0 - if err != nil { - return 0 - } - return h.Sum32() -} - -// userToMap convert the user to a MAP to use the query on it. -func userToMap(u ffuser.User) map[string]interface{} { - // We don't have a json copy of the user. - userCopy := make(map[string]interface{}) - - // Duplicate the map to keep User un-mutable - for key, value := range u.GetCustom() { - userCopy[key] = value - } - userCopy["anonymous"] = u.IsAnonymous() - userCopy["key"] = u.GetKey() - return userCopy -} - // getActualPercentage return the the actual percentage of the flag. // the result value is the version with the percentageMultiplier. func (f *FlagData) getActualPercentage() float64 { - flagPercentage := f.GetPercentage() * percentageMultiplier + flagPercentage := f.getPercentage() * percentageMultiplier if f.Rollout == nil || f.Rollout.Progressive == nil { return flagPercentage } @@ -327,7 +239,7 @@ func (f *FlagData) mergeChanges(stepFlag ScheduledStep) { } // GetRule is the getter of the field Rule -func (f *FlagData) GetRule() string { +func (f *FlagData) getRule() string { if f.Rule == nil { return "" } @@ -335,7 +247,7 @@ func (f *FlagData) GetRule() string { } // GetPercentage is the getter of the field Percentage -func (f *FlagData) GetPercentage() float64 { +func (f *FlagData) getPercentage() float64 { if f.Percentage == nil { return 0 } @@ -343,15 +255,15 @@ func (f *FlagData) GetPercentage() float64 { } // GetTrue is the getter of the field True -func (f *FlagData) GetTrue() interface{} { +func (f *FlagData) getTrue() interface{} { if f.True == nil { return nil } return *f.True } -// GetFalse is the getter of the field False -func (f *FlagData) GetFalse() interface{} { +// f.getFalse is the getter of the field False +func (f *FlagData) getFalse() interface{} { if f.False == nil { return nil } @@ -359,7 +271,7 @@ func (f *FlagData) GetFalse() interface{} { } // GetDefault is the getter of the field Default -func (f *FlagData) GetDefault() interface{} { +func (f *FlagData) getDefault() interface{} { if f.Default == nil { return nil } @@ -383,7 +295,7 @@ func (f *FlagData) GetDisable() bool { } // GetRollout is the getter for the field Rollout -func (f *FlagData) GetRollout() *Rollout { +func (f *FlagData) getRollout() *Rollout { return f.Rollout } @@ -394,3 +306,47 @@ func (f *FlagData) GetVersion() float64 { } return *f.Version } + +// GetVariationValue return the value of variation from his name +func (f *FlagData) GetVariationValue(variationName string) interface{} { + switch variationName { + case VariationDefault: + return f.getDefault() + case VariationTrue: + return f.getTrue() + case VariationFalse: + return f.getFalse() + default: + return nil + } +} + +func (f *FlagData) GetDefaultVariation() string { + return VariationDefault +} + +func (f *FlagData) GetRawValues() map[string]string { + var rawValues = make(map[string]string) + rawValues["Rule"] = f.getRule() + rawValues["Percentage"] = fmt.Sprintf("%.2f", f.getPercentage()) + + if f.getRollout() == nil { + rawValues["Rollout"] = "" + } else { + rawValues["Rollout"] = fmt.Sprintf("%v", f.getRollout()) + } + rawValues["True"] = convertNilEmpty(f.getTrue()) + rawValues["False"] = convertNilEmpty(f.getFalse()) + rawValues["Default"] = convertNilEmpty(f.getDefault()) + rawValues["TrackEvents"] = fmt.Sprintf("%t", f.GetTrackEvents()) + rawValues["Disable"] = fmt.Sprintf("%t", f.GetDisable()) + rawValues["Version"] = fmt.Sprintf("%v", f.GetVersion()) + return rawValues +} + +func convertNilEmpty(input interface{}) string { + if input == nil { + return "" + } + return fmt.Sprintf("%v", input) +} diff --git a/internal/model/flag_priv_test.go b/internal/flagv1/flag_priv_test.go similarity index 97% rename from internal/model/flag_priv_test.go rename to internal/flagv1/flag_priv_test.go index 46bd0db7bc0..0fdb2cb80df 100644 --- a/internal/model/flag_priv_test.go +++ b/internal/flagv1/flag_priv_test.go @@ -1,4 +1,4 @@ -package model +package flagv1 import ( "github.com/stretchr/testify/assert" @@ -249,21 +249,21 @@ func TestFlag_getPercentage(t *testing.T) { flag: FlagData{ Percentage: testconvert.Float64(100), }, - want: float64(100 * percentageMultiplier), + want: 100 * percentageMultiplier, }, { name: "No rollout strategy 0", flag: FlagData{ Percentage: testconvert.Float64(0), }, - want: float64(0 * percentageMultiplier), + want: 0 * percentageMultiplier, }, { name: "No rollout strategy 50", flag: FlagData{ Percentage: testconvert.Float64(50), }, - want: float64(50 * percentageMultiplier), + want: 50 * percentageMultiplier, }, { name: "Progressive rollout no explicit percentage", @@ -277,7 +277,7 @@ func TestFlag_getPercentage(t *testing.T) { }, }, }, - want: float64(50 * percentageMultiplier), + want: 50 * percentageMultiplier, }, { name: "Progressive rollout explicit initial percentage", diff --git a/internal/model/flag_pub_test.go b/internal/flagv1/flag_pub_test.go similarity index 80% rename from internal/model/flag_pub_test.go rename to internal/flagv1/flag_pub_test.go index 8dbae2ef43a..f1c59552be7 100644 --- a/internal/model/flag_pub_test.go +++ b/internal/flagv1/flag_pub_test.go @@ -1,12 +1,14 @@ -package model_test +package flagv1_test import ( + "fmt" "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "testing" "time" "github.com/thomaspoignant/go-feature-flag/ffuser" - "github.com/thomaspoignant/go-feature-flag/internal/model" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" ) @@ -18,7 +20,7 @@ func TestFlag_value(t *testing.T) { True interface{} False interface{} Default interface{} - Rollout model.Rollout + Rollout flagv1.Rollout } type args struct { flagName string @@ -26,7 +28,7 @@ func TestFlag_value(t *testing.T) { } type want struct { value interface{} - variationType model.VariationType + variationType flagv1.VariationType } tests := []struct { name string @@ -48,7 +50,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "default", - variationType: model.VariationDefault, + variationType: flagv1.VariationDefault, }, }, { @@ -66,7 +68,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "true", - variationType: model.VariationTrue, + variationType: flagv1.VariationTrue, }, }, { @@ -77,8 +79,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"7e50ee61-06ad-4bb0-9034-38ad7cdea9f5\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Now().Add(-1 * time.Minute)), End: nil, }, @@ -90,7 +92,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "true", - variationType: model.VariationTrue, + variationType: flagv1.VariationTrue, }, }, { @@ -101,8 +103,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"user66\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Now().Add(1 * time.Minute)), End: nil, }, @@ -114,7 +116,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "default", - variationType: model.VariationDefault, + variationType: flagv1.VariationDefault, }, }, { @@ -125,8 +127,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"7e50ee61-06ad-4bb0-9034-38ad7cdea9f5\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Now().Add(-1 * time.Minute)), End: testconvert.Time(time.Now().Add(1 * time.Minute)), }, @@ -138,7 +140,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "true", - variationType: model.VariationTrue, + variationType: flagv1.VariationTrue, }, }, { @@ -149,8 +151,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"user66\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Now().Add(1 * time.Minute)), End: testconvert.Time(time.Now().Add(2 * time.Minute)), }, @@ -162,7 +164,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "default", - variationType: model.VariationDefault, + variationType: flagv1.VariationDefault, }, }, { @@ -173,8 +175,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"user66\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Now().Add(-2 * time.Minute)), End: testconvert.Time(time.Now().Add(-1 * time.Minute)), }, @@ -186,7 +188,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "default", - variationType: model.VariationDefault, + variationType: flagv1.VariationDefault, }, }, { @@ -197,8 +199,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"user66\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: nil, End: testconvert.Time(time.Now().Add(-1 * time.Minute)), }, @@ -210,7 +212,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "default", - variationType: model.VariationDefault, + variationType: flagv1.VariationDefault, }, }, { @@ -221,8 +223,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"7e50ee61-06ad-4bb0-9034-38ad7cdea9f5\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: nil, End: testconvert.Time(time.Now().Add(1 * time.Minute)), }, @@ -234,7 +236,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "true", - variationType: model.VariationTrue, + variationType: flagv1.VariationTrue, }, }, { @@ -245,8 +247,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"7e50ee61-06ad-4bb0-9034-38ad7cdea9f5\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: nil, End: nil, }, @@ -258,7 +260,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "true", - variationType: model.VariationTrue, + variationType: flagv1.VariationTrue, }, }, { @@ -269,8 +271,8 @@ func TestFlag_value(t *testing.T) { Default: "default", Rule: "key == \"user66\"", Percentage: 10, - Rollout: model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Now().Add(1 * time.Minute)), End: testconvert.Time(time.Now().Add(-1 * time.Minute)), }, @@ -282,7 +284,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "default", - variationType: model.VariationDefault, + variationType: flagv1.VariationDefault, }, }, { @@ -300,7 +302,7 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "default", - variationType: model.VariationDefault, + variationType: flagv1.VariationDefault, }, }, { @@ -318,13 +320,13 @@ func TestFlag_value(t *testing.T) { }, want: want{ value: "false", - variationType: model.VariationFalse, + variationType: flagv1.VariationFalse, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - f := &model.FlagData{ + f := &flagv1.FlagData{ Disable: testconvert.Bool(tt.fields.Disable), Rule: testconvert.String(tt.fields.Rule), Percentage: testconvert.Float64(tt.fields.Percentage), @@ -342,13 +344,13 @@ func TestFlag_value(t *testing.T) { } func TestFlag_ProgressiveRollout(t *testing.T) { - f := &model.FlagData{ + f := &flagv1.FlagData{ Percentage: testconvert.Float64(0), True: testconvert.Interface("True"), False: testconvert.Interface("False"), Default: testconvert.Interface("Default"), - Rollout: &model.Rollout{Progressive: &model.Progressive{ - ReleaseRamp: model.ProgressiveReleaseRamp{ + Rollout: &flagv1.Rollout{Progressive: &flagv1.Progressive{ + ReleaseRamp: flagv1.ProgressiveReleaseRamp{ Start: testconvert.Time(time.Now().Add(1 * time.Second)), End: testconvert.Time(time.Now().Add(2 * time.Second)), }, @@ -360,41 +362,41 @@ func TestFlag_ProgressiveRollout(t *testing.T) { // We evaluate the same flag multiple time overtime. v, _ := f.Value(flagName, user) - assert.Equal(t, f.GetFalse(), v) + assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v) time.Sleep(1 * time.Second) v2, _ := f.Value(flagName, user) - assert.Equal(t, f.GetFalse(), v2) + assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v2) time.Sleep(1 * time.Second) v3, _ := f.Value(flagName, user) - assert.Equal(t, f.GetTrue(), v3) + assert.Equal(t, f.GetVariationValue(flagv1.VariationTrue), v3) } func TestFlag_ScheduledRollout(t *testing.T) { - f := &model.FlagData{ + f := &flagv1.FlagData{ Rule: testconvert.String("key eq \"test\""), Percentage: testconvert.Float64(0), True: testconvert.Interface("True"), False: testconvert.Interface("False"), Default: testconvert.Interface("Default"), - Rollout: &model.Rollout{ - Scheduled: &model.ScheduledRollout{ - Steps: []model.ScheduledStep{ + Rollout: &flagv1.Rollout{ + Scheduled: &flagv1.ScheduledRollout{ + Steps: []flagv1.ScheduledStep{ { - FlagData: model.FlagData{ + FlagData: flagv1.FlagData{ Version: testconvert.Float64(1.1), }, Date: testconvert.Time(time.Now().Add(1 * time.Second)), }, { - FlagData: model.FlagData{ + FlagData: flagv1.FlagData{ Percentage: testconvert.Float64(100), }, Date: testconvert.Time(time.Now().Add(1 * time.Second)), }, { - FlagData: model.FlagData{ + FlagData: flagv1.FlagData{ True: testconvert.Interface("True2"), False: testconvert.Interface("False2"), Default: testconvert.Interface("Default2"), @@ -403,7 +405,7 @@ func TestFlag_ScheduledRollout(t *testing.T) { Date: testconvert.Time(time.Now().Add(2 * time.Second)), }, { - FlagData: model.FlagData{ + FlagData: flagv1.FlagData{ True: testconvert.Interface("True2"), False: testconvert.Interface("False2"), Default: testconvert.Interface("Default2"), @@ -412,22 +414,22 @@ func TestFlag_ScheduledRollout(t *testing.T) { Date: testconvert.Time(time.Now().Add(3 * time.Second)), }, { - FlagData: model.FlagData{ + FlagData: flagv1.FlagData{ Disable: testconvert.Bool(true), }, Date: testconvert.Time(time.Now().Add(4 * time.Second)), }, { - FlagData: model.FlagData{ + FlagData: flagv1.FlagData{ Percentage: testconvert.Float64(0), }, }, { - FlagData: model.FlagData{ + FlagData: flagv1.FlagData{ Disable: testconvert.Bool(false), TrackEvents: testconvert.Bool(true), - Rollout: &model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: &flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Now().Add(6 * time.Second)), End: testconvert.Time(time.Now().Add(7 * time.Second)), }, @@ -445,7 +447,7 @@ func TestFlag_ScheduledRollout(t *testing.T) { // We evaluate the same flag multiple time overtime. v, _ := f.Value(flagName, user) - assert.Equal(t, f.GetFalse(), v) + assert.Equal(t, f.GetVariationValue(flagv1.VariationFalse), v) time.Sleep(1 * time.Second) @@ -537,7 +539,7 @@ func TestFlag_String(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - f := &model.FlagData{ + f := &flagv1.FlagData{ Disable: testconvert.Bool(tt.fields.Disable), Rule: testconvert.String(tt.fields.Rule), Percentage: testconvert.Float64(tt.fields.Percentage), @@ -558,21 +560,22 @@ func TestFlag_Getter(t *testing.T) { True interface{} False interface{} Default interface{} - Rollout *model.Rollout + Rollout *flagv1.Rollout Disable bool TrackEvents bool Percentage float64 Rule string Version float64 + RawValues map[string]string } tests := []struct { name string - flag model.Flag + flag flag.Flag want expected }{ { name: "all default", - flag: &model.FlagData{}, + flag: &flagv1.FlagData{}, want: expected{ True: nil, False: nil, @@ -583,11 +586,22 @@ func TestFlag_Getter(t *testing.T) { Percentage: 0, Rule: "", Version: 0, + RawValues: map[string]string{ + "Default": "", + "Disable": "false", + "False": "", + "Percentage": "0.00", + "Rollout": "", + "Rule": "", + "TrackEvents": "true", + "True": "", + "Version": "0", + }, }, }, { name: "custom flag", - flag: &model.FlagData{ + flag: &flagv1.FlagData{ Rule: testconvert.String("test"), Percentage: testconvert.Float64(90), True: testconvert.Interface(12.2), @@ -606,21 +620,30 @@ func TestFlag_Getter(t *testing.T) { Percentage: 90, Rule: "test", Version: 127, + RawValues: map[string]string{ + "Default": "14.2", + "Disable": "true", + "False": "13.2", + "Percentage": "90.00", + "Rollout": "", + "Rule": "test", + "TrackEvents": "false", + "True": "12.2", + "Version": "127", + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want.True, tt.flag.GetTrue()) - assert.Equal(t, tt.want.False, tt.flag.GetFalse()) - assert.Equal(t, tt.want.Default, tt.flag.GetDefault()) - assert.Equal(t, tt.want.Rollout, tt.flag.GetRollout()) assert.Equal(t, tt.want.Disable, tt.flag.GetDisable()) assert.Equal(t, tt.want.TrackEvents, tt.flag.GetTrackEvents()) - assert.Equal(t, tt.want.Percentage, tt.flag.GetPercentage()) - assert.Equal(t, tt.want.Rule, tt.flag.GetRule()) assert.Equal(t, tt.want.Version, tt.flag.GetVersion()) + assert.Equal(t, flagv1.VariationDefault, tt.flag.GetDefaultVariation()) + fmt.Println(tt.want.Default, tt.flag.GetVariationValue(tt.flag.GetDefaultVariation())) + assert.Equal(t, tt.want.Default, tt.flag.GetVariationValue(tt.flag.GetDefaultVariation())) + assert.Equal(t, tt.want.RawValues, tt.flag.GetRawValues()) }) } } diff --git a/internal/model/rollout.go b/internal/flagv1/rollout.go similarity index 94% rename from internal/model/rollout.go rename to internal/flagv1/rollout.go index 0c5dfadbbb8..6b03a0cb67c 100644 --- a/internal/model/rollout.go +++ b/internal/flagv1/rollout.go @@ -1,4 +1,4 @@ -package model +package flagv1 import ( "fmt" @@ -10,21 +10,23 @@ type Rollout struct { // Experimentation is your struct to configure an experimentation, it will allow you to configure a start date and // an end date for your flag. // When the experimentation is not running, the flag will serve the default value. - Experimentation *Experimentation `json:"experimentation,omitempty" yaml:"experimentation,omitempty" toml:"experimentation,omitempty" slack_short:"false"` // nolint: lll + Experimentation *Experimentation `json:"experimentation,omitempty" yaml:"experimentation,omitempty" toml:"experimentation,omitempty"` // nolint: lll // Progressive is your struct to configure a progressive rollout deployment of your flag. // It will allow you to ramp up the percentage of your flag over time. // You can decide at which percentage you starts and at what percentage you ends in your release ramp. // Before the start date we will serve the initial percentage and after we will serve the end percentage. - Progressive *Progressive `json:"progressive,omitempty" yaml:"progressive,omitempty" toml:"progressive,omitempty" slack_short:"false"` // nolint: lll + Progressive *Progressive `json:"progressive,omitempty" yaml:"progressive,omitempty" toml:"progressive,omitempty"` // nolint: lll // Scheduled is your struct to configure an update on some fields of your flag over time. // You can add several steps that updates the flag, this is typically used if you want to gradually add more user // in your flag. - Scheduled *ScheduledRollout `json:"scheduled,omitempty" yaml:"scheduled,omitempty" toml:"scheduled,omitempty" slack_short:"false"` // nolint: lll + Scheduled *ScheduledRollout `json:"scheduled,omitempty" yaml:"scheduled,omitempty" toml:"scheduled,omitempty"` // nolint: lll } + func (e Rollout) String() string { + // TODO: other rollout if e.Experimentation == nil { return "" } diff --git a/internal/model/rollout_test.go b/internal/flagv1/rollout_test.go similarity index 87% rename from internal/model/rollout_test.go rename to internal/flagv1/rollout_test.go index 339e3cfe966..babdbeb71f9 100644 --- a/internal/model/rollout_test.go +++ b/internal/flagv1/rollout_test.go @@ -1,11 +1,11 @@ -package model_test +package flagv1_test import ( "github.com/stretchr/testify/assert" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "testing" "time" - "github.com/thomaspoignant/go-feature-flag/internal/model" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" ) @@ -46,7 +46,7 @@ func TestExperimentation_String(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - e := model.Experimentation{ + e := flagv1.Experimentation{ End: tt.fields.End, Start: tt.fields.Start, } @@ -59,12 +59,12 @@ func TestExperimentation_String(t *testing.T) { func TestRollout_String(t *testing.T) { tests := []struct { name string - rollout model.Rollout + rollout flagv1.Rollout want string }{ { name: "experimentation", - rollout: model.Rollout{Experimentation: &model.Experimentation{ + rollout: flagv1.Rollout{Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Unix(1095379400, 0)), End: testconvert.Time(time.Unix(1095379500, 0)), }}, @@ -72,7 +72,7 @@ func TestRollout_String(t *testing.T) { }, { name: "empty", - rollout: model.Rollout{}, + rollout: flagv1.Rollout{}, want: "", }, } diff --git a/internal/flagv1/variation_type.go b/internal/flagv1/variation_type.go new file mode 100644 index 00000000000..0d9e7137c77 --- /dev/null +++ b/internal/flagv1/variation_type.go @@ -0,0 +1,18 @@ +package flagv1 + +// VariationType enum which describe the decision taken +type VariationType = string + +const ( + // VariationTrue is a constant to explain that we are using the "True" variation + VariationTrue VariationType = "True" + + // VariationFalse is a constant to explain that we are using the "False" variation + VariationFalse VariationType = "False" + + // VariationDefault is a constant to explain that we are using the "Default" variation + VariationDefault VariationType = "Default" + + // VariationSDKDefault is a constant to explain that we are using the default from the SDK variation + VariationSDKDefault VariationType = "SdkDefault" +) diff --git a/internal/model/diff_cache.go b/internal/model/diff_cache.go index 426ddaff06a..a62837c296a 100644 --- a/internal/model/diff_cache.go +++ b/internal/model/diff_cache.go @@ -1,10 +1,14 @@ package model +import ( + "github.com/thomaspoignant/go-feature-flag/internal/flag" +) + // DiffCache contains the changes made in the cache, to be able // to notify the user that something has changed (logs, webhook ...) type DiffCache struct { - Deleted map[string]Flag `json:"deleted"` - Added map[string]Flag `json:"added"` + Deleted map[string]flag.Flag `json:"deleted"` + Added map[string]flag.Flag `json:"added"` Updated map[string]DiffUpdated `json:"updated"` } @@ -14,6 +18,6 @@ func (d *DiffCache) HasDiff() bool { } type DiffUpdated struct { - Before Flag `json:"old_value"` - After Flag `json:"new_value"` + Before flag.Flag `json:"old_value"` + After flag.Flag `json:"new_value"` } diff --git a/internal/model/diff_cache_test.go b/internal/model/diff_cache_test.go index 318da580f93..b42bd72ca10 100644 --- a/internal/model/diff_cache_test.go +++ b/internal/model/diff_cache_test.go @@ -2,6 +2,8 @@ package model_test import ( "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "github.com/thomaspoignant/go-feature-flag/internal/model" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" "testing" @@ -9,8 +11,8 @@ import ( func TestDiffCache_HasDiff(t *testing.T) { type fields struct { - Deleted map[string]model.Flag - Added map[string]model.Flag + Deleted map[string]flag.Flag + Added map[string]flag.Flag Updated map[string]model.DiffUpdated } tests := []struct { @@ -26,8 +28,8 @@ func TestDiffCache_HasDiff(t *testing.T) { { name: "empty fields", fields: fields{ - Deleted: map[string]model.Flag{}, - Added: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, + Added: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, }, want: false, @@ -35,15 +37,15 @@ func TestDiffCache_HasDiff(t *testing.T) { { name: "only Deleted", fields: fields{ - Deleted: map[string]model.Flag{ - "flag": &model.FlagData{ + Deleted: map[string]flag.Flag{ + "flag": &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), Default: testconvert.Interface(true), }, }, - Added: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, }, want: true, @@ -51,15 +53,15 @@ func TestDiffCache_HasDiff(t *testing.T) { { name: "only Added", fields: fields{ - Added: map[string]model.Flag{ - "flag": &model.FlagData{ + Added: map[string]flag.Flag{ + "flag": &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), Default: testconvert.Interface(true), }, }, - Deleted: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, }, want: true, @@ -67,17 +69,17 @@ func TestDiffCache_HasDiff(t *testing.T) { { name: "only Updated", fields: fields{ - Added: map[string]model.Flag{}, - Deleted: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{ "flag": { - Before: &model.FlagData{ + Before: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), Default: testconvert.Interface(true), }, - After: &model.FlagData{ + After: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), @@ -91,16 +93,16 @@ func TestDiffCache_HasDiff(t *testing.T) { { name: "all fields", fields: fields{ - Added: map[string]model.Flag{ - "flag": &model.FlagData{ + Added: map[string]flag.Flag{ + "flag": &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), Default: testconvert.Interface(true), }, }, - Deleted: map[string]model.Flag{ - "flag": &model.FlagData{ + Deleted: map[string]flag.Flag{ + "flag": &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), @@ -109,13 +111,13 @@ func TestDiffCache_HasDiff(t *testing.T) { }, Updated: map[string]model.DiffUpdated{ "flag": { - Before: &model.FlagData{ + Before: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), Default: testconvert.Interface(true), }, - After: &model.FlagData{ + After: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(true), diff --git a/internal/model/variation_result.go b/internal/model/variation_result.go index e13a44bccc7..6b40177b04b 100644 --- a/internal/model/variation_result.go +++ b/internal/model/variation_result.go @@ -2,7 +2,7 @@ package model type VariationResult struct { TrackEvents bool - VariationType VariationType + VariationType string Failed bool Version float64 } diff --git a/internal/notifier/notifier_log_test.go b/internal/notifier/notifier_log_test.go index 7ac1bfe046e..5572def42cd 100644 --- a/internal/notifier/notifier_log_test.go +++ b/internal/notifier/notifier_log_test.go @@ -2,6 +2,8 @@ package notifier import ( "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" "io/ioutil" "log" @@ -27,8 +29,8 @@ func TestLogNotifier_Notify(t *testing.T) { name: "Flag deleted", args: args{ diff: model.DiffCache{ - Deleted: map[string]model.Flag{ - "test-flag": &model.FlagData{ + Deleted: map[string]flag.Flag{ + "test-flag": &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -36,7 +38,7 @@ func TestLogNotifier_Notify(t *testing.T) { }, }, Updated: map[string]model.DiffUpdated{}, - Added: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, }, wg: &sync.WaitGroup{}, }, @@ -46,17 +48,17 @@ func TestLogNotifier_Notify(t *testing.T) { name: "Update flag", args: args{ diff: model.DiffCache{ - Deleted: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{ "test-flag": { - Before: &model.FlagData{ + Before: &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, - After: &model.FlagData{ + After: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), @@ -64,7 +66,7 @@ func TestLogNotifier_Notify(t *testing.T) { }, }, }, - Added: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, }, wg: &sync.WaitGroup{}, }, @@ -74,17 +76,17 @@ func TestLogNotifier_Notify(t *testing.T) { name: "Disable flag", args: args{ diff: model.DiffCache{ - Deleted: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{ "test-flag": { - Before: &model.FlagData{ + Before: &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, - After: &model.FlagData{ + After: &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Disable: testconvert.Bool(true), Percentage: testconvert.Float64(100), @@ -94,7 +96,7 @@ func TestLogNotifier_Notify(t *testing.T) { }, }, }, - Added: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, }, wg: &sync.WaitGroup{}, }, @@ -104,10 +106,10 @@ func TestLogNotifier_Notify(t *testing.T) { name: "Add flag", args: args{ diff: model.DiffCache{ - Deleted: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, - Added: map[string]model.Flag{ - "add-test-flag": &model.FlagData{ + Added: map[string]flag.Flag{ + "add-test-flag": &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), @@ -124,17 +126,17 @@ func TestLogNotifier_Notify(t *testing.T) { name: "Enable flag", args: args{ diff: model.DiffCache{ - Deleted: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{ "test-flag": { - After: &model.FlagData{ + After: &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, - Before: &model.FlagData{ + Before: &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Disable: testconvert.Bool(true), Percentage: testconvert.Float64(100), @@ -144,7 +146,7 @@ func TestLogNotifier_Notify(t *testing.T) { }, }, }, - Added: map[string]model.Flag{}, + Added: map[string]flag.Flag{}, }, wg: &sync.WaitGroup{}, }, diff --git a/internal/notifier/notifier_slack.go b/internal/notifier/notifier_slack.go index 3e76a15b1b4..15fbbe1b0d0 100644 --- a/internal/notifier/notifier_slack.go +++ b/internal/notifier/notifier_slack.go @@ -9,7 +9,7 @@ import ( "net/http" "net/url" "os" - "strconv" + "sort" "sync" "github.com/thomaspoignant/go-feature-flag/internal" @@ -22,6 +22,7 @@ const slackFooter = "go-feature-flag" const colorDeleted = "#FF0000" const colorUpdated = "#FFA500" const colorAdded = "#008000" +const longSlackAttachment = 35 func NewSlackNotifier(logger *log.Logger, httpClient internal.HTTPClient, webhookURL string) SlackNotifier { slackURL, _ := url.Parse(webhookURL) @@ -96,7 +97,6 @@ func convertDeletedFlagsToSlackMessage(diff model.DiffCache) []attachment { func convertUpdatedFlagsToSlackMessage(diff model.DiffCache) []attachment { var attachments = make([]attachment, 0) - // updated flags - use reflection to list the flags for key, value := range diff.Updated { attachment := attachment{ Title: fmt.Sprintf("✏️ Flag \"%s\" updated", key), @@ -106,64 +106,24 @@ func convertUpdatedFlagsToSlackMessage(diff model.DiffCache) []attachment { Fields: []Field{}, } - before := value.Before - after := value.After - const compareFormat = "%v => %v" - - // rule - if before.GetRule() != after.GetRule() { - attachment.Fields = append(attachment.Fields, Field{Title: "Rule", Short: false, - Value: fmt.Sprintf(compareFormat, before.GetRule(), after.GetRule())}) - } - - // Percentage - if before.GetPercentage() != after.GetPercentage() { - attachment.Fields = append(attachment.Fields, Field{Title: "Percentage", Short: true, - Value: fmt.Sprintf(compareFormat, before.GetPercentage(), after.GetPercentage())}) - } - - // True - if before.GetTrue() != after.GetTrue() { - attachment.Fields = append(attachment.Fields, Field{Title: "True", Short: true, - Value: fmt.Sprintf(compareFormat, before.GetTrue(), after.GetTrue())}) - } - - // False - if before.GetFalse() != after.GetFalse() { - attachment.Fields = append(attachment.Fields, Field{Title: "False", Short: true, - Value: fmt.Sprintf(compareFormat, before.GetFalse(), after.GetFalse())}) - } - - // Default - if before.GetDefault() != after.GetDefault() { - attachment.Fields = append(attachment.Fields, Field{Title: "Default", Short: true, - Value: fmt.Sprintf(compareFormat, before.GetDefault(), after.GetDefault())}) - } - - // TrackEvents - if before.GetTrackEvents() != after.GetTrackEvents() { - attachment.Fields = append(attachment.Fields, Field{Title: "TrackEvents", Short: true, - Value: fmt.Sprintf(compareFormat, before.GetTrackEvents(), after.GetTrackEvents())}) + before := value.Before.GetRawValues() + after := value.After.GetRawValues() + sortedKey := sortedKeys(before) + for _, bKey := range sortedKey { + if before[bKey] != after[bKey] { + // format output if empty + if before[bKey] == "" { + before[bKey] = "" + } + if after[bKey] == "" { + after[bKey] = "" + } + + value := fmt.Sprintf("%v => %v", before[bKey], after[bKey]) + short := len(value) < longSlackAttachment + attachment.Fields = append(attachment.Fields, Field{Title: bKey, Short: short, Value: value}) + } } - - // Disable - if before.GetDisable() != after.GetDisable() { - attachment.Fields = append(attachment.Fields, Field{Title: "Disable", Short: true, - Value: fmt.Sprintf(compareFormat, before.GetDisable(), after.GetDisable())}) - } - - // Rollout - if before.GetRollout() != after.GetRollout() { - attachment.Fields = append(attachment.Fields, Field{Title: "Rollout", Short: false, - Value: fmt.Sprintf(compareFormat, before.GetRollout(), after.GetRollout())}) - } - - // Version - if before.GetVersion() != after.GetVersion() { - attachment.Fields = append(attachment.Fields, Field{Title: "Version", Short: true, - Value: fmt.Sprintf(compareFormat, before.GetVersion(), after.GetVersion())}) - } - attachments = append(attachments, attachment) } return attachments @@ -180,24 +140,14 @@ func convertAddedFlagsToSlackMessage(diff model.DiffCache) []attachment { Fields: []Field{}, } - attachment.Fields = append(attachment.Fields, Field{Title: "Rule", Short: false, - Value: fmt.Sprintf("%v", value.GetRule())}) - attachment.Fields = append(attachment.Fields, Field{Title: "Percentage", Short: true, - Value: fmt.Sprintf("%v", value.GetPercentage())}) - attachment.Fields = append(attachment.Fields, Field{Title: "True", Short: true, - Value: fmt.Sprintf("%v", value.GetTrue())}) - attachment.Fields = append(attachment.Fields, Field{Title: "False", Short: true, - Value: fmt.Sprintf("%v", value.GetFalse())}) - attachment.Fields = append(attachment.Fields, Field{Title: "Default", Short: true, - Value: fmt.Sprintf("%v", value.GetDefault())}) - attachment.Fields = append(attachment.Fields, Field{Title: "TrackEvents", Short: true, - Value: fmt.Sprintf("%v", value.GetTrackEvents())}) - attachment.Fields = append(attachment.Fields, Field{Title: "Disable", Short: true, - Value: fmt.Sprintf("%v", value.GetDisable())}) - - if value.GetVersion() != 0 { - attachment.Fields = append(attachment.Fields, Field{Title: "Version", Short: true, - Value: strconv.FormatFloat(value.GetVersion(), 'f', -1, 64)}) + rawValues := value.GetRawValues() + sortedKey := sortedKeys(rawValues) + for _, bKey := range sortedKey { + if rawValues[bKey] != "" { + value := fmt.Sprintf("%v", rawValues[bKey]) + short := len(value) < longSlackAttachment + attachment.Fields = append(attachment.Fields, Field{Title: bKey, Short: short, Value: value}) + } } attachments = append(attachments, attachment) } @@ -223,3 +173,14 @@ type Field struct { Value string `json:"value"` Short bool `json:"short"` } + +func sortedKeys(m map[string]string) []string { + keys := make([]string, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Strings(keys) + return keys +} diff --git a/internal/notifier/notifier_slack_test.go b/internal/notifier/notifier_slack_test.go index ae683df700e..88cd755cdb4 100644 --- a/internal/notifier/notifier_slack_test.go +++ b/internal/notifier/notifier_slack_test.go @@ -2,6 +2,8 @@ package notifier_test import ( "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "github.com/thomaspoignant/go-feature-flag/internal/notifier" "io/ioutil" "log" @@ -42,8 +44,8 @@ func TestSlackNotifier_Notify(t *testing.T) { args: args{ statusCode: http.StatusOK, diff: model.DiffCache{ - Added: map[string]model.Flag{ - "test-flag3": &model.FlagData{ + Added: map[string]flag.Flag{ + "test-flag3": &flagv1.FlagData{ Percentage: testconvert.Float64(5), True: testconvert.Interface("test"), False: testconvert.Interface("false"), @@ -54,8 +56,8 @@ func TestSlackNotifier_Notify(t *testing.T) { Version: testconvert.Float64(1.1), }, }, - Deleted: map[string]model.Flag{ - "test-flag": &model.FlagData{ + Deleted: map[string]flag.Flag{ + "test-flag": &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), @@ -65,21 +67,20 @@ func TestSlackNotifier_Notify(t *testing.T) { }, Updated: map[string]model.DiffUpdated{ "test-flag2": { - Before: &model.FlagData{ - Rule: testconvert.String("key eq \"not-a-key\""), + Before: &flagv1.FlagData{ Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), Disable: testconvert.Bool(false), TrackEvents: testconvert.Bool(true), - Rollout: &model.Rollout{ - Experimentation: &model.Experimentation{ + Rollout: &flagv1.Rollout{ + Experimentation: &flagv1.Experimentation{ Start: testconvert.Time(time.Unix(1095379400, 0)), End: testconvert.Time(time.Unix(1095371000, 0)), }}, }, - After: &model.FlagData{ + After: &flagv1.FlagData{ Rule: testconvert.String("key eq \"not-a-ke\""), Percentage: testconvert.Float64(80), True: testconvert.Interface("strTrue"), diff --git a/internal/notifier/notifier_webhook_test.go b/internal/notifier/notifier_webhook_test.go index 29a6f75aede..ea43568b1c6 100644 --- a/internal/notifier/notifier_webhook_test.go +++ b/internal/notifier/notifier_webhook_test.go @@ -2,6 +2,8 @@ package notifier import ( "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" "io/ioutil" "log" @@ -48,16 +50,16 @@ func Test_webhookNotifier_Notify(t *testing.T) { args: args{ statusCode: http.StatusOK, diff: model.DiffCache{ - Added: map[string]model.Flag{ - "test-flag3": &model.FlagData{ + Added: map[string]flag.Flag{ + "test-flag3": &flagv1.FlagData{ Percentage: testconvert.Float64(5), True: testconvert.Interface("test"), False: testconvert.Interface("false"), Default: testconvert.Interface("default"), }, }, - Deleted: map[string]model.Flag{ - "test-flag": &model.FlagData{ + Deleted: map[string]flag.Flag{ + "test-flag": &flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), @@ -67,14 +69,14 @@ func Test_webhookNotifier_Notify(t *testing.T) { }, Updated: map[string]model.DiffUpdated{ "test-flag2": { - Before: &model.FlagData{ + Before: &flagv1.FlagData{ Rule: testconvert.String("key eq \"not-a-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), False: testconvert.Interface(false), Default: testconvert.Interface(false), }, - After: &model.FlagData{ + After: &flagv1.FlagData{ Rule: testconvert.String("key eq \"not-a-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), @@ -96,15 +98,15 @@ func Test_webhookNotifier_Notify(t *testing.T) { args: args{ statusCode: http.StatusOK, diff: model.DiffCache{ - Added: map[string]model.Flag{ - "test-flag3": &model.FlagData{ + Added: map[string]flag.Flag{ + "test-flag3": &flagv1.FlagData{ Percentage: testconvert.Float64(5), True: testconvert.Interface("test"), False: testconvert.Interface("false"), Default: testconvert.Interface("default"), }, }, - Deleted: map[string]model.Flag{}, + Deleted: map[string]flag.Flag{}, Updated: map[string]model.DiffUpdated{}, }, }, diff --git a/internal/utils/hash.go b/internal/utils/hash.go new file mode 100644 index 00000000000..d2f358ceca5 --- /dev/null +++ b/internal/utils/hash.go @@ -0,0 +1,14 @@ +package utils + +import "hash/fnv" + +// Hash is taking a string and convert. +func Hash(s string) uint32 { + h := fnv.New32a() + _, err := h.Write([]byte(s)) + // if we have a problem to get the hash we return 0 + if err != nil { + return 0 + } + return h.Sum32() +} diff --git a/internal/utils/serializer.go b/internal/utils/serializer.go new file mode 100644 index 00000000000..5f5e7b791da --- /dev/null +++ b/internal/utils/serializer.go @@ -0,0 +1,17 @@ +package utils + +import "github.com/thomaspoignant/go-feature-flag/ffuser" + +// UserToMap convert the user to a MAP to use the query on it. +func UserToMap(u ffuser.User) map[string]interface{} { + // We don't have a json copy of the user. + userCopy := make(map[string]interface{}) + + // Duplicate the map to keep User un-mutable + for key, value := range u.GetCustom() { + userCopy[key] = value + } + userCopy["anonymous"] = u.IsAnonymous() + userCopy["key"] = u.GetKey() + return userCopy +} diff --git a/testdata/internal/notifier/slack/should_call_webhook_and_have_valid_results.json b/testdata/internal/notifier/slack/should_call_webhook_and_have_valid_results.json index 6176e79e911..1500059371e 100644 --- a/testdata/internal/notifier/slack/should_call_webhook_and_have_valid_results.json +++ b/testdata/internal/notifier/slack/should_call_webhook_and_have_valid_results.json @@ -14,18 +14,13 @@ "title": "✏️ Flag \"test-flag2\" updated", "fields": [ { - "title": "Rule", - "value": "key eq \"not-a-key\" =\u003e key eq \"not-a-ke\"", - "short": false - }, - { - "title": "Percentage", - "value": "100 =\u003e 80", + "title": "Default", + "value": "false =\u003e strDefault", "short": true }, { - "title": "True", - "value": "true =\u003e strTrue", + "title": "Disable", + "value": "false =\u003e true", "short": true }, { @@ -34,24 +29,29 @@ "short": true }, { - "title": "Default", - "value": "false =\u003e strDefault", + "title": "Percentage", + "value": "100.00 =\u003e 80.00", "short": true }, { - "title": "TrackEvents", - "value": "true =\u003e false", + "title": "Rollout", + "value": "experimentation: start:[2004-09-17T00:03:20Z] end:[2004-09-16T21:43:20Z] =\u003e \u003cempty\u003e", + "short": false + }, + { + "title": "Rule", + "value": " =\u003e key eq \"not-a-ke\"", "short": true }, { - "title": "Disable", - "value": "false =\u003e true", + "title": "TrackEvents", + "value": "true =\u003e false", "short": true }, { - "title": "Rollout", - "value": "experimentation: start:[2004-09-17T00:03:20Z] end:[2004-09-16T21:43:20Z] => ", - "short": false + "title": "True", + "value": "true =\u003e strTrue", + "short": true }, { "title": "Version", @@ -67,18 +67,13 @@ "title": "🆕 Flag \"test-flag3\" created", "fields": [ { - "title": "Rule", - "value": "key eq \"random-key\"", - "short": false - }, - { - "title": "Percentage", - "value": "5", + "title": "Default", + "value": "default", "short": true }, { - "title": "True", - "value": "test", + "title": "Disable", + "value": "false", "short": true }, { @@ -87,8 +82,13 @@ "short": true }, { - "title": "Default", - "value": "default", + "title": "Percentage", + "value": "5.00", + "short": true + }, + { + "title": "Rule", + "value": "key eq \"random-key\"", "short": true }, { @@ -97,8 +97,8 @@ "short": true }, { - "title": "Disable", - "value": "false", + "title": "True", + "value": "test", "short": true }, { diff --git a/variation.go b/variation.go index 15208807fb2..d0690e390da 100644 --- a/variation.go +++ b/variation.go @@ -2,18 +2,19 @@ package ffclient import ( "fmt" - "github.com/thomaspoignant/go-feature-flag/internal/cache" - "github.com/thomaspoignant/go-feature-flag/ffuser" + "github.com/thomaspoignant/go-feature-flag/internal/cache" "github.com/thomaspoignant/go-feature-flag/internal/exporter" + "github.com/thomaspoignant/go-feature-flag/internal/flag" "github.com/thomaspoignant/go-feature-flag/internal/flagstate" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "github.com/thomaspoignant/go-feature-flag/internal/model" ) const errorFlagNotAvailable = "flag %v is not present or disabled" const errorWrongVariation = "wrong variation used for flag %v" -var offlineVariationResult = model.VariationResult{VariationType: model.VariationSDKDefault, Failed: true} +var offlineVariationResult = model.VariationResult{VariationType: flagv1.VariationSDKDefault, Failed: true} // BoolVariation return the value of the flag in boolean. // An error is return if you don't have init the library before calling the function. @@ -140,38 +141,17 @@ func (g *GoFeatureFlag) AllFlagsState(user ffuser.User) flagstate.AllFlags { } allFlags := flagstate.NewAllFlags() - for key, flag := range flags { - // True is not configured the flag is not valid - if flags[key].True == nil { + for key, currentFlag := range flags { + flagValue, varType := currentFlag.Value(key, user) + switch v := flagValue; v.(type) { + case int, float64, bool, string, []interface{}, map[string]interface{}: + allFlags.AddFlag(key, flagstate.NewFlagState(currentFlag.GetTrackEvents(), v, varType, false)) + + default: + defaultVariationName := currentFlag.GetDefaultVariation() + defaultVariationValue := currentFlag.GetVariationValue(defaultVariationName) allFlags.AddFlag( - key, flagstate.NewFlagState(flag.GetTrackEvents(), flag.GetDefault(), model.VariationDefault, true)) - continue - } - - switch trueValue := *flags[key].True; trueValue.(type) { - case int: - f, _ := g.intVariation(key, user, 0) - allFlags.AddFlag(key, flagstate.NewFlagState(f.TrackEvents, f.Value, f.VariationType, f.Failed)) - - case float64: - f, _ := g.float64Variation(key, user, 0) - allFlags.AddFlag(key, flagstate.NewFlagState(f.TrackEvents, f.Value, f.VariationType, f.Failed)) - - case bool: - f, _ := g.boolVariation(key, user, false) - allFlags.AddFlag(key, flagstate.NewFlagState(f.TrackEvents, f.Value, f.VariationType, f.Failed)) - - case string: - f, _ := g.stringVariation(key, user, "") - allFlags.AddFlag(key, flagstate.NewFlagState(f.TrackEvents, f.Value, f.VariationType, f.Failed)) - - case []interface{}: - f, _ := g.jsonArrayVariation(key, user, nil) - allFlags.AddFlag(key, flagstate.NewFlagState(f.TrackEvents, f.Value, f.VariationType, f.Failed)) - - case map[string]interface{}: - f, _ := g.jsonVariation(key, user, nil) - allFlags.AddFlag(key, flagstate.NewFlagState(f.TrackEvents, f.Value, f.VariationType, f.Failed)) + key, flagstate.NewFlagState(currentFlag.GetTrackEvents(), defaultVariationValue, defaultVariationName, true)) } } return allFlags @@ -189,7 +169,7 @@ func (g *GoFeatureFlag) boolVariation(flagKey string, user ffuser.User, sdkDefau if err != nil { return model.BoolVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, err } @@ -198,7 +178,7 @@ func (g *GoFeatureFlag) boolVariation(flagKey string, user ffuser.User, sdkDefau if !ok { return model.BoolVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, fmt.Errorf(errorWrongVariation, flagKey) } return model.BoolVarResult{Value: res, @@ -217,7 +197,7 @@ func (g *GoFeatureFlag) intVariation(flagKey string, user ffuser.User, sdkDefaul flag, err := g.getFlagFromCache(flagKey) if err != nil { return model.IntVarResult{Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, err } @@ -234,7 +214,7 @@ func (g *GoFeatureFlag) intVariation(flagKey string, user ffuser.User, sdkDefaul return model.IntVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, fmt.Errorf(errorWrongVariation, flagKey) } return model.IntVarResult{Value: res, @@ -254,7 +234,7 @@ func (g *GoFeatureFlag) float64Variation(flagKey string, user ffuser.User, sdkDe if err != nil { return model.Float64VarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, err } @@ -263,7 +243,7 @@ func (g *GoFeatureFlag) float64Variation(flagKey string, user ffuser.User, sdkDe if !ok { return model.Float64VarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, fmt.Errorf(errorWrongVariation, flagKey) } return model.Float64VarResult{ @@ -284,7 +264,7 @@ func (g *GoFeatureFlag) stringVariation(flagKey string, user ffuser.User, sdkDef if err != nil { return model.StringVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, err } @@ -293,7 +273,7 @@ func (g *GoFeatureFlag) stringVariation(flagKey string, user ffuser.User, sdkDef if !ok { return model.StringVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, fmt.Errorf(errorWrongVariation, flagKey) } return model.StringVarResult{ @@ -314,7 +294,7 @@ func (g *GoFeatureFlag) jsonArrayVariation(flagKey string, user ffuser.User, sdk if err != nil { return model.JSONArrayVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, err } @@ -323,7 +303,7 @@ func (g *GoFeatureFlag) jsonArrayVariation(flagKey string, user ffuser.User, sdk if !ok { return model.JSONArrayVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, fmt.Errorf(errorWrongVariation, flagKey) } return model.JSONArrayVarResult{ @@ -344,7 +324,7 @@ func (g *GoFeatureFlag) jsonVariation(flagKey string, user ffuser.User, sdkDefau if err != nil { return model.JSONVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, err } @@ -353,7 +333,7 @@ func (g *GoFeatureFlag) jsonVariation(flagKey string, user ffuser.User, sdkDefau if !ok { return model.JSONVarResult{ Value: sdkDefaultValue, - VariationResult: computeVariationResult(flag, model.VariationSDKDefault, true), + VariationResult: computeVariationResult(flag, flagv1.VariationSDKDefault, true), }, fmt.Errorf(errorWrongVariation, flagKey) } return model.JSONVarResult{ @@ -363,7 +343,7 @@ func (g *GoFeatureFlag) jsonVariation(flagKey string, user ffuser.User, sdkDefau } // computeVariationResult is creating a model.VariationResult -func computeVariationResult(flag model.Flag, variationType model.VariationType, failed bool) model.VariationResult { +func computeVariationResult(flag flag.Flag, variationType string, failed bool) model.VariationResult { varResult := model.VariationResult{ VariationType: variationType, Failed: failed, @@ -396,7 +376,7 @@ func (g *GoFeatureFlag) notifyVariation( // getFlagFromCache try to get the flag from the cache. // It returns an error if the cache is not init or if the flag is not present or disabled. -func (g *GoFeatureFlag) getFlagFromCache(flagKey string) (model.Flag, error) { +func (g *GoFeatureFlag) getFlagFromCache(flagKey string) (flag.Flag, error) { flag, err := g.cache.GetFlag(flagKey) if err != nil || flag.GetDisable() { return flag, fmt.Errorf(errorFlagNotAvailable, flagKey) diff --git a/variation_test.go b/variation_test.go index fefd455d5be..9f30fd7f9cf 100644 --- a/variation_test.go +++ b/variation_test.go @@ -5,6 +5,8 @@ import ( "encoding/json" "errors" "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/internal/flag" + flagv1 "github.com/thomaspoignant/go-feature-flag/internal/flagv1" "io/ioutil" "log" "os" @@ -15,17 +17,16 @@ import ( "github.com/thomaspoignant/go-feature-flag/ffuser" "github.com/thomaspoignant/go-feature-flag/internal/cache" "github.com/thomaspoignant/go-feature-flag/internal/exporter" - "github.com/thomaspoignant/go-feature-flag/internal/model" "github.com/thomaspoignant/go-feature-flag/testutils" "github.com/thomaspoignant/go-feature-flag/testutils/testconvert" ) type cacheMock struct { - flag model.Flag + flag flag.Flag err error } -func NewCacheMock(flag model.Flag, err error) cache.Cache { +func NewCacheMock(flag flag.Flag, err error) cache.Cache { return &cacheMock{ flag: flag, err: err, @@ -35,7 +36,7 @@ func (c *cacheMock) UpdateCache(loadedFlags []byte, fileFormat string) error { return nil } func (c *cacheMock) Close() {} -func (c *cacheMock) GetFlag(key string) (model.Flag, error) { +func (c *cacheMock) GetFlag(key string) (flag.Flag, error) { return c.flag, c.err } func (c *cacheMock) AllFlags() (cache.FlagsCache, error) { return nil, nil } @@ -61,7 +62,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: true, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -76,7 +77,7 @@ func TestBoolVariation(t *testing.T) { user: ffuser.NewUser("random-key"), defaultValue: true, cacheMock: NewCacheMock( - &model.FlagData{}, + &flagv1.FlagData{}, errors.New("impossible to read the toggle before the initialisation")), }, want: true, @@ -89,7 +90,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "key-not-exist", user: ffuser.NewUser("random-key"), defaultValue: true, - cacheMock: NewCacheMock(&model.FlagData{}, errors.New("flag [key-not-exist] does not exists")), + cacheMock: NewCacheMock(&flagv1.FlagData{}, errors.New("flag [key-not-exist] does not exists")), }, want: true, wantErr: true, @@ -101,7 +102,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key"), defaultValue: true, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(true), @@ -119,7 +120,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: true, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(false), @@ -137,7 +138,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key-ssss1"), defaultValue: true, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(10), Default: testconvert.Interface(true), @@ -155,7 +156,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key-ssss1"), defaultValue: true, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Percentage: testconvert.Float64(100), Default: testconvert.Interface("xxx"), True: testconvert.Interface("xxx"), @@ -172,7 +173,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: true, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), True: testconvert.Interface(true), @@ -192,7 +193,7 @@ func TestBoolVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: false, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -263,7 +264,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: 120.12, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -278,7 +279,7 @@ func TestFloat64Variation(t *testing.T) { user: ffuser.NewUser("random-key"), defaultValue: 118.12, cacheMock: NewCacheMock( - &model.FlagData{}, + &flagv1.FlagData{}, errors.New("impossible to read the toggle before the initialisation")), }, want: 118.12, @@ -291,7 +292,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "key-not-exist", user: ffuser.NewUser("random-key"), defaultValue: 118.12, - cacheMock: NewCacheMock(&model.FlagData{}, errors.New("flag [key-not-exist] does not exists")), + cacheMock: NewCacheMock(&flagv1.FlagData{}, errors.New("flag [key-not-exist] does not exists")), }, want: 118.12, wantErr: true, @@ -303,7 +304,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key"), defaultValue: 118.12, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(119.12), @@ -321,7 +322,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: 118.12, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(119.12), @@ -339,7 +340,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key-ssss1"), defaultValue: 118.12, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(10), Default: testconvert.Interface(119.12), @@ -357,7 +358,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key-ssss1"), defaultValue: 118.12, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Percentage: testconvert.Float64(100), Default: testconvert.Interface("xxx"), True: testconvert.Interface("xxx"), @@ -374,7 +375,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: 118.12, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(119.12), @@ -394,7 +395,7 @@ func TestFloat64Variation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: 118.12, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -464,7 +465,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -479,7 +480,7 @@ func TestJSONArrayVariation(t *testing.T) { user: ffuser.NewUser("random-key"), defaultValue: []interface{}{"toto"}, cacheMock: NewCacheMock( - &model.FlagData{}, + &flagv1.FlagData{}, errors.New("impossible to read the toggle before the initialisation")), }, want: []interface{}{"toto"}, @@ -492,7 +493,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "key-not-exist", user: ffuser.NewUser("random-key"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{}, errors.New("flag [key-not-exist] does not exists")), + cacheMock: NewCacheMock(&flagv1.FlagData{}, errors.New("flag [key-not-exist] does not exists")), }, want: []interface{}{"toto"}, wantErr: true, @@ -504,7 +505,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface([]interface{}{"default"}), @@ -522,7 +523,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface([]interface{}{"default"}), @@ -540,7 +541,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key-ssss1"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(10), Default: testconvert.Interface([]interface{}{"default"}), @@ -558,7 +559,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key-ssss1"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Percentage: testconvert.Float64(100), Default: testconvert.Interface("xxx"), True: testconvert.Interface("xxx"), @@ -575,7 +576,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key-ssss1"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Percentage: testconvert.Float64(100), Default: testconvert.Interface([]interface{}{"default"}), True: testconvert.Interface([]interface{}{"true"}), @@ -593,7 +594,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key-ssss1"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(10), Default: testconvert.Interface([]interface{}{"default"}), @@ -613,7 +614,7 @@ func TestJSONArrayVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: []interface{}{"toto"}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -679,7 +680,7 @@ func TestJSONVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: map[string]interface{}{"default-notkey": true}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -694,7 +695,7 @@ func TestJSONVariation(t *testing.T) { user: ffuser.NewUser("random-key"), defaultValue: map[string]interface{}{"default-notkey": true}, cacheMock: NewCacheMock( - &model.FlagData{}, + &flagv1.FlagData{}, errors.New("impossible to read the toggle before the initialisation")), }, want: map[string]interface{}{"default-notkey": true}, @@ -707,7 +708,7 @@ func TestJSONVariation(t *testing.T) { flagKey: "key-not-exist", user: ffuser.NewUser("random-key"), defaultValue: map[string]interface{}{"default-notkey": true}, - cacheMock: NewCacheMock(&model.FlagData{}, errors.New("flag [key-not-exist] does not exists")), + cacheMock: NewCacheMock(&flagv1.FlagData{}, errors.New("flag [key-not-exist] does not exists")), }, want: map[string]interface{}{"default-notkey": true}, wantErr: true, @@ -719,7 +720,7 @@ func TestJSONVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key"), defaultValue: map[string]interface{}{"default-notkey": true}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(map[string]interface{}{"default": true}), @@ -737,7 +738,7 @@ func TestJSONVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: map[string]interface{}{"default-notkey": true}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(map[string]interface{}{"default": true}), @@ -755,7 +756,7 @@ func TestJSONVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key-ssss1"), defaultValue: map[string]interface{}{"default-notkey": true}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(10), Default: testconvert.Interface(map[string]interface{}{"default": true}), @@ -773,7 +774,7 @@ func TestJSONVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key-ssss1"), defaultValue: map[string]interface{}{"default-notkey": true}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Percentage: testconvert.Float64(100), Default: testconvert.Interface("xxx"), True: testconvert.Interface("xxx"), @@ -791,7 +792,7 @@ func TestJSONVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: map[string]interface{}{"default-notkey": true}, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -862,7 +863,7 @@ func TestStringVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: "default-notkey", - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -877,7 +878,7 @@ func TestStringVariation(t *testing.T) { user: ffuser.NewUser("random-key"), defaultValue: "default-notkey", cacheMock: NewCacheMock( - &model.FlagData{}, + &flagv1.FlagData{}, errors.New("impossible to read the toggle before the initialisation")), }, want: "default-notkey", @@ -890,7 +891,7 @@ func TestStringVariation(t *testing.T) { flagKey: "key-not-exist", user: ffuser.NewUser("random-key"), defaultValue: "default-notkey", - cacheMock: NewCacheMock(&model.FlagData{}, errors.New("flag [key-not-exist] does not exists")), + cacheMock: NewCacheMock(&flagv1.FlagData{}, errors.New("flag [key-not-exist] does not exists")), }, want: "default-notkey", wantErr: true, @@ -903,7 +904,7 @@ func TestStringVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key"), defaultValue: "default-notkey", - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface("default"), @@ -921,7 +922,7 @@ func TestStringVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: "default-notkey", - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface("default"), @@ -939,7 +940,7 @@ func TestStringVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key-ssss1"), defaultValue: "default-notkey", - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(10), Default: testconvert.Interface("default"), @@ -957,7 +958,7 @@ func TestStringVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key-ssss1"), defaultValue: "default-notkey", - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(50), Default: testconvert.Interface(111), @@ -976,7 +977,7 @@ func TestStringVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: "default-notkey", - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -1046,7 +1047,7 @@ func TestIntVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: 125, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), }, @@ -1061,7 +1062,7 @@ func TestIntVariation(t *testing.T) { user: ffuser.NewUser("random-key"), defaultValue: 118, cacheMock: NewCacheMock( - &model.FlagData{}, + &flagv1.FlagData{}, errors.New("impossible to read the toggle before the initialisation")), }, want: 118, @@ -1074,7 +1075,7 @@ func TestIntVariation(t *testing.T) { flagKey: "key-not-exist", user: ffuser.NewUser("random-key"), defaultValue: 118, - cacheMock: NewCacheMock(&model.FlagData{}, errors.New("flag [key-not-exist] does not exists")), + cacheMock: NewCacheMock(&flagv1.FlagData{}, errors.New("flag [key-not-exist] does not exists")), }, want: 118, wantErr: true, @@ -1086,7 +1087,7 @@ func TestIntVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key"), defaultValue: 118, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(119), @@ -1104,7 +1105,7 @@ func TestIntVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: 118, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(119), @@ -1122,7 +1123,7 @@ func TestIntVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key-ssss1"), defaultValue: 118, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(10), Default: testconvert.Interface(119), @@ -1140,7 +1141,7 @@ func TestIntVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewUser("random-key-ssss1"), defaultValue: 118, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("anonymous eq true"), Percentage: testconvert.Float64(50), Default: testconvert.Interface("default"), @@ -1158,7 +1159,7 @@ func TestIntVariation(t *testing.T) { flagKey: "test-flag", user: ffuser.NewAnonymousUser("random-key"), defaultValue: 118, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Rule: testconvert.String("key eq \"random-key\""), Percentage: testconvert.Float64(100), Default: testconvert.Interface(119.1), @@ -1177,7 +1178,7 @@ func TestIntVariation(t *testing.T) { flagKey: "disable-flag", user: ffuser.NewUser("random-key"), defaultValue: 125, - cacheMock: NewCacheMock(&model.FlagData{ + cacheMock: NewCacheMock(&flagv1.FlagData{ Disable: testconvert.Bool(true), }, nil), },