fix(feature_flags): missing context key never satisfies a condition - #8429
fix(feature_flags): missing context key never satisfies a condition#8429dreamorosi wants to merge 2 commits into
Conversation
Conditions read the context with context.get(key), so an absent key was compared as None. Negative actions (NOT_EQUALS, NOT_IN, KEY_NOT_IN_VALUE, VALUE_NOT_IN_KEY) therefore matched requests that carried no such key at all, firing rules meant for one segment on anonymous or partially populated traffic. This is a regression from ab9078c (#2052). Until v2.11.0 _match_by_action returned False for any falsy context value, so a missing key never matched. That guard was removed to allow falsy values such as 0 and "" to be compared, and allowing absent keys to match was an unintended side effect. Restore the original semantics for missing keys by checking key presence before invoking the comparator, while keeping the #2051 behaviour for keys that are present with a falsy or None value. Time based actions are unaffected since they never read the user context. Document the behaviour in the rule actions section. Fixes #8424
c53a906 to
80e50af
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #8429 +/- ##
========================================
Coverage 96.65% 96.65%
========================================
Files 296 296
Lines 14767 14774 +7
Branches 1246 1247 +1
========================================
+ Hits 14273 14280 +7
Misses 359 359
Partials 135 135 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
leandrodamascena
left a comment
There was a problem hiding this comment.
Thanks for working on this. There is an existing Feature Flags behavior that this change does not take into account, and the result is a regression.
Users can register a validation_exception_handler to catch errors raised while evaluating conditions and decide how to handle them:
@feature_flags.validation_exception_handler(ValueError)
def handle_invalid_context(exc):
return TrueFor example, evaluating ANY_IN_VALUE without the expected context key currently reaches the comparator, raises ValueError, and calls the registered handler.
With this PR, the missing-key check returns False before _match_by_action runs. The registered handler is never called. I reproduced the difference:
develop: the handler is called and its result is used.- This branch: the handler is skipped and the condition returns
False.
Could you fix this and add a test covering a missing key with a registered handler? Please hold off merging for now. Ping me when it is ready and I will review it again.
…text keys The missing-key guard returned False before the comparator ran, so a comparator that raises for a None context value (ANY_IN_VALUE, ALL_IN_VALUE, NONE_IN_VALUE) never reached a registered validation_exception_handler. That was a regression from develop, where the handler was called and its result used. Run the comparator first and only then apply the missing-key rule. Any exception still flows through the existing handler lookup unchanged; the only difference from develop is that a comparator returning True for an absent key now yields False.
|
@leandrodamascena thanks, good catch. Fixed in 95094f9. The guard now lives inside Added two tests at the end of
Ready for another look whenever you have time. |
|



Issue number: closes #8424
Summary
Changes
A condition whose
keyis absent from the evaluation context no longer matches, regardless of action._evaluate_conditionsread the context withcontext.get(key), so an absent key was compared asNone. Negative actions then matched trivially:None != "premium"isTrue,None not in [...]isTrue. Rules such as "tier is not premium" fired for anonymous or partially populated requests that carried notierat all._match_by_actionnow takes acontext_key_presentflag. The comparator runs exactly as before; if the key was absent and the comparator returned normally, the result is forced toFalse. Running the comparator first matters: actions such asANY_IN_VALUEraiseValueErrorfor aNonecontext value, and that exception must keep reaching anyvalidation_exception_handlerthe user registered, with the handler's return value honoured. Only the "comparator quietly returnedTruefor an absent key" path changes.A key that is present with an explicit
Nonevalue is still compared as before, so this is about absence rather than falsiness. Time-based actions are unaffected since they never read the user context.Why this is a regression fix, not a behaviour change
From the utility's introduction (#563, 2021) until v2.11.0,
_match_by_actionopened with:so a missing key (
None) could never satisfy a condition, for any action. #2052 (fixing #2051) removed that guard so that falsy values such as0,"", andFalsecould be compared. That was the intended scope of the change; allowing an absent key to satisfyNOT_EQUALSand friends was a side effect, never documented, and no test asserted it. This PR restores the original "missing key never matches" semantics while keeping the #2051 behaviour for keys that are present with a falsy value.Tests cover: the four negative actions with a missing key; an explicit
Nonevalue still being compared; a missing key withANY_IN_VALUEand a registeredValueErrorhandler, asserting the handler is called and its result used; and the same without a handler, asserting no match. The behaviour is also now documented in the rule actions section of the feature flags docs.User experience
Given:
{ "discount_banner": { "default": false, "rules": { "non premium users": { "when_match": true, "conditions": [{ "action": "NOT_EQUALS", "key": "tier", "value": "premium" }] } } } }context{}FalseTrueFalse{"tier": None}FalseTrueTrue{"tier": ""}FalseTrueTrue{"tier": "free"}TrueTrueTrue{"tier": "premium"}FalseFalseFalseOnly the first row changes.
validation_exception_handlerbehaviour is unchanged: comparators raise for the same inputs as before and handlers see the same exceptions.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.