configcat 5.0.1 → 6.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: 980bb5bf4d92b6a479a10727b25ea488461317569f3b60943642db8fa9960898
4
- data.tar.gz: 9de2dc2d9fe881372f70e862bb72af396a9ea99b81defabf3c82e1b19423078e
3
+ metadata.gz: ce490d2a3a5e6bc5762305dbd0740f2658b5e316a514306a7ab2061ccafe4382
4
+ data.tar.gz: c4ed8386bb8644fa723f82a677f64fe60c7dab44aa03eacceeec965231402d82
5
5
  SHA512:
6
- metadata.gz: 7d1e19a4729ef760d686a4e47c2efd2d82464adfa652caf2782b4ebcfefc3edc7f27baddfdcdd9e66264ee0cd13d9806a51b8c171d74cdd21b6088073aff0368
7
- data.tar.gz: 1640d5a7a23f9af9461ee38bfb6a789cbed6d898c582bb325f18c27b20dec7944c60dbbe9dc749800a0325be844a252225dfb769f89acd157f644bb1d1a93ef9
6
+ metadata.gz: 0f80a0a78a5d0023de47a59eafc4fbb18ba40cbab7302bd260cd54b6b383113039a1a087b74cf72657e49bfcc1af84afd9d76d220c9696807f600dd4c393d1a4
7
+ data.tar.gz: 134cf29821feeb9f23debde30ede59545a45da81c8d8a0100a4684d01909c84f67c48d5029ca5734a618d226e55411f35fc6f412710b668b6b62f1ca15f82635
@@ -1,17 +1,32 @@
1
1
  require 'configcat/interfaces'
2
2
 
3
3
  module ConfigCat
4
+ class NullConfigCache < ConfigCache
5
+ def initialize
6
+ @value = {}
7
+ end
8
+
9
+ def get(key)
10
+ return nil
11
+ end
12
+
13
+ def set(key, value)
14
+ # do nothing
15
+ end
16
+ end
17
+
4
18
  class InMemoryConfigCache < ConfigCache
5
- def initialize()
6
- @_value = {}
19
+ attr_reader :value
20
+ def initialize
21
+ @value = {}
7
22
  end
8
23
 
9
24
  def get(key)
10
- return @_value.fetch(key, nil)
25
+ return @value.fetch(key, nil)
11
26
  end
12
27
 
13
28
  def set(key, value)
14
- @_value[key] = value
29
+ @value[key] = value
15
30
  end
16
31
  end
17
32
  end
@@ -1,114 +1,182 @@
1
1
  require 'configcat/interfaces'
2
2
  require 'configcat/configcache'
3
+ require 'configcat/configcatoptions'
3
4
  require 'configcat/configfetcher'
4
- require 'configcat/autopollingcachepolicy'
5
- require 'configcat/manualpollingcachepolicy'
6
- require 'configcat/lazyloadingcachepolicy'
7
5
  require 'configcat/rolloutevaluator'
8
- require 'configcat/datagovernance'
6
+ require 'configcat/utils'
7
+ require 'configcat/configcatlogger'
8
+ require 'configcat/overridedatasource'
9
+ require 'configcat/configservice'
10
+ require 'configcat/evaluationdetails'
9
11
 
10
12
 
11
13
  module ConfigCat
12
14
  KeyValue = Struct.new(:key, :value)
13
15
  class ConfigCatClient
14
- @@sdk_keys = []
15
-
16
- def initialize(sdk_key,
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)
31
- if sdk_key === nil
32
- raise ConfigCatClientException, "SDK Key is required."
16
+ attr_reader :log, :hooks
17
+
18
+ @@lock = Mutex.new
19
+ @@instances = {}
20
+
21
+ # Creates a new or gets an already existing `ConfigCatClient` for the given `sdk_key`.
22
+ #
23
+ # :param sdk_key [String] ConfigCat SDK Key to access your configuration.
24
+ # :param options [ConfigCatOptions] Configuration for `ConfigCatClient`.
25
+ # :return [ConfigCatClient] the `ConfigCatClient` instance.
26
+ def self.get(sdk_key, options = nil)
27
+ @@lock.synchronize do
28
+ client = @@instances[sdk_key]
29
+ if client
30
+ if options
31
+ client.log.warn("Client for sdk_key `#{sdk_key}` is already created and will be reused; " +
32
+ "options passed are being ignored.")
33
+ end
34
+ return client
35
+ end
36
+
37
+ options ||= ConfigCatOptions.new
38
+ client = ConfigCatClient.new(sdk_key, options)
39
+ @@instances[sdk_key] = client
40
+ return client
33
41
  end
42
+ end
34
43
 
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)
44
+ # Closes all ConfigCatClient instances.
45
+ def self.close_all
46
+ @@lock.synchronize do
47
+ @@instances.each do |key, value|
48
+ value.send(:_close_resources)
49
+ end
50
+ @@instances.clear
41
51
  end
