configcat 6.1.0 → 8.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: 6979b91044d036414367912894396e332a9fdd10b4239e72294a74949d8e5c82
4
- data.tar.gz: fd58f1ea297a2fce1cf47e4bfe521724677617a32163d7b5bde3f2f80234482e
3
+ metadata.gz: 360b940ee0e89f6e86195d9ba17d4b08df9a8c3dedb237dc3c57e162f7c5cf22
4
+ data.tar.gz: bf281cf73d78d460e7c9d327f5518d707064920f4ee74b15b2db8730b9e69628
5
5
  SHA512:
6
- metadata.gz: ae734ae5e04ae73c6ea7eb62c48a5633a943ac009c2f7b59729028a141932cdfdfceabbdef207d774153f586c0c418df939c0042ea288debe6bfccf877765ff7
7
- data.tar.gz: 50b222dd87b113807f72ab24f3e0dfd01bb752639ab394094e40c34461e57ca0637d59038883b78d02dcdcfcd0469bfc21c6b6a5f74ab0b4658c17fcd8d7e8bc
6
+ metadata.gz: 8bbaae3fba88573a213cbd9d71e512f1ef9bb6b8d59c0a84ce77a867b4df610aaa1a2d5cdfa0fb252db78b53e4522fcd1bb450f7e232b233e4a8f422beab0c39
7
+ data.tar.gz: 23172cbd4aa0e5620bfd8b18ce6274f28b9b3ab8c10b13033d545ddf02e1c7dad95bba4f77add31a5b6013395760862841d718ff8a0b5d5b510a778d94889434
@@ -0,0 +1,317 @@
1
+ module ConfigCat
2
+ CONFIG_FILE_NAME = 'config_v6'
3
+ SERIALIZATION_FORMAT_VERSION = 'v2'
4
+
5
+ # Config
6
+ PREFERENCES = 'p'
7
+ SEGMENTS = 's'
8
+ FEATURE_FLAGS = 'f'
9
+
10
+ # Preferences
11
+ BASE_URL = 'u'
12
+ REDIRECT = 'r'
13
+ SALT = 's'
14
+
15
+ # Segment
16
+ SEGMENT_NAME = 'n' # The first 4 characters of the Segment's name
17
+ SEGMENT_CONDITIONS = 'r' # The list of segment rule conditions (has a logical AND relation between the items).
18
+
19
+ # Segment Condition (User Condition)
20
+ COMPARISON_ATTRIBUTE = 'a' # The attribute of the user object that should be used to evaluate this rule
21
+ COMPARATOR = 'c'
22
+
23
+ # Feature flag (Evaluation Formula)
24
+ SETTING_TYPE = 't' # 0 = bool, 1 = string, 2 = int, 3 = double
25
+ PERCENTAGE_RULE_ATTRIBUTE = 'a' # Percentage rule evaluation hashes this attribute of the User object to calculate the buckets
26
+ TARGETING_RULES = 'r' # Targeting Rules (Logically connected by OR)
27
+ PERCENTAGE_OPTIONS = 'p' # Percentage Options without conditions
28
+ VALUE = 'v'
29
+ VARIATION_ID = 'i'
30
+ INLINE_SALT = 'inline_salt'
31
+
32
+ # Targeting Rule (Evaluation Rule)
33
+ CONDITIONS = 'c'
34
+ SERVED_VALUE = 's' # Value and Variation ID
35
+ TARGETING_RULE_PERCENTAGE_OPTIONS = 'p'
36
+
37
+ # Condition
38
+ USER_CONDITION = 'u'
39
+ SEGMENT_CONDITION = 's' # Segment targeting rule
40
+ PREREQUISITE_FLAG_CONDITION = 'p' # Prerequisite flag targeting rule
41
+
42
+ # Segment Condition
43
+ SEGMENT_INDEX = 's'
44
+ SEGMENT_COMPARATOR = 'c'
45
+ INLINE_SEGMENT = 'inline_segment'
46
+
47
+ # Prerequisite Flag Condition
48
+ PREREQUISITE_FLAG_KEY = 'f'
49
+ PREREQUISITE_COMPARATOR = 'c'
50
+
51
+ # Percentage Option
52
+ PERCENTAGE = 'p'
53
+
54
+ # Value
55
+ BOOL_VALUE = 'b'
56
+ STRING_VALUE = 's'
57
+ INT_VALUE = 'i'
58
+ DOUBLE_VALUE = 'd'
59
+ STRING_LIST_VALUE = 'l'
60
+ UNSUPPORTED_VALUE = 'unsupported_value'
61
+
62
+ module Config
63
+ def self.is_type_mismatch(value, ruby_type)
64
+ is_float_int_mismatch = \
65
+ (value.is_a?(Float) && ruby_type == Integer) || \
66
+ (value.is_a?(Integer) && ruby_type == Float)
67
+
68
+ is_bool_mismatch = value.is_a?(TrueClass) && ruby_type == FalseClass || \
69
+ value.is_a?(FalseClass) && ruby_type == TrueClass
70
+
71
+ if value.class != ruby_type && !is_float_int_mismatch && !is_bool_mismatch
72
+ return true
73
+ end
74
+
75
+ return false
76
+ end
77
+
78
+ def self.get_value(dictionary, setting_type)
79
+ value_descriptor = dictionary[VALUE]
80
+ if value_descriptor.nil?
81
+ raise 'Value is missing'
82
+ end
83
+
84
+ expected_value_type, expected_ruby_type = SettingType.get_type_info(setting_type)
85
+ if expected_value_type.nil?
86
+ raise 'Unsupported setting type'
87
+ end
88
+
89
+ value = value_descriptor[expected_value_type]
90
+ if value.nil? || is_type_mismatch(value, expected_ruby_type)
91
+ raise "Setting value is not of the expected type #{expected_ruby_type}"
92
+ end
93
+
94
+ return value
95
+ end
96
+
97
+ def self.get_value_type(dictionary)
98
+ value = dictionary[VALUE]
99
+ if !value.nil?
100
+ if !value[BOOL_VALUE].nil?
101
+ return TrueClass
102
+ end
103
+ if !value[STRING_VALUE].nil?
104
+ return String
105
+ end
106
+ if !value[INT_VALUE].nil?
107
+ return Integer
108
+ end
109
+ if !value[DOUBLE_VALUE].nil?
110
+ return Float
111
+ end
112
+ end
113
+
114
+ return nil
115
+ end
116
+
117
+ def self.fixup_config_salt_and_segments(config)
118
+ """
119
+ Adds the inline salt and segment to the config.
120
+ When using flag overrides, the original salt and segment indexes may become invalid. Therefore, we copy the
121
+ object references to the locations where they are referenced and use these references instead of the indexes.
122
+ """
123
+ salt = config.fetch(PREFERENCES, {}).fetch(SALT, '')
124
+ segments = config[SEGMENTS] || []
125
+ settings = config[FEATURE_FLAGS] || {}
126
+ settings.each do |_, setting|
127
+ next unless setting.is_a?(Hash)
128
+
129
+ # add salt
130
+ setting[INLINE_SALT] = salt
131
+
132
+ # add segment to the segment conditions
133
+ targeting_rules = setting[TARGETING_RULES] || []
134
+ targeting_rules.each do |targeting_rule|
135
+ conditions = targeting_rule[CONDITIONS] || []
136
+ conditions.each do |condition|
137
+ segment_condition = condition[SEGMENT_CONDITION]
138
+ if segment_condition
139
+ segment_index = segment_condition[SEGMENT_INDEX]
140
+ segment = segments[segment_index]
141
+ segment_condition[INLINE_SEGMENT] = segment
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ class SettingType
150
+ BOOL = 0
151
+ STRING = 1
152
+ INT = 2
153
+ DOUBLE = 3
154
+
155
+ @@setting_type_mapping = {
156
+ SettingType::BOOL => [BOOL_VALUE, TrueClass],
157
+ SettingType::STRING => [STRING_VALUE, String],
158
+ SettingType::INT => [INT_VALUE, Integer],
159
+ SettingType::DOUBLE => [DOUBLE_VALUE, Float]
160
+ }
161
+
162
+ def self.get_type_info(setting_type)
163
+ return @@setting_type_mapping[setting_type] || [nil, nil]
164
+ end
165
+
166
+ def self.from_type(object_type)
167
+ if object_type == TrueClass || object_type == FalseClass
168
+ return BOOL
169
+ elsif object_type == String
170
+ return STRING
171
+ elsif object_type == Integer
172
+ return INT
173
+ elsif object_type == Float
174
+ return DOUBLE
175
+ end
176
+
177
+ return nil
178
+ end
179
+
180
+ def self.to_type(setting_type)
181
+ return get_type_info(setting_type)[1]
182
+ end
183
+
184
+ def self.to_value_type(setting_type)
185
+ return get_type_info(setting_type)[0]
186
+ end
187
+ end
188
+
189
+ module PrerequisiteComparator
190
+ EQUALS = 0
191
+ NOT_EQUALS = 1
192
+ end
193
+
194
+ module SegmentComparator
195
+ IS_IN = 0
196
+ IS_NOT_IN = 1
197
+ end
198
+
199
+ module Comparator
200
+ IS_ONE_OF = 0
201
+ IS_NOT_ONE_OF = 1
202
+ CONTAINS_ANY_OF = 2
203
+ NOT_CONTAINS_ANY_OF = 3
204
+ IS_ONE_OF_SEMVER = 4
205
+ IS_NOT_ONE_OF_SEMVER = 5
206
+ LESS_THAN_SEMVER = 6
207
+ LESS_THAN_OR_EQUAL_SEMVER = 7
208
+ GREATER_THAN_SEMVER = 8
209
+ GREATER_THAN_OR_EQUAL_SEMVER = 9
210
+ EQUALS_NUMBER = 10
211
+ NOT_EQUALS_NUMBER = 11
212
+ LESS_THAN_NUMBER = 12
213
+ LESS_THAN_OR_EQUAL_NUMBER = 13
214
+ GREATER_THAN_NUMBER = 14
215
+ GREATER_THAN_OR_EQUAL_NUMBER = 15
216
+ IS_ONE_OF_HASHED = 16
217
+ IS_NOT_ONE_OF_HASHED = 17
218
+ BEFORE_DATETIME = 18
219
+ AFTER_DATETIME = 19
220
+ EQUALS_HASHED = 20
221
+ NOT_EQUALS_HASHED = 21
222
+ STARTS_WITH_ANY_OF_HASHED = 22
223
+ NOT_STARTS_WITH_ANY_OF_HASHED = 23
224
+ ENDS_WITH_ANY_OF_HASHED = 24
225
+ NOT_ENDS_WITH_ANY_OF_HASHED = 25
226
+ ARRAY_CONTAINS_ANY_OF_HASHED = 26
227
+ ARRAY_NOT_CONTAINS_ANY_OF_HASHED = 27
228
+ EQUALS = 28
229
+ NOT_EQUALS = 29
230
+ STARTS_WITH_ANY_OF = 30
231
+ NOT_STARTS_WITH_ANY_OF = 31
232
+ ENDS_WITH_ANY_OF = 32
233
+ NOT_ENDS_WITH_ANY_OF = 33
234
+ ARRAY_CONTAINS_ANY_OF = 34
235
+ ARRAY_NOT_CONTAINS_ANY_OF = 35
236
+ end
237
+
238
+ COMPARATOR_TEXTS = [
239
+ 'IS ONE OF', # IS_ONE_OF
240
+ 'IS NOT ONE OF', # IS_NOT_ONE_OF
241
+ 'CONTAINS ANY OF', # CONTAINS_ANY_OF
242
+ 'NOT CONTAINS ANY OF', # NOT_CONTAINS_ANY_OF
243
+ 'IS ONE OF', # IS_ONE_OF_SEMVER
244
+ 'IS NOT ONE OF', # IS_NOT_ONE_OF_SEMVER
245
+ '<', # LESS_THAN_SEMVER
246
+ '<=', # LESS_THAN_OR_EQUAL_SEMVER
247
+ '>', # GREATER_THAN_SEMVER
248
+ '>=', # GREATER_THAN_OR_EQUAL_SEMVER
249
+ '=', # EQUALS_NUMBER
250
+ '!=', # NOT_EQUALS_NUMBER
251
+ '<', # LESS_THAN_NUMBER
252
+ '<=', # LESS_THAN_OR_EQUAL_NUMBER
253
+ '>', # GREATER_THAN_NUMBER
254
+ '>=', # GREATER_THAN_OR_EQUAL_NUMBER
255
+ 'IS ONE OF', # IS_ONE_OF_HASHED
256
+ 'IS NOT ONE OF', # IS_NOT_ONE_OF_HASHED
257
+ 'BEFORE', # BEFORE_DATETIME
258
+ 'AFTER', # AFTER_DATETIME
259
+ 'EQUALS', # EQUALS_HASHED
260
+ 'NOT EQUALS', # NOT_EQUALS_HASHED
261
+ 'STARTS WITH ANY OF', # STARTS_WITH_ANY_OF_HASHED
262
+ 'NOT STARTS WITH ANY OF', # NOT_STARTS_WITH_ANY_OF_HASHED
263
+ 'ENDS WITH ANY OF', # ENDS_WITH_ANY_OF_HASHED
264
+ 'NOT ENDS WITH ANY OF', # NOT_ENDS_WITH_ANY_OF_HASHED
265
+ 'ARRAY CONTAINS ANY OF', # ARRAY_CONTAINS_ANY_OF_HASHED
266
+ 'ARRAY NOT CONTAINS ANY OF', # ARRAY_NOT_CONTAINS_ANY_OF_HASHED
267
+ 'EQUALS', # EQUALS
268
+ 'NOT EQUALS', # NOT_EQUALS
269
+ 'STARTS WITH ANY OF', # STARTS_WITH_ANY_OF
270
+ 'NOT STARTS WITH ANY OF', # NOT_STARTS_WITH_ANY_OF
271
+ 'ENDS WITH ANY OF', # ENDS_WITH_ANY_OF
272
+ 'NOT ENDS WITH ANY OF', # NOT_ENDS_WITH_ANY_OF
273
+ 'ARRAY CONTAINS ANY OF', # ARRAY_CONTAINS_ANY_OF
274
+ 'ARRAY NOT CONTAINS ANY OF' # ARRAY_NOT_CONTAINS_ANY_OF
275
+ ]
276
+
277
+ COMPARISON_VALUES = [
278
+ STRING_LIST_VALUE, # IS_ONE_OF
279
+ STRING_LIST_VALUE, # IS_NOT_ONE_OF
280
+ STRING_LIST_VALUE, # CONTAINS_ANY_OF
281
+ STRING_LIST_VALUE, # NOT_CONTAINS_ANY_OF
282
+ STRING_LIST_VALUE, # IS_ONE_OF_SEMVER
283
+ STRING_LIST_VALUE, # IS_NOT_ONE_OF_SEMVER
284
+ STRING_VALUE, # LESS_THAN_SEMVER
285
+ STRING_VALUE, # LESS_THAN_OR_EQUAL_SEMVER
286
+ STRING_VALUE, # GREATER_THAN_SEMVER
287
+ STRING_VALUE, # GREATER_THAN_OR_EQUAL_SEMVER
288
+ DOUBLE_VALUE, # EQUALS_NUMBER
289
+ DOUBLE_VALUE, # NOT_EQUALS_NUMBER
290
+ DOUBLE_VALUE, # LESS_THAN_NUMBER
291
+ DOUBLE_VALUE, # LESS_THAN_OR_EQUAL_NUMBER
292
+ DOUBLE_VALUE, # GREATER_THAN_NUMBER
293
+ DOUBLE_VALUE, # GREATER_THAN_OR_EQUAL_NUMBER
294
+ STRING_LIST_VALUE, # IS_ONE_OF_HASHED
295
+ STRING_LIST_VALUE, # IS_NOT_ONE_OF_HASHED
296
+ DOUBLE_VALUE, # BEFORE_DATETIME
297
+ DOUBLE_VALUE, # AFTER_DATETIME
298
+ STRING_VALUE, # EQUALS_HASHED
299
+ STRING_VALUE, # NOT_EQUALS_HASHED
300
+ STRING_LIST_VALUE, # STARTS_WITH_ANY_OF_HASHED
301
+ STRING_LIST_VALUE, # NOT_STARTS_WITH_ANY_OF_HASHED
302
+ STRING_LIST_VALUE, # ENDS_WITH_ANY_OF_HASHED
303
+ STRING_LIST_VALUE, # NOT_ENDS_WITH_ANY_OF_HASHED
304
+ STRING_LIST_VALUE, # ARRAY_CONTAINS_ANY_OF_HASHED
305
+ STRING_LIST_VALUE, # ARRAY_NOT_CONTAINS_ANY_OF_HASHED
306
+ STRING_VALUE, # EQUALS
307
+ STRING_VALUE, # NOT_EQUALS
308
+ STRING_LIST_VALUE, # STARTS_WITH_ANY_OF
309
+ STRING_LIST_VALUE, # NOT_STARTS_WITH_ANY_OF
310
+ STRING_LIST_VALUE, # ENDS_WITH_ANY_OF
311
+ STRING_LIST_VALUE, # NOT_ENDS_WITH_ANY_OF
312
+ STRING_LIST_VALUE, # ARRAY_CONTAINS_ANY_OF
313
+ STRING_LIST_VALUE # ARRAY_NOT_CONTAINS_ANY_OF
314
+ ]
315
+ SEGMENT_COMPARATOR_TEXTS = ['IS IN SEGMENT', 'IS NOT IN SEGMENT']
316
+ PREREQUISITE_COMPARATOR_TEXTS = ['EQUALS', 'DOES NOT EQUAL']
317
+ end
@@ -60,15 +60,26 @@ module ConfigCat
60
60
  raise ConfigCatClientException, "SDK Key is required."
61
61
  end
62
62
 
63
- @_sdk_key = sdk_key
64
- @_default_user = options.default_user
65
- @_rollout_evaluator = RolloutEvaluator.new(@log)
66
63
  if options.flag_overrides
67
64
  @_override_data_source = options.flag_overrides.create_data_source(@log)
68
65
  else
69
66
  @_override_data_source = nil
70
67
  end
71
68
 
69
+ # In case of local only flag overrides mode, we accept any SDK Key format.
70
+ if @_override_data_source.nil? || @_override_data_source.get_behaviour() != OverrideBehaviour::LOCAL_ONLY
71
+ is_valid_sdk_key = /^.{22}\/.{22}$/.match?(sdk_key) ||
72
+ /^configcat-sdk-1\/.{22}\/.{22}$/.match?(sdk_key) ||
73
+ (options.base_url && /^configcat-proxy\/.+$/.match?(sdk_key))
74
+ unless is_valid_sdk_key
75
+ raise ConfigCatClientException, "SDK Key `#{sdk_key}` is invalid."
76
+ end
77
+ end
78
+
79
+ @_sdk_key = sdk_key
80
+ @_default_user = options.default_user
81
+ @_rollout_evaluator = RolloutEvaluator.new(@log)
82
+
72
83
  config_cache = options.config_cache.nil? ? NullConfigCache.new : options.config_cache
