From 216707324a6c24d758d9a21a73a166decf935a52 Mon Sep 17 00:00:00 2001 From: "tyler.potter" Date: Wed, 4 Nov 2026 19:35:31 +0000 Subject: [PATCH 1/3] feat(openfeature): request dependent flags with query parameter --- .../delivery/CdnConfigurationSource.java | 21 +++++++++++++------ .../evaluation/EvaluatorCapabilities.java | 3 +++ .../delivery/CdnConfigurationSourceTest.java | 16 ++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java index ad7efef8bd7..39a0d72c4c1 100644 --- a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java +++ b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java @@ -17,6 +17,7 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -298,15 +299,10 @@ private HttpResponse fetchWithRetries() throws IOException, InterruptedE private HttpRequest buildRequest() { final HttpRequest.Builder request = - HttpRequest.newBuilder(this.endpoint) + HttpRequest.newBuilder(endpointForCapabilities(this.endpoint, EvaluatorCapabilities.supported())) .timeout(this.requestTimeout) .header("Accept-Encoding", "gzip"); Headers.addMetadata(request); - if (EvaluatorCapabilities.supported() - .contains(EvaluatorCapabilities.DEPENDENT_FLAG_VARIANT_KEY_V1)) { - request.header( - "X-DD-FEATURE-FLAGS-CAPABILITIES", EvaluatorCapabilities.DEPENDENT_FLAG_VARIANT_KEY_V1); - } if (this.apiKey != null) { request.header("DD-API-KEY", this.apiKey); } @@ -317,6 +313,19 @@ private HttpRequest buildRequest() { return request.GET().build(); } + static URI endpointForCapabilities(final URI endpoint, final Set capabilities) { + if (!capabilities.contains(EvaluatorCapabilities.DEPENDENT_FLAG_VARIANT_KEY_V1)) { + return endpoint; + } + final String separator = endpoint.getRawQuery() == null ? "?" : "&"; + return URI.create( + endpoint + + separator + + EvaluatorCapabilities.DEPENDENT_FLAGS_VARIANT_KEY_QUERY_PARAMETER + + "=" + + EvaluatorCapabilities.DEPENDENT_FLAGS_VARIANT_KEY_QUERY_VERSION); + } + private synchronized void warn(final String message, final Object... arguments) { final long now = System.nanoTime(); if (this.lastWarningNanos == 0 || now - this.lastWarningNanos >= WARNING_INTERVAL_NANOS) { diff --git a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java index d4ea1e64a28..e66f042a4c7 100644 --- a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java +++ b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java @@ -7,6 +7,9 @@ /** Capabilities supported by both local evaluation and exposure reporting. */ public final class EvaluatorCapabilities { public static final String DEPENDENT_FLAG_VARIANT_KEY_V1 = "dependent-flag-variant-key-v1"; + public static final String DEPENDENT_FLAGS_VARIANT_KEY_QUERY_PARAMETER = + "dependent-flags-variant-key"; + public static final String DEPENDENT_FLAGS_VARIANT_KEY_QUERY_VERSION = "v1"; private EvaluatorCapabilities() {} diff --git a/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java b/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java index 32dbf46dba4..4e715e2fdc8 100644 --- a/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java +++ b/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java @@ -28,6 +28,7 @@ import java.net.http.HttpClient; import java.time.Duration; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.FutureTask; @@ -174,6 +175,21 @@ void sendsApiKeyAndMetadataHeaders() throws Exception { assertNull(request.header("X-DD-FEATURE-FLAGS-CAPABILITIES")); } + @Test + void carriesEvaluatorCapabilityAsQueryParameter() { + final URI endpoint = URI.create("https://example.test/ufc?token=preserved"); + + assertEquals(endpoint, CdnConfigurationSource.endpointForCapabilities(endpoint, Set.of())); + assertEquals( + endpoint, + CdnConfigurationSource.endpointForCapabilities(endpoint, Set.of("unknown-v1"))); + assertEquals( + URI.create( + "https://example.test/ufc?token=preserved&dependent-flags-variant-key=v1"), + CdnConfigurationSource.endpointForCapabilities( + endpoint, Set.of("dependent-flag-variant-key-v1"))); + } + @Test void appliesJsonApiConfiguration() { this.server.enqueue(200, CONFIG, "ETag", "\"v1\""); From d3421850254a2dd0360781938dbbc202cd5db512 Mon Sep 17 00:00:00 2001 From: "tyler.potter" Date: Wed, 4 Nov 2026 19:37:49 +0000 Subject: [PATCH 2/3] fix(openfeature): normalize dependent flags query parameter --- .../delivery/CdnConfigurationSource.java | 34 +++++++++++++++---- .../delivery/CdnConfigurationSourceTest.java | 7 ++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java index 39a0d72c4c1..0f69632e214 100644 --- a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java +++ b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSource.java @@ -317,13 +317,33 @@ static URI endpointForCapabilities(final URI endpoint, final Set capabil if (!capabilities.contains(EvaluatorCapabilities.DEPENDENT_FLAG_VARIANT_KEY_V1)) { return endpoint; } - final String separator = endpoint.getRawQuery() == null ? "?" : "&"; - return URI.create( - endpoint - + separator - + EvaluatorCapabilities.DEPENDENT_FLAGS_VARIANT_KEY_QUERY_PARAMETER - + "=" - + EvaluatorCapabilities.DEPENDENT_FLAGS_VARIANT_KEY_QUERY_VERSION); + final String parameter = EvaluatorCapabilities.DEPENDENT_FLAGS_VARIANT_KEY_QUERY_PARAMETER; + final StringBuilder query = new StringBuilder(); + if (endpoint.getRawQuery() != null) { + for (final String item : endpoint.getRawQuery().split("&", -1)) { + if (!item.equals(parameter) && !item.startsWith(parameter + "=")) { + if (query.length() > 0) { + query.append('&'); + } + query.append(item); + } + } + } + if (query.length() > 0) { + query.append('&'); + } + query + .append(parameter) + .append('=') + .append(EvaluatorCapabilities.DEPENDENT_FLAGS_VARIANT_KEY_QUERY_VERSION); + + final String endpointText = endpoint.toString(); + final int fragmentStart = endpointText.indexOf('#'); + final String fragment = fragmentStart < 0 ? "" : endpointText.substring(fragmentStart); + final String withoutFragment = fragmentStart < 0 ? endpointText : endpointText.substring(0, fragmentStart); + final int queryStart = withoutFragment.indexOf('?'); + final String path = queryStart < 0 ? withoutFragment : withoutFragment.substring(0, queryStart); + return URI.create(path + "?" + query + fragment); } private synchronized void warn(final String message, final Object... arguments) { diff --git a/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java b/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java index 4e715e2fdc8..ee8392970d4 100644 --- a/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java +++ b/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java @@ -188,6 +188,13 @@ void carriesEvaluatorCapabilityAsQueryParameter() { "https://example.test/ufc?token=preserved&dependent-flags-variant-key=v1"), CdnConfigurationSource.endpointForCapabilities( endpoint, Set.of("dependent-flag-variant-key-v1"))); + assertEquals( + URI.create( + "https://example.test/ufc?token=preserved&dependent-flags-variant-key=v1#section"), + CdnConfigurationSource.endpointForCapabilities( + URI.create( + "https://example.test/ufc?dependent-flags-variant-key=old&token=preserved#section"), + Set.of("dependent-flag-variant-key-v1"))); } @Test From 2cd5ba1de6e58f7bceb6044214fa1b6e1935541f Mon Sep 17 00:00:00 2001 From: "tyler.potter" Date: Wed, 4 Nov 2026 20:13:18 +0000 Subject: [PATCH 3/3] fix(openfeature): use capability-prefixed query parameter --- .../internal/evaluation/EvaluatorCapabilities.java | 2 +- .../internal/delivery/CdnConfigurationSourceTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java index e66f042a4c7..0633eae74e7 100644 --- a/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java +++ b/dd-openfeature/src/main/java/com/datadog/openfeature/internal/evaluation/EvaluatorCapabilities.java @@ -8,7 +8,7 @@ public final class EvaluatorCapabilities { public static final String DEPENDENT_FLAG_VARIANT_KEY_V1 = "dependent-flag-variant-key-v1"; public static final String DEPENDENT_FLAGS_VARIANT_KEY_QUERY_PARAMETER = - "dependent-flags-variant-key"; + "cap_dependent-flags-variant-key"; public static final String DEPENDENT_FLAGS_VARIANT_KEY_QUERY_VERSION = "v1"; private EvaluatorCapabilities() {} diff --git a/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java b/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java index ee8392970d4..128ab6eef85 100644 --- a/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java +++ b/dd-openfeature/src/test/java/com/datadog/openfeature/internal/delivery/CdnConfigurationSourceTest.java @@ -185,15 +185,15 @@ void carriesEvaluatorCapabilityAsQueryParameter() { CdnConfigurationSource.endpointForCapabilities(endpoint, Set.of("unknown-v1"))); assertEquals( URI.create( - "https://example.test/ufc?token=preserved&dependent-flags-variant-key=v1"), + "https://example.test/ufc?token=preserved&cap_dependent-flags-variant-key=v1"), CdnConfigurationSource.endpointForCapabilities( endpoint, Set.of("dependent-flag-variant-key-v1"))); assertEquals( URI.create( - "https://example.test/ufc?token=preserved&dependent-flags-variant-key=v1#section"), + "https://example.test/ufc?token=preserved&cap_dependent-flags-variant-key=v1#section"), CdnConfigurationSource.endpointForCapabilities( URI.create( - "https://example.test/ufc?dependent-flags-variant-key=old&token=preserved#section"), + "https://example.test/ufc?cap_dependent-flags-variant-key=old&token=preserved#section"), Set.of("dependent-flag-variant-key-v1"))); }