52
+ end
42
53
 
43
- @_sdk_key = sdk_key
44
- @_override_data_source = flag_overrides
54
+ private def initialize(sdk_key, options = ConfigCatOptions.new)
55
+ @hooks = options.hooks || Hooks.new
56
+ @log = ConfigCatLogger.new(@hooks)
45
57
 
46
- if config_cache_class
47
- @_config_cache = config_cache_class.new()
58
+ if sdk_key === nil
59
+ raise ConfigCatClientException, "SDK Key is required."
60
+ end
61
+
62
+ @_sdk_key = sdk_key
63
+ @_default_user = options.default_user
64
+ @_rollout_evaluator = RolloutEvaluator.new(@log)
65
+ if options.flag_overrides
66
+ @_override_data_source = options.flag_overrides.create_data_source(@log)
48
67
  else
49
- @_config_cache = InMemoryConfigCache.new()
68
+ @_override_data_source = nil
50
69
  end
51
70
 
52
- if !@_override_data_source.equal?(nil) && @_override_data_source.get_behaviour() == OverrideBehaviour::LOCAL_ONLY
71
+ config_cache = options.config_cache.nil? ? NullConfigCache.new : options.config_cache
72
+
73
+ if @_override_data_source && @_override_data_source.get_behaviour() == OverrideBehaviour::LOCAL_ONLY
53
74
  @_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)
75
+ @_config_service = nil
67
76
  else
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())
77
+ @_config_fetcher = ConfigFetcher.new(@_sdk_key,
78
+ @log,
79
+ options.polling_mode.identifier,
80
+ base_url: options.base_url,
81
+ proxy_address: options.proxy_address,
82
+ proxy_port: options.proxy_port,
83
+ proxy_user: options.proxy_user,
84
+ proxy_pass: options.proxy_pass,
85
+ open_timeout: options.open_timeout_seconds,
86
+ read_timeout: options.read_timeout_seconds,
87
+ data_governance: options.data_governance)
88
+
89
+ @_config_service = ConfigService.new(@sdk_key,
90
+ options.polling_mode,
91
+ @hooks,
92
+ @_config_fetcher,
93
+ @log,
94
+ config_cache,
95
+ options.offline)
73
96
  end
74
97
  end
75
98
 
76
- def get_value(key, default_value, user=nil)
77
- config = _get_settings()
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])
99
+ # Gets the value of a feature flag or setting identified by the given `key`.
100
+ #
101
+ # :param key [String] the identifier of the feature flag or setting.
102
+ # :param default_value in case of any failure, this value will be returned.
103
+ # :param user [User] the user object to identify the caller.
104
+ # :return the value.
105
+ def get_value(key, default_value, user = nil)
106
+ settings, fetch_time = _get_settings()
107
+ if settings.nil?
108
+ message = "Evaluating get_value('%s') failed. Cache is empty. " \
109
+ "Returning default_value in your get_value call: [%s]." % [key, default_value.to_s]
110
+ @log.error(message)
111
+ @hooks.invoke_on_flag_evaluated(EvaluationDetails.from_error(key, default_value, error: message))
81
112
  return default_value
82
113
  end
83
- value, variation_id = RolloutEvaluator.evaluate(key, user, default_value, nil, config)
84
- return value
114
+ details = _evaluate(key, user, default_value, nil, settings, fetch_time)
115
+ return details.value
85
116
  end
86
117
 
87
- def get_all_keys()
88
- config = _get_settings()
89
- if config === nil
90
- return []
118
+ # Gets the value and evaluation details of a feature flag or setting identified by the given `key`.
119
+ #
120
+ # :param key [String] the identifier of the feature flag or setting.
121
+ # :param default_value in case of any failure, this value will be returned.
122
+ # :param user [User] the user object to identify the caller.
123
+ # :return [EvaluationDetails] the evaluation details.
124
+ def get_value_details(key, default_value, user = nil)
125
+ settings, fetch_time = _get_settings()
126
+ if settings.nil?
127
+ message = "Evaluating get_value_details('%s') failed. Cache is empty. " \
128
+ "Returning default_value in your get_value_details call: [%s]." % [key, default_value.to_s]
129
+ @log.error(message)
130
+ @hooks.invoke_on_flag_evaluated(EvaluationDetails.from_error(key, default_value, error: message))
131
+ return default_value
91
132
  end
92
- feature_flags = config.fetch(FEATURE_FLAGS, nil)
93
- if feature_flags === nil
133
+ details = _evaluate(key, user, default_value, nil, settings, fetch_time)
134
+ return details
135
+ end
136
+
137
+ # Gets all setting keys.
138
+ #
139
+ # :return list of keys.
140
+ def get_all_keys
141
+ settings, _ = _get_settings()
142
+ if settings === nil
94
143
  return []
