diff --git a/client_initialize_response.go b/client_initialize_response.go index 7983140..5d77fe7 100644 --- a/client_initialize_response.go +++ b/client_initialize_response.go @@ -1,7 +1,6 @@ package statsig import ( - "encoding/json" "fmt" "strings" ) @@ -116,12 +115,9 @@ func getClientInitializeResponse( *result.ExplicitParameters = spec.ExplicitParameters layerName, _ := store.getExperimentLayer(spec.Name) layer, exists := store.getLayerConfig(layerName) - var defaultValue map[string]interface{} + defaultValue := make(map[string]interface{}) if exists { - err := json.Unmarshal(layer.DefaultValue, &defaultValue) - if err != nil { - defaultValue = make(map[string]interface{}) - } + mergeMaps(defaultValue, layer.DefaultValueJSON) mergeMaps(defaultValue, result.Value) result.Value = defaultValue } diff --git a/evaluator.go b/evaluator.go index 14ade96..eba9932 100644 --- a/evaluator.go +++ b/evaluator.go @@ -3,7 +3,6 @@ package statsig import ( "crypto/sha256" "encoding/base64" - "encoding/json" "errors" "fmt" "reflect" @@ -214,10 +213,7 @@ func (e *evaluator) eval(user User, spec configSpec, depth int) *evalResult { evalDetails := e.createEvaluationDetails(reason) isDynamicConfig := strings.ToLower(spec.Type) == dynamicConfigType if isDynamicConfig { - err := json.Unmarshal(spec.DefaultValue, &configValue) - if err != nil { - configValue = make(map[string]interface{}) - } + configValue = spec.DefaultValueJSON } var exposures = make([]map[string]string, 0) @@ -239,12 +235,7 @@ func (e *evaluator) eval(user User, spec configSpec, depth int) *evalResult { pass := evalPassPercent(user, rule, spec) if isDynamicConfig { if pass { - var ruleConfigValue map[string]interface{} - err := json.Unmarshal(rule.ReturnValue, &ruleConfigValue) - if err != nil { - ruleConfigValue = make(map[string]interface{}) - } - configValue = ruleConfigValue + configValue = rule.ReturnValueJSON } result := &evalResult{ Pass: pass, @@ -426,11 +417,19 @@ func (e *evaluator) evalCondition(user User, cond configCondition, depth int) *e // array operations case "any": pass = arrayAny(cond.TargetValue, value, func(x, y interface{}) bool { - return compareStrings(x, y, true, func(s1, s2 string) bool { return s1 == s2 }) + if condType == "user_bucket" { + return compareNumbers(x, y, func(s1, s2 float64) bool { return s1 == s2 }) + } else { + return compareStrings(x, y, true, func(s1, s2 string) bool { return s1 == s2 }) + } }) case "none": pass = !arrayAny(cond.TargetValue, value, func(x, y interface{}) bool { - return compareStrings(x, y, true, func(s1, s2 string) bool { return s1 == s2 }) + if condType == "user_bucket" { + return compareNumbers(x, y, func(s1, s2 float64) bool { return s1 == s2 }) + } else { + return compareStrings(x, y, true, func(s1, s2 string) bool { return s1 == s2 }) + } }) case "any_case_sensitive": pass = arrayAny(cond.TargetValue, value, func(x, y interface{}) bool { diff --git a/store.go b/store.go index a36e57f..765d113 100644 --- a/store.go +++ b/store.go @@ -12,18 +12,20 @@ import ( ) type configSpec struct { - Name string `json:"name"` - Type string `json:"type"` - Salt string `json:"salt"` - Enabled bool `json:"enabled"` - Rules []configRule `json:"rules"` - DefaultValue json.RawMessage `json:"defaultValue"` - IDType string `json:"idType"` - ExplicitParameters []string `json:"explicitParameters"` - Entity string `json:"entity"` - IsActive *bool `json:"isActive,omitempty"` - HasSharedParams *bool `json:"hasSharedParams,omitempty"` - TargetAppIDs []string `json:"targetAppIDs,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Salt string `json:"salt"` + Enabled bool `json:"enabled"` + Rules []configRule `json:"rules"` + DefaultValue json.RawMessage `json:"defaultValue"` + DefaultValueJSON map[string]interface{} `json:"-"` + DefaultValueBool *bool `json:"-"` + IDType string `json:"idType"` + ExplicitParameters []string `json:"explicitParameters"` + Entity string `json:"entity"` + IsActive *bool `json:"isActive,omitempty"` + HasSharedParams *bool `json:"hasSharedParams,omitempty"` + TargetAppIDs []string `json:"targetAppIDs,omitempty"` } func (c configSpec) hasTargetAppID(appId string) bool { @@ -39,16 +41,18 @@ func (c configSpec) hasTargetAppID(appId string) bool { } type configRule struct { - Name string `json:"name"` - ID string `json:"id"` - GroupName string `json:"groupName,omitempty"` - Salt string `json:"salt"` - PassPercentage float64 `json:"passPercentage"` - Conditions []configCondition `json:"conditions"` - ReturnValue json.RawMessage `json:"returnValue"` - IDType string `json:"idType"` - ConfigDelegate string `json:"configDelegate"` - IsExperimentGroup *bool `json:"isExperimentGroup,omitempty"` + Name string `json:"name"` + ID string `json:"id"` + GroupName string `json:"groupName,omitempty"` + Salt string `json:"salt"` + PassPercentage float64 `json:"passPercentage"` + Conditions []configCondition `json:"conditions"` + ReturnValue json.RawMessage `json:"returnValue"` + ReturnValueJSON map[string]interface{} `json:"-"` + ReturnValueBool *bool `json:"-"` + IDType string `json:"idType"` + ConfigDelegate string `json:"configDelegate"` + IsExperimentGroup *bool `json:"isExperimentGroup,omitempty"` } type configCondition struct { @@ -331,6 +335,23 @@ func (s *store) processConfigSpecs(configSpecs interface{}, diagnosticsMarker *m return parsed, updated } +func (s *store) parseJSONValuesFromSpec(spec *configSpec) { + var defaultValue map[string]interface{} + err := json.Unmarshal(spec.DefaultValue, &defaultValue) + if err != nil { + defaultValue = make(map[string]interface{}) + } + spec.DefaultValueJSON = defaultValue + for i, rule := range spec.Rules { + var ruleValue map[string]interface{} + err := json.Unmarshal(rule.ReturnValue, &ruleValue) + if err != nil { + ruleValue = make(map[string]interface{}) + } + spec.Rules[i].ReturnValueJSON = ruleValue + } +} + // Returns a tuple of booleans indicating 1. parsed, 2. updated func (s *store) setConfigSpecs(specs downloadConfigSpecResponse) (bool, bool) { s.diagnostics.initDiagnostics.updateSamplingRates(specs.DiagnosticsSampleRates) @@ -349,11 +370,13 @@ func (s *store) setConfigSpecs(specs downloadConfigSpecResponse) (bool, bool) { newConfigs := make(map[string]configSpec) for _, config := range specs.DynamicConfigs { + s.parseJSONValuesFromSpec(&config) newConfigs[config.Name] = config } newLayers := make(map[string]configSpec) for _, layer := range specs.LayerConfigs { + s.parseJSONValuesFromSpec(&layer) newLayers[layer.Name] = layer }