configcat 6.1.0 → 8.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: 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