optimizely-sdk 3.10.1 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +202 -202
  3. data/lib/optimizely/audience.rb +127 -97
  4. data/lib/optimizely/bucketer.rb +156 -156
  5. data/lib/optimizely/condition_tree_evaluator.rb +123 -123
  6. data/lib/optimizely/config/datafile_project_config.rb +539 -552
  7. data/lib/optimizely/config/proxy_config.rb +34 -34
  8. data/lib/optimizely/config_manager/async_scheduler.rb +95 -95
  9. data/lib/optimizely/config_manager/http_project_config_manager.rb +330 -329
  10. data/lib/optimizely/config_manager/project_config_manager.rb +24 -24
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +53 -52
  12. data/lib/optimizely/decide/optimizely_decide_option.rb +28 -28
  13. data/lib/optimizely/decide/optimizely_decision.rb +60 -60
  14. data/lib/optimizely/decide/optimizely_decision_message.rb +26 -26
  15. data/lib/optimizely/decision_service.rb +563 -563
  16. data/lib/optimizely/error_handler.rb +39 -39
  17. data/lib/optimizely/event/batch_event_processor.rb +235 -234
  18. data/lib/optimizely/event/entity/conversion_event.rb +44 -43
  19. data/lib/optimizely/event/entity/decision.rb +38 -38
  20. data/lib/optimizely/event/entity/event_batch.rb +86 -86
  21. data/lib/optimizely/event/entity/event_context.rb +50 -50
  22. data/lib/optimizely/event/entity/impression_event.rb +48 -47
  23. data/lib/optimizely/event/entity/snapshot.rb +33 -33
  24. data/lib/optimizely/event/entity/snapshot_event.rb +48 -48
  25. data/lib/optimizely/event/entity/user_event.rb +22 -22
  26. data/lib/optimizely/event/entity/visitor.rb +36 -35
  27. data/lib/optimizely/event/entity/visitor_attribute.rb +38 -37
  28. data/lib/optimizely/event/event_factory.rb +156 -155
  29. data/lib/optimizely/event/event_processor.rb +25 -25
  30. data/lib/optimizely/event/forwarding_event_processor.rb +44 -43
  31. data/lib/optimizely/event/user_event_factory.rb +88 -88
  32. data/lib/optimizely/event_builder.rb +221 -228
  33. data/lib/optimizely/event_dispatcher.rb +71 -71
  34. data/lib/optimizely/exceptions.rb +135 -139
  35. data/lib/optimizely/helpers/constants.rb +415 -397
  36. data/lib/optimizely/helpers/date_time_utils.rb +30 -30
  37. data/lib/optimizely/helpers/event_tag_utils.rb +132 -132
  38. data/lib/optimizely/helpers/group.rb +31 -31
  39. data/lib/optimizely/helpers/http_utils.rb +65 -64
  40. data/lib/optimizely/helpers/validator.rb +183 -183
  41. data/lib/optimizely/helpers/variable_type.rb +67 -67
  42. data/lib/optimizely/logger.rb +46 -45
  43. data/lib/optimizely/notification_center.rb +174 -176
  44. data/lib/optimizely/optimizely_config.rb +271 -272
  45. data/lib/optimizely/optimizely_factory.rb +181 -181
  46. data/lib/optimizely/optimizely_user_context.rb +204 -179
  47. data/lib/optimizely/params.rb +31 -31
  48. data/lib/optimizely/project_config.rb +99 -91
  49. data/lib/optimizely/semantic_version.rb +166 -166
  50. data/lib/optimizely/{custom_attribute_condition_evaluator.rb → user_condition_evaluator.rb} +391 -369
  51. data/lib/optimizely/user_profile_service.rb +35 -35
  52. data/lib/optimizely/version.rb +21 -21
  53. data/lib/optimizely.rb +1130 -1145
  54. metadata +15 -14