73
84
 
74
85
  if @_override_data_source && @_override_data_source.get_behaviour() == OverrideBehaviour::LOCAL_ONLY
@@ -104,14 +115,14 @@ module ConfigCat
104
115
  # :param user [User] the user object to identify the caller.
105
116
  # :return the value.
106
117
  def get_value(key, default_value, user = nil)
107
- settings, fetch_time = _get_settings()
108
- if settings.nil?
118
+ config, fetch_time = _get_config()
119
+ if config.nil? || config[FEATURE_FLAGS].nil?
109
120
  message = "Config JSON is not present when evaluating setting '#{key}'. Returning the `default_value` parameter that you specified in your application: '#{default_value}'."
110
121
  @log.error(1000, message)
111
122
  @hooks.invoke_on_flag_evaluated(EvaluationDetails.from_error(key, default_value, error: message))
112
123
  return default_value
113
124
  end
114
- details = _evaluate(key, user, default_value, nil, settings, fetch_time)
125
+ details = _evaluate(key, user, default_value, nil, config, fetch_time)
115
126
  return details.value
116
127
  end
117
128
 
@@ -122,15 +133,15 @@ module ConfigCat
122
133
  # :param user [User] the user object to identify the caller.
123
134
  # :return [EvaluationDetails] the evaluation details.
