configcat 7.0.0 → 8.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/configcat/config.rb +317 -0
- data/lib/configcat/configcatclient.rb +100 -52
- data/lib/configcat/configcatlogger.rb +4 -0
- data/lib/configcat/configentry.rb +1 -0
- data/lib/configcat/configfetcher.rb +7 -4
- data/lib/configcat/configservice.rb +30 -33
- data/lib/configcat/evaluationcontext.rb +14 -0
- data/lib/configcat/evaluationdetails.rb +22 -4
- data/lib/configcat/evaluationlogbuilder.rb +81 -0
- data/lib/configcat/localdictionarydatasource.rb +20 -4
- data/lib/configcat/localfiledatasource.rb +23 -7
- data/lib/configcat/rolloutevaluator.rb +714 -151
- data/lib/configcat/user.rb +43 -4
- data/lib/configcat/utils.rb +21 -1
- data/lib/configcat/version.rb +1 -1
- metadata +5 -3
- data/lib/configcat/constants.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c56d0edbcf7a8b19dcf8f416207267a6f2c946008ede6cf56c8e490e0d1d6d6b
|
4
|
+
data.tar.gz: f8c90823173383727345ac58322cc91142df354e77c02bc5540182d2125b609e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 492f842a79e071e638d5f6884b3ad4a4c29a2cfe38d7205c320a5d31b36503bc4d43d64af6c52a056753ab0eec97fc14d2740d996adf458972e27d1557aa579a
|
7
|
+
data.tar.gz: 17860b6a2d09ab3fa65f5b3a858161145578c2e47f682e645a5ebf514ce792ec4be7fd3f21bcdaaa42f296613287854bc6a447ae3df36a9fa4057b87eca815e8
|
@@ -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
|
-
|
108
|
-
if
|
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,
|
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
|
-
|
126
|
-
if
|
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,
|
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
|
-
|
142
|
-
if
|
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
|
-
|
155
|
-
if
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
189
|
-
if
|
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
|
-
|
210
|
-
if
|
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,
|
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
|
-
|
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
|
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
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
result =
|
292
|
-
result.update(
|
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
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
result =
|
300
|
-
result.update(
|
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.
|
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,
|
308
|
-
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
|
-
|
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
|
-
|
324
|
-
|
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
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'configcat/interfaces'
|
2
2
|
require 'configcat/version'
|
3
3
|
require 'configcat/datagovernance'
|
4
|
-
require 'configcat/
|
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
|
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
|