95
144
  end
96
- return feature_flags.keys
145
+ return settings.keys
97
146
  end
98
147
 
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])
148
+ # Gets the Variation ID (analytics) of a feature flag or setting based on it's key.
149
+ #
150
+ # :param key [String] the identifier of the feature flag or setting.
151
+ # :param default_variation_id in case of any failure, this value will be returned.
152
+ # :param user [User] the user object to identify the caller.
153
+ # :return the variation ID.
154
+ def get_variation_id(key, default_variation_id, user = nil)
155
+ @log.warn("get_variation_id is deprecated and will be removed in a future major version. "\
156
+ "Please use [get_value_details] instead.")
157
+
158
+ settings, fetch_time = _get_settings()
159
+ if settings === nil
160
+ message = "Evaluating get_variation_id('%s') failed. Cache is empty. "\
161
+ "Returning default_variation_id in your get_variation_id call: [%s]." %
162
+ [key, default_variation_id.to_s]
163
+ @log.error(message)
164
+ @hooks.invoke_on_flag_evaluated(EvaluationDetails.from_error(key, nil, error: message,
165
+ variation_id: default_variation_id))
105
166
  return default_variation_id
106
167
  end
107
- value, variation_id = RolloutEvaluator.evaluate(key, user, nil, default_variation_id, config)
108
- return variation_id
168
+ details = _evaluate(key, user, nil, default_variation_id, settings, fetch_time)
169
+ return details.variation_id
109
170
  end
110
171
 
111
- def get_all_variation_ids(user: nil)
172
+ # Gets the Variation IDs (analytics) of all feature flags or settings.
173
+ #
174
+ # :param user [User] the user object to identify the caller.
175
+ # :return list of variation IDs
176
+ def get_all_variation_ids(user = nil)
177
+ @log.warn("get_all_variation_ids is deprecated and will be removed in a future major version. "\
178
+ "Please use [get_value_details] instead.")
179
+
112
180
  keys = get_all_keys()
113
181
  variation_ids = []
114
182
  for key in keys
@@ -120,20 +188,18 @@ module ConfigCat
120
188
  return variation_ids
121
189
  end
122
190
 
191
+ # Gets the key of a setting, and it's value identified by the given Variation ID (analytics)
192
+ #
193
+ # :param variation_id [String] variation ID
194
+ # :return key and value
123
195
  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)
196
+ settings, _ = _get_settings()
197
+ if settings === nil
198
+ @log.warn("Evaluating get_key_and_value('%s') failed. Cache is empty. Returning nil." % variation_id)
133
199
  return nil
134
200
  end
135
201
 
136
- for key, value in feature_flags
202
+ for key, value in settings
137
203
  if variation_id == value.fetch(VARIATION_ID, nil)
138
204
  return KeyValue.new(key, value[VALUE])
139
205
  end
@@ -152,9 +218,15 @@ module ConfigCat
152
218
  end
153
219
  end
154
220
  end
221
+
222
+ @log.error("Could not find the setting for the given variation_id: " + variation_id)
155
223
  end
156
224
 
157
- def get_all_values(user: nil)
225
+ # Evaluates and returns the values of all feature flags and settings.
226
+ #
227
+ # :param user [User] the user object to identify the caller.
228
+ # :return dictionary of values
229
+ def get_all_values(user = nil)
158
230
  keys = get_all_keys()
159
231
  all_values = {}
160
232
  for key in keys
@@ -166,47 +238,131 @@ module ConfigCat
166
238
  return all_values
167
239
  end
168
240
 
169
- def force_refresh()
170
- @_cache_policy.force_refresh()
241
+ # Gets the values along with evaluation details of all feature flags and settings.
242
+ #
243
+ # :param user [User] the user object to identify the caller.
244
+ # :return list of all evaluation details
245
+ def get_all_value_details(user = nil)
246
+ settings, fetch_time = _get_settings()
247
+ if settings.nil?
248
+ @log.error("Evaluating get_all_value_details() failed. Cache is empty. Returning empty list.")
249
+ return []
250
+ end
251
+
252
+ details_result = []
253
+ for key in settings.keys
254
+ details = _evaluate(key, user, nil, nil, settings, fetch_time)
255
+ details_result.push(details)
256
+ end
257
+
258
+ return details_result
259
+ end
260
+
261
+ # Initiates a force refresh on the cached configuration.
262
+ #
263
+ # :return [RefreshResult]
264
+ def force_refresh
265
+ return @_config_service.refresh if @_config_service
266
+
267
+ return RefreshResult(false,
268
+ "The SDK uses the LocalOnly flag override behavior which prevents making HTTP requests.")
269
+ end
270
+
271
+ # Sets the default user.
272
+ #
273
+ # :param user [User] the user object to identify the caller.
274
+ def set_default_user(user)
275
+ @_default_user = user
276
+ end
277
+
278
+ # Sets the default user to nil.
279
+ def clear_default_user
280
+ @_default_user = nil
171
281
  end