124
135
  def get_value_details(key, default_value, user = nil)
125
- settings, fetch_time = _get_settings()
126
- if settings.nil?
136
+ config, fetch_time = _get_config()
137
+ if config.nil? || config[FEATURE_FLAGS].nil?
127
138
  message = "Config JSON is not present when evaluating setting '#{key}'. Returning the `default_value` parameter that you specified in your application: '#{default_value}'."
128
139
  @log.error(1000, message)
129
140
  details = EvaluationDetails.from_error(key, default_value, error: message)
130
141
  @hooks.invoke_on_flag_evaluated(details)
131
142
  return details
132
143
  end
133
- details = _evaluate(key, user, default_value, nil, settings, fetch_time)
144
+ details = _evaluate(key, user, default_value, nil, config, fetch_time)
134
145
  return details
135
146
  end
136
147
 
@@ -138,89 +149,61 @@ module ConfigCat
138
149
  #
139
150
  # :return list of keys.
140
151
  def get_all_keys
141
- settings, _ = _get_settings()
142
- if settings === nil
152
+ config, _ = _get_config()
153
+ if config.nil?
143
154
  @log.error(1000, "Config JSON is not present. Returning empty list.")
144
155
  return []
145
156
  end
157
+ settings = config.fetch(FEATURE_FLAGS, {})
146
158
  return settings.keys
