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 +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 +26 -30
- 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: 360b940ee0e89f6e86195d9ba17d4b08df9a8c3dedb237dc3c57e162f7c5cf22
|
4
|
+
data.tar.gz: bf281cf73d78d460e7c9d327f5518d707064920f4ee74b15b2db8730b9e69628
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|