configcat 7.0.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: 4e0dc6578b7a148dda7c630d560098fb295eb139f237362db056b235d0feaa7e
4
- data.tar.gz: e9f97b6d5f0af6fa72288bfeac3b13c6354bb1726af129349653b9365a10e665
3
+ metadata.gz: 360b940ee0e89f6e86195d9ba17d4b08df9a8c3dedb237dc3c57e162f7c5cf22
4
+ data.tar.gz: bf281cf73d78d460e7c9d327f5518d707064920f4ee74b15b2db8730b9e69628
5
5
  SHA512:
6
- metadata.gz: a895b147c9238dbeb7e2f02d5021002032b47e023214842fb4528bdfabdf5c6c729bc746ff630f1f5cfb325442ba70ae71e7e3ee2257b3d81cfc8cf60ad06dee
7
- data.tar.gz: 95d70831f41b93bcfa3306e9af41de05eb27ce80df4b734c0f1a6f7f98f83da468ed8abacbd4b252c105cd807bd21859b2cd4f19699b5a426c689bf6b15eedd7
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,11 +149,12 @@ 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
 
@@ -151,30 +163,47 @@ module ConfigCat
151
163
  # :param variation_id [String] variation ID
152
164
  # :return key and value
153
165
  def get_key_and_value(variation_id)
154
- settings, _ = _get_settings()
155
- if settings === nil
166
+ config, _ = _get_config()
167
+ if config.nil?
156
168
  @log.error(1000, "Config JSON is not present. Returning nil.")
157
169
  return nil
158
170
  end
159
171
 
160
- for key, value in settings
161
- if variation_id == value.fetch(VARIATION_ID, nil)
162
- return KeyValue.new(key, value[VALUE])
163
- 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
164
179
 
165
- rollout_rules = value.fetch(ROLLOUT_RULES, [])
166
- for rollout_rule in rollout_rules
167
- if variation_id == rollout_rule.fetch(VARIATION_ID, nil)
168
- 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
169
195
  end
170
- end
171
196
 
172
- rollout_percentage_items = value.fetch(ROLLOUT_PERCENTAGE_ITEMS, [])
173
- for rollout_percentage_item in rollout_percentage_items
174
- if variation_id == rollout_percentage_item.fetch(VARIATION_ID, nil)
175
- 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
176
202
  end
177
203
  end
204
+ rescue => e
205
+ @log.error("Error occurred in the `#{self.class.name}` method. Returning nil.", event_id: 1002)
206
+ return nil
178
207
  end
179
208
 
180
209
  @log.error(2011, "Could not find the setting for the specified variation ID: '#{variation_id}'.")
@@ -185,12 +214,13 @@ module ConfigCat
185
214
  # :param user [User] the user object to identify the caller.
186
215
  # :return dictionary of values
187
216
  def get_all_values(user = nil)
188
- settings, _ = _get_settings()
189
- if settings === nil
217
+ config, _ = _get_config()
218
+ if config.nil?
190
219
  @log.error(1000, "Config JSON is not present. Returning empty dictionary.")
191
220
  return {}
192
221
  end
193
222
 
223
+ settings = config.fetch(FEATURE_FLAGS, {})
194
224
  all_values = {}
195
225
  for key in settings.keys
196
226
  value = get_value(key, nil, user)
@@ -206,15 +236,16 @@ module ConfigCat
206
236
  # :param user [User] the user object to identify the caller.
207
237
  # :return list of all evaluation details
208
238
  def get_all_value_details(user = nil)
209
- settings, fetch_time = _get_settings()
210
- if settings.nil?
239
+ config, fetch_time = _get_config()
240
+ if config.nil?
211
241
  @log.error(1000, "Config JSON is not present. Returning empty list.")
212
242
  return []
213
243
  end
214
244
 
215
245
  details_result = []
246
+ settings = config.fetch(FEATURE_FLAGS, {})
216
247
  for key in settings.keys
217
- details = _evaluate(key, user, nil, nil, settings, fetch_time)
248
+ details = _evaluate(key, user, nil, nil, config, fetch_time)
218
249
  details_result.push(details)
219
250
  end
220
251
 
@@ -227,8 +258,8 @@ module ConfigCat
227
258
  def force_refresh
228
259
  return @_config_service.refresh if @_config_service
229
260
 