147
159
  end
148
160
 
149
- # Gets the Variation ID (analytics) of a feature flag or setting based on it's key.
150
- #
151
- # :param key [String] the identifier of the feature flag or setting.
152
- # :param default_variation_id in case of any failure, this value will be returned.
153
- # :param user [User] the user object to identify the caller.
154
- # :return the variation ID.
155
- def get_variation_id(key, default_variation_id, user = nil)
156
- ConfigCat.logger.warn("get_variation_id is deprecated and will be removed in a future major version. " \
157
- "Please use [get_value_details] instead.")
158
-
159
- settings, fetch_time = _get_settings()
160
- if settings === nil
161
- message = "Config JSON is not present when evaluating setting '#{key}'. Returning the `default_variation_id` parameter that you specified in your application: '#{default_variation_id}'."
162
- @log.error(1000, message)
163
- @hooks.invoke_on_flag_evaluated(EvaluationDetails.from_error(key, nil, error: message,
164
- variation_id: default_variation_id))
165
- return default_variation_id
166
- end
167
- details = _evaluate(key, user, nil, default_variation_id, settings, fetch_time)
168
- return details.variation_id
169
- end
170
-
171
- # Gets the Variation IDs (analytics) of all feature flags or settings.
172
- #
173
- # :param user [User] the user object to identify the caller.
174
- # :return list of variation IDs
175
- def get_all_variation_ids(user = nil)
176
- ConfigCat.logger.warn("get_all_variation_ids is deprecated and will be removed in a future major version. " \
177
- "Please use [get_value_details] instead.")
178
-
179
- settings, _ = _get_settings()
180
- if settings === nil
181
- @log.error(1000, "Config JSON is not present. Returning empty list.")
182
- return []
183
- end
184
-
185
- variation_ids = []
186
- for key in settings.keys
187
- variation_id = get_variation_id(key, nil, user)
188
- if !variation_id.equal?(nil)
189
- variation_ids.push(variation_id)
190
- end
191
- end
192
- return variation_ids
193
- end
194
-
195
161
  # Gets the key of a setting, and it's value identified by the given Variation ID (analytics)
196
162
  #
197
163
  # :param variation_id [String] variation ID
198
164
  # :return key and value
199
165
  def get_key_and_value(variation_id)
200
- settings, _ = _get_settings()
201
- if settings === nil
166
+ config, _ = _get_config()
167
+ if config.nil?
202
168
  @log.error(1000, "Config JSON is not present. Returning nil.")
203
169
  return nil
204
170
  end
205
171
 
206
- for key, value in settings
207
- if variation_id == value.fetch(VARIATION_ID, nil)
208
- return KeyValue.new(key, value[VALUE])
209
- end
172
+ settings = config.fetch(FEATURE_FLAGS, {})
173
+ begin
174
+ settings.each do |key, value|
175
+ setting_type = value.fetch(SETTING_TYPE, nil)
176
+ if variation_id == value.fetch(VARIATION_ID, nil)
177
+ return KeyValue.new(key, Config.get_value(value, setting_type))
178
+ end
210
179
 
211
- rollout_rules = value.fetch(ROLLOUT_RULES, [])
212
- for rollout_rule in rollout_rules
213
- if variation_id == rollout_rule.fetch(VARIATION_ID, nil)
214
- return KeyValue.new(key, rollout_rule[VALUE])
180
+ targeting_rules = value.fetch(TARGETING_RULES, [])
181
+ targeting_rules.each do |targeting_rule|
182
+ served_value = targeting_rule.fetch(SERVED_VALUE, nil)
183
+ if !served_value.nil?
184
+ if variation_id == served_value.fetch(VARIATION_ID, nil)
185
+ return KeyValue.new(key, Config.get_value(served_value, setting_type))
186
+ end
187
+ else
188
+ percentage_options = targeting_rule.fetch(PERCENTAGE_OPTIONS, [])
189
+ percentage_options.each do |percentage_option|
190
+ if variation_id == percentage_option.fetch(VARIATION_ID, nil)
191
+ return KeyValue.new(key, Config.get_value(percentage_option, setting_type))
192
+ end
193
+ end
194
+ end
215
195
  end
