optimizely-sdk 5.0.0.pre.beta → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
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 +48 -32
  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 -338
  10. data/lib/optimizely/config_manager/project_config_manager.rb +25 -25
  11. data/lib/optimizely/config_manager/static_project_config_manager.rb +55 -54
  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 -149
  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 -238
  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 -271
  55. data/lib/optimizely/optimizely_factory.rb +184 -186
  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 +12 -10
@@ -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