230
- return RefreshResult(false,
231
- "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.")
232
263
  end
233
264
 
234
265
  # Sets the default user.
@@ -278,40 +309,57 @@ module ConfigCat
278
309
  @hooks.clear
279
310
  end
280
311
 
281
- def _get_settings
312
+ def _get_config
282
313
  if !@_override_data_source.nil?
283
314
  behaviour = @_override_data_source.get_behaviour()
284
315
  if behaviour == OverrideBehaviour::LOCAL_ONLY
285
316
  return @_override_data_source.get_overrides(), Utils::DISTANT_PAST
286
317
  elsif behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL
287
- remote_settings, fetch_time = @_config_service.get_settings()
288
- local_settings = @_override_data_source.get_overrides()
289
- remote_settings ||= {}
290
- local_settings ||= {}
291
- result = local_settings.clone()
292
- 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])
293
324
  return result, fetch_time
294
325
  elsif behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE
295
- remote_settings, fetch_time = @_config_service.get_settings()
296
- local_settings = @_override_data_source.get_overrides()
297
- remote_settings ||= {}
298
- local_settings ||= {}
299
- result = remote_settings.clone()
300
- 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])
301
332
  return result, fetch_time
302
333
  end
303
334
  end
304
- return @_config_service.get_settings()
335
+ return @_config_service.get_config()
336
+ end
337
+
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
305
344
  end
306
345
 
307
- def _evaluate(key, user, default_value, default_variation_id, settings, fetch_time)
308
- 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
+
309
352
  value, variation_id, rule, percentage_rule, error = @_rollout_evaluator.evaluate(
310
353
  key: key,
311
354
  user: user,
312
355
  default_value: default_value,
313
356
  default_variation_id: default_variation_id,
314
- 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
315
363
 
316
364
  details = EvaluationDetails.new(key: key,
317
365
  value: value,
@@ -320,8 +368,8 @@ module ConfigCat
320
368
  user: user,
321
369
  is_default_value: error.nil? || error.empty? ? false : true,
322
370
  error: error,
323
- matched_evaluation_rule: rule,
324
- matched_evaluation_percentage_rule: percentage_rule)
371
+ matched_targeting_rule: rule,
372
+ matched_percentage_option: percentage_rule)
325
373
  @hooks.invoke_on_flag_evaluated(details)
326
374
  return details
327
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
@@ -41,6 +41,7 @@ module ConfigCat
41
41
  begin
42
42
  config_json = string[etag_index + 1..-1]
43
43
  config = JSON.parse(config_json)
44
+ Config.fixup_config_salt_and_segments(config)
44
45
  rescue => e
45
46
  raise "Invalid config JSON: #{config_json}. #{e.message}"
46
47
  end
@@ -1,7 +1,7 @@
1
1
  require 'configcat/interfaces'
2
2
  require 'configcat/version'
3
3
  require 'configcat/datagovernance'
4
- require 'configcat/constants'
4
+ require 'configcat/config'
5
5
  require 'configcat/configentry'
6
6
  require 'net/http'
7
7
  require 'uri'
@@ -20,8 +20,8 @@ module ConfigCat
20
20
  end
21
21
 
22
22
  class Status
23
- FETCHED = 0,
24
- NOT_MODIFIED = 1,
23
+ FETCHED = 0
24
+ NOT_MODIFIED = 1
25
25
  FAILURE = 2
26
26
  end
27
27
 
@@ -179,6 +179,7 @@ module ConfigCat
179
179
  response_etag = ""
180
180
  end
181
181
  config = JSON.parse(response.body)
182
+ Config.fixup_config_salt_and_segments(config)
182
183
  return FetchResponse.success(ConfigEntry.new(config, response_etag, response.body, Utils.get_utc_now_seconds_since_epoch))
183
184
  when Net::HTTPNotModified
184
185
  return FetchResponse.not_modified
@@ -198,7 +199,9 @@ module ConfigCat
198
199
  @log.error(1102, error)
199
200
  return FetchResponse.failure(error, true)
200
201
  rescue Exception => e
201
- error = "Unexpected error occurred while trying to fetch config JSON: #{e}"
202
+ error = "Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network " \
203
+ "issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) " \
204
+ "over HTTP. #{e}"
202
205
  @log.error(1103, error)
203
206
  return FetchResponse.failure(error, true)
204
207
  end