216
- end
217
196
 
218
- rollout_percentage_items = value.fetch(ROLLOUT_PERCENTAGE_ITEMS, [])
219
- for rollout_percentage_item in rollout_percentage_items
220
- if variation_id == rollout_percentage_item.fetch(VARIATION_ID, nil)
221
- return KeyValue.new(key, rollout_percentage_item[VALUE])
197
+ percentage_options = value.fetch(PERCENTAGE_OPTIONS, [])
198
+ percentage_options.each do |percentage_option|
199
+ if variation_id == percentage_option.fetch(VARIATION_ID, nil)
200
+ return KeyValue.new(key, Config.get_value(percentage_option, setting_type))
201
+ end
222
202
  end
223
203
  end
204
+ rescue => e
205
+ @log.error("Error occurred in the `#{self.class.name}` method. Returning nil.", event_id: 1002)
206
+ return nil
224
207
  end
225
208
 
226
209
  @log.error(2011, "Could not find the setting for the specified variation ID: '#{variation_id}'.")
@@ -231,12 +214,13 @@ module ConfigCat
231
214
  # :param user [User] the user object to identify the caller.
232
215
  # :return dictionary of values
233
216
  def get_all_values(user = nil)
234
- settings, _ = _get_settings()
235
- if settings === nil
217
+ config, _ = _get_config()
218
+ if config.nil?
236
219
  @log.error(1000, "Config JSON is not present. Returning empty dictionary.")
237
220
  return {}
238
221
  end
239
222
 
223
+ settings = config.fetch(FEATURE_FLAGS, {})
240
224
  all_values = {}
241
225
  for key in settings.keys
242
226
  value = get_value(key, nil, user)
@@ -252,15 +236,16 @@ module ConfigCat
252
236
  # :param user [User] the user object to identify the caller.
253
237
  # :return list of all evaluation details
254
238
  def get_all_value_details(user = nil)
255
- settings, fetch_time = _get_settings()
256
- if settings.nil?
239
+ config, fetch_time = _get_config()
240
+ if config.nil?
257
241
  @log.error(1000, "Config JSON is not present. Returning empty list.")
258
242
  return []
259
243
  end
260
244
 
261
245
  details_result = []
246
+ settings = config.fetch(FEATURE_FLAGS, {})
262
247
  for key in settings.keys
263
- details = _evaluate(key, user, nil, nil, settings, fetch_time)
248
+ details = _evaluate(key, user, nil, nil, config, fetch_time)
264
249
  details_result.push(details)
265
250
  end
266
251
 
@@ -273,8 +258,8 @@ module ConfigCat
273
258
  def force_refresh
274
259
  return @_config_service.refresh if @_config_service
275
260
 
276
- return RefreshResult(false,
277
- "The SDK uses the LocalOnly flag override behavior which prevents making HTTP requests.")
261
+ return RefreshResult.new(false,
262
+ "The SDK uses the LocalOnly flag override behavior which prevents making HTTP requests.")
278
263
  end
279
264
 
280
265
  # Sets the default user.
@@ -324,44 +309,57 @@ module ConfigCat
324
309
  @hooks.clear
325
310
  end
326
311
 
327
- def _get_settings
312
+ def _get_config
328
313
  if !@_override_data_source.nil?
329
314
  behaviour = @_override_data_source.get_behaviour()
