optimizely-sdk 5.0.0 → 5.0.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +202 -202
  3. data/lib/optimizely/audience.rb +127 -127
  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 +558 -558
  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 +340 -340
  10. data/lib/optimizely/config_manager/project_config_manager.rb +25 -25
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +55 -55
  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 -235
  18. data/lib/optimizely/event/entity/conversion_event.rb +44 -44
  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 -48
  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 -36
  27. data/lib/optimizely/event/entity/visitor_attribute.rb +38 -38
  28. data/lib/optimizely/event/event_factory.rb +156 -156
  29. data/lib/optimizely/event/event_processor.rb +25 -25
  30. data/lib/optimizely/event/forwarding_event_processor.rb +44 -44
  31. data/lib/optimizely/event/user_event_factory.rb +88 -88
  32. data/lib/optimizely/event_builder.rb +221 -221
  33. data/lib/optimizely/event_dispatcher.rb +69 -69
  34. data/lib/optimizely/exceptions.rb +193 -193
  35. data/lib/optimizely/helpers/constants.rb +459 -459
  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 +68 -68
  40. data/lib/optimizely/helpers/sdk_settings.rb +61 -61
  41. data/lib/optimizely/helpers/validator.rb +236 -236
  42. data/lib/optimizely/helpers/variable_type.rb +67 -67
  43. data/lib/optimizely/logger.rb +46 -46
  44. data/lib/optimizely/notification_center.rb +174 -174
  45. data/lib/optimizely/notification_center_registry.rb +71 -71
  46. data/lib/optimizely/odp/lru_cache.rb +114 -114
  47. data/lib/optimizely/odp/odp_config.rb +102 -102
  48. data/lib/optimizely/odp/odp_event.rb +75 -75
  49. data/lib/optimizely/odp/odp_event_api_manager.rb +70 -70
  50. data/lib/optimizely/odp/odp_event_manager.rb +286 -286
  51. data/lib/optimizely/odp/odp_manager.rb +159 -159
  52. data/lib/optimizely/odp/odp_segment_api_manager.rb +122 -122
  53. data/lib/optimizely/odp/odp_segment_manager.rb +97 -97
  54. data/lib/optimizely/optimizely_config.rb +273 -273
  55. data/lib/optimizely/optimizely_factory.rb +184 -184
  56. data/lib/optimizely/optimizely_user_context.rb +238 -238
  57. data/lib/optimizely/params.rb +31 -31
  58. data/lib/optimizely/project_config.rb +99 -99
  59. data/lib/optimizely/semantic_version.rb +166 -166
  60. data/lib/optimizely/user_condition_evaluator.rb +391 -391
  61. data/lib/optimizely/user_profile_service.rb +35 -35
  62. data/lib/optimizely/version.rb +21 -21
  63. data/lib/optimizely.rb +1262 -1262
  64. metadata +7 -5
@@ -1,391 +1,391 @@
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 qualified 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
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 qualified 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