diff --git a/evaluator/flags/testkit-flags.json b/evaluator/flags/testkit-flags.json index 7a0efc8..ae722fc 100644 --- a/evaluator/flags/testkit-flags.json +++ b/evaluator/flags/testkit-flags.json @@ -386,6 +386,66 @@ null ] } + }, + "fractional-nested-if-flag": { + "state": "ENABLED", + "variants": { "premium": "premium", "standard": "standard", "fallback": "fallback" }, + "defaultVariant": "fallback", + "targeting": { + "fractional": [ + {"var": "targetingKey"}, + [{"if": [{"==": [{"var": "tier"}, "premium"]}, "premium", "standard"]}, 50], + ["standard", 50] + ] + } + }, + "fractional-nested-var-flag": { + "state": "ENABLED", + "variants": { "red": "red", "green": "green", "blue": "blue", "fallback": "fallback" }, + "defaultVariant": "fallback", + "targeting": { + "fractional": [ + {"var": "targetingKey"}, + [{"var": "color"}, 50], + ["blue", 50] + ] + } + }, + "fractional-nested-weight-flag": { + "state": "ENABLED", + "variants": { "red": "red", "blue": "blue", "fallback": "fallback" }, + "defaultVariant": "fallback", + "targeting": { + "fractional": [ + {"var": "targetingKey"}, + ["red", {"if": [{"==": [{"var": "tier"}, "premium"]}, 100, 0]}], + ["blue", 10] + ] + } + }, + "fractional-as-condition-flag": { + "state": "ENABLED", + "variants": { "big": "hundreds", "small": "ones", "fallback": "zero" }, + "defaultVariant": "fallback", + "targeting": { + "if": [ + {"fractional": [[false, 0], [true, 100]]}, + "big", + "small" + ] + } + }, + "fractional-as-condition-false-flag": { + "state": "ENABLED", + "variants": { "big": "hundreds", "small": "ones", "fallback": "zero" }, + "defaultVariant": "fallback", + "targeting": { + "if": [ + {"fractional": [[false, 100], [true, 0]]}, + "big", + "small" + ] + } } }, "$evaluators": { diff --git a/evaluator/gherkin/fractional.feature b/evaluator/gherkin/fractional.feature index c71b7fd..eb9ca7b 100644 --- a/evaluator/gherkin/fractional.feature +++ b/evaluator/gherkin/fractional.feature @@ -110,3 +110,74 @@ Feature: Evaluator fractional operator | SI7p- | lower | | 6LvT0 | upper | | ceQdGm | upper | + + # Nested JSON Logic expressions as bucket variant names / weights. + # Requires evaluator implementations to support the @fractional-nested feature. + # Use -t "not @fractional-nested" to exclude during transition. + + @fractional-nested + Scenario Outline: Fractional operator with nested if expression as variant name + # bucket0=[if(tier=="premium","premium","standard"),50], bucket1=["standard",50] + # jon@company.com bv(100)=36 → bucket0; user1 bv(100)=76 → bucket1 + Given an evaluator + And a String-flag with key "fractional-nested-if-flag" and a fallback value "fallback" + And a context containing a targeting key with value "" + And a context containing a key "tier", with type "String" and with value "" + When the flag was evaluated with details + Then the resolved details value should be "" + Examples: + | targetingKey | tier | value | + | jon@company.com | premium | premium | + | jon@company.com | basic | standard | + | user1 | premium | standard | + | user1 | basic | standard | + + @fractional-nested + Scenario Outline: Fractional operator with nested var expression as variant name + # bucket0=[var("color"),50], bucket1=["blue",50] + # jon@company.com bv(100)=36 → bucket0 (resolves var "color"); user1 bv(100)=76 → bucket1 ("blue") + Given an evaluator + And a String-flag with key "fractional-nested-var-flag" and a fallback value "fallback" + And a context containing a targeting key with value "" + And a context containing a key "color", with type "String" and with value "" + When the flag was evaluated with details + Then the resolved details value should be "" + Examples: + | targetingKey | color | value | + | jon@company.com | red | red | + | jon@company.com | green | green | + | user1 | red | blue | + | jon@company.com | yellow | fallback | + | jon@company.com | | fallback | + + @fractional-nested + Scenario Outline: Fractional operator with nested if expression as weight + # bucket0=["red",if(tier=="premium",100,0)], bucket1=["blue",10] + Given an evaluator + And a String-flag with key "fractional-nested-weight-flag" and a fallback value "fallback" + And a context containing a targeting key with value "" + And a context containing a key "tier", with type "String" and with value "" + When the flag was evaluated with details + Then the resolved details value should be "" + Examples: + | targetingKey | tier | value | + | jon@company.com | premium | red | + | jon@company.com | basic | blue | + | user1 | premium | red | + | user1 | basic | blue | + + @fractional-nested + Scenario: Fractional as condition + Given an evaluator + And a String-flag with key "fractional-as-condition-flag" and a fallback value "zero" + And a context containing a targeting key with value "some-targeting-key" + When the flag was evaluated with details + Then the resolved details value should be "hundreds" + + @fractional-nested + Scenario: Fractional as condition evaluates false path + Given an evaluator + And a String-flag with key "fractional-as-condition-false-flag" and a fallback value "zero" + And a context containing a targeting key with value "some-targeting-key" + When the flag was evaluated with details + Then the resolved details value should be "ones" diff --git a/flags/custom-ops.json b/flags/custom-ops.json index 3cc73e7..85f5f9a 100644 --- a/flags/custom-ops.json +++ b/flags/custom-ops.json @@ -186,6 +186,26 @@ ] } }, + "fractional-as-condition-false-flag": { + "state": "ENABLED", + "variants": { + "big": "hundreds", + "small": "ones", + "fallback": "zero" + }, + "defaultVariant": "fallback", + "targeting": { + "if": [ + { + "fractional": [ + [ false, 100 ], + [ true, 0 ] + ] + }, + "big", "small" + ] + } + }, "starts-ends-flag": { "state": "ENABLED", "variants": { diff --git a/gherkin/targeting.feature b/gherkin/targeting.feature index f888904..5f7e2ba 100644 --- a/gherkin/targeting.feature +++ b/gherkin/targeting.feature @@ -193,6 +193,13 @@ Feature: Targeting rules When the flag was evaluated with details Then the resolved details value should be "hundreds" + @fractional @fractional-nested + Scenario: Fractional as condition evaluates false path + Given a String-flag with key "fractional-as-condition-false-flag" and a default value "zero" + And a context containing a targeting key with value "some-targeting-key" + When the flag was evaluated with details + Then the resolved details value should be "ones" + @string Scenario Outline: Substring operators Given a String-flag with key "starts-ends-flag" and a default value "fallback"