330
315
  if behaviour == OverrideBehaviour::LOCAL_ONLY
331
316
  return @_override_data_source.get_overrides(), Utils::DISTANT_PAST
332
317
  elsif behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL
333
- remote_settings, fetch_time = @_config_service.get_settings()
334
- local_settings = @_override_data_source.get_overrides()
335
- remote_settings ||= {}
336
- local_settings ||= {}
337
- result = local_settings.clone()
338
- result.update(remote_settings)
318
+ remote_config, fetch_time = @_config_service.get_config()
319
+ local_config = @_override_data_source.get_overrides()
320
+ remote_config ||= { FEATURE_FLAGS => {} }
321
+ local_config ||= { FEATURE_FLAGS => {} }
322
+ result = local_config.clone()
323
+ result[FEATURE_FLAGS].update(remote_config[FEATURE_FLAGS])
339
324
  return result, fetch_time
340
325
  elsif behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE
341
- remote_settings, fetch_time = @_config_service.get_settings()
342
- local_settings = @_override_data_source.get_overrides()
343
- remote_settings ||= {}
344
- local_settings ||= {}
345
- result = remote_settings.clone()
346
- result.update(local_settings)
326
+ remote_config, fetch_time = @_config_service.get_config()
327
+ local_config = @_override_data_source.get_overrides()
328
+ remote_config ||= { FEATURE_FLAGS => {} }
329
+ local_config ||= { FEATURE_FLAGS => {} }
330
+ result = remote_config.clone()
331
+ result[FEATURE_FLAGS].update(local_config[FEATURE_FLAGS])
347
332
  return result, fetch_time
348
333
  end
349
334
  end
350
- return @_config_service.get_settings()
335
+ return @_config_service.get_config()
351
336
  end
352
337
 
353
- def _get_cache_key
354
- return Digest::SHA1.hexdigest("ruby_" + CONFIG_FILE_NAME + "_" + @_sdk_key)
338
+ def _check_type_mismatch(value, default_value)
339
+ if !default_value.nil? && Config.is_type_mismatch(value, default_value.class)
340
+ @log.warn(4002, "The type of a setting does not match the type of the specified default value (#{default_value}). " \
341
+ "Setting's type was #{value.class} but the default value's type was #{default_value.class}. " \
342
+ "Please make sure that using a default value not matching the setting's type was intended.")
343
+ end
355
344
  end
356
345
 
357
- def _evaluate(key, user, default_value, default_variation_id, settings, fetch_time)
358
- user = user || @_default_user
346
+ def _evaluate(key, user, default_value, default_variation_id, config, fetch_time)
347
+ user ||= @_default_user
348
+
349
+ # Skip building the evaluation log if it won't be logged.
350
+ log_builder = EvaluationLogBuilder.new if @log.enabled_for?(Logger::INFO)
351
+
359
352
  value, variation_id, rule, percentage_rule, error = @_rollout_evaluator.evaluate(
360
353
  key: key,
361
354
  user: user,
362
355
  default_value: default_value,
363
356
  default_variation_id: default_variation_id,
364
- settings: settings)
357
+ config: config,
358
+ log_builder: log_builder)
359
+
360
+ _check_type_mismatch(value, default_value)
361
+
362
+ @log.info(5000, log_builder.to_s) if log_builder
365
363
 
366
364
  details = EvaluationDetails.new(key: key,
367
365
  value: value,
@@ -370,8 +368,8 @@ module ConfigCat
370
368
  user: user,
371
369
  is_default_value: error.nil? || error.empty? ? false : true,
372
370
  error: error,
373
- matched_evaluation_rule: rule,
374
- matched_evaluation_percentage_rule: percentage_rule)
371
+ matched_targeting_rule: rule,
372
+ matched_percentage_option: percentage_rule)
375
373
  @hooks.invoke_on_flag_evaluated(details)
376
374
  return details
377
375
  end
@@ -4,6 +4,10 @@ module ConfigCat
4
4
  @hooks = hooks
5
5
  end
6
6
 
7
+ def enabled_for?(log_level)
8
+ ConfigCat.logger.level <= log_level
9
+ end
10
+
7
11
  def debug(message)
8
12
  ConfigCat.logger.debug("[0] " + message)
9
13
  end