configcat 5.0.1 → 6.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: 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