diff --git a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py index 3d913de98d4..1606933bbc2 100644 --- a/aws_lambda_powertools/utilities/feature_flags/feature_flags.py +++ b/aws_lambda_powertools/utilities/feature_flags/feature_flags.py @@ -46,7 +46,8 @@ def _match_by_action(action: str, condition_value: Any, context_value: Any) -> b schema.RuleAction.EQUALS.value: lambda a, b: a == b, schema.RuleAction.STARTSWITH.value: lambda a, b: a.startswith(b), schema.RuleAction.ENDSWITH.value: lambda a, b: a.endswith(b), - schema.RuleAction.CONTAINS.value: lambda a, b: a in b, + schema.RuleAction.IN.value: lambda a, b: a in b, + schema.RuleAction.NOT_IN.value: lambda a, b: a not in b, } try: diff --git a/aws_lambda_powertools/utilities/feature_flags/schema.py b/aws_lambda_powertools/utilities/feature_flags/schema.py index 6f2d2bf713c..efce82018db 100644 --- a/aws_lambda_powertools/utilities/feature_flags/schema.py +++ b/aws_lambda_powertools/utilities/feature_flags/schema.py @@ -20,7 +20,8 @@ class RuleAction(str, Enum): EQUALS = "EQUALS" STARTSWITH = "STARTSWITH" ENDSWITH = "ENDSWITH" - CONTAINS = "CONTAINS" + IN = "IN" + NOT_IN = "NOT_IN" class SchemaValidator(BaseValidator): @@ -79,7 +80,7 @@ class SchemaValidator(BaseValidator): The value MUST contain the following members: * **action**: `str`. Operation to perform to match a key and value. - The value MUST be either EQUALS, STARTSWITH, ENDSWITH, CONTAINS + The value MUST be either EQUALS, STARTSWITH, ENDSWITH, IN, NOT_IN * **key**: `str`. Key in given context to perform operation * **value**: `Any`. Value in given context that should match action operation. diff --git a/tests/functional/feature_flags/test_feature_flags.py b/tests/functional/feature_flags/test_feature_flags.py index d2150268062..c0f463c78d0 100644 --- a/tests/functional/feature_flags/test_feature_flags.py +++ b/tests/functional/feature_flags/test_feature_flags.py @@ -263,7 +263,7 @@ def test_flags_conditions_rule_match_multiple_actions_multiple_rules_multiple_co # check a case where the feature exists but the rule doesn't match so we revert to the default value of the feature -def test_flags_match_rule_with_contains_action(mocker, config): +def test_flags_match_rule_with_in_action(mocker, config): expected_value = True mocked_app_config_schema = { "my_feature": { @@ -273,7 +273,7 @@ def test_flags_match_rule_with_contains_action(mocker, config): "when_match": expected_value, "conditions": [ { - "action": RuleAction.CONTAINS.value, + "action": RuleAction.IN.value, "key": "tenant_id", "value": ["6", "2"], } @@ -287,7 +287,7 @@ def test_flags_match_rule_with_contains_action(mocker, config): assert toggle == expected_value -def test_flags_no_match_rule_with_contains_action(mocker, config): +def test_flags_no_match_rule_with_in_action(mocker, config): expected_value = False mocked_app_config_schema = { "my_feature": { @@ -297,7 +297,7 @@ def test_flags_no_match_rule_with_contains_action(mocker, config): "when_match": True, "conditions": [ { - "action": RuleAction.CONTAINS.value, + "action": RuleAction.IN.value, "key": "tenant_id", "value": ["8", "2"], } @@ -311,6 +311,54 @@ def test_flags_no_match_rule_with_contains_action(mocker, config): assert toggle == expected_value +def test_flags_match_rule_with_not_in_action(mocker, config): + expected_value = True + mocked_app_config_schema = { + "my_feature": { + "default": False, + "rules": { + "tenant id is contained in [8, 2]": { + "when_match": expected_value, + "conditions": [ + { + "action": RuleAction.NOT_IN.value, + "key": "tenant_id", + "value": ["10", "4"], + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "6", "username": "a"}, default=False) + assert toggle == expected_value + + +def test_flags_no_match_rule_with_not_in_action(mocker, config): + expected_value = False + mocked_app_config_schema = { + "my_feature": { + "default": expected_value, + "rules": { + "tenant id is contained in [8, 2]": { + "when_match": True, + "conditions": [ + { + "action": RuleAction.NOT_IN.value, + "key": "tenant_id", + "value": ["6", "4"], + } + ], + } + }, + } + } + feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config) + toggle = feature_flags.evaluate(name="my_feature", context={"tenant_id": "6", "username": "a"}, default=False) + assert toggle == expected_value + + def test_multiple_features_enabled(mocker, config): expected_value = ["my_feature", "my_feature2"] mocked_app_config_schema = { @@ -321,7 +369,7 @@ def test_multiple_features_enabled(mocker, config): "when_match": True, "conditions": [ { - "action": RuleAction.CONTAINS.value, + "action": RuleAction.IN.value, "key": "tenant_id", "value": ["6", "2"], } @@ -351,7 +399,7 @@ def test_multiple_features_only_some_enabled(mocker, config): "when_match": True, "conditions": [ { - "action": RuleAction.CONTAINS.value, + "action": RuleAction.IN.value, "key": "tenant_id", "value": ["6", "2"], } @@ -464,7 +512,7 @@ def test_features_jmespath_envelope(mocker, config): assert toggle == expected_value -# test_match_rule_with_contains_action +# test_match_rule_with_equals_action def test_match_condition_with_dict_value(mocker, config): expected_value = True mocked_app_config_schema = { diff --git a/tests/functional/feature_flags/test_schema_validation.py b/tests/functional/feature_flags/test_schema_validation.py index 2c33d3c61cc..ce85494afce 100644 --- a/tests/functional/feature_flags/test_schema_validation.py +++ b/tests/functional/feature_flags/test_schema_validation.py @@ -211,10 +211,15 @@ def test_valid_condition_all_actions(): CONDITION_VALUE: "a", }, { - CONDITION_ACTION: RuleAction.CONTAINS.value, + CONDITION_ACTION: RuleAction.IN.value, CONDITION_KEY: "username", CONDITION_VALUE: ["a", "b"], }, + { + CONDITION_ACTION: RuleAction.NOT_IN.value, + CONDITION_KEY: "username", + CONDITION_VALUE: ["c"], + }, ], } },