diff --git a/client.go b/client.go index 3b23df8..815f398 100644 --- a/client.go +++ b/client.go @@ -158,6 +158,11 @@ func (c *Client) OverrideConfig(config string, val map[string]interface{}) { c.evaluator.OverrideConfig(config, val) } +// Override the Layer value for the given user +func (c *Client) OverrideLayer(layer string, val map[string]interface{}) { + c.evaluator.OverrideLayer(layer, val) +} + func (c *Client) LogImmediate(events []Event) (*http.Response, error) { if len(events) > 500 { err := errors.New(EventBatchSizeError) diff --git a/evaluator.go b/evaluator.go index ff0c35a..ec56faf 100644 --- a/evaluator.go +++ b/evaluator.go @@ -17,13 +17,13 @@ import ( ) type evaluator struct { - store *store - gateOverrides map[string]bool - gateOverridesLock sync.RWMutex - configOverrides map[string]map[string]interface{} - configOverridesLock sync.RWMutex - countryLookup *countrylookup.CountryLookup - uaParser *uaparser.Parser + store *store + gateOverrides map[string]bool + configOverrides map[string]map[string]interface{} + layerOverrides map[string]map[string]interface{} + countryLookup *countrylookup.CountryLookup + uaParser *uaparser.Parser + mu sync.RWMutex } type evalResult struct { @@ -62,6 +62,7 @@ func newEvaluator( uaParser: parser, gateOverrides: make(map[string]bool), configOverrides: make(map[string]map[string]interface{}), + layerOverrides: make(map[string]map[string]interface{}), } } @@ -118,6 +119,16 @@ func (e *evaluator) getConfig(user User, configName string) *evalResult { } func (e *evaluator) getLayer(user User, name string) *evalResult { + if layerOverride, hasOverride := e.getLayerOverride(name); hasOverride { + evalDetails := e.createEvaluationDetails(reasonLocalOverride) + return &evalResult{ + Pass: true, + ConfigValue: *NewConfig(name, layerOverride, "override"), + Id: "override", + EvaluationDetails: evalDetails, + SecondaryExposures: make([]map[string]string, 0), + } + } if config, hasConfig := e.store.getLayerConfig(name); hasConfig { return e.eval(user, config) } @@ -128,33 +139,47 @@ func (e *evaluator) getLayer(user User, name string) *evalResult { } func (e *evaluator) getGateOverride(name string) (bool, bool) { - e.gateOverridesLock.RLock() - defer e.gateOverridesLock.RUnlock() + e.mu.RLock() + defer e.mu.RUnlock() gate, ok := e.gateOverrides[name] return gate, ok } func (e *evaluator) getConfigOverride(name string) (map[string]interface{}, bool) { - e.configOverridesLock.RLock() - defer e.configOverridesLock.RUnlock() + e.mu.RLock() + defer e.mu.RUnlock() config, ok := e.configOverrides[name] return config, ok } +func (e *evaluator) getLayerOverride(name string) (map[string]interface{}, bool) { + e.mu.RLock() + defer e.mu.RUnlock() + layer, ok := e.layerOverrides[name] + return layer, ok +} + // Override the value of a Feature Gate for the given user func (e *evaluator) OverrideGate(gate string, val bool) { - e.gateOverridesLock.Lock() - defer e.gateOverridesLock.Unlock() + e.mu.Lock() + defer e.mu.Unlock() e.gateOverrides[gate] = val } // Override the DynamicConfig value for the given user func (e *evaluator) OverrideConfig(config string, val map[string]interface{}) { - e.configOverridesLock.Lock() - defer e.configOverridesLock.Unlock() + e.mu.Lock() + defer e.mu.Unlock() e.configOverrides[config] = val } +// Override the Layer value for the given user +func (e *evaluator) OverrideLayer(layer string, val map[string]interface{}) { + e.mu.Lock() + defer e.mu.Unlock() + e.layerOverrides[layer] = val +} + // Gets all evaluated values for the given user. // These values can then be given to a Statsig Client SDK via bootstrapping. func (e *evaluator) getClientInitializeResponse(user User) ClientInitializeResponse { diff --git a/overrides_test.go b/overrides_test.go index 8c98150..96d5bc0 100644 --- a/overrides_test.go +++ b/overrides_test.go @@ -52,4 +52,23 @@ func TestOverrides(t *testing.T) { if !reflect.DeepEqual(newConfigOverride.Value, newConfig) { t.Errorf("Failed to get override value for a config when in LocalMode") } + + layer := make(map[string]interface{}) + layer["test"] = 123 + + c.OverrideLayer("any_layer", layer) + layerOverride := c.GetLayer(user, "any_layer") + if !reflect.DeepEqual(layerOverride.Value, layer) { + t.Errorf("Failed to get override value for a layer when in LocalMode") + } + + newLayer := make(map[string]interface{}) + newLayer["test"] = 456 + newLayer["test2"] = "hello" + + c.OverrideLayer("any_layer", newLayer) + newLayerOverride := c.GetLayer(user, "any_layer") + if !reflect.DeepEqual(newLayerOverride.Value, newLayer) { + t.Errorf("Failed to get override value for a layer when in LocalMode") + } } diff --git a/statsig.go b/statsig.go index 8a2d46a..1da7b99 100644 --- a/statsig.go +++ b/statsig.go @@ -137,6 +137,14 @@ func OverrideConfig(config string, val map[string]interface{}) { instance.OverrideConfig(config, val) } +// Override the Layer value for the given user +func OverrideLayer(layer string, val map[string]interface{}) { + if !IsInitialized() { + panic(fmt.Errorf("must Initialize() statsig before calling OverrideLayer")) + } + instance.OverrideLayer(layer, val) +} + // Gets the DynamicConfig value of an Experiment for the given user func GetExperiment(user User, experiment string) DynamicConfig { if !IsInitialized() { diff --git a/statsig_metadata.go b/statsig_metadata.go index c18f2bb..39fbfc9 100644 --- a/statsig_metadata.go +++ b/statsig_metadata.go @@ -13,7 +13,7 @@ type statsigMetadata struct { func getStatsigMetadata() statsigMetadata { return statsigMetadata{ SDKType: "go-sdk", - SDKVersion: "1.8.1", + SDKVersion: "1.9.0", LanguageVersion: runtime.Version()[2:], } } diff --git a/store.go b/store.go index 9ed05b4..c02f6f0 100644 --- a/store.go +++ b/store.go @@ -186,8 +186,8 @@ func (s *store) getDynamicConfig(name string) (configSpec, bool) { func (s *store) getLayerConfig(name string) (configSpec, bool) { s.mu.RLock() defer s.mu.RUnlock() - config, ok := s.layerConfigs[name] - return config, ok + layer, ok := s.layerConfigs[name] + return layer, ok } func (s *store) getExperimentLayer(experimentName string) (string, bool) {