11# frozen_string_literal: true
22
33#
4- # Copyright 2019, Optimizely and contributors
4+ # Copyright 2019-2020 , Optimizely and contributors
55#
66# Licensed under the Apache License, Version 2.0 (the "License");
77# you may not use this file except in compliance with the License.
1515# See the License for the specific language governing permissions and
1616# limitations under the License.
1717#
18+ require_relative 'exceptions'
1819require_relative 'helpers/constants'
1920require_relative 'helpers/validator'
21+ require_relative 'semantic_version'
2022
2123module Optimizely
2224 class CustomAttributeConditionEvaluator
@@ -26,15 +28,29 @@ class CustomAttributeConditionEvaluator
2628 EXACT_MATCH_TYPE = 'exact'
2729 EXISTS_MATCH_TYPE = 'exists'
2830 GREATER_THAN_MATCH_TYPE = 'gt'
31+ GREATER_EQUAL_MATCH_TYPE = 'ge'
2932 LESS_THAN_MATCH_TYPE = 'lt'
33+ LESS_EQUAL_MATCH_TYPE = 'le'
3034 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'
3140
3241 EVALUATORS_BY_MATCH_TYPE = {
3342 EXACT_MATCH_TYPE => :exact_evaluator ,
3443 EXISTS_MATCH_TYPE => :exists_evaluator ,
3544 GREATER_THAN_MATCH_TYPE => :greater_than_evaluator ,
45+ GREATER_EQUAL_MATCH_TYPE => :greater_than_or_equal_evaluator ,
3646 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
3854 } . freeze
3955
4056 attr_reader :user_attributes
@@ -95,7 +111,35 @@ def evaluate(leaf_condition)
95111 return nil
96112 end
97113
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
99143 end
100144
101145 def exact_evaluator ( condition )
@@ -122,16 +166,7 @@ def exact_evaluator(condition)
122166
123167 if !value_type_valid_for_exact_conditions? ( user_provided_value ) ||
124168 !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
135170 end
136171
137172 if user_provided_value . is_a? ( Numeric ) && !Helpers ::Validator . finite_number? ( user_provided_value )
@@ -173,6 +208,20 @@ def greater_than_evaluator(condition)
173208 user_provided_value > condition_value
174209 end
175210
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+
176225 def less_than_evaluator ( condition )
177226 # Evaluate the given less than match condition for the given user attributes.
178227 # Returns boolean true if the user attribute value is less than the condition value,
@@ -187,6 +236,20 @@ def less_than_evaluator(condition)
187236 user_provided_value < condition_value
188237 end
189238
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+
190253 def substring_evaluator ( condition )
191254 # Evaluate the given substring match condition for the given user attributes.
192255 # Returns boolean true if the condition value is a substring of the user attribute value,
@@ -204,22 +267,66 @@ def substring_evaluator(condition)
204267 return nil
205268 end
206269
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 )
219271
220272 user_provided_value . include? condition_value
221273 end
222274
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+
223330 private
224331
225332 def valid_numeric_values? ( user_value , condition_value , condition )
@@ -234,18 +341,7 @@ def valid_numeric_values?(user_value, condition_value, condition)
234341 return false
235342 end
236343
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 )
249345
250346 unless Helpers ::Validator . finite_number? ( user_value )
251347 @logger . log (
0 commit comments