configcat 7.0.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: 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