@@ -1,369 +1,391 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Copyright 2019-2020, Optimizely and contributors
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
- require_relative 'exceptions'
19
- require_relative 'helpers/constants'
20
- require_relative 'helpers/validator'
21
- require_relative 'semantic_version'
22
-
23
- module Optimizely
24
- class CustomAttributeConditionEvaluator
25
- CUSTOM_ATTRIBUTE_CONDITION_TYPE = 'custom_attribute'
26
-
27
- # Conditional match types
28
- EXACT_MATCH_TYPE = 'exact'
29
- EXISTS_MATCH_TYPE = 'exists'
30
- GREATER_THAN_MATCH_TYPE = 'gt'
31
- GREATER_EQUAL_MATCH_TYPE = 'ge'
32
- LESS_THAN_MATCH_TYPE = 'lt'
33
- LESS_EQUAL_MATCH_TYPE = 'le'
34
- SUBSTRING_MATCH_TYPE = 'substring'
35
- SEMVER_EQ = 'semver_eq'
36
- SEMVER_GE = 'semver_ge'
37
- SEMVER_GT = 'semver_gt'
38
- SEMVER_LE = 'semver_le'
39
- SEMVER_LT = 'semver_lt'
40
-
41
- EVALUATORS_BY_MATCH_TYPE = {
42
- EXACT_MATCH_TYPE => :exact_evaluator,
43
- EXISTS_MATCH_TYPE => :exists_evaluator,
44
- GREATER_THAN_MATCH_TYPE => :greater_than_evaluator,
45
- GREATER_EQUAL_MATCH_TYPE => :greater_than_or_equal_evaluator,
46
- LESS_THAN_MATCH_TYPE => :less_than_evaluator,
47
- LESS_EQUAL_MATCH_TYPE => :less_than_or_equal_evaluator,
48
- SUBSTRING_MATCH_TYPE => :substring_evaluator,
49
- SEMVER_EQ => :semver_equal_evaluator,
50
- SEMVER_GE => :semver_greater_than_or_equal_evaluator,
51
- SEMVER_GT => :semver_greater_than_evaluator,
52
- SEMVER_LE => :semver_less_than_or_equal_evaluator,
53
- SEMVER_LT => :semver_less_than_evaluator
54
- }.freeze
55
-
56
- attr_reader :user_attributes
57
-
58
- def initialize(user_attributes, logger)
59
- @user_attributes = user_attributes
60
- @logger = logger
61
- end
62
-
63
- def evaluate(leaf_condition)
64
- # Top level method to evaluate audience conditions.
65
- #
66
- # conditions - Nested array of and/or conditions.
67
- # Example: ['and', operand_1, ['or', operand_2, operand_3]]
68
- #
69
- # Returns boolean if the given user attributes match/don't match the given conditions,
70
- # nil if the given conditions can't be evaluated.
71
-
72
- unless leaf_condition['type'] == CUSTOM_ATTRIBUTE_CONDITION_TYPE
73
- @logger.log(
74
- Logger::WARN,
75
- format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_TYPE'], leaf_condition)
76
- )
77
- return nil
78
- end
79
-
80
- condition_match = leaf_condition['match'] || EXACT_MATCH_TYPE
81
-
82
- if !@user_attributes.key?(leaf_condition['name']) && condition_match != EXISTS_MATCH_TYPE
83
- @logger.log(
84
- Logger::DEBUG,
85
- format(
86
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['MISSING_ATTRIBUTE_VALUE'],
87
- leaf_condition,
88
- leaf_condition['name']
89
- )
90
- )
91
- return nil
92
- end
93
-
94
- if @user_attributes[leaf_condition['name']].nil? && condition_match != EXISTS_MATCH_TYPE
95
- @logger.log(
96
- Logger::DEBUG,
97
- format(
98
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['NULL_ATTRIBUTE_VALUE'],
99
- leaf_condition,
100
- leaf_condition['name']
101
- )
102
- )
103
- return nil
104
- end
105
-
106
- unless EVALUATORS_BY_MATCH_TYPE.include?(condition_match)
107
- @logger.log(
108
- Logger::WARN,
109
- format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_MATCH_TYPE'], leaf_condition)
110
- )
111
- return nil
112
- end
113
-
114
- begin
115
- send(EVALUATORS_BY_MATCH_TYPE[condition_match], leaf_condition)
116
- rescue InvalidAttributeType
117
- condition_name = leaf_condition['name']
118
- user_value = @user_attributes[condition_name]
119
-
120
- @logger.log(
121
- Logger::WARN,
122
- format(
123
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNEXPECTED_TYPE'],
124
- leaf_condition,
125
- user_value.class,
126
- condition_name
127
- )
128
- )
129
- return nil
130
- rescue InvalidSemanticVersion
131
- condition_name = leaf_condition['name']
132
-
133
- @logger.log(
134
- Logger::WARN,
135
- format(
136
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INVALID_SEMANTIC_VERSION'],
137
- leaf_condition,
138
- condition_name
139
- )
140
- )
141
- return nil
142
- end
143
- end
144
-
145
- def exact_evaluator(condition)
146
- # Evaluate the given exact match condition for the given user attributes.
147
- #
148
- # Returns boolean true if numbers values matched, i.e 2 is equal to 2.0
149
- # true if the user attribute value is equal (===) to the condition value,
150
- # false if the user attribute value is not equal (!==) to the condition value,
151
- # nil if the condition value or user attribute value has an invalid type,
152
- # or if there is a mismatch between the user attribute type and the condition value type.
153
-
154
- condition_value = condition['value']
155
-
156
- user_provided_value = @user_attributes[condition['name']]
157
-
158
- if !value_type_valid_for_exact_conditions?(condition_value) ||
159
- (condition_value.is_a?(Numeric) && !Helpers::Validator.finite_number?(condition_value))
160
- @logger.log(
161
- Logger::WARN,
162
- format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
163
- )
164
- return nil
165
- end
166
-
167
- if !value_type_valid_for_exact_conditions?(user_provided_value) ||
168
- !Helpers::Validator.same_types?(condition_value, user_provided_value)
169
- raise InvalidAttributeType
170
- end
171
-
172
- if user_provided_value.is_a?(Numeric) && !Helpers::Validator.finite_number?(user_provided_value)
173
- @logger.log(
174
- Logger::WARN,
175
- format(
176
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INFINITE_ATTRIBUTE_VALUE'],
177
- condition,
178
- condition['name']
179
- )
180
- )
181
- return nil
182
- end
183
-
184
- condition_value == user_provided_value
185
- end
186
-
187
- def exists_evaluator(condition)
188
- # Evaluate the given exists match condition for the given user attributes.
189
- # Returns boolean true if both:
190
- # 1) the user attributes have a value for the given condition, and
191
- # 2) the user attribute value is neither nil nor undefined
192
- # Returns false otherwise
193
-
194
- !@user_attributes[condition['name']].nil?
195
- end
196
-
197
- def greater_than_evaluator(condition)
198
- # Evaluate the given greater than match condition for the given user attributes.
199
- # Returns boolean true if the user attribute value is greater than the condition value,
200
- # false if the user attribute value is less than or equal to the condition value,
201
- # nil if the condition value isn't a number or the user attribute value isn't a number.
202
-
203
- condition_value = condition['value']
204
- user_provided_value = @user_attributes[condition['name']]
205
-
206
- return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
207
-
208
- user_provided_value > condition_value
209
- end
210
-
211
- def greater_than_or_equal_evaluator(condition)
212
- # Evaluate the given greater than or equal match condition for the given user attributes.
213
- # Returns boolean true if the user attribute value is greater than or equal to the condition value,
214
- # false if the user attribute value is less than the condition value,
215
- # nil if the condition value isn't a number or the user attribute value isn't a number.
216
-
217
- condition_value = condition['value']
218
- user_provided_value = @user_attributes[condition['name']]
219
-
220
- return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
221
-
222
- user_provided_value >= condition_value
223
- end
224
-
225
- def less_than_evaluator(condition)
226
- # Evaluate the given less than match condition for the given user attributes.
227
- # Returns boolean true if the user attribute value is less than the condition value,
228
- # false if the user attribute value is greater than or equal to the condition value,
229
- # nil if the condition value isn't a number or the user attribute value isn't a number.
230
-
231
- condition_value = condition['value']
232
- user_provided_value = @user_attributes[condition['name']]
233
-
234
- return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
235
-
236
- user_provided_value < condition_value
237
- end
238
-
239
- def less_than_or_equal_evaluator(condition)
240
- # Evaluate the given less than or equal match condition for the given user attributes.
241
- # Returns boolean true if the user attribute value is less than or equal to the condition value,
242
- # false if the user attribute value is greater than the condition value,
243
- # nil if the condition value isn't a number or the user attribute value isn't a number.
244
-
245
- condition_value = condition['value']
246
- user_provided_value = @user_attributes[condition['name']]
247
-
248
- return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
249
-
250
- user_provided_value <= condition_value
251
- end
252
-
253
- def substring_evaluator(condition)
254
- # Evaluate the given substring match condition for the given user attributes.
255
- # Returns boolean true if the condition value is a substring of the user attribute value,
256
- # false if the condition value is not a substring of the user attribute value,
257
- # nil if the condition value isn't a string or the user attribute value isn't a string.
258
-
259
- condition_value = condition['value']
260
- user_provided_value = @user_attributes[condition['name']]
261
-
262
- unless condition_value.is_a?(String)
263
- @logger.log(
264
- Logger::WARN,
265
- format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
266
- )
267
- return nil
268
- end
269
-
270
- raise InvalidAttributeType unless user_provided_value.is_a?(String)
271
-
272
- user_provided_value.include? condition_value
273
- end
274
-
275
- def semver_equal_evaluator(condition)
276
- # Evaluate the given semantic version equal match target version for the user version.
277
- # Returns boolean true if the user version is equal to the target version,
278
- # false if the user version is not equal to the target version
279
-
280
- target_version = condition['value']
281
- user_version = @user_attributes[condition['name']]
282
-
283
- SemanticVersion.compare_user_version_with_target_version(target_version, user_version).zero?
284
- end
285
-
286
- def semver_greater_than_evaluator(condition)
287
- # Evaluate the given semantic version greater than match target version for the user version.
288
- # Returns boolean true if the user version is greater than the target version,
289
- # false if the user version is less than or equal to the target version
290
-
291
- target_version = condition['value']
292
- user_version = @user_attributes[condition['name']]
293
-
294
- SemanticVersion.compare_user_version_with_target_version(target_version, user_version).positive?
295
- end
296
-
297
- def semver_greater_than_or_equal_evaluator(condition)
298
- # Evaluate the given semantic version greater than or equal to match target version for the user version.
299
- # Returns boolean true if the user version is greater than or equal to the target version,
300
- # false if the user version is less than the target version
301
-
302
- target_version = condition['value']
303
- user_version = @user_attributes[condition['name']]
304
-
305
- SemanticVersion.compare_user_version_with_target_version(target_version, user_version) >= 0
306
- end
307
-
308
- def semver_less_than_evaluator(condition)
309
- # Evaluate the given semantic version less than match target version for the user version.
310
- # Returns boolean true if the user version is less than the target version,
311
- # false if the user version is greater than or equal to the target version
312
-
313
- target_version = condition['value']
314
- user_version = @user_attributes[condition['name']]
315
-
316
- SemanticVersion.compare_user_version_with_target_version(target_version, user_version).negative?
317
- end
318
-
319
- def semver_less_than_or_equal_evaluator(condition)
320
- # Evaluate the given semantic version less than or equal to match target version for the user version.
321
- # Returns boolean true if the user version is less than or equal to the target version,
322
- # false if the user version is greater than the target version
323
-
324
- target_version = condition['value']
325
- user_version = @user_attributes[condition['name']]
326
-
327
- SemanticVersion.compare_user_version_with_target_version(target_version, user_version) <= 0
328
- end
329
-
330
- private
331
-
332
- def valid_numeric_values?(user_value, condition_value, condition)
333
- # Returns true if user and condition values are valid numeric.
334
- # false otherwise.
335
-
336
- unless Helpers::Validator.finite_number?(condition_value)
337
- @logger.log(
338
- Logger::WARN,
339
- format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
340
- )
341
- return false
342
- end
343
-
344
- raise InvalidAttributeType unless user_value.is_a?(Numeric)
345
-
346
- unless Helpers::Validator.finite_number?(user_value)
347
- @logger.log(
348
- Logger::WARN,
349
- format(
350
- Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INFINITE_ATTRIBUTE_VALUE'],
351
- condition,
352
- condition['name']
353
- )
354
- )
355
- return false
356
- end
357
-
358
- true
359
- end
360
-
361
- def value_type_valid_for_exact_conditions?(value)
362
- # Returns true if the value is valid for exact conditions. Valid values include
363
- # strings or booleans or is a number.
364
- # false otherwise.
365
-
366
- (Helpers::Validator.boolean? value) || (value.is_a? String) || value.is_a?(Numeric)
367
- end
368
- end
369
- end
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright 2019-2020, 2022, Optimizely and contributors
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ require_relative 'exceptions'
19
+ require_relative 'helpers/constants'
20
+ require_relative 'helpers/validator'
21
+ require_relative 'semantic_version'
22
+
23
+ module Optimizely
24
+ class UserConditionEvaluator
25
+ CONDITION_TYPES = %w[custom_attribute third_party_dimension].freeze
26
+
27
+ # Conditional match types
28
+ EXACT_MATCH_TYPE = 'exact'
29
+ EXISTS_MATCH_TYPE = 'exists'
30
+ GREATER_THAN_MATCH_TYPE = 'gt'
31
+ GREATER_EQUAL_MATCH_TYPE = 'ge'
32
+ LESS_THAN_MATCH_TYPE = 'lt'
33
+ LESS_EQUAL_MATCH_TYPE = 'le'
34
+ SUBSTRING_MATCH_TYPE = 'substring'
35
+ SEMVER_EQ = 'semver_eq'
36
+ SEMVER_GE = 'semver_ge'
37
+ SEMVER_GT = 'semver_gt'
38
+ SEMVER_LE = 'semver_le'
39
+ SEMVER_LT = 'semver_lt'
40
+ QUALIFIED_MATCH_TYPE = 'qualified'
41
+
42
+ EVALUATORS_BY_MATCH_TYPE = {
43
+ EXACT_MATCH_TYPE => :exact_evaluator,
44
+ EXISTS_MATCH_TYPE => :exists_evaluator,
45
+ GREATER_THAN_MATCH_TYPE => :greater_than_evaluator,
46
+ GREATER_EQUAL_MATCH_TYPE => :greater_than_or_equal_evaluator,
47
+ LESS_THAN_MATCH_TYPE => :less_than_evaluator,
48
+ LESS_EQUAL_MATCH_TYPE => :less_than_or_equal_evaluator,
49
+ SUBSTRING_MATCH_TYPE => :substring_evaluator,
50
+ SEMVER_EQ => :semver_equal_evaluator,
51
+ SEMVER_GE => :semver_greater_than_or_equal_evaluator,
52
+ SEMVER_GT => :semver_greater_than_evaluator,
53
+ SEMVER_LE => :semver_less_than_or_equal_evaluator,
54
+ SEMVER_LT => :semver_less_than_evaluator,
55
+ QUALIFIED_MATCH_TYPE => :qualified_evaluator
56
+ }.freeze
57
+
58
+ attr_reader :user_attributes
59
+
60
+ def initialize(user_context, logger)
61
+ @user_context = user_context
62
+ @user_attributes = user_context.user_attributes
63
+ @logger = logger
64
+ end
65
+
66
+ def evaluate(leaf_condition)
67
+ # Top level method to evaluate audience conditions.
68
+ #
69
+ # conditions - Nested array of and/or conditions.
70
+ # Example: ['and', operand_1, ['or', operand_2, operand_3]]
71
+ #
72
+ # Returns boolean if the given user attributes match/don't match the given conditions,
73
+ # nil if the given conditions can't be evaluated.
74
+
75
+ unless CONDITION_TYPES.include? leaf_condition['type']
76
+ @logger.log(
77
+ Logger::WARN,
78
+ format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_TYPE'], leaf_condition)
79
+ )
80
+ return nil
81
+ end
82
+
83
+ condition_match = leaf_condition['match'] || EXACT_MATCH_TYPE
84
+
85
+ if !@user_attributes.key?(leaf_condition['name']) && ![EXISTS_MATCH_TYPE, QUALIFIED_MATCH_TYPE].include?(condition_match)
86
+ @logger.log(
87
+ Logger::DEBUG,
88
+ format(
89
+ Helpers::Constants::AUDIENCE_EVALUATION_LOGS['MISSING_ATTRIBUTE_VALUE'],
90
+ leaf_condition,
91
+ leaf_condition['name']
92
+ )
93
+ )
94
+ return nil
95
+ end
96
+
97
+ if @user_attributes[leaf_condition['name']].nil? && ![EXISTS_MATCH_TYPE, QUALIFIED_MATCH_TYPE].include?(condition_match)
98
+ @logger.log(
99
+ Logger::DEBUG,
100
+ format(
101
+ Helpers::Constants::AUDIENCE_EVALUATION_LOGS['NULL_ATTRIBUTE_VALUE'],
102
+ leaf_condition,
103
+ leaf_condition['name']
104
+ )
105
+ )
106
+ return nil
107
+ end
108
+
109
+ unless EVALUATORS_BY_MATCH_TYPE.include?(condition_match)
110
+ @logger.log(
111
+ Logger::WARN,
112
+ format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_MATCH_TYPE'], leaf_condition)
113
+ )
114
+ return nil
115
+ end
116
+
117
+ begin
118
+ send(EVALUATORS_BY_MATCH_TYPE[condition_match], leaf_condition)
119
+ rescue InvalidAttributeType
120
+ condition_name = leaf_condition['name']
121
+ user_value = @user_attributes[condition_name]
122
+
123
+ @logger.log(
124
+ Logger::WARN,
125
+ format(
126
+ Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNEXPECTED_TYPE'],
127
+ leaf_condition,
128
+ user_value.class,
129
+ condition_name
130
+ )
131
+ )
132
+ nil
133
+ rescue InvalidSemanticVersion
134
+ condition_name = leaf_condition['name']
135
+
136
+ @logger.log(
137
+ Logger::WARN,
138
+ format(
139
+ Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INVALID_SEMANTIC_VERSION'],
140
+ leaf_condition,
141
+ condition_name
142
+ )
143
+ )
144
+ nil
145
+ end
146
+ end
147
+
148
+ def exact_evaluator(condition)
149
+ # Evaluate the given exact match condition for the given user attributes.
150
+ #
151
+ # Returns boolean true if numbers values matched, i.e 2 is equal to 2.0
152
+ # true if the user attribute value is equal (===) to the condition value,
153
+ # false if the user attribute value is not equal (!==) to the condition value,
154
+ # nil if the condition value or user attribute value has an invalid type,
155
+ # or if there is a mismatch between the user attribute type and the condition value type.
156
+
157
+ condition_value = condition['value']
158
+
159
+ user_provided_value = @user_attributes[condition['name']]
160
+
161
+ if !value_type_valid_for_exact_conditions?(condition_value) ||
162
+ (condition_value.is_a?(Numeric) && !Helpers::Validator.finite_number?(condition_value))
163
+ @logger.log(
164
+ Logger::WARN,
165
+ format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
166
+ )
167
+ return nil
168
+ end
169
+
170
+ if !value_type_valid_for_exact_conditions?(user_provided_value) ||
171
+ !Helpers::Validator.same_types?(condition_value, user_provided_value)
172
+ raise InvalidAttributeType
173
+ end
174
+
175
+ if user_provided_value.is_a?(Numeric) && !Helpers::Validator.finite_number?(user_provided_value)
176
+ @logger.log(
177
+ Logger::WARN,
178
+ format(
179
+ Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INFINITE_ATTRIBUTE_VALUE'],
180
+ condition,
181
+ condition['name']
182
+ )
183
+ )
184
+ return nil
185
+ end
186
+
187
+ condition_value == user_provided_value
188
+ end
189
+
190
+ def exists_evaluator(condition)
191
+ # Evaluate the given exists match condition for the given user attributes.
192
+ # Returns boolean true if both:
193
+ # 1) the user attributes have a value for the given condition, and
194
+ # 2) the user attribute value is neither nil nor undefined
195
+ # Returns false otherwise
196
+
197
+ !@user_attributes[condition['name']].nil?
198
+ end
199
+
200
+ def greater_than_evaluator(condition)
201
+ # Evaluate the given greater than match condition for the given user attributes.
202
+ # Returns boolean true if the user attribute value is greater than the condition value,
203
+ # false if the user attribute value is less than or equal to the condition value,
204
+ # nil if the condition value isn't a number or the user attribute value isn't a number.
205
+
206
+ condition_value = condition['value']
207
+ user_provided_value = @user_attributes[condition['name']]
208
+
209
+ return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
210
+
211
+ user_provided_value > condition_value
212
+ end
213
+
214
+ def greater_than_or_equal_evaluator(condition)
215
+ # Evaluate the given greater than or equal match condition for the given user attributes.
216
+ # Returns boolean true if the user attribute value is greater than or equal to the condition value,
217
+ # false if the user attribute value is less than the condition value,
218
+ # nil if the condition value isn't a number or the user attribute value isn't a number.
219
+
220
+ condition_value = condition['value']
221
+ user_provided_value = @user_attributes[condition['name']]
222
+
223
+ return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
224
+
225
+ user_provided_value >= condition_value
226
+ end
227
+
228
+ def less_than_evaluator(condition)
229
+ # Evaluate the given less than match condition for the given user attributes.
230
+ # Returns boolean true if the user attribute value is less than the condition value,
231
+ # false if the user attribute value is greater than or equal to the condition value,
232
+ # nil if the condition value isn't a number or the user attribute value isn't a number.
233
+
234
+ condition_value = condition['value']
235
+ user_provided_value = @user_attributes[condition['name']]
236
+
237
+ return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
238
+
239
+ user_provided_value < condition_value
240
+ end
241
+
242
+ def less_than_or_equal_evaluator(condition)
243
+ # Evaluate the given less than or equal match condition for the given user attributes.
244
+ # Returns boolean true if the user attribute value is less than or equal to the condition value,
245
+ # false if the user attribute value is greater than the condition value,
246
+ # nil if the condition value isn't a number or the user attribute value isn't a number.
247
+
248
+ condition_value = condition['value']
249
+ user_provided_value = @user_attributes[condition['name']]
250
+
251
+ return nil unless valid_numeric_values?(user_provided_value, condition_value, condition)
252
+
253
+ user_provided_value <= condition_value
254
+ end
255
+
256
+ def substring_evaluator(condition)
257
+ # Evaluate the given substring match condition for the given user attributes.
258
+ # Returns boolean true if the condition value is a substring of the user attribute value,
259
+ # false if the condition value is not a substring of the user attribute value,
260
+ # nil if the condition value isn't a string or the user attribute value isn't a string.
261
+
262
+ condition_value = condition['value']
263
+ user_provided_value = @user_attributes[condition['name']]
264
+
265
+ unless condition_value.is_a?(String)
266
+ @logger.log(
267
+ Logger::WARN,
268
+ format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
269
+ )
270
+ return nil
271
+ end
272
+
273
+ raise InvalidAttributeType unless user_provided_value.is_a?(String)
274
+
275
+ user_provided_value.include? condition_value
276
+ end
277
+
278
+ def semver_equal_evaluator(condition)
279
+ # Evaluate the given semantic version equal match target version for the user version.
280
+ # Returns boolean true if the user version is equal to the target version,
281
+ # false if the user version is not equal to the target version
282
+
283
+ target_version = condition['value']
284
+ user_version = @user_attributes[condition['name']]
285
+
286
+ SemanticVersion.compare_user_version_with_target_version(target_version, user_version).zero?
287
+ end
288
+
289
+ def semver_greater_than_evaluator(condition)
290
+ # Evaluate the given semantic version greater than match target version for the user version.
291
+ # Returns boolean true if the user version is greater than the target version,
292
+ # false if the user version is less than or equal to the target version
293
+
294
+ target_version = condition['value']
295
+ user_version = @user_attributes[condition['name']]
296
+
297
+ SemanticVersion.compare_user_version_with_target_version(target_version, user_version).positive?
298
+ end
299
+
300
+ def semver_greater_than_or_equal_evaluator(condition)
301
+ # Evaluate the given semantic version greater than or equal to match target version for the user version.
302
+ # Returns boolean true if the user version is greater than or equal to the target version,
303
+ # false if the user version is less than the target version
304
+
305
+ target_version = condition['value']
306
+ user_version = @user_attributes[condition['name']]
307
+
308
+ SemanticVersion.compare_user_version_with_target_version(target_version, user_version) >= 0
309
+ end
310
+
311
+ def semver_less_than_evaluator(condition)
312
+ # Evaluate the given semantic version less than match target version for the user version.
313
+ # Returns boolean true if the user version is less than the target version,
314
+ # false if the user version is greater than or equal to the target version
315
+
316
+ target_version = condition['value']
317
+ user_version = @user_attributes[condition['name']]
318
+
319
+ SemanticVersion.compare_user_version_with_target_version(target_version, user_version).negative?
320
+ end
321
+
322
+ def semver_less_than_or_equal_evaluator(condition)
323
+ # Evaluate the given semantic version less than or equal to match target version for the user version.
324
+ # Returns boolean true if the user version is less than or equal to the target version,
325
+ # false if the user version is greater than the target version
326
+
327
+ target_version = condition['value']
328
+ user_version = @user_attributes[condition['name']]
329
+
330
+ SemanticVersion.compare_user_version_with_target_version(target_version, user_version) <= 0
331
+ end
332
+
333
+ def qualified_evaluator(condition)
334
+ # Evaluate the given match condition for the given user qaulified segments.
335
+ # Returns boolean true if condition value is in the user's qualified segments,
336
+ # false if the condition value is not in the user's qualified segments,
337
+ # nil if the condition value isn't a string.
338
+
339
+ condition_value = condition['value']
340
+
341
+ unless condition_value.is_a?(String)
342
+ @logger.log(
343
+ Logger::WARN,
344
+ format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
345
+ )
346
+ return nil
347
+ end
348
+
349
+ @user_context.qualified_for?(condition_value)
350
+ end
351
+
352
+ private
353
+
354
+ def valid_numeric_values?(user_value, condition_value, condition)
355
+ # Returns true if user and condition values are valid numeric.
356
+ # false otherwise.
357
+
358
+ unless Helpers::Validator.finite_number?(condition_value)
359
+ @logger.log(
360
+ Logger::WARN,
361
+ format(Helpers::Constants::AUDIENCE_EVALUATION_LOGS['UNKNOWN_CONDITION_VALUE'], condition)
362
+ )
363
+ return false
364
+ end
365
+
366
+ raise InvalidAttributeType unless user_value.is_a?(Numeric)
367
+
368
+ unless Helpers::Validator.finite_number?(user_value)
369
+ @logger.log(
370
+ Logger::WARN,
371
+ format(
372
+ Helpers::Constants::AUDIENCE_EVALUATION_LOGS['INFINITE_ATTRIBUTE_VALUE'],
373
+ condition,
374
+ condition['name']
375
+ )
376
+ )
377
+ return false
378
+ end
379
+
380
+ true
381
+ end
382
+
383
+ def value_type_valid_for_exact_conditions?(value)
384
+ # Returns true if the value is valid for exact conditions. Valid values include
385
+ # strings or booleans or is a number.
386
+ # false otherwise.
387
+
388
+ (Helpers::Validator.boolean? value) || (value.is_a? String) || value.is_a?(Numeric)
389
+ end
390
+ end
391
+ end