optimizely-sdk 3.9.0 → 4.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.
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 -508
  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 -321
  10. data/lib/optimizely/config_manager/project_config_manager.rb +24 -24
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +53 -47
  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 -500
  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 -107
  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 -1117
  54. metadata +13 -13
@@ -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