Skip to content

fix: respect rule type for sub-rules in segment evaluation#36

Open
Zaimwa9 wants to merge 2 commits intomainfrom
feat/apply-top-rules-with-any-type
Open

fix: respect rule type for sub-rules in segment evaluation#36
Zaimwa9 wants to merge 2 commits intomainfrom
feat/apply-top-rules-with-any-type

Conversation

@Zaimwa9
Copy link
Copy Markdown
Contributor

@Zaimwa9 Zaimwa9 commented May 7, 2026

Summary

  • Bumped engine-test-data submodule from v3.5.0 to v3.7.0
  • Fixed segment evaluation in both evaluator paths:
    • New context-based evaluator (engine_eval/segment_evaluator.rs): sub-rules now respect the parent rule's type (ANY/ALL/NONE) instead of hardcoding ALL
    • Legacy evaluator (segments/evaluator.rs): same sub-rules fix + fixed NONE rule type for conditions (was hardcoded to true)

Equivalent fix to flagsmith-engine#313.

Test plan

  • Bumped engine-test-data to v3.7.0 (includes new sub-rule type test cases)
  • Verified tests fail before the fix
  • Applied the fix
  • Verified all tests pass after the fix (156 tests: 134 unit + 22 integration)

Sub-rules were always evaluated with ALL logic, ignoring the parent
rule's type field (ANY/ALL/NONE). Now uses type-aware matching for
sub-rules in both the new context-based evaluator and the legacy
evaluator.

Also fixes the NONE rule type in the legacy evaluator which was
hardcoded to always return true.

Bumps engine-test-data from v3.5.0 to v3.7.0 which includes test cases
for this fix.

Equivalent fix to flagsmith-engine#313.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Zaimwa9 Zaimwa9 requested a review from a team as a code owner May 7, 2026 08:34
@Zaimwa9 Zaimwa9 requested review from gagantrivedi and removed request for a team May 7, 2026 08:34
khvn26
khvn26 previously approved these changes May 7, 2026
matches_sub_rules_by_rule_type(ec, &rule.rules, &rule.rule_type, segment_key)
}

fn matches_sub_rules_by_rule_type(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matches_sub_rules_by_rule_type in src/engine_eval/segment_evaluator.rs and the new sub-rule block in src/segments/evaluator.rs implement the same operation with two different idioms. The
legacy evaluator uses iterator combinators directly:

  match rule.segment_rule_type.as_str() {
      constants::ANY_RULE => sub_rules_iterator.any(|result| result),
      constants::ALL_RULE => sub_rules_iterator.all(|result| result),
      constants::NONE_RULE => !sub_rules_iterator.any(|result| result),
      _ => false,
  }

  …while the engine_eval version uses an imperative loop with *rule_type != SegmentRuleType::Any as the fallthrough. That works (All != Any → true, None != Any → true, Any != Any → false), but
  it forces the reader to derive that "!= Any" is the right default for both All and None.

  Suggest matching the legacy form so both evaluators read identically:

  fn matches_sub_rules_by_rule_type(
      ec: &EngineEvaluationContext,
      sub_rules: &[SegmentRule],
      rule_type: &SegmentRuleType,
      segment_key: &str,
  ) -> bool {
      let mut results = sub_rules
          .iter()
          .map(|sr| context_matches_segment_rule(ec, sr, segment_key));
      match rule_type {
          SegmentRuleType::All  => results.all(|r| r),
          SegmentRuleType::Any  => results.any(|r| r),
          SegmentRuleType::None => !results.any(|r| r),
      }
  }

Same short-circuiting, no clever fallthrough, and the two evaluators stay in sync — which matters since they're meant to encode identical semantics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b7fc915

Comment thread src/segments/evaluator.rs Outdated
let mut sub_rules_iterator = rule.rules.iter().map(|sub_rule| {
traits_match_segment_rule(&identity_traits, sub_rule.as_ref(), segment_id, identity_id)
});
match rule.segment_rule_type.as_str() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In src/segments/evaluator.rs, traits_match_segment_rule now matches on rule.segment_rule_type.as_str() twice — once over rules_iterator for conditions, then again over sub_rules_iterator for
sub-rules. The two arms are identical:

  constants::ANY_RULE  => iter.any(|r| r),
  constants::ALL_RULE  => iter.all(|r| r),
  constants::NONE_RULE => !iter.any(|r| r),
  _ => false,

  Minor, but worth lifting into a small helper so the combine logic lives in one place. Naming it matches_by_rule_type to fit the existing matches_conditions_by_rule_type /
  matches_sub_rules_by_rule_type convention:

  fn matches_by_rule_type(rule_type: &str, mut results: impl Iterator<Item = bool>) -> bool {
      match rule_type {
          constants::ANY_RULE  => results.any(|r| r),
          constants::ALL_RULE  => results.all(|r| r),
          constants::NONE_RULE => !results.any(|r| r),
          _ => false,
      }
  }

  Then the body becomes:

  if !matches_by_rule_type(rule.segment_rule_type.as_str(), rules_iterator) {
      return false;
  }
  if rule.rules.is_empty() {
      return true;
  }
  matches_by_rule_type(rule.segment_rule_type.as_str(), sub_rules_iterator)

Same short-circuiting, one source of truth for the ANY/ALL/NONE mapping, and if a new rule type is added later there's only one place to update

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b7fc915

Address review feedback:
- Replace imperative loop in matches_sub_rules_by_rule_type with
  iterator combinators (all/any/!any) matching the legacy form
- Extract matches_by_rule_type helper in legacy evaluator to DRY up
  the duplicated ANY/ALL/NONE match blocks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants