configcat 3.0.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|