1
1
# frozen_string_literal: true
2
2
3
3
#
4
- # Copyright 2019, Optimizely and contributors
4
+ # Copyright 2019-2020 , Optimizely and contributors
5
5
#
6
6
# Licensed under the Apache License, Version 2.0 (the "License");
7
7
# you may not use this file except in compliance with the License.
15
15
# See the License for the specific language governing permissions and
16
16
# limitations under the License.
17
17
#
18
+ require_relative 'exceptions'
18
19
require_relative 'helpers/constants'
19
20
require_relative 'helpers/validator'
21
+ require_relative 'semantic_version'
20
22
21
23
module Optimizely
22
24
class CustomAttributeConditionEvaluator
@@ -26,15 +28,29 @@ class CustomAttributeConditionEvaluator
26
28
EXACT_MATCH_TYPE = 'exact'
27
29
EXISTS_MATCH_TYPE = 'exists'
28
30
GREATER_THAN_MATCH_TYPE = 'gt'
31
+ GREATER_EQUAL_MATCH_TYPE = 'ge'
29
32
LESS_THAN_MATCH_TYPE = 'lt'
33
+ LESS_EQUAL_MATCH_TYPE = 'le'
30
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'
31
40
32
41
EVALUATORS_BY_MATCH_TYPE = {
33
42
EXACT_MATCH_TYPE => :exact_evaluator ,
34
43
EXISTS_MATCH_TYPE => :exists_evaluator ,
35
44
GREATER_THAN_MATCH_TYPE => :greater_than_evaluator ,
45
+ GREATER_EQUAL_MATCH_TYPE => :greater_than_or_equal_evaluator ,
36
46
LESS_THAN_MATCH_TYPE => :less_than_evaluator ,
37
- SUBSTRING_MATCH_TYPE => :substring_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
38
54
} . freeze
39
55
40
56
attr_reader :user_attributes
@@ -95,7 +111,35 @@ def evaluate(leaf_condition)
95
111
return nil
96
112
end
97
113
98
- send ( EVALUATORS_BY_MATCH_TYPE [ condition_match ] , leaf_condition )
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
99
143
end
100
144
101
145
def exact_evaluator ( condition )
@@ -122,16 +166,7 @@ def exact_evaluator(condition)
122
166
123
167
if !value_type_valid_for_exact_conditions? ( user_provided_value ) ||
124
168
!Helpers ::Validator . same_types? ( condition_value , user_provided_value )
125
- @logger . log (
126
- Logger ::WARN ,
127
- format (
128
- Helpers ::Constants ::AUDIENCE_EVALUATION_LOGS [ 'UNEXPECTED_TYPE' ] ,
129
- condition ,
130
- user_provided_value . class ,
131
- condition [ 'name' ]
132
- )
133
- )
134
- return nil
169
+ raise InvalidAttributeType
135
170
end
136
171
137
172
if user_provided_value . is_a? ( Numeric ) && !Helpers ::Validator . finite_number? ( user_provided_value )
@@ -173,6 +208,20 @@ def greater_than_evaluator(condition)
173
208
user_provided_value > condition_value
174
209
end
175
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
+
176
225
def less_than_evaluator ( condition )
177
226
# Evaluate the given less than match condition for the given user attributes.
178
227
# Returns boolean true if the user attribute value is less than the condition value,
@@ -187,6 +236,20 @@ def less_than_evaluator(condition)
187
236
user_provided_value < condition_value
188
237
end
189
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
+
190
253
def substring_evaluator ( condition )
191
254
# Evaluate the given substring match condition for the given user attributes.
192
255
# Returns boolean true if the condition value is a substring of the user attribute value,
@@ -204,22 +267,66 @@ def substring_evaluator(condition)
204
267
return nil
205
268
end
206
269
207
- unless user_provided_value . is_a? ( String )
208
- @logger . log (
209
- Logger ::WARN ,
210
- format (
211
- Helpers ::Constants ::AUDIENCE_EVALUATION_LOGS [ 'UNEXPECTED_TYPE' ] ,
212
- condition ,
213
- user_provided_value . class ,
214
- condition [ 'name' ]
215
- )
216
- )
217
- return nil
218
- end
270
+ raise InvalidAttributeType unless user_provided_value . is_a? ( String )
219
271
220
272
user_provided_value . include? condition_value
221
273
end
222
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
+
223
330
private
224
331
225
332
def valid_numeric_values? ( user_value , condition_value , condition )
@@ -234,18 +341,7 @@ def valid_numeric_values?(user_value, condition_value, condition)
234
341
return false
235
342
end
236
343
237
- unless user_value . is_a? ( Numeric )
238
- @logger . log (
239
- Logger ::WARN ,
240
- format (
241
- Helpers ::Constants ::AUDIENCE_EVALUATION_LOGS [ 'UNEXPECTED_TYPE' ] ,
242
- condition ,
243
- user_value . class ,
244
- condition [ 'name' ]
245
- )
246
- )
247
- return false
248
- end
344
+ raise InvalidAttributeType unless user_value . is_a? ( Numeric )
249
345
250
346
unless Helpers ::Validator . finite_number? ( user_value )
251
347
@logger . log (
0 commit comments