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..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 @@ -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,39 @@ 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 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) { 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..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 @@ -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 = + "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 32dbf46dba4..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 @@ -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,28 @@ 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&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&cap_dependent-flags-variant-key=v1#section"), + CdnConfigurationSource.endpointForCapabilities( + URI.create( + "https://example.test/ufc?cap_dependent-flags-variant-key=old&token=preserved#section"), + Set.of("dependent-flag-variant-key-v1"))); + } + @Test void appliesJsonApiConfiguration() { this.server.enqueue(200, CONFIG, "ETag", "\"v1\"");