configcat 3.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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.