configcat 6.1.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 +98 -100
- data/lib/configcat/configcatlogger.rb +4 -0
- data/lib/configcat/configentry.rb +35 -21
- data/lib/configcat/configfetcher.rb +8 -5
- data/lib/configcat/configservice.rb +33 -34
- 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
- data/lib/configcat.rb +0 -160
- metadata +5 -3
- data/lib/configcat/constants.rb +0 -17
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,89 +149,61 @@ 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
|
|
149
|
-
# Gets the Variation ID (analytics) of a feature flag or setting based on it's key.
|
150
|
-
#
|
151
|
-
# :param key [String] the identifier of the feature flag or setting.
|
152
|
-
# :param default_variation_id in case of any failure, this value will be returned.
|
153
|
-
# :param user [User] the user object to identify the caller.
|
154
|
-
# :return the variation ID.
|
155
|
-
def get_variation_id(key, default_variation_id, user = nil)
|
156
|
-
ConfigCat.logger.warn("get_variation_id is deprecated and will be removed in a future major version. " \
|
157
|
-
"Please use [get_value_details] instead.")
|
158
|
-
|
159
|
-
settings, fetch_time = _get_settings()
|
160
|
-
if settings === nil
|
161
|
-
message = "Config JSON is not present when evaluating setting '#{key}'. Returning the `default_variation_id` parameter that you specified in your application: '#{default_variation_id}'."
|
162
|
-
@log.error(1000, message)
|
163
|
-
@hooks.invoke_on_flag_evaluated(EvaluationDetails.from_error(key, nil, error: message,
|
164
|
-
variation_id: default_variation_id))
|
165
|
-
return default_variation_id
|
166
|
-
end
|
167
|
-
details = _evaluate(key, user, nil, default_variation_id, settings, fetch_time)
|
168
|
-
return details.variation_id
|
169
|
-
end
|
170
|
-
|
171
|
-
# Gets the Variation IDs (analytics) of all feature flags or settings.
|
172
|
-
#
|
173
|
-
# :param user [User] the user object to identify the caller.
|
174
|
-
# :return list of variation IDs
|
175
|
-
def get_all_variation_ids(user = nil)
|
176
|
-
ConfigCat.logger.warn("get_all_variation_ids is deprecated and will be removed in a future major version. " \
|
177
|
-
"Please use [get_value_details] instead.")
|
178
|
-
|
179
|
-
settings, _ = _get_settings()
|
180
|
-
if settings === nil
|
181
|
-
@log.error(1000, "Config JSON is not present. Returning empty list.")
|
182
|
-
return []
|
183
|
-
end
|
184
|
-
|
185
|
-
variation_ids = []
|
186
|
-
for key in settings.keys
|
187
|
-
variation_id = get_variation_id(key, nil, user)
|
188
|
-
if !variation_id.equal?(nil)
|
189
|
-
variation_ids.push(variation_id)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
return variation_ids
|
193
|
-
end
|
194
|
-
|
195
161
|
# Gets the key of a setting, and it's value identified by the given Variation ID (analytics)
|
196
162
|
#
|
197
163
|
# :param variation_id [String] variation ID
|
198
164
|
# :return key and value
|
199
165
|
def get_key_and_value(variation_id)
|
200
|
-
|
201
|
-
if
|
166
|
+
config, _ = _get_config()
|
167
|
+
if config.nil?
|
202
168
|
@log.error(1000, "Config JSON is not present. Returning nil.")
|
203
169
|
return nil
|
204
170
|
end
|
205
171
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
172
|
+
settings = config.fetch(FEATURE_FLAGS, {})
|
173
|
+
begin
|
174
|
+
settings.each do |key, value|
|
175
|
+
setting_type = value.fetch(SETTING_TYPE, nil)
|
176
|
+
if variation_id == value.fetch(VARIATION_ID, nil)
|
177
|
+
return KeyValue.new(key, Config.get_value(value, setting_type))
|
178
|
+
end
|
210
179
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
180
|
+
targeting_rules = value.fetch(TARGETING_RULES, [])
|
181
|
+
targeting_rules.each do |targeting_rule|
|
182
|
+
served_value = targeting_rule.fetch(SERVED_VALUE, nil)
|
183
|
+
if !served_value.nil?
|
184
|
+
if variation_id == served_value.fetch(VARIATION_ID, nil)
|
185
|
+
return KeyValue.new(key, Config.get_value(served_value, setting_type))
|
186
|
+
end
|
187
|
+
else
|
188
|
+
percentage_options = targeting_rule.fetch(PERCENTAGE_OPTIONS, [])
|
189
|
+
percentage_options.each do |percentage_option|
|
190
|
+
if variation_id == percentage_option.fetch(VARIATION_ID, nil)
|
191
|
+
return KeyValue.new(key, Config.get_value(percentage_option, setting_type))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
215
195
|
end
|
216
|
-
end
|
217
196
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
197
|
+
percentage_options = value.fetch(PERCENTAGE_OPTIONS, [])
|
198
|
+
percentage_options.each do |percentage_option|
|
199
|
+
if variation_id == percentage_option.fetch(VARIATION_ID, nil)
|
200
|
+
return KeyValue.new(key, Config.get_value(percentage_option, setting_type))
|
201
|
+
end
|
222
202
|
end
|
223
203
|
end
|
204
|
+
rescue => e
|
205
|
+
@log.error("Error occurred in the `#{self.class.name}` method. Returning nil.", event_id: 1002)
|
206
|
+
return nil
|
224
207
|
end
|
225
208
|
|
226
209
|
@log.error(2011, "Could not find the setting for the specified variation ID: '#{variation_id}'.")
|
@@ -231,12 +214,13 @@ module ConfigCat
|
|
231
214
|
# :param user [User] the user object to identify the caller.
|
232
215
|
# :return dictionary of values
|
233
216
|
def get_all_values(user = nil)
|
234
|
-
|
235
|
-
if
|
217
|
+
config, _ = _get_config()
|
218
|
+
if config.nil?
|
236
219
|
@log.error(1000, "Config JSON is not present. Returning empty dictionary.")
|
237
220
|
return {}
|
238
221
|
end
|
239
222
|
|
223
|
+
settings = config.fetch(FEATURE_FLAGS, {})
|
240
224
|
all_values = {}
|
241
225
|
for key in settings.keys
|
242
226
|
value = get_value(key, nil, user)
|
@@ -252,15 +236,16 @@ module ConfigCat
|
|
252
236
|
# :param user [User] the user object to identify the caller.
|
253
237
|
# :return list of all evaluation details
|
254
238
|
def get_all_value_details(user = nil)
|
255
|
-
|
256
|
-
if
|
239
|
+
config, fetch_time = _get_config()
|
240
|
+
if config.nil?
|
257
241
|
@log.error(1000, "Config JSON is not present. Returning empty list.")
|
258
242
|
return []
|
259
243
|
end
|
260
244
|
|
261
245
|
details_result = []
|
246
|
+
settings = config.fetch(FEATURE_FLAGS, {})
|
262
247
|
for key in settings.keys
|
263
|
-
details = _evaluate(key, user, nil, nil,
|
248
|
+
details = _evaluate(key, user, nil, nil, config, fetch_time)
|
264
249
|
details_result.push(details)
|
265
250
|
end
|
266
251
|
|
@@ -273,8 +258,8 @@ module ConfigCat
|
|
273
258
|
def force_refresh
|
274
259
|
return @_config_service.refresh if @_config_service
|
275
260
|
|
276
|
-
return RefreshResult(false,
|
277
|
-
|
261
|
+
return RefreshResult.new(false,
|
262
|
+
"The SDK uses the LocalOnly flag override behavior which prevents making HTTP requests.")
|
278
263
|
end
|
279
264
|
|
280
265
|
# Sets the default user.
|
@@ -324,44 +309,57 @@ module ConfigCat
|
|
324
309
|
@hooks.clear
|
325
310
|
end
|
326
311
|
|
327
|
-
def
|
312
|
+
def _get_config
|
328
313
|
if !@_override_data_source.nil?
|
329
314
|
behaviour = @_override_data_source.get_behaviour()
|
330
315
|
if behaviour == OverrideBehaviour::LOCAL_ONLY
|
331
316
|
return @_override_data_source.get_overrides(), Utils::DISTANT_PAST
|
332
317
|
elsif behaviour == OverrideBehaviour::REMOTE_OVER_LOCAL
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
result =
|
338
|
-
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])
|
339
324
|
return result, fetch_time
|
340
325
|
elsif behaviour == OverrideBehaviour::LOCAL_OVER_REMOTE
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
result =
|
346
|
-
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])
|
347
332
|
return result, fetch_time
|
348
333
|
end
|
349
334
|
end
|
350
|
-
return @_config_service.
|
335
|
+
return @_config_service.get_config()
|
351
336
|
end
|
352
337
|
|
353
|
-
def
|
354
|
-
|
338
|
+
def _check_type_mismatch(value, default_value)
|
339
|
+
if !default_value.nil? && Config.is_type_mismatch(value, default_value.class)
|
340
|
+
@log.warn(4002, "The type of a setting does not match the type of the specified default value (#{default_value}). " \
|
341
|
+
"Setting's type was #{value.class} but the default value's type was #{default_value.class}. " \
|
342
|
+
"Please make sure that using a default value not matching the setting's type was intended.")
|
343
|
+
end
|
355
344
|
end
|
356
345
|
|
357
|
-
def _evaluate(key, user, default_value, default_variation_id,
|
358
|
-
user
|
346
|
+
def _evaluate(key, user, default_value, default_variation_id, config, fetch_time)
|
347
|
+
user ||= @_default_user
|
348
|
+
|
349
|
+
# Skip building the evaluation log if it won't be logged.
|
350
|
+
log_builder = EvaluationLogBuilder.new if @log.enabled_for?(Logger::INFO)
|
351
|
+
|
359
352
|
value, variation_id, rule, percentage_rule, error = @_rollout_evaluator.evaluate(
|
360
353
|
key: key,
|
361
354
|
user: user,
|
362
355
|
default_value: default_value,
|
363
356
|
default_variation_id: default_variation_id,
|
364
|
-
|
357
|
+
config: config,
|
358
|
+
log_builder: log_builder)
|
359
|
+
|
360
|
+
_check_type_mismatch(value, default_value)
|
361
|
+
|
362
|
+
@log.info(5000, log_builder.to_s) if log_builder
|
365
363
|
|
366
364
|
details = EvaluationDetails.new(key: key,
|
367
365
|
value: value,
|
@@ -370,8 +368,8 @@ module ConfigCat
|
|
370
368
|
user: user,
|
371
369
|
is_default_value: error.nil? || error.empty? ? false : true,
|
372
370
|
error: error,
|
373
|
-
|
374
|
-
|
371
|
+
matched_targeting_rule: rule,
|
372
|
+
matched_percentage_option: percentage_rule)
|
375
373
|
@hooks.invoke_on_flag_evaluated(details)
|
376
374
|
return details
|
377
375
|
end
|