Module: Feat::Eval

Defined in:
lib/feat/eval.rb

Class Method Summary collapse

Class Method Details

.call(flag_key:, default_value:, ctx:, datafile:) ⇒ Object

Run the precedence pipeline:

1. archived flag        -> off variation        DISABLED
2. !isEnabled           -> off variation        DISABLED
3. individual target    -> target variation     TARGETING_MATCH
4. first matching rule  -> rule variation/rollout TARGETING_MATCH / SPLIT
5. default              -> default variation/rollout FALLTHROUGH / SPLIT
6. nothing matched      -> off variation        DEFAULT


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/feat/eval.rb', line 31

def call(flag_key:, default_value:, ctx:, datafile:)
  flag = datafile.flags[flag_key]
  if flag.nil?
    return EvaluationResult.new(
      value: default_value, reason: Reason::ERROR,
      error_message: "flag could not be evaluated"
    )
  end

  if flag.archived || !flag.isEnabled
    return resolve_variation(flag, flag.offVariationId, Reason::DISABLED, default_value)
  end

  flag.targets.each do |target|
    ctx_key = ContextResolver.read_context_key(ctx, target.contextKindKey)
    if !ctx_key.nil? && ctx_key == target.contextKey
      return resolve_variation(flag, target.variationId, Reason::TARGETING_MATCH, default_value)
    end
  end

  flag.rules.each do |rule|
    next unless match_rule?(rule, ctx, datafile)

    if !rule.variationId.nil?
      return resolve_variation(flag, rule.variationId, Reason::TARGETING_MATCH, default_value)
    end
    if !rule.rollout.nil?
      picked = pick_rollout(flag, rule.rollout, ctx)
      return resolve_variation(flag, picked, Reason::SPLIT, default_value) unless picked.nil?
    end
  end

  if !flag.defaultVariationId.nil?
    return resolve_variation(flag, flag.defaultVariationId, Reason::FALLTHROUGH, default_value)
  end
  if !flag.defaultRollout.nil?
    picked = pick_rollout(flag, flag.defaultRollout, ctx)
    return resolve_variation(flag, picked, Reason::SPLIT, default_value) unless picked.nil?
  end

  resolve_variation(flag, flag.offVariationId, Reason::DEFAULT, default_value)
end

.match_rule?(rule, ctx, datafile) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
77
78
79
80
81
82
# File 'lib/feat/eval.rb', line 74

def match_rule?(rule, ctx, datafile)
  return false if rule.groups.empty?

  rule.groups.any? do |group|
    next false if group.conditions.empty?

    group.conditions.all? { |cond| Segments.match_condition(cond, ctx, datafile) }
  end
end

.pick_rollout(flag, rollout, ctx) ⇒ Object



84
85
86
87
88
89
90
# File 'lib/feat/eval.rb', line 84

def pick_rollout(flag, rollout, ctx)
  ctx_key = ContextResolver.read_context_key(ctx, rollout.bucketingContextKindKey)
  return nil if ctx_key.nil?

  b = Bucketing.bucket(salt: flag.salt, flag_key: flag.key, context_key: ctx_key)
  Bucketing.pick_by_weight(b, rollout.variations)
end

.resolve_variation(flag, variation_id, reason, default_value) ⇒ Object



92
93
94
95
96
97
98
99
100
101
# File 'lib/feat/eval.rb', line 92

def resolve_variation(flag, variation_id, reason, default_value)
  v = flag.variations.find { |x| x.id == variation_id }
  if v.nil?
    return EvaluationResult.new(
      value: default_value, reason: Reason::ERROR,
      error_message: "flag could not be evaluated"
    )
  end
  EvaluationResult.new(value: v.value, variation_id: variation_id, reason: reason)
end