From 739358b75c2e671568e53dc44f14e690475e053a Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Sat, 28 Mar 2026 11:13:03 -0400 Subject: [PATCH 1/4] fix(openfeature): defer null targeting key check to shard evaluation point - Remove early null targeting key guard at evaluate() entry point - Add deferred null check inside shard else-branch before matchesShard call - Static and rule-only flags can now evaluate with null targeting key - TARGETING_KEY_MISSING only returned when shard computation needs it --- .../java/datadog/trace/api/openfeature/DDEvaluator.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/DDEvaluator.java b/products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/DDEvaluator.java index 8b04534b7d1..44cd1fc9efe 100644 --- a/products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/DDEvaluator.java +++ b/products/feature-flagging/feature-flagging-api/src/main/java/datadog/trace/api/openfeature/DDEvaluator.java @@ -87,10 +87,6 @@ public ProviderEvaluation evaluate( return error(defaultValue, ErrorCode.INVALID_CONTEXT); } - if (context.getTargetingKey() == null) { - return error(defaultValue, ErrorCode.TARGETING_KEY_MISSING); - } - final Flag flag = config.flags.get(key); if (flag == null) { return error(defaultValue, ErrorCode.FLAG_NOT_FOUND); @@ -127,6 +123,9 @@ public ProviderEvaluation evaluate( return resolveVariant( target, key, defaultValue, flag, split.variationKey, allocation, context); } else { + if (targetingKey == null) { + return error(defaultValue, ErrorCode.TARGETING_KEY_MISSING); + } // To match a split, subject must match ALL underlying shards boolean allShardsMatch = true; for (final Shard shard : split.shards) { From a2a32af7296ebeab7cc2596bbe845aba17261cb3 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Sat, 28 Mar 2026 11:15:32 -0400 Subject: [PATCH 2/4] test(openfeature): add 3 null targeting key test cases for DDEvaluatorTest - Update existing test: null TK on static flag now expects success (was incorrectly expecting TARGETING_KEY_MISSING error) - Add test: null TK on sharded flag expects TARGETING_KEY_MISSING error - Add test: null TK on rule-only flag (country attribute) expects success - Add createCountryRuleFlag() helper: rule matching on 'country' attribute with no shards, validates rule evaluation works without targeting key --- .../api/openfeature/DDEvaluatorTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java b/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java index 4b723fac7fb..92bbe3d4b60 100644 --- a/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java +++ b/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java @@ -210,9 +210,22 @@ public void testFlattening( private static List> evaluateTestCases() { return Arrays.asList( + // OF spec 3.1.1: targeting key is optional; static flags must evaluate successfully without it new TestCase<>("default") .flag("simple-string") + // no .targetingKey() -- null by default + .result(new Result<>("test-value").reason(TARGETING_MATCH.name()).variant("on")), + // Null targeting key on sharded flag must return TARGETING_KEY_MISSING + new TestCase<>("default") + .flag("shard-flag") + // no .targetingKey() -- null .result(new Result<>("default").reason(ERROR.name()).errorCode(TARGETING_KEY_MISSING)), + // Null targeting key on rule-only flag (matching on non-id attribute) must succeed + new TestCase<>("default") + .flag("country-rule-flag") + // no .targetingKey() -- null + .context("country", "US") + .result(new Result<>("us-value").reason(TARGETING_MATCH.name()).variant("us")), // OF.7: Empty string is a valid targeting key - evaluation should proceed as normal new TestCase<>("default") .flag("simple-string") @@ -536,6 +549,7 @@ private ServerConfiguration createTestConfiguration() { flags.put("not-matches-false-flag", createNotMatchesFalseFlag()); flags.put("not-one-of-false-flag", createNotOneOfFalseFlag()); flags.put("null-context-values-flag", createNullContextValuesFlag()); + flags.put("country-rule-flag", createCountryRuleFlag()); return new ServerConfiguration(null, null, null, flags); } @@ -1194,6 +1208,33 @@ private Flag createNullContextValuesFlag() { "null-context-values-flag", true, ValueType.STRING, variants, singletonList(allocation)); } + private Flag createCountryRuleFlag() { + final Map variants = new HashMap<>(); + variants.put("us", new Variant("us", "us-value")); + variants.put("global", new Variant("global", "global-value")); + + // Rule: country ONE_OF ["US"] -> us (no shards, so null targeting key is fine) + final List usCountries = singletonList("US"); + final List usConditions = + singletonList(new ConditionConfiguration(ConditionOperator.ONE_OF, "country", usCountries)); + final List usRules = singletonList(new Rule(usConditions)); + final List usSplits = singletonList(new Split(emptyList(), "us", null)); + final Allocation usAllocation = + new Allocation("us-alloc", usRules, null, null, usSplits, false); + + // Fallback allocation (no rules, no shards) + final List globalSplits = singletonList(new Split(emptyList(), "global", null)); + final Allocation globalAllocation = + new Allocation("global-alloc", null, null, null, globalSplits, false); + + return new Flag( + "country-rule-flag", + true, + ValueType.STRING, + variants, + asList(usAllocation, globalAllocation)); + } + private static Map mapOf(final Object... props) { final Map result = new HashMap<>(props.length << 1); int index = 0; From 71c2f6198cc55f821ea99265da68810dc31261dc Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 30 Mar 2026 13:24:53 -0400 Subject: [PATCH 3/4] style: fix spotless formatting in DDEvaluatorTest --- .../java/datadog/trace/api/openfeature/DDEvaluatorTest.java | 3 ++- .../src/test/resources/ffe-system-test-data | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 160000 products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data diff --git a/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java b/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java index 92bbe3d4b60..3df51d1bde3 100644 --- a/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java +++ b/products/feature-flagging/feature-flagging-api/src/test/java/datadog/trace/api/openfeature/DDEvaluatorTest.java @@ -210,7 +210,8 @@ public void testFlattening( private static List> evaluateTestCases() { return Arrays.asList( - // OF spec 3.1.1: targeting key is optional; static flags must evaluate successfully without it + // OF spec 3.1.1: targeting key is optional; static flags must evaluate successfully without + // it new TestCase<>("default") .flag("simple-string") // no .targetingKey() -- null by default diff --git a/products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data b/products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data new file mode 160000 index 00000000000..0d882aa2f2e --- /dev/null +++ b/products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data @@ -0,0 +1 @@ +Subproject commit 0d882aa2f2eeaa9514fa3a799644bfc5c13d2830 From 1c8b32c8d8fc6b5065a7f8b208f8d4379e378a85 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 30 Mar 2026 13:25:04 -0400 Subject: [PATCH 4/4] fix: remove accidentally staged ffe-system-test-data submodule pointer --- .../feature-flagging-api/src/test/resources/ffe-system-test-data | 1 - 1 file changed, 1 deletion(-) delete mode 160000 products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data diff --git a/products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data b/products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data deleted file mode 160000 index 0d882aa2f2e..00000000000 --- a/products/feature-flagging/feature-flagging-api/src/test/resources/ffe-system-test-data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0d882aa2f2eeaa9514fa3a799644bfc5c13d2830