Class: Featureflip::Evaluation::ConditionEvaluator
- Inherits:
-
Object
- Object
- Featureflip::Evaluation::ConditionEvaluator
- Defined in:
- lib/featureflip/evaluation/condition_evaluator.rb
Constant Summary collapse
- REGEX_TIMEOUT_SECONDS =
Per-regex match timeout (seconds) for MatchesRegex, mirroring the engine's 100ms RegexMatchTimeout guard against catastrophic backtracking / ReDoS (#1460). Requires Ruby >= 3.2 (gemspec floor), which added the Regexp
timeout:keyword. 0.1
Instance Method Summary collapse
- #evaluate_condition(condition, context) ⇒ Object
- #evaluate_condition_groups(condition_groups, context) ⇒ Object
- #evaluate_conditions(conditions, logic, context) ⇒ Object
Instance Method Details
#evaluate_condition(condition, context) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/featureflip/evaluation/condition_evaluator.rb', line 12 def evaluate_condition(condition, context) attr_value = context[condition.attribute] return condition.negate if attr_value.nil? # Issue #1458: when the attribute is a native numeric (Integer/Float — # Ruby's `true`/`false` are NOT Numeric, so booleans are naturally # excluded), the equality-family operators coerce the condition values to # numbers and compare numerically, so 1.0 matches "1". This mirrors the # engine's type-aware path and runs BEFORE stringification — a String # attribute (even "1.0") stays on the string path below. if attr_value.is_a?(Numeric) && NUMERIC_EQUALITY_OPERATORS.include?(condition.operator) return evaluate_numeric_equality(condition, attr_value) end # Pass the raw (case-preserved) strings to the operator dispatcher. # Most operators compare case-insensitively and downcase internally, but # the semver operators rely on case-sensitive prerelease precedence # (semver §11), so the original casing must survive to that point. str_value = attr_value.to_s targets = condition.values.map(&:to_s) result = evaluate_operator(condition.operator, str_value, targets) # Issue #2262: an unrecognised operator fails CLOSED. `!nil` is `true` # in Ruby, so without this guard a negated unknown operator would match # every user and roll the flag out to 100% of traffic. The realistic # trigger is a new operator shipped server-side reaching an SDK pinned # to an older version. return false if result.nil? condition.negate ? !result : result end |
#evaluate_condition_groups(condition_groups, context) ⇒ Object
56 57 58 59 60 61 62 |
# File 'lib/featureflip/evaluation/condition_evaluator.rb', line 56 def evaluate_condition_groups(condition_groups, context) return true if condition_groups.nil? || condition_groups.empty? condition_groups.all? do |group| evaluate_conditions(group.conditions, group.operator, context) end end |
#evaluate_conditions(conditions, logic, context) ⇒ Object
46 47 48 49 50 51 52 53 54 |
# File 'lib/featureflip/evaluation/condition_evaluator.rb', line 46 def evaluate_conditions(conditions, logic, context) return true if conditions.empty? if logic == "And" conditions.all? { |c| evaluate_condition(c, context) } else conditions.any? { |c| evaluate_condition(c, context) } end end |