Skip to content

Commit

Permalink
Merge pull request #8 from configcat/variation-id
Browse files Browse the repository at this point in the history
Add variation id related functionality
  • Loading branch information
z4kn4fein authored Aug 14, 2020
2 parents 1c3e750 + 1254986 commit 05828b8
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 84 deletions.
1 change: 1 addition & 0 deletions resources/testmatrix.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Identifier;Email;Country;Custom1;bool30TrueAdvancedRules;boolDefaultFalse;boolDefaultTrue;double25Pi25E25Gr25Zero;doubleDefaultPi;integer25One25Two25Three25FourAdvancedRules;integerDefaultOne;string25Cat25Dog25Falcon25Horse;string25Cat25Dog25Falcon25HorseAdvancedRules;string75Cat0Dog25Falcon0Horse;stringContainsDogDefaultCat;stringDefaultCat;stringIsInDogDefaultCat;stringIsNotInDogDefaultCat;stringNotContainsDogDefaultCat
##null##;;;;True;False;True;-1.0;3.1415;-1;1;Chicken;Chicken;Chicken;Cat;Cat;Cat;Cat;Cat
;;;;False;False;True;2.7182;3.1415;4;1;Dog;Dog;Cat;Cat;Cat;Cat;Cat;Cat
a@configcat.com;a@configcat.com;Hungary;admin;False;False;True;5.561;3.1415;5;1;Cat;Dolphin;Cat;Dog;Cat;Dog;Cat;Cat
b@configcat.com;b@configcat.com;Hungary;;False;False;True;5.561;3.1415;5;1;Falcon;Dolphin;Cat;Dog;Cat;Dog;Cat;Cat
c@configcat.com;c@configcat.com;United Kingdom;admin;False;False;True;5.561;3.1415;5;1;Dog;Dolphin;Falcon;Dog;Cat;Dog;Dog;Cat
Expand Down
8 changes: 8 additions & 0 deletions resources/testmatrix_variationId.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Identifier;Email;Country;Custom1;boolean;decimal;text;whole
##null##;;;;a0e56eda;63612d39;3f05be89;cf2e9162
a@configcat.com;a@configcat.com;Hungary;admin;67787ae4;8f9559cf;9bdc6a1f;ab30533b
b@configcat.com;b@configcat.com;Hungary;admin;67787ae4;8f9559cf;9bdc6a1f;ab30533b
a@test.com;a@test.com;Hungary;admin;67787ae4;d66c5781;65310deb;ec14f6a9
b@test.com;b@test.com;Hungary;admin;a0e56eda;d66c5781;65310deb;ec14f6a9
cliffordj@aol.com;cliffordj@aol.com;Hungary;admin;67787ae4;8155ad7b;cf19e913;ec14f6a9
bryanw@verizon.net;bryanw@verizon.net;Hungary;;a0e56eda;d0dbc27f;30ba32b9;61a5a033
4 changes: 0 additions & 4 deletions v5/config_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@ import (
"net/http"
)

// configProvider describes a configuration provider which used to collect the actual configuration.
type configProvider interface {
// getConfigurationAsync collects the actual configuration.
getConfigurationAsync() *asyncResult
}

// configFetcher used to fetch the actual configuration over HTTP.
type configFetcher struct {
sdkKey, eTag, mode, baseUrl string
client *http.Client
Expand All @@ -26,7 +23,6 @@ func newConfigFetcher(sdkKey string, config ClientConfig) *configFetcher {
client: &http.Client{Timeout: config.HttpTimeout, Transport: config.Transport}}
}

// getConfigurationAsync collects the actual configuration over HTTP.
func (fetcher *configFetcher) getConfigurationAsync() *asyncResult {
result := newAsyncResult()

Expand Down
78 changes: 53 additions & 25 deletions v5/config_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,35 @@ import (
"strings"
)

// ParseError describes JSON parsing related errors.
type ParseError struct {
type parseError struct {
msg string
}

// Error is the error message.
func (p *ParseError) Error() string {
func (p *parseError) Error() string {
return p.msg
}

// ConfigParser describes a JSON configuration parser.
type ConfigParser struct {
type configParser struct {
evaluator *rolloutEvaluator
logger Logger
}

func newParser(logger Logger) *ConfigParser {
func newParser(logger Logger) *configParser {
evaluator := newRolloutEvaluator(logger)
return &ConfigParser{evaluator: evaluator, logger: logger}
return &configParser{evaluator: evaluator, logger: logger}
}

// Parse converts a json element identified by a key from the given json string into an interface{} value.
func (parser *ConfigParser) Parse(jsonBody string, key string) (interface{}, error) {
return parser.ParseWithUser(jsonBody, key, nil)
func (parser *configParser) parse(jsonBody string, key string, user *User) (interface{}, error) {
result, _, err := parser.parseInternal(jsonBody, key, user)
return result, err
}

// ParseWithUser converts a json element identified by the key from the given json
// string into an interface{} value. Optional user argument can be passed to identify the caller.
func (parser *ConfigParser) ParseWithUser(jsonBody string, key string, user *User) (interface{}, error) {
return parser.parse(jsonBody, key, user)
func (parser *configParser) parseVariationId(jsonBody string, key string, user *User) (string, error) {
_, variationId, err := parser.parseInternal(jsonBody, key, user)
return variationId, err
}

// GetAllKeys retrieves all the setting keys from the given json config.
func (parser *ConfigParser) GetAllKeys(jsonBody string) ([]string, error) {
func (parser *configParser) getAllKeys(jsonBody string) ([]string, error) {
rootNode, err := parser.deserialize(jsonBody)
if err != nil {
return nil, err
Expand All @@ -54,14 +49,47 @@ func (parser *ConfigParser) GetAllKeys(jsonBody string) ([]string, error) {
return keys, nil
}

func (parser *ConfigParser) parse(jsonBody string, key string, user *User) (interface{}, error) {
func (parser *configParser) parseKeyValue(jsonBody string, variationId string) (string, interface{}, error) {
rootNode, err := parser.deserialize(jsonBody)
if err != nil {
return "", nil, &parseError{"JSON parsing failed. " + err.Error() + "."}
}

for key, value := range rootNode {
node := value.(map[string]interface{})
if node[settingVariationId].(string) == variationId {
return key, node[settingValue], nil
}

rolloutRules := node[settingRolloutRules].([]interface{})
percentageRules := node[settingRolloutPercentageItems].([]interface{})

for _, rolloutItem := range rolloutRules {
rule := rolloutItem.(map[string]interface{})
if rule[rolloutVariationId].(string) == variationId {
return key, rule[rolloutValue], nil
}
}

for _, percentageItem := range percentageRules {
rule := percentageItem.(map[string]interface{})
if rule[percentageItemVariationId].(string) == variationId {
return key, rule[percentageItemValue], nil
}
}
}

return "", nil, &parseError{"JSON parsing failed." }
}

func (parser *configParser) parseInternal(jsonBody string, key string, user *User) (interface{}, string, error) {
if len(key) == 0 {
panic("Key cannot be empty")
}

rootNode, err := parser.deserialize(jsonBody)
if err != nil {
return nil, &ParseError{"JSON parsing failed. " + err.Error() + "."}
return nil, "", &parseError{"JSON parsing failed. " + err.Error() + "."}
}

node := rootNode[key]
Expand All @@ -73,19 +101,19 @@ func (parser *ConfigParser) parse(jsonBody string, key string, user *User) (inte
i++
}

return nil, &ParseError{"Value not found for key " + key +
return nil, "", &parseError{"Value not found for key " + key +
". Here are the available keys: " + strings.Join(keys, ", ")}
}

parsed := parser.evaluator.evaluate(node, key, user)
parsed, variationId := parser.evaluator.evaluate(node, key, user)
if parsed == nil {
return nil, &ParseError{"Null evaluated for key " + key + "."}
return nil, "", &parseError{"Null evaluated for key " + key + "."}
}

return parsed, nil
return parsed, variationId, nil
}

func (parser *ConfigParser) deserialize(jsonBody string) (map[string]interface{}, error) {
func (parser *configParser) deserialize(jsonBody string) (map[string]interface{}, error) {
var root interface{}
err := json.Unmarshal([]byte(jsonBody), &root)
if err != nil {
Expand All @@ -94,7 +122,7 @@ func (parser *ConfigParser) deserialize(jsonBody string) (map[string]interface{}

rootNode, ok := root.(map[string]interface{})
if !ok {
return nil, &ParseError{"JSON mapping failed, json: " + jsonBody}
return nil, &parseError{"JSON mapping failed, json: " + jsonBody}
}

return rootNode, nil
Expand Down
12 changes: 6 additions & 6 deletions v5/config_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import (
)

func TestConfigParser_Parse(t *testing.T) {
jsonBody := "{ \"keyDouble\": { \"v\": 120.121238476, \"p\": [], \"r\": [] }}"
jsonBody := "{ \"keyDouble\": { \"v\": 120.121238476, \"p\": [], \"r\": [], \"i\":\"\" }}"
parser := newParser(DefaultLogger(LogLevelWarn))

val, err := parser.Parse(jsonBody, "keyDouble")
val, err := parser.parse(jsonBody, "keyDouble", nil)

if err != nil || val != 120.121238476 {
t.Error("Expecting 120.121238476 as interface")
Expand All @@ -19,7 +19,7 @@ func TestConfigParser_BadJson(t *testing.T) {
jsonBody := ""
parser := newParser(DefaultLogger(LogLevelWarn))

_, err := parser.Parse(jsonBody, "keyDouble")
_, err := parser.parse(jsonBody, "keyDouble", nil)

if err == nil {
t.Error("Expecting JSON error")
Expand All @@ -32,7 +32,7 @@ func TestConfigParser_BadJson_String(t *testing.T) {
jsonBody := ""
parser := newParser(DefaultLogger(LogLevelWarn))

_, err := parser.Parse(jsonBody, "key")
_, err := parser.parse(jsonBody, "key", nil)

if err == nil {
t.Error("Expecting JSON error")
Expand All @@ -45,7 +45,7 @@ func TestConfigParser_WrongKey(t *testing.T) {
jsonBody := "{ \"keyDouble\": { \"Value\": 120.121238476, \"SettingType\": 0, \"RolloutPercentageItems\": [], \"RolloutRules\": [] }}"
parser := newParser(DefaultLogger(LogLevelWarn))

_, err := parser.Parse(jsonBody, "wrongKey")
_, err := parser.parse(jsonBody, "wrongKey", nil)

if err == nil {
t.Error("Expecting key not found error")
Expand All @@ -58,7 +58,7 @@ func TestConfigParser_EmptyNode(t *testing.T) {
jsonBody := "{ \"keyDouble\": { }}"
parser := newParser(DefaultLogger(LogLevelWarn))

_, err := parser.Parse(jsonBody, "keyDouble")
_, err := parser.parse(jsonBody, "keyDouble", nil)

if err == nil {
t.Error("Expecting invalid JSON error")
Expand Down
Loading

0 comments on commit 05828b8

Please sign in to comment.