Class: Featureflip::Evaluation::ConditionEvaluator

Inherits:
Object
  • Object
show all
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

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
# 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)
  condition.negate ? !result : result
end

#evaluate_condition_groups(condition_groups, context) ⇒ Object



48
49
50
51
52
53
54
# File 'lib/featureflip/evaluation/condition_evaluator.rb', line 48

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



38
39
40
41
42
43
44
45
46
# File 'lib/featureflip/evaluation/condition_evaluator.rb', line 38

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