Skip to content

Commit bf7b01a

Browse files
committed
fix: segment rule with no conditions or nested rules crashes translation
`translate_rule` asserted every rule has at least one child, so a segment rule with no conditions and no nested rules raised AssertionError and crashed the project's segment-count refresh task. The flag engine's `context_matches_rule` treats such a rule as always-matching, so mirror that: build conditions and nested rules as two independent groups AND-ed together, each vacuously true when empty, yielding TRUE for an empty rule. This also corrects `ANY`, which the engine evaluates as `any(conditions) AND any(rules)` rather than a single OR over the merged set (equivalent for ALL/NONE, divergent for ANY with both groups). Fixes FLAGSMITH-API-5Q8 beep boop
1 parent 0c91032 commit bf7b01a

2 files changed

Lines changed: 31 additions & 12 deletions

File tree

src/flagsmith_sql_flag_engine/translator.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -517,26 +517,33 @@ def translate_condition(cond: SegmentCondition, ctx: TranslateContext) -> str |
517517

518518

519519
def translate_rule(rule: SegmentRule, ctx: TranslateContext) -> str | None:
520-
children: list[str] = []
520+
cond_children: list[str] = []
521521
for cond in rule.get("conditions") or []:
522522
sql = translate_condition(cond, ctx)
523523
if sql is None:
524524
return None
525-
children.append(f"({sql})")
525+
cond_children.append(f"({sql})")
526+
rule_children: list[str] = []
526527
for nested in rule.get("rules") or []:
527528
sql = translate_rule(nested, ctx)
528529
if sql is None:
529530
return None
530-
children.append(f"({sql})")
531-
532-
assert children, "segment rule must have at least one condition or nested rule"
533-
match rule["type"]:
534-
case "ALL":
535-
return " AND ".join(children)
536-
case "ANY":
537-
return " OR ".join(children)
538-
case "NONE":
539-
return f"NOT ({' OR '.join(children)})"
531+
rule_children.append(f"({sql})")
532+
533+
# Mirror the engine's `context_matches_rule`: conditions and nested rules
534+
# are two independent groups AND-ed together, each vacuously true when
535+
# empty.
536+
op = {"ALL": " AND ", "ANY": " OR ", "NONE": " OR "}[rule["type"]]
537+
groups = [
538+
f"NOT ({op.join(c)})" if rule["type"] == "NONE" else op.join(c)
539+
for c in (cond_children, rule_children)
540+
if c
541+
]
542+
if not groups:
543+
return "TRUE"
544+
if len(groups) == 1:
545+
return groups[0]
546+
return " AND ".join(f"({g})" for g in groups)
540547

541548

542549
def translate_segment(segment: SegmentContext, ctx: TranslateContext) -> str | None:

tests/test_translator_unit.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,18 @@ def test_translate_segment__empty_rules__returns_false() -> None:
282282
assert translate_segment(seg, _ctx()) == "FALSE"
283283

284284

285+
def test_translate_segment__rule_with_no_conditions_or_nested_rules__matches_all() -> None:
286+
# Given
287+
seg: SegmentContext = {
288+
"key": "10",
289+
"name": "s",
290+
"rules": [{"type": "ALL", "conditions": [], "rules": []}],
291+
}
292+
293+
# When / Then
294+
assert translate_segment(seg, _ctx()) == "(TRUE)"
295+
296+
285297
def test_translate_segment__trait_key_with_hyphens__quotes_subcolumn_path() -> None:
286298
# Given a trait key with a hyphen (illegal as an unquoted SQL identifier)
287299
seg: SegmentContext = {

0 commit comments

Comments
 (0)