configcat 3.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/configcat/autopollingcachepolicy.rb +9 -4
- data/lib/configcat/configcache.rb +5 -5
- data/lib/configcat/configcatclient.rb +165 -26
- data/lib/configcat/configfetcher.rb +100 -11
- data/lib/configcat/constants.rb +17 -0
- data/lib/configcat/datagovernance.rb +10 -0
- data/lib/configcat/interfaces.rb +2 -2
- data/lib/configcat/lazyloadingcachepolicy.rb +7 -4
- data/lib/configcat/localdictionarydatasource.rb +20 -0
- data/lib/configcat/localfiledatasource.rb +49 -0
- data/lib/configcat/manualpollingcachepolicy.rb +5 -3
- data/lib/configcat/overridedatasource.rb +32 -0
- data/lib/configcat/rolloutevaluator.rb +136 -123
- data/lib/configcat/user.rb +24 -5
- data/lib/configcat/version.rb +1 -1
- data/lib/configcat.rb +66 -17
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84a383f0a76b49e3daf0072676592b23836937e54430b16fc3183c8f517c0dac
|
4
|
+
data.tar.gz: 8d56fcce1d537ce84360a4f9cdc36ac795102ed37015c6efc9e872b8d44cb765
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4bf91c58c110054015a41062f23dfd8d48b4998b892a255bc8872eab5c7a87209f244a99a1f0dd4face989d1da8cd73c64e3de0efa6154f01bf48f8a1e635f81
|
7
|
+
data.tar.gz: 52669ab34441b4abae836c0b474358a78ebbb1c0c1d56447d823eb51a1cfa2c32e999c27f2d5e904aba53a6dc925bf77e227f5e1897d087b585a7655cb07dc6c
|
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'concurrent'
|
3
4
|
|
4
5
|
module ConfigCat
|
5
6
|
class AutoPollingCachePolicy < CachePolicy
|
6
|
-
def initialize(config_fetcher, config_cache, poll_interval_seconds=60, max_init_wait_time_seconds=5, on_configuration_changed_callback=nil)
|
7
|
+
def initialize(config_fetcher, config_cache, cache_key, poll_interval_seconds=60, max_init_wait_time_seconds=5, on_configuration_changed_callback=nil)
|
7
8
|
if poll_interval_seconds < 1
|
8
9
|
poll_interval_seconds = 1
|
9
10
|
end
|
@@ -12,6 +13,7 @@ module ConfigCat
|
|
12
13
|
end
|
13
14
|
@_config_fetcher = config_fetcher
|
14
15
|
@_config_cache = config_cache
|
16
|
+
@_cache_key = cache_key
|
15
17
|
@_poll_interval_seconds = poll_interval_seconds
|
16
18
|
@_max_init_wait_time_seconds = max_init_wait_time_seconds
|
17
19
|
@_on_configuration_changed_callback = on_configuration_changed_callback
|
@@ -40,7 +42,7 @@ module ConfigCat
|
|
40
42
|
end
|
41
43
|
begin
|
42
44
|
@_lock.acquire_read_lock()
|
43
|
-
return @_config_cache.get()
|
45
|
+
return @_config_cache.get(@_cache_key)
|
44
46
|
ensure
|
45
47
|
@_lock.release_read_lock()
|
46
48
|
end
|
@@ -52,7 +54,7 @@ module ConfigCat
|
|
52
54
|
|
53
55
|
begin
|
54
56
|
@_lock.acquire_read_lock()
|
55
|
-
old_configuration = @_config_cache.get()
|
57
|
+
old_configuration = @_config_cache.get(@_cache_key)
|
56
58
|
ensure
|
57
59
|
@_lock.release_read_lock()
|
58
60
|
end
|
@@ -62,7 +64,7 @@ module ConfigCat
|
|
62
64
|
if configuration != old_configuration
|
63
65
|
begin
|
64
66
|
@_lock.acquire_write_lock()
|
65
|
-
@_config_cache.set(configuration)
|
67
|
+
@_config_cache.set(@_cache_key, configuration)
|
66
68
|
@_initialized = true
|
67
69
|
ensure
|
68
70
|
@_lock.release_write_lock()
|
@@ -80,6 +82,9 @@ module ConfigCat
|
|
80
82
|
if !@_initialized && !old_configuration.equal?(nil)
|
81
83
|
@_initialized = true
|
82
84
|
end
|
85
|
+
rescue Timeout::Error => e
|
86
|
+
ConfigCat.logger.error("Request timed out. Timeout values: [open: %ss, read: %ss]" %
|
87
|
+
[@_config_fetcher.get_open_timeout(), @_config_fetcher.get_read_timeout()])
|
83
88
|
rescue Exception => e
|
84
89
|
ConfigCat.logger.error("Double-check your SDK Key at https://app.configcat.com/sdkkey.")
|
85
90
|
ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
|
@@ -3,15 +3,15 @@ require 'configcat/interfaces'
|
|
3
3
|
module ConfigCat
|
4
4
|
class InMemoryConfigCache < ConfigCache
|
5
5
|
def initialize()
|
6
|
-
@_value =
|
6
|
+
@_value = {}
|
7
7
|
end
|
8
8
|
|
9
|
-
def get()
|
10
|
-
return @_value
|
9
|
+
def get(key)
|
10
|
+
return @_value.fetch(key, nil)
|
11
11
|
end
|
12
12
|
|
13
|
-
def set(value)
|
14
|
-
@_value = value
|
13
|
+
def set(key, value)
|
14
|
+
@_value[key] = value
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -5,24 +5,43 @@ require 'configcat/autopollingcachepolicy'
|
|
5
5
|
require 'configcat/manualpollingcachepolicy'
|
6
6
|
require 'configcat/lazyloadingcachepolicy'
|
7
7
|
require 'configcat/rolloutevaluator'
|
8
|
+
require 'configcat/datagovernance'
|
9
|
+
|
8
10
|
|
9
11
|
module ConfigCat
|
12
|
+
KeyValue = Struct.new(:key, :value)
|
10
13
|
class ConfigCatClient
|
14
|
+
@@sdk_keys = []
|
15
|
+
|
11
16
|
def initialize(sdk_key,
|
12
|
-
poll_interval_seconds:60,
|
13
|
-
max_init_wait_time_seconds:5,
|
14
|
-
on_configuration_changed_callback:nil,
|
15
|
-
cache_time_to_live_seconds:60,
|
16
|
-
config_cache_class:nil,
|
17
|
-
base_url:nil,
|
18
|
-
proxy_address:nil,
|
19
|
-
proxy_port:nil,
|
20
|
-
proxy_user:nil,
|
21
|
-
proxy_pass:nil
|
17
|
+
poll_interval_seconds: 60,
|
18
|
+
max_init_wait_time_seconds: 5,
|
19
|
+
on_configuration_changed_callback: nil,
|
20
|
+
cache_time_to_live_seconds: 60,
|
21
|
+
config_cache_class: nil,
|
22
|
+
base_url: nil,
|
23
|
+
proxy_address: nil,
|
24
|
+
proxy_port: nil,
|
25
|
+
proxy_user: nil,
|
26
|
+
proxy_pass: nil,
|
27
|
+
open_timeout: 10,
|
28
|
+
read_timeout: 30,
|
29
|
+
flag_overrides: nil,
|
30
|
+
data_governance: DataGovernance::GLOBAL)
|
22
31
|
if sdk_key === nil
|
23
32
|
raise ConfigCatClientException, "SDK Key is required."
|
24
33
|
end
|
34
|
+
|
35
|
+
if @@sdk_keys.include?(sdk_key)
|
36
|
+
ConfigCat.logger.warn("A ConfigCat Client is already initialized with sdk_key %s. "\
|
37
|
+
"We strongly recommend you to use the ConfigCat Client as "\
|
38
|
+
"a Singleton object in your application." % sdk_key)
|
39
|
+
else
|
40
|
+
@@sdk_keys.push(sdk_key)
|
41
|
+
end
|
42
|
+
|
25
43
|
@_sdk_key = sdk_key
|
44
|
+
@_override_data_source = flag_overrides
|
26
45
|
|
27
46
|
if config_cache_class
|
28
47
|
@_config_cache = config_cache_class.new()
|
@@ -30,34 +49,121 @@ module ConfigCat
|
|
30
49
|
@_config_cache = InMemoryConfigCache.new()
|
31
50
|
end
|
32
51
|
|
33
|
-
if
|
34
|
-
@_config_fetcher =
|
35
|
-
@_cache_policy =
|
52
|
+
if !@_override_data_source.equal?(nil) && @_override_data_source.get_behaviour() == OverrideBehaviour::LOCAL_ONLY
|
53
|
+
@_config_fetcher = nil
|
54
|
+
@_cache_policy = nil
|
55
|
+
elsif poll_interval_seconds > 0
|
56
|
+
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url: base_url,
|
57
|
+
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
|
58
|
+
open_timeout: open_timeout, read_timeout: read_timeout,
|
59
|
+
data_governance: data_governance)
|
60
|
+
@_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
|
61
|
+
elsif cache_time_to_live_seconds > 0
|
62
|
+
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url: base_url,
|
63
|
+
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
|
64
|
+
open_timeout: open_timeout, read_timeout: read_timeout,
|
65
|
+
data_governance: data_governance)
|
66
|
+
@_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key(), cache_time_to_live_seconds)
|
36
67
|
else
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache)
|
43
|
-
end
|
68
|
+
@_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url: base_url,
|
69
|
+
proxy_address: proxy_address, proxy_port: proxy_port, proxy_user: proxy_user, proxy_pass: proxy_pass,
|
70
|
+
open_timeout: open_timeout, read_timeout: read_timeout,
|
71
|
+
data_governance: data_governance)
|
72
|
+
@_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache, _get_cache_key())
|
44
73
|
end
|
45
74
|
end
|
46
75
|
|
47
76
|
def get_value(key, default_value, user=nil)
|
48
|
-
config =
|
77
|
+
config = _get_settings()
|
49
78
|
if config === nil
|
79
|
+
ConfigCat.logger.warn("Evaluating get_value('%s') failed. Cache is empty. "\
|
80
|
+
"Returning default_value in your get_value call: [%s]." % [key, default_value.to_s])
|
50
81
|
return default_value
|
51
82
|
end
|
52
|
-
|
83
|
+
value, variation_id = RolloutEvaluator.evaluate(key, user, default_value, nil, config)
|
84
|
+
return value
|
53
85
|
end
|
54
86
|
|
55
87
|
def get_all_keys()
|
56
|
-
config =
|
88
|
+
config = _get_settings()
|
57
89
|
if config === nil
|
58
90
|
return []
|
59
91
|
end
|
60
|
-
|
92
|
+
feature_flags = config.fetch(FEATURE_FLAGS, nil)
|
93
|
+
if feature_flags === nil
|
94
|
+
return []
|
95
|
+
end
|
96
|
+
return feature_flags.keys
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_variation_id(key, default_variation_id, user=nil)
|
100
|
+
config = _get_settings()
|
101
|
+
if config === nil
|
102
|
+
ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. "\
|
103
|
+
"Returning default_variation_id in your get_variation_id call: [%s]." %
|
104
|
+
[key, default_variation_id.to_s])
|
105
|
+
return default_variation_id
|
106
|
+
end
|
107
|
+
value, variation_id = RolloutEvaluator.evaluate(key, user, nil, default_variation_id, config)
|
108
|
+
return variation_id
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_all_variation_ids(user: nil)
|
112
|
+
keys = get_all_keys()
|
113
|
+
variation_ids = []
|
114
|
+
for key in keys
|
115
|
+
variation_id = get_variation_id(key, nil, user)
|
116
|
+
if !variation_id.equal?(nil)
|
117
|
+
variation_ids.push(variation_id)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
return variation_ids
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_key_and_value(variation_id)
|
124
|
+
config = _get_settings()
|
125
|
+
if config === nil
|
126
|
+
ConfigCat.logger.warn("Evaluating get_variation_id('%s') failed. Cache is empty. Returning nil." % variation_id)
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
|
130
|
+
feature_flags = config.fetch(FEATURE_FLAGS, nil)
|
131
|
+
if feature_flags === nil
|
132
|
+
ConfigCat.logger.warn("Evaluating get_key_and_value('%s') failed. Cache is empty. Returning None." % variation_id)
|
133
|
+
return nil
|
134
|
+
end
|
135
|
+
|
136
|
+
for key, value in feature_flags
|
137
|
+
if variation_id == value.fetch(VARIATION_ID, nil)
|
138
|
+
return KeyValue.new(key, value[VALUE])
|
139
|
+
end
|
140
|
+
|
141
|
+
rollout_rules = value.fetch(ROLLOUT_RULES, [])
|
142
|
+
for rollout_rule in rollout_rules
|
143
|
+
if variation_id == rollout_rule.fetch(VARIATION_ID, nil)
|
144
|
+
return KeyValue.new(key, rollout_rule[VALUE])
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
rollout_percentage_items = value.fetch(ROLLOUT_PERCENTAGE_ITEMS, [])
|
149
|
+
for rollout_percentage_item in rollout_percentage_items
|
150
|
+
if variation_id == rollout_percentage_item.fetch(VARIATION_ID, nil)
|
151
|
+
return KeyValue.new(key, rollout_percentage_item[VALUE])
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_all_values(user: nil)
|
158
|
+
keys = get_all_keys()
|
159
|
+
all_values = {}
|
160
|
+
for key in keys
|
161
|
+
value = get_value(key, nil, user)
|
162
|
+
if !value.equal?(nil)
|
163
|
+
all_values[key] = value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
return all_values
|
61
167
|
end
|
62
168
|
|
63
169
|
def force_refresh()
|
@@ -65,8 +171,41 @@ module ConfigCat
|
|
65
171
|
end
|
66
172
|
|
67
173
|
def stop()
|
68
|
-
@_cache_policy.stop()
|
69
|
-
@_config_fetcher.close()
|
174
|
+
@_cache_policy.stop() if @_cache_policy
|
175
|
+
@_config_fetcher.close() if @_config_fetcher
|
176
|
+
@@sdk_keys.delete(@_sdk_key)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def _get_settings()
|
182
|
+
if !@_override_data_source.nil?
|
183
|
+
behaviour = @_override_data_source.get_behaviour()
|
184
|
+
if behaviour == OverrideBehaviour::LOCAL_ONLY
|
185
|
+
return @_override_data_source.get_overrides()
|
186
|
+
elsif behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL
|
187
|
+
remote_settings = @_cache_policy.get()
|
188
|
+
local_settings = @_override_data_source.get_overrides()
|
189
|
+
result = local_settings.clone()
|
190
|
+
if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS)
|
191
|
+
result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(remote_settings[FEATURE_FLAGS])
|
192
|
+
end
|
193
|
+
return result
|
194
|
+
elsif behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE
|
195
|
+
remote_settings = @_cache_policy.get()
|
196
|
+
local_settings = @_override_data_source.get_overrides()
|
197
|
+
result = remote_settings.clone()
|
198
|
+
if remote_settings.key?(FEATURE_FLAGS) && local_settings.key?(FEATURE_FLAGS)
|
199
|
+
result[FEATURE_FLAGS] = result[FEATURE_FLAGS].merge(local_settings[FEATURE_FLAGS])
|
200
|
+
end
|
201
|
+
return result
|
202
|
+
end
|
203
|
+
end
|
204
|
+
return @_cache_policy.get()
|
205
|
+
end
|
206
|
+
|
207
|
+
def _get_cache_key()
|
208
|
+
return Digest::SHA1.hexdigest("ruby_" + CONFIG_FILE_NAME + "_" + @_sdk_key)
|
70
209
|
end
|
71
210
|
|
72
211
|
end
|
@@ -1,13 +1,22 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
2
|
require 'configcat/version'
|
3
|
+
require 'configcat/datagovernance'
|
4
|
+
require 'configcat/constants'
|
3
5
|
require 'net/http'
|
4
6
|
require 'uri'
|
5
7
|
require 'json'
|
6
8
|
|
7
9
|
module ConfigCat
|
8
|
-
|
10
|
+
BASE_URL_GLOBAL = "https://cdn-global.configcat.com"
|
11
|
+
BASE_URL_EU_ONLY = "https://cdn-eu.configcat.com"
|
9
12
|
BASE_PATH = "configuration-files/"
|
10
|
-
BASE_EXTENSION = "/
|
13
|
+
BASE_EXTENSION = "/" + CONFIG_FILE_NAME + ".json"
|
14
|
+
|
15
|
+
class RedirectMode
|
16
|
+
NO_REDIRECT = 0
|
17
|
+
SHOULD_REDIRECT = 1
|
18
|
+
FORCE_REDIRECT = 2
|
19
|
+
end
|
11
20
|
|
12
21
|
class FetchResponse
|
13
22
|
def initialize(response)
|
@@ -32,34 +41,100 @@ module ConfigCat
|
|
32
41
|
end
|
33
42
|
|
34
43
|
class CacheControlConfigFetcher < ConfigFetcher
|
35
|
-
def initialize(sdk_key, mode, base_url
|
44
|
+
def initialize(sdk_key, mode, base_url:nil, proxy_address:nil, proxy_port:nil, proxy_user:nil, proxy_pass:nil,
|
45
|
+
open_timeout:10, read_timeout:30,
|
46
|
+
data_governance:DataGovernance::GLOBAL)
|
36
47
|
@_sdk_key = sdk_key
|
48
|
+
@_proxy_address = proxy_address
|
49
|
+
@_proxy_port = proxy_port
|
50
|
+
@_proxy_user = proxy_user
|
51
|
+
@_proxy_pass = proxy_pass
|
52
|
+
@_open_timeout = open_timeout
|
53
|
+
@_read_timeout = read_timeout
|
37
54
|
@_etag = ""
|
38
55
|
@_headers = {"User-Agent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "X-ConfigCat-UserAgent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "Content-Type" => "application/json"}
|
39
56
|
if !base_url.equal?(nil)
|
57
|
+
@_base_url_overridden = true
|
40
58
|
@_base_url = base_url.chomp("/")
|
41
59
|
else
|
42
|
-
@
|
60
|
+
@_base_url_overridden = false
|
61
|
+
if data_governance == DataGovernance::EU_ONLY
|
62
|
+
@_base_url = BASE_URL_EU_ONLY
|
63
|
+
else
|
64
|
+
@_base_url = BASE_URL_GLOBAL
|
65
|
+
end
|
43
66
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@
|
48
|
-
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_open_timeout()
|
70
|
+
return @_open_timeout
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_read_timeout()
|
74
|
+
return @_read_timeout
|
49
75
|
end
|
50
76
|
|
51
77
|
# Returns the FetchResponse object contains configuration json Dictionary
|
52
|
-
def get_configuration_json()
|
78
|
+
def get_configuration_json(retries=0)
|
53
79
|
ConfigCat.logger.debug "Fetching configuration from ConfigCat"
|
54
80
|
uri = URI.parse((((@_base_url + ("/")) + BASE_PATH) + @_sdk_key) + BASE_EXTENSION)
|
55
81
|
headers = @_headers
|
56
82
|
headers["If-None-Match"] = @_etag unless @_etag.empty?
|
83
|
+
_create_http()
|
57
84
|
request = Net::HTTP::Get.new(uri.request_uri, headers)
|
58
85
|
response = @_http.request(request)
|
59
86
|
etag = response["ETag"]
|
60
87
|
@_etag = etag unless etag.nil? || etag.empty?
|
61
88
|
ConfigCat.logger.debug "ConfigCat configuration json fetch response code:#{response.code} Cached:#{response['ETag']}"
|
62
|
-
|
89
|
+
fetch_response = FetchResponse.new(response)
|
90
|
+
|
91
|
+
# If there wasn't a config change, we return the response.
|
92
|
+
if !fetch_response.is_fetched()
|
93
|
+
return fetch_response
|
94
|
+
end
|
95
|
+
|
96
|
+
preferences = fetch_response.json().fetch(PREFERENCES, nil)
|
97
|
+
if preferences === nil
|
98
|
+
return fetch_response
|
99
|
+
end
|
100
|
+
|
101
|
+
base_url = preferences.fetch(BASE_URL, nil)
|
102
|
+
|
103
|
+
# If the base_url is the same as the last called one, just return the response.
|
104
|
+
if base_url.equal?(nil) || @_base_url == base_url
|
105
|
+
return fetch_response
|
106
|
+
end
|
107
|
+
|
108
|
+
redirect = preferences.fetch(REDIRECT, nil)
|
109
|
+
# If the base_url is overridden, and the redirect parameter is not 2 (force),
|
110
|
+
# the SDK should not redirect the calls and it just have to return the response.
|
111
|
+
if @_base_url_overridden && redirect != RedirectMode::FORCE_REDIRECT
|
112
|
+
return fetch_response
|
113
|
+
end
|
114
|
+
|
115
|
+
# The next call should use the base_url provided in the config json
|
116
|
+
@_base_url = base_url
|
117
|
+
|
118
|
+
# If the redirect property == 0 (redirect not needed), return the response
|
119
|
+
if redirect == RedirectMode::NO_REDIRECT
|
120
|
+
# Return the response
|
121
|
+
return fetch_response
|
122
|
+
end
|
123
|
+
|
124
|
+
# Try to download again with the new url
|
125
|
+
|
126
|
+
if redirect == RedirectMode::SHOULD_REDIRECT
|
127
|
+
ConfigCat.logger.warn("Your data_governance parameter at ConfigCatClient initialization is not in sync with your preferences on the ConfigCat Dashboard: https://app.configcat.com/organization/data-governance. Only Organization Admins can set this preference.")
|
128
|
+
end
|
129
|
+
|
130
|
+
# To prevent loops we check if we retried at least 3 times with the new base_url
|
131
|
+
if retries >= 2
|
132
|
+
ConfigCat.logger.error("Redirect loop during config.json fetch. Please contact support@configcat.com.")
|
133
|
+
return fetch_response
|
134
|
+
end
|
135
|
+
|
136
|
+
# Retry the config download with the new base_url
|
137
|
+
return get_configuration_json(retries + 1)
|
63
138
|
end
|
64
139
|
|
65
140
|
def close()
|
@@ -67,5 +142,19 @@ module ConfigCat
|
|
67
142
|
@_http = nil
|
68
143
|
end
|
69
144
|
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def _create_http()
|
149
|
+
uri = URI.parse(@_base_url)
|
150
|
+
use_ssl = true if uri.scheme == 'https'
|
151
|
+
if @_http.equal?(nil) || @_http.address != uri.host || @_http.port != uri.port || @_http.use_ssl? != use_ssl
|
152
|
+
close()
|
153
|
+
@_http = Net::HTTP.new(uri.host, uri.port, @_proxy_address, @_proxy_port, @_proxy_user, @_proxy_pass)
|
154
|
+
@_http.use_ssl = use_ssl
|
155
|
+
@_http.open_timeout = @_open_timeout
|
156
|
+
@_http.read_timeout = @_read_timeout
|
157
|
+
end
|
158
|
+
end
|
70
159
|
end
|
71
160
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ConfigCat
|
2
|
+
CONFIG_FILE_NAME = "config_v5"
|
3
|
+
|
4
|
+
PREFERENCES = "p"
|
5
|
+
BASE_URL = "u"
|
6
|
+
REDIRECT = "r"
|
7
|
+
|
8
|
+
FEATURE_FLAGS = "f"
|
9
|
+
VALUE = "v"
|
10
|
+
COMPARATOR = "t"
|
11
|
+
COMPARISON_ATTRIBUTE = "a"
|
12
|
+
COMPARISON_VALUE = "c"
|
13
|
+
ROLLOUT_PERCENTAGE_ITEMS = "p"
|
14
|
+
PERCENTAGE = "p"
|
15
|
+
ROLLOUT_RULES = "r"
|
16
|
+
VARIATION_ID = "i"
|
17
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ConfigCat
|
2
|
+
class DataGovernance
|
3
|
+
# Control the location of the config.json files containing your feature flags
|
4
|
+
# and settings within the ConfigCat CDN.
|
5
|
+
# Global: Select this if your feature flags are published to all global CDN nodes.
|
6
|
+
# EuOnly: Select this if your feature flags are published to CDN nodes only in the EU.
|
7
|
+
GLOBAL = 0
|
8
|
+
EU_ONLY = 1
|
9
|
+
end
|
10
|
+
end
|
data/lib/configcat/interfaces.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'concurrent'
|
3
4
|
|
5
|
+
|
4
6
|
module ConfigCat
|
5
7
|
class LazyLoadingCachePolicy < CachePolicy
|
6
8
|
|
7
|
-
def initialize(config_fetcher, config_cache, cache_time_to_live_seconds=60)
|
9
|
+
def initialize(config_fetcher, config_cache, cache_key, cache_time_to_live_seconds=60)
|
8
10
|
if cache_time_to_live_seconds < 1
|
9
11
|
cache_time_to_live_seconds = 1
|
10
12
|
end
|
11
13
|
@_config_fetcher = config_fetcher
|
12
14
|
@_config_cache = config_cache
|
15
|
+
@_cache_key = cache_key
|
13
16
|
@_cache_time_to_live = cache_time_to_live_seconds
|
14
17
|
@_lock = Concurrent::ReadWriteLock.new()
|
15
18
|
@_last_updated = nil
|
@@ -20,7 +23,7 @@ module ConfigCat
|
|
20
23
|
@_lock.acquire_read_lock()
|
21
24
|
utc_now = Time.now.utc
|
22
25
|
if !@_last_updated.equal?(nil) && (@_last_updated + @_cache_time_to_live > utc_now)
|
23
|
-
config = @_config_cache.get()
|
26
|
+
config = @_config_cache.get(@_cache_key)
|
24
27
|
if !config.equal?(nil)
|
25
28
|
return config
|
26
29
|
end
|
@@ -31,7 +34,7 @@ module ConfigCat
|
|
31
34
|
force_refresh()
|
32
35
|
begin
|
33
36
|
@_lock.acquire_read_lock()
|
34
|
-
config = @_config_cache.get()
|
37
|
+
config = @_config_cache.get(@_cache_key)
|
35
38
|
return config
|
36
39
|
ensure
|
37
40
|
@_lock.release_read_lock()
|
@@ -45,7 +48,7 @@ module ConfigCat
|
|
45
48
|
configuration = configuration_response.json()
|
46
49
|
begin
|
47
50
|
@_lock.acquire_write_lock()
|
48
|
-
@_config_cache.set(configuration)
|
51
|
+
@_config_cache.set(@_cache_key, configuration)
|
49
52
|
@_last_updated = Time.now.utc
|
50
53
|
ensure
|
51
54
|
@_lock.release_write_lock()
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'configcat/overridedatasource'
|
2
|
+
require 'configcat/constants'
|
3
|
+
|
4
|
+
|
5
|
+
module ConfigCat
|
6
|
+
class LocalDictionaryDataSource < OverrideDataSource
|
7
|
+
def initialize(source, override_behaviour)
|
8
|
+
super(override_behaviour)
|
9
|
+
dictionary = {}
|
10
|
+
source.each do |key, value|
|
11
|
+
dictionary[key] = {VALUE => value}
|
12
|
+
end
|
13
|
+
@_settings = {FEATURE_FLAGS => dictionary}
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_overrides()
|
17
|
+
return @_settings
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'configcat/overridedatasource'
|
2
|
+
require 'configcat/constants'
|
3
|
+
|
4
|
+
|
5
|
+
module ConfigCat
|
6
|
+
class LocalFileDataSource < OverrideDataSource
|
7
|
+
def initialize(file_path, override_behaviour)
|
8
|
+
super(override_behaviour)
|
9
|
+
if File.exists?(file_path)
|
10
|
+
ConfigCat.logger.error("The file '%s' does not exists." % file_path)
|
11
|
+
end
|
12
|
+
@_file_path = file_path
|
13
|
+
@_settings = nil
|
14
|
+
@_cached_file_stamp = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_overrides()
|
18
|
+
reload_file_content()
|
19
|
+
return @_settings
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def reload_file_content()
|
25
|
+
begin
|
26
|
+
stamp = File.mtime(@_file_path)
|
27
|
+
if stamp != @_cached_file_stamp
|
28
|
+
@_cached_file_stamp = stamp
|
29
|
+
file = File.read(@_file_path)
|
30
|
+
data = JSON.parse(file)
|
31
|
+
if data.key?("flags")
|
32
|
+
dictionary = {}
|
33
|
+
source = data["flags"]
|
34
|
+
source.each do |key, value|
|
35
|
+
dictionary[key] = {VALUE => value}
|
36
|
+
end
|
37
|
+
@_settings = {FEATURE_FLAGS => dictionary}
|
38
|
+
else
|
39
|
+
@_settings = data
|
40
|
+
end
|
41
|
+
end
|
42
|
+
rescue JSON::ParserError => e
|
43
|
+
ConfigCat.logger.error("Could not decode json from file %s. %s" % [@_file_path, e.to_s])
|
44
|
+
rescue Exception => e
|
45
|
+
ConfigCat.logger.error("Could not read the content of the file %s. %s" % [@_file_path, e.to_s])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,18 +1,20 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'concurrent'
|
3
4
|
|
4
5
|
module ConfigCat
|
5
6
|
class ManualPollingCachePolicy < CachePolicy
|
6
|
-
def initialize(config_fetcher, config_cache)
|
7
|
+
def initialize(config_fetcher, config_cache, cache_key)
|
7
8
|
@_config_fetcher = config_fetcher
|
8
9
|
@_config_cache = config_cache
|
10
|
+
@_cache_key = cache_key
|
9
11
|
@_lock = Concurrent::ReadWriteLock.new()
|
10
12
|
end
|
11
13
|
|
12
14
|
def get()
|
13
15
|
begin
|
14
16
|
@_lock.acquire_read_lock()
|
15
|
-
config = @_config_cache.get()
|
17
|
+
config = @_config_cache.get(@_cache_key)
|
16
18
|
return config
|
17
19
|
ensure
|
18
20
|
@_lock.release_read_lock()
|
@@ -26,7 +28,7 @@ module ConfigCat
|
|
26
28
|
configuration = configuration_response.json()
|
27
29
|
begin
|
28
30
|
@_lock.acquire_write_lock()
|
29
|
-
@_config_cache.set(configuration)
|
31
|
+
@_config_cache.set(@_cache_key, configuration)
|
30
32
|
ensure
|
31
33
|
@_lock.release_write_lock()
|
32
34
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ConfigCat
|
2
|
+
class OverrideBehaviour
|
3
|
+
# When evaluating values, the SDK will not use feature flags & settings from the ConfigCat CDN, but it will use
|
4
|
+
# all feature flags & settings that are loaded from local-override sources.
|
5
|
+
LOCAL_ONLY = 0
|
6
|
+
|
7
|
+
# When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN,
|
8
|
+
# plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is
|
9
|
+
# defined both in the fetched and the local-override source then the local-override version will take precedence.
|
10
|
+
LOCAL_OVER_REMOTE = 1
|
11
|
+
|
12
|
+
# When evaluating values, the SDK will use all feature flags & settings that are downloaded from the ConfigCat CDN,
|
13
|
+
# plus all feature flags & settings that are loaded from local-override sources. If a feature flag or a setting is
|
14
|
+
# defined both in the fetched and the local-override source then the fetched version will take precedence.
|
15
|
+
REMOTE_OVER_LOCAL = 2
|
16
|
+
end
|
17
|
+
|
18
|
+
class OverrideDataSource
|
19
|
+
def initialize(override_behaviour)
|
20
|
+
@_override_behaviour = override_behaviour
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_behaviour()
|
24
|
+
return @_override_behaviour
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_overrides()
|
28
|
+
# :returns the override dictionary
|
29
|
+
return {}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'configcat/user'
|
2
|
+
require 'configcat/constants'
|
2
3
|
require 'digest'
|
3
4
|
require 'semantic'
|
4
5
|
|
@@ -6,21 +7,19 @@ module ConfigCat
|
|
6
7
|
class RolloutEvaluator
|
7
8
|
COMPARATOR_TEXTS = ["IS ONE OF", "IS NOT ONE OF", "CONTAINS", "DOES NOT CONTAIN", "IS ONE OF (SemVer)", "IS NOT ONE OF (SemVer)", "< (SemVer)", "<= (SemVer)", "> (SemVer)", ">= (SemVer)", "= (Number)", "<> (Number)", "< (Number)", "<= (Number)", "> (Number)", ">= (Number)"]
|
8
9
|
|
9
|
-
|
10
|
-
COMPARATOR = "t"
|
11
|
-
COMPARISON_ATTRIBUTE = "a"
|
12
|
-
COMPARISON_VALUE = "c"
|
13
|
-
ROLLOUT_PERCENTAGE_ITEMS = "p"
|
14
|
-
PERCENTAGE = "p"
|
15
|
-
ROLLOUT_RULES = "r"
|
16
|
-
|
17
|
-
def self.evaluate(key, user, default_value, config)
|
10
|
+
def self.evaluate(key, user, default_value, default_variation_id, config)
|
18
11
|
ConfigCat.logger.info("Evaluating get_value('%s')." % key)
|
19
12
|
|
20
|
-
|
13
|
+
feature_flags = config.fetch(FEATURE_FLAGS, nil)
|
14
|
+
if feature_flags === nil
|
15
|
+
ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s' Returning default_value: [%s]." % [key, key, default_value.to_s])
|
16
|
+
return default_value, default_variation_id
|
17
|
+
end
|
18
|
+
|
19
|
+
setting_descriptor = feature_flags.fetch(key, nil)
|
21
20
|
if setting_descriptor === nil
|
22
|
-
ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s'. Returning default_value: [%s]. Here are the available keys: %s" % [key, key, default_value.to_s,
|
23
|
-
return default_value
|
21
|
+
ConfigCat.logger.error("Evaluating get_value('%s') failed. Value not found for key '%s'. Returning default_value: [%s]. Here are the available keys: %s" % [key, key, default_value.to_s, feature_flags.keys.join(", ")])
|
22
|
+
return default_value, default_variation_id
|
24
23
|
end
|
25
24
|
|
26
25
|
rollout_rules = setting_descriptor.fetch(ROLLOUT_RULES, [])
|
@@ -35,133 +34,147 @@ module ConfigCat
|
|
35
34
|
ConfigCat.logger.warn("Evaluating get_value('%s'). UserObject missing! You should pass a UserObject to get_value(), in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/" % key)
|
36
35
|
end
|
37
36
|
return_value = setting_descriptor.fetch(VALUE, default_value)
|
37
|
+
return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
|
38
38
|
ConfigCat.logger.info("Returning [%s]" % return_value.to_s)
|
39
|
-
return return_value
|
39
|
+
return return_value, return_variation_id
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
log_entries = ["Evaluating get_value('%s')." % key, "User object:\n%s" % user.to_s]
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
begin
|
45
|
+
# Evaluate targeting rules
|
46
|
+
for rollout_rule in rollout_rules
|
47
|
+
comparison_attribute = rollout_rule.fetch(COMPARISON_ATTRIBUTE)
|
48
|
+
comparison_value = rollout_rule.fetch(COMPARISON_VALUE, nil)
|
49
|
+
comparator = rollout_rule.fetch(COMPARATOR, nil)
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
user_value = user.get_attribute(comparison_attribute)
|
52
|
+
if user_value === nil || !user_value
|
53
|
+
log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
|
54
|
+
next
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
+
value = rollout_rule.fetch(VALUE, nil)
|
58
|
+
variation_id = rollout_rule.fetch(VARIATION_ID, default_variation_id)
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
# IS NOT ONE OF
|
65
|
-
elsif comparator == 1
|
66
|
-
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
|
67
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
68
|
-
return value
|
69
|
-
end
|
70
|
-
# CONTAINS
|
71
|
-
elsif comparator == 2
|
72
|
-
if user_value.to_s.include?(comparison_value.to_s)
|
73
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
74
|
-
return value
|
75
|
-
end
|
76
|
-
# DOES NOT CONTAIN
|
77
|
-
elsif comparator == 3
|
78
|
-
if !user_value.to_s.include?(comparison_value.to_s)
|
79
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
80
|
-
return value
|
81
|
-
end
|
82
|
-
# IS ONE OF, IS NOT ONE OF (Semantic version)
|
83
|
-
elsif (4 <= comparator) && (comparator <= 5)
|
84
|
-
begin
|
85
|
-
match = false
|
86
|
-
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
87
|
-
((comparison_value.to_s.split(",").map { |x| x.strip() }).reject { |c| c.empty? }).each { |x|
|
88
|
-
version = Semantic::Version.new(x)
|
89
|
-
match = (user_value_version == version) || match
|
90
|
-
}
|
91
|
-
if match && comparator == 4 || !match && comparator == 5
|
92
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
93
|
-
return value
|
60
|
+
# IS ONE OF
|
61
|
+
if comparator == 0
|
62
|
+
if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
|
63
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
64
|
+
return value, variation_id
|
94
65
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
elsif (6 <= comparator) && (comparator <= 9)
|
101
|
-
begin
|
102
|
-
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
103
|
-
comparison_value_version = Semantic::Version.new(comparison_value.to_s.strip())
|
104
|
-
if (comparator == 6 && user_value_version < comparison_value_version) ||
|
105
|
-
(comparator == 7 && user_value_version <= comparison_value_version) ||
|
106
|
-
(comparator == 8 && user_value_version > comparison_value_version) ||
|
107
|
-
(comparator == 9 && user_value_version >= comparison_value_version)
|
108
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
109
|
-
return value
|
66
|
+
# IS NOT ONE OF
|
67
|
+
elsif comparator == 1
|
68
|
+
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
|
69
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
70
|
+
return value, variation_id
|
110
71
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
if (
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
72
|
+
# CONTAINS
|
73
|
+
elsif comparator == 2
|
74
|
+
if user_value.to_s.include?(comparison_value.to_s)
|
75
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
76
|
+
return value, variation_id
|
77
|
+
end
|
78
|
+
# DOES NOT CONTAIN
|
79
|
+
elsif comparator == 3
|
80
|
+
if !user_value.to_s.include?(comparison_value.to_s)
|
81
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
82
|
+
return value, variation_id
|
83
|
+
end
|
84
|
+
# IS ONE OF, IS NOT ONE OF (Semantic version)
|
85
|
+
elsif (4 <= comparator) && (comparator <= 5)
|
86
|
+
begin
|
87
|
+
match = false
|
88
|
+
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
89
|
+
((comparison_value.to_s.split(",").map { |x| x.strip() }).reject { |c| c.empty? }).each { |x|
|
90
|
+
version = Semantic::Version.new(x)
|
91
|
+
match = (user_value_version == version) || match
|
92
|
+
}
|
93
|
+
if match && comparator == 4 || !match && comparator == 5
|
94
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
95
|
+
return value, variation_id
|
96
|
+
end
|
97
|
+
rescue ArgumentError => e
|
98
|
+
message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
|
99
|
+
ConfigCat.logger.warn(message)
|
100
|
+
log_entries.push(message)
|
101
|
+
next
|
102
|
+
end
|
103
|
+
# LESS THAN, LESS THAN OR EQUALS TO, GREATER THAN, GREATER THAN OR EQUALS TO (Semantic version)
|
104
|
+
elsif (6 <= comparator) && (comparator <= 9)
|
105
|
+
begin
|
106
|
+
user_value_version = Semantic::Version.new(user_value.to_s.strip())
|
107
|
+
comparison_value_version = Semantic::Version.new(comparison_value.to_s.strip())
|
108
|
+
if (comparator == 6 && user_value_version < comparison_value_version) ||
|
109
|
+
(comparator == 7 && user_value_version <= comparison_value_version) ||
|
110
|
+
(comparator == 8 && user_value_version > comparison_value_version) ||
|
111
|
+
(comparator == 9 && user_value_version >= comparison_value_version)
|
112
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
113
|
+
return value, variation_id
|
114
|
+
end
|
115
|
+
rescue ArgumentError => e
|
116
|
+
message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
|
117
|
+
ConfigCat.logger.warn(message)
|
118
|
+
log_entries.push(message)
|
119
|
+
next
|
120
|
+
end
|
121
|
+
elsif (10 <= comparator) && (comparator <= 15)
|
122
|
+
begin
|
123
|
+
user_value_float = Float(user_value.to_s.gsub(",", "."))
|
124
|
+
comparison_value_float = Float(comparison_value.to_s.gsub(",", "."))
|
125
|
+
if (comparator == 10 && user_value_float == comparison_value_float) ||
|
126
|
+
(comparator == 11 && user_value_float != comparison_value_float) ||
|
127
|
+
(comparator == 12 && user_value_float < comparison_value_float) ||
|
128
|
+
(comparator == 13 && user_value_float <= comparison_value_float) ||
|
129
|
+
(comparator == 14 && user_value_float > comparison_value_float) ||
|
130
|
+
(comparator == 15 && user_value_float >= comparison_value_float)
|
131
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
132
|
+
return value, variation_id
|
133
|
+
end
|
134
|
+
rescue Exception => e
|
135
|
+
message = format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s)
|
136
|
+
ConfigCat.logger.warn(message)
|
137
|
+
log_entries.push(message)
|
138
|
+
next
|
139
|
+
end
|
140
|
+
# IS ONE OF (Sensitive)
|
141
|
+
elsif comparator == 16
|
142
|
+
if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
143
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
144
|
+
return value, variation_id
|
145
|
+
end
|
146
|
+
# IS NOT ONE OF (Sensitive)
|
147
|
+
elsif comparator == 17
|
148
|
+
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
149
|
+
log_entries.push(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
150
|
+
return value, variation_id
|
127
151
|
end
|
128
|
-
rescue Exception => e
|
129
|
-
ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s))
|
130
|
-
next
|
131
|
-
end
|
132
|
-
# IS ONE OF (Sensitive)
|
133
|
-
elsif comparator == 16
|
134
|
-
if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
135
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
136
|
-
return value
|
137
|
-
end
|
138
|
-
# IS NOT ONE OF (Sensitive)
|
139
|
-
elsif comparator == 17
|
140
|
-
if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(Digest::SHA1.hexdigest(user_value).to_s)
|
141
|
-
ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
|
142
|
-
return value
|
143
152
|
end
|
153
|
+
log_entries.push(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
|
144
154
|
end
|
145
|
-
ConfigCat.logger.info(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
|
146
|
-
end
|
147
155
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
156
|
+
if rollout_percentage_items.size > 0
|
157
|
+
user_key = user.get_identifier()
|
158
|
+
hash_candidate = ("%s%s" % [key, user_key]).encode("utf-8")
|
159
|
+
hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base=16) % 100
|
160
|
+
bucket = 0
|
161
|
+
for rollout_percentage_item in rollout_percentage_items || []
|
162
|
+
bucket += rollout_percentage_item.fetch(PERCENTAGE, 0)
|
163
|
+
if hash_val < bucket
|
164
|
+
percentage_value = rollout_percentage_item.fetch(VALUE, nil)
|
165
|
+
variation_id = rollout_percentage_item.fetch(VARIATION_ID, default_variation_id)
|
166
|
+
log_entries.push("Evaluating %% options. Returning %s" % percentage_value)
|
167
|
+
return percentage_value, variation_id
|
168
|
+
end
|
159
169
|
end
|
160
170
|
end
|
171
|
+
return_value = setting_descriptor.fetch(VALUE, default_value)
|
172
|
+
return_variation_id = setting_descriptor.fetch(VARIATION_ID, default_variation_id)
|
173
|
+
log_entries.push("Returning %s" % return_value)
|
174
|
+
return return_value, return_variation_id
|
175
|
+
ensure
|
176
|
+
ConfigCat.logger.info(log_entries.join("\n"))
|
161
177
|
end
|
162
|
-
def_value = setting_descriptor.fetch(VALUE, default_value)
|
163
|
-
ConfigCat.logger.info("Returning %s" % def_value)
|
164
|
-
return def_value
|
165
178
|
end
|
166
179
|
|
167
180
|
private
|
data/lib/configcat/user.rb
CHANGED
@@ -5,11 +5,11 @@ module ConfigCat
|
|
5
5
|
# The user object for variation evaluation
|
6
6
|
#
|
7
7
|
|
8
|
-
PREDEFINED = ["
|
8
|
+
PREDEFINED = ["Identifier", "Email", "Country"]
|
9
9
|
|
10
10
|
def initialize(identifier, email: nil, country: nil, custom: nil)
|
11
|
-
@__identifier = identifier
|
12
|
-
@__data = {"
|
11
|
+
@__identifier = (!identifier.equal?(nil)) ? identifier : ""
|
12
|
+
@__data = {"Identifier" => identifier, "Email" => email, "Country" => country}
|
13
13
|
@__custom = custom
|
14
14
|
end
|
15
15
|
|
@@ -18,20 +18,39 @@ module ConfigCat
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def get_attribute(attribute)
|
21
|
-
attribute = attribute.to_s
|
21
|
+
attribute = attribute.to_s
|
22
22
|
if PREDEFINED.include?(attribute)
|
23
23
|
return @__data[attribute]
|
24
24
|
end
|
25
25
|
|
26
26
|
if !@__custom.equal?(nil)
|
27
27
|
@__custom.each do |customField, customValue|
|
28
|
-
if customField.to_s
|
28
|
+
if customField.to_s == attribute
|
29
29
|
return customValue
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
33
|
return nil
|
34
34
|
end
|
35
|
+
|
36
|
+
def to_s()
|
37
|
+
r = %Q({\n "Identifier": "#{@__identifier}")
|
38
|
+
if !@__data["Email"].equal?(nil)
|
39
|
+
r += %Q(,\n "Email": "#{@__data["Email"]}")
|
40
|
+
end
|
41
|
+
if !@__data["Country"].equal?(nil)
|
42
|
+
r += %Q(,\n "Country": "#{@__data["Country"]}")
|
43
|
+
end
|
44
|
+
if !@__custom.equal?(nil)
|
45
|
+
r += %Q(,\n "Custom": {)
|
46
|
+
for customField in @__custom
|
47
|
+
r += %Q(\n "#{customField}": "#{@__custom[customField]}",)
|
48
|
+
end
|
49
|
+
r += "\n }"
|
50
|
+
end
|
51
|
+
r += "\n}"
|
52
|
+
return r
|
53
|
+
end
|
35
54
|
end
|
36
55
|
|
37
56
|
end
|
data/lib/configcat/version.rb
CHANGED
data/lib/configcat.rb
CHANGED
@@ -10,13 +10,17 @@ module ConfigCat
|
|
10
10
|
attr_accessor :logger
|
11
11
|
end
|
12
12
|
|
13
|
-
def ConfigCat.create_client(sdk_key)
|
13
|
+
def ConfigCat.create_client(sdk_key, data_governance: DataGovernance::GLOBAL)
|
14
14
|
#
|
15
15
|
# Create an instance of ConfigCatClient and setup Auto Poll mode with default options
|
16
16
|
#
|
17
17
|
# :param sdk_key: ConfigCat SDK Key to access your configuration.
|
18
|
+
# :param data_governance:
|
19
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
20
|
+
# https://app.configcat.com/organization/data-governance
|
21
|
+
# (Only Organization Admins have access)
|
18
22
|
#
|
19
|
-
return create_client_with_auto_poll(sdk_key)
|
23
|
+
return create_client_with_auto_poll(sdk_key, data_governance: data_governance)
|
20
24
|
end
|
21
25
|
|
22
26
|
def ConfigCat.create_client_with_auto_poll(sdk_key,
|
@@ -25,10 +29,14 @@ module ConfigCat
|
|
25
29
|
on_configuration_changed_callback: nil,
|
26
30
|
config_cache_class: nil,
|
27
31
|
base_url: nil,
|
28
|
-
proxy_address:nil,
|
29
|
-
proxy_port:nil,
|
30
|
-
proxy_user:nil,
|
31
|
-
proxy_pass:nil
|
32
|
+
proxy_address: nil,
|
33
|
+
proxy_port: nil,
|
34
|
+
proxy_user: nil,
|
35
|
+
proxy_pass: nil,
|
36
|
+
open_timeout: 10,
|
37
|
+
read_timeout: 30,
|
38
|
+
flag_overrides: nil,
|
39
|
+
data_governance: DataGovernance::GLOBAL)
|
32
40
|
#
|
33
41
|
# Create an instance of ConfigCatClient and setup Auto Poll mode with custom options
|
34
42
|
#
|
@@ -43,6 +51,13 @@ module ConfigCat
|
|
43
51
|
# :param proxy_port: Proxy port
|
44
52
|
# :param proxy_user: username for proxy authentication
|
45
53
|
# :param proxy_pass: password for proxy authentication
|
54
|
+
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
|
55
|
+
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
|
56
|
+
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
|
57
|
+
# :param data_governance:
|
58
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
59
|
+
# https://app.configcat.com/organization/data-governance
|
60
|
+
# (Only Organization Admins have access)
|
46
61
|
#
|
47
62
|
if sdk_key === nil
|
48
63
|
raise ConfigCatClientException, "SDK Key is required."
|
@@ -63,17 +78,25 @@ module ConfigCat
|
|
63
78
|
proxy_address: proxy_address,
|
64
79
|
proxy_port: proxy_port,
|
65
80
|
proxy_user: proxy_user,
|
66
|
-
proxy_pass: proxy_pass
|
81
|
+
proxy_pass: proxy_pass,
|
82
|
+
open_timeout: open_timeout,
|
83
|
+
read_timeout: read_timeout,
|
84
|
+
flag_overrides: flag_overrides,
|
85
|
+
data_governance: data_governance)
|
67
86
|
end
|
68
87
|
|
69
88
|
def ConfigCat.create_client_with_lazy_load(sdk_key,
|
70
89
|
cache_time_to_live_seconds: 60,
|
71
90
|
config_cache_class: nil,
|
72
91
|
base_url: nil,
|
73
|
-
proxy_address:nil,
|
74
|
-
proxy_port:nil,
|
75
|
-
proxy_user:nil,
|
76
|
-
proxy_pass:nil
|
92
|
+
proxy_address: nil,
|
93
|
+
proxy_port: nil,
|
94
|
+
proxy_user: nil,
|
95
|
+
proxy_pass: nil,
|
96
|
+
open_timeout: 10,
|
97
|
+
read_timeout: 30,
|
98
|
+
flag_overrides: nil,
|
99
|
+
data_governance: DataGovernance::GLOBAL)
|
77
100
|
#
|
78
101
|
# Create an instance of ConfigCatClient and setup Lazy Load mode with custom options
|
79
102
|
#
|
@@ -86,6 +109,13 @@ module ConfigCat
|
|
86
109
|
# :param proxy_port: Proxy port
|
87
110
|
# :param proxy_user: username for proxy authentication
|
88
111
|
# :param proxy_pass: password for proxy authentication
|
112
|
+
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
|
113
|
+
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
|
114
|
+
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
|
115
|
+
# :param data_governance:
|
116
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
117
|
+
# https://app.configcat.com/organization/data-governance
|
118
|
+
# (Only Organization Admins have access)
|
89
119
|
#
|
90
120
|
if sdk_key === nil
|
91
121
|
raise ConfigCatClientException, "SDK Key is required."
|
@@ -103,16 +133,24 @@ module ConfigCat
|
|
103
133
|
proxy_address: proxy_address,
|
104
134
|
proxy_port: proxy_port,
|
105
135
|
proxy_user: proxy_user,
|
106
|
-
proxy_pass: proxy_pass
|
136
|
+
proxy_pass: proxy_pass,
|
137
|
+
open_timeout: open_timeout,
|
138
|
+
read_timeout: read_timeout,
|
139
|
+
flag_overrides: flag_overrides,
|
140
|
+
data_governance: data_governance)
|
107
141
|
end
|
108
142
|
|
109
143
|
def ConfigCat.create_client_with_manual_poll(sdk_key,
|
110
144
|
config_cache_class: nil,
|
111
145
|
base_url: nil,
|
112
|
-
proxy_address:nil,
|
113
|
-
proxy_port:nil,
|
114
|
-
proxy_user:nil,
|
115
|
-
proxy_pass:nil
|
146
|
+
proxy_address: nil,
|
147
|
+
proxy_port: nil,
|
148
|
+
proxy_user: nil,
|
149
|
+
proxy_pass: nil,
|
150
|
+
open_timeout: 10,
|
151
|
+
read_timeout: 30,
|
152
|
+
flag_overrides: nil,
|
153
|
+
data_governance: DataGovernance::GLOBAL)
|
116
154
|
#
|
117
155
|
# Create an instance of ConfigCatClient and setup Manual Poll mode with custom options
|
118
156
|
#
|
@@ -124,6 +162,13 @@ module ConfigCat
|
|
124
162
|
# :param proxy_port: Proxy port
|
125
163
|
# :param proxy_user: username for proxy authentication
|
126
164
|
# :param proxy_pass: password for proxy authentication
|
165
|
+
# :param open_timeout: The number of seconds to wait for the server to make the initial connection. Default: 10 seconds.
|
166
|
+
# :param read_timeout: The number of seconds to wait for the server to respond before giving up. Default: 30 seconds.
|
167
|
+
# :param flag_overrides: An OverrideDataSource implementation used to override feature flags & settings.
|
168
|
+
# :param data_governance:
|
169
|
+
# Default: Global. Set this parameter to be in sync with the Data Governance preference on the Dashboard:
|
170
|
+
# https://app.configcat.com/organization/data-governance
|
171
|
+
# (Only Organization Admins have access)
|
127
172
|
#
|
128
173
|
if sdk_key === nil
|
129
174
|
raise ConfigCatClientException, "SDK Key is required."
|
@@ -138,7 +183,11 @@ module ConfigCat
|
|
138
183
|
proxy_address: proxy_address,
|
139
184
|
proxy_port: proxy_port,
|
140
185
|
proxy_user: proxy_user,
|
141
|
-
proxy_pass: proxy_pass
|
186
|
+
proxy_pass: proxy_pass,
|
187
|
+
open_timeout: open_timeout,
|
188
|
+
read_timeout: read_timeout,
|
189
|
+
flag_overrides: flag_overrides,
|
190
|
+
data_governance: data_governance)
|
142
191
|
end
|
143
192
|
|
144
193
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: configcat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ConfigCat
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -67,19 +67,19 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '12.3'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: codecov
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '0.
|
75
|
+
version: '0.5'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '0.
|
82
|
+
version: '0.5'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: webmock
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,9 +110,14 @@ files:
|
|
110
110
|
- lib/configcat/configcache.rb
|
111
111
|
- lib/configcat/configcatclient.rb
|
112
112
|
- lib/configcat/configfetcher.rb
|
113
|
+
- lib/configcat/constants.rb
|
114
|
+
- lib/configcat/datagovernance.rb
|
113
115
|
- lib/configcat/interfaces.rb
|
114
116
|
- lib/configcat/lazyloadingcachepolicy.rb
|
117
|
+
- lib/configcat/localdictionarydatasource.rb
|
118
|
+
- lib/configcat/localfiledatasource.rb
|
115
119
|
- lib/configcat/manualpollingcachepolicy.rb
|
120
|
+
- lib/configcat/overridedatasource.rb
|
116
121
|
- lib/configcat/rolloutevaluator.rb
|
117
122
|
- lib/configcat/user.rb
|
118
123
|
- lib/configcat/version.rb
|
@@ -135,8 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
140
|
- !ruby/object:Gem::Version
|
136
141
|
version: '0'
|
137
142
|
requirements: []
|
138
|
-
|
139
|
-
rubygems_version: 2.7.7
|
143
|
+
rubygems_version: 3.0.3.1
|
140
144
|
signing_key:
|
141
145
|
specification_version: 4
|
142
146
|
summary: ConfigCat SDK for Ruby.
|