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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed70c40f6865601f6d5476cbb12b158e359413baa9fc186a58c0bc0b1334e9a5
4
- data.tar.gz: 848a6b3550eee7cdc6cb0467b0b0893e6f37ce9d1a0dffb95c77ca4f248e8d74
3
+ metadata.gz: 84a383f0a76b49e3daf0072676592b23836937e54430b16fc3183c8f517c0dac
4
+ data.tar.gz: 8d56fcce1d537ce84360a4f9cdc36ac795102ed37015c6efc9e872b8d44cb765
5
5
  SHA512:
6
- metadata.gz: 9aa50e9733ab9ed0612d28fe2daee49ce0396feb1fab353324b2d3ae63ce67722a58d7c0c67d5884d4146106bd52859e36e08725a1a1757390bb6462baf4c1e0
7
- data.tar.gz: 40a2aa9993dc5ba3cba64c944a06c9c4c15895ba4ee6f227db250abe051894956f7c3fc2fe7dff298582ce4e573190e5cc8d4e0055afcdff0e5ed278f7d2a52e
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 = nil
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 poll_interval_seconds > 0
34
- @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "p", base_url, proxy_address, proxy_port, proxy_user, proxy_pass)
35
- @_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
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
- if cache_time_to_live_seconds > 0
38
- @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "l", base_url, proxy_address, proxy_port, proxy_user, proxy_pass)
39
- @_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, cache_time_to_live_seconds)
40
- else
41
- @_config_fetcher = CacheControlConfigFetcher.new(sdk_key, "m", base_url, proxy_address, proxy_port, proxy_user, proxy_pass)
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 = @_cache_policy.get()
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
- return RolloutEvaluator.evaluate(key, user, default_value, config)
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 = @_cache_policy.get()
88
+ config = _get_settings()
57
89
  if config === nil
58
90
  return []
59
91
  end
60
- return config.keys
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
- BASE_URL = "https://cdn.configcat.com"
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 = "/config_v4.json"
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=nil, proxy_address=nil, proxy_port=nil, proxy_user=nil, proxy_pass=nil)
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
- @_base_url = BASE_URL
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
- uri = URI.parse(@_base_url)
45
- @_http = Net::HTTP.new(uri.host, uri.port, proxy_address, proxy_port, proxy_user, proxy_pass)
46
- @_http.use_ssl = true if uri.scheme == 'https'
47
- @_http.open_timeout = 10 # in seconds
48
- @_http.read_timeout = 30 # in seconds
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
- return FetchResponse.new(response)
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
@@ -23,13 +23,13 @@ module ConfigCat
23
23
  # Config cache interface
24
24
  #
25
25
 
26
- def get()
26
+ def get(key)
27
27
  #
28
28
  # :returns the config json object from the cache
29
29
  #
30
30
  end
31
31
 
32
- def set(value)
32
+ def set(key, value)
33
33
  #
34
34
  # Sets the config json cache.
35
35
  #
@@ -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
- 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
-
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
- setting_descriptor = config.fetch(key, nil)
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, config.keys.join(", ")])
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
- ConfigCat.logger.info("User object:\n%s" % user.to_s)
42
+ log_entries = ["Evaluating get_value('%s')." % key, "User object:\n%s" % user.to_s]
43
43
 
44
- # Evaluate targeting rules
45
- for rollout_rule in rollout_rules
46
- comparison_attribute = rollout_rule.fetch(COMPARISON_ATTRIBUTE)
47
- comparison_value = rollout_rule.fetch(COMPARISON_VALUE, nil)
48
- comparator = rollout_rule.fetch(COMPARATOR, nil)
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
- user_value = user.get_attribute(comparison_attribute)
51
- if user_value === nil || !user_value
52
- ConfigCat.logger.info(format_no_match_rule(comparison_attribute, user_value, comparator, comparison_value))
53
- next
54
- end
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
- value = rollout_rule.fetch(VALUE, nil)
57
+ value = rollout_rule.fetch(VALUE, nil)
58
+ variation_id = rollout_rule.fetch(VARIATION_ID, default_variation_id)
57
59
 
58
- # IS ONE OF
59
- if comparator == 0
60
- if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
61
- ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
62
- return value
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
- rescue ArgumentError => e
96
- ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s))
97
- next
98
- end
99
- # LESS THAN, LESS THAN OR EQUALS TO, GREATER THAN, GREATER THAN OR EQUALS TO (Semantic version)
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
- rescue ArgumentError => e
112
- ConfigCat.logger.warn(format_validation_error_rule(comparison_attribute, user_value, comparator, comparison_value, e.to_s))
113
- next
114
- end
115
- elsif (10 <= comparator) && (comparator <= 15)
116
- begin
117
- user_value_float = Float(user_value.to_s.gsub(",", "."))
118
- comparison_value_float = Float(comparison_value.to_s.gsub(",", "."))
119
- if (comparator == 10 && user_value_float == comparison_value_float) ||
120
- (comparator == 11 && user_value_float != comparison_value_float) ||
121
- (comparator == 12 && user_value_float < comparison_value_float) ||
122
- (comparator == 13 && user_value_float <= comparison_value_float) ||
123
- (comparator == 14 && user_value_float > comparison_value_float) ||
124
- (comparator == 15 && user_value_float >= comparison_value_float)
125
- ConfigCat.logger.info(format_match_rule(comparison_attribute, user_value, comparator, comparison_value, value))
126
- return value
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
- if rollout_percentage_items.size > 0
149
- user_key = user.get_identifier()
150
- hash_candidate = ("%s%s" % [key, user_key]).encode("utf-8")
151
- hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base=16) % 100
152
- bucket = 0
153
- for rollout_percentage_item in rollout_percentage_items || []
154
- bucket += rollout_percentage_item.fetch(PERCENTAGE, 0)
155
- if hash_val < bucket
156
- percentage_value = rollout_percentage_item.fetch(VALUE, nil)
157
- ConfigCat.logger.info("Evaluating %% options. Returning %s" % percentage_value)
158
- return percentage_value
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
@@ -5,11 +5,11 @@ module ConfigCat
5
5
  # The user object for variation evaluation
6
6
  #
7
7
 
8
- PREDEFINED = ["identifier", "email", "country"]
8
+ PREDEFINED = ["Identifier", "Email", "Country"]
9
9
 
10
10
  def initialize(identifier, email: nil, country: nil, custom: nil)
11
- @__identifier = identifier
12
- @__data = {"identifier" => identifier, "email" => email, "country" => country}
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.downcase()
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.downcase() == attribute
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
@@ -1,3 +1,3 @@
1
1
  module ConfigCat
2
- VERSION = "3.0.0"
2
+ VERSION = "5.0.0"
3
3
  end
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: 3.0.0
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: 2020-04-09 00:00:00.000000000 Z
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: coveralls
70
+ name: codecov
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.8'
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.8'
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
- rubyforge_project:
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.