172
282
 
173
- def stop()
174
- @_cache_policy.stop() if @_cache_policy
175
- @_config_fetcher.close() if @_config_fetcher
176
- @@sdk_keys.delete(@_sdk_key)
283
+ # Configures the SDK to allow HTTP requests.
284
+ def set_online
285
+ @_config_service.set_online if @_config_service
286
+ @log.debug('Switched to ONLINE mode.')
287
+ end
288
+
289
+ # Configures the SDK to not initiate HTTP requests and work only from its cache.
290
+ def set_offline
291
+ @_config_service.set_offline if @_config_service
292
+ @log.debug('Switched to OFFLINE mode.')
293
+ end
294
+
295
+ # Returns true when the SDK is configured not to initiate HTTP requests, otherwise false.
296
+ def offline?
297
+ return @_config_service ? @_config_service.offline? : true
298
+ end
299
+
300
+ # Closes the underlying resources.
301
+ def close
302
+ @@lock.synchronize do
303
+ _close_resources
304
+ @@instances.delete(@_sdk_key)
305
+ end
177
306
  end
178
307
 
179
308
  private
180
309
 
181
- def _get_settings()
310
+ def _close_resources
311
+ @_config_service.close if @_config_service
312
+ @_config_fetcher.close if @_config_fetcher
313
+ @hooks.clear
314
+ end
315
+
316
+ def _get_settings
182
317
  if !@_override_data_source.nil?
183
318
  behaviour = @_override_data_source.get_behaviour()
184
319
  if behaviour == OverrideBehaviour::LOCAL_ONLY
185
- return @_override_data_source.get_overrides()
320
+ return @_override_data_source.get_overrides(), Utils::DISTANT_PAST
186
321
  elsif behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL
187
- remote_settings = @_cache_policy.get()
322
+ remote_settings, fetch_time = @_config_service.get_settings()
188
323
  local_settings = @_override_data_source.get_overrides()
324
+ remote_settings ||= {}
325
+ local_settings ||= {}
189
326
  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
327
+ result.update(remote_settings)
328
+ return result, fetch_time
194
329
  elsif behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE
195
- remote_settings = @_cache_policy.get()
330
+ remote_settings, fetch_time = @_config_service.get_settings()
196
331
  local_settings = @_override_data_source.get_overrides()
332
+ remote_settings ||= {}
333
+ local_settings ||= {}
197
334
  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
335
+ result.update(local_settings)
336
+ return result, fetch_time
202
337
  end
203
338
  end
204
- return @_cache_policy.get()
339
+ return @_config_service.get_settings()
205
340
  end
206
341
 
207
- def _get_cache_key()
342
+ def _get_cache_key
208
343
  return Digest::SHA1.hexdigest("ruby_" + CONFIG_FILE_NAME + "_" + @_sdk_key)
209
344
  end
210
345
 
346
+ def _evaluate(key, user, default_value, default_variation_id, settings, fetch_time)
347
+ user = user || @_default_user
348
+ value, variation_id, rule, percentage_rule, error = @_rollout_evaluator.evaluate(
349
+ key: key,
350
+ user: user,
351
+ default_value: default_value,
352
+ default_variation_id: default_variation_id,
353
+ settings: settings)
354
+
355
+ details = EvaluationDetails.new(key: key,
356
+ value: value,
357
+ variation_id: variation_id,
358
+ fetch_time: !fetch_time.nil? ? Time.at(fetch_time).utc : nil,
359
+ user: user,
360
+ is_default_value: error.nil? || error.empty? ? false : true,
361
+ error: error,
362
+ matched_evaluation_rule: rule,
363
+ matched_evaluation_percentage_rule: percentage_rule)
364
+ @hooks.invoke_on_flag_evaluated(details)
365
+ return details
366
+ end
211
367
  end
212
368
  end
@@ -0,0 +1,24 @@
1
+ module ConfigCat
2
+ class ConfigCatLogger
3
+ def initialize(hooks)
4
+ @hooks = hooks
5
+ end
6
+
7
+ def debug(message)
8
+ ConfigCat.logger.debug(message)
9
+ end
10
+
11
+ def info(message)
12
+ ConfigCat.logger.info(message)
13
+ end
14
+
15
+ def warn(message)
16
+ ConfigCat.logger.warn(message)
17
+ end
18
+
19
+ def error(message)
20
+ @hooks.invoke_on_error(message)
21
+ ConfigCat.logger.error(message)
22
+ end
23
+ end
24
+ end