diff --git a/src/main/java/dev/openfeature/sdk/Client.java b/src/main/java/dev/openfeature/sdk/Client.java index ebca0b131..4494180aa 100644 --- a/src/main/java/dev/openfeature/sdk/Client.java +++ b/src/main/java/dev/openfeature/sdk/Client.java @@ -18,7 +18,7 @@ public interface Client extends Features, EventBus { * Set the client-level evaluation context. * @param ctx Client level context. */ - void setEvaluationContext(EvaluationContext ctx); + Client setEvaluationContext(EvaluationContext ctx); /** * Adds hooks for evaluation. @@ -26,7 +26,7 @@ public interface Client extends Features, EventBus { * * @param hooks The hook to add. */ - void addHooks(Hook... hooks); + Client addHooks(Hook... hooks); /** * Fetch the hooks associated to this client. diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index b63f9b314..7de394f0a 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -87,10 +87,11 @@ public MutableContext add(String key, List value) { /** * Override or set targeting key for this mutable context. Value should be non-null and non-empty to be accepted. */ - public void setTargetingKey(String targetingKey) { + public MutableContext setTargetingKey(String targetingKey) { if (targetingKey != null && !targetingKey.trim().isEmpty()) { this.add(TARGETING_KEY, targetingKey); } + return this; } diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java index eb9d3a714..896d60a34 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.java @@ -83,10 +83,11 @@ public Client getClient(@Nullable String name, @Nullable String version) { /** * {@inheritDoc} */ - public void setEvaluationContext(EvaluationContext evaluationContext) { + public OpenFeatureAPI setEvaluationContext(EvaluationContext evaluationContext) { try (AutoCloseableLock __ = lock.writeLockAutoCloseable()) { this.evaluationContext = evaluationContext; } + return this; } /** diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index 3455c0c0e..ce763a34b 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -56,10 +56,11 @@ public OpenFeatureClient(OpenFeatureAPI openFeatureAPI, String name, String vers * {@inheritDoc} */ @Override - public void addHooks(Hook... hooks) { + public OpenFeatureClient addHooks(Hook... hooks) { try (AutoCloseableLock __ = this.hooksLock.writeLockAutoCloseable()) { this.clientHooks.addAll(Arrays.asList(hooks)); } + return this; } /** @@ -76,10 +77,11 @@ public List getHooks() { * {@inheritDoc} */ @Override - public void setEvaluationContext(EvaluationContext evaluationContext) { + public OpenFeatureClient setEvaluationContext(EvaluationContext evaluationContext) { try (AutoCloseableLock __ = contextLock.writeLockAutoCloseable()) { this.evaluationContext = evaluationContext; } + return this; } /** diff --git a/src/test/java/dev/openfeature/sdk/MutableContextTest.java b/src/test/java/dev/openfeature/sdk/MutableContextTest.java index 1d462b12c..df21e6eca 100644 --- a/src/test/java/dev/openfeature/sdk/MutableContextTest.java +++ b/src/test/java/dev/openfeature/sdk/MutableContextTest.java @@ -116,4 +116,19 @@ void mergeShouldRetainItsSubkeysWhenOverridingContextHasNoTargetingKey() { Structure value = key1.asStructure(); assertArrayEquals(new Object[]{"key1_1"}, value.keySet().toArray()); } + + @DisplayName("Ensure mutations are chainable") + @Test + void shouldAllowChainingOfMutations() { + MutableContext context = new MutableContext(); + context.add("key1", "val1") + .add("key2", 2) + .setTargetingKey("TARGETING_KEY") + .add("key3", 3.0); + + assertEquals("TARGETING_KEY", context.getTargetingKey()); + assertEquals("val1", context.getValue("key1").asString()); + assertEquals(2, context.getValue("key2").asInteger()); + assertEquals(3.0, context.getValue("key3").asDouble()); + } } diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java index 63a1dadd6..eceace2bb 100644 --- a/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java +++ b/src/test/java/dev/openfeature/sdk/OpenFeatureAPITest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Collections; +import java.util.HashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -78,4 +79,12 @@ void settingNamedClientProviderToNullErrors() { void settingTransactionalContextPropagatorToNullErrors() { assertThatCode(() -> api.setTransactionContextPropagator(null)).isInstanceOf(IllegalArgumentException.class); } + + @Test + void setEvaluationContextShouldAllowChaining() { + OpenFeatureClient client = new OpenFeatureClient(api, "name", "version"); + EvaluationContext ctx = new ImmutableContext("targeting key", new HashMap<>()); + OpenFeatureClient result = client.setEvaluationContext(ctx); + assertEquals(client, result); + } } diff --git a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java index 9036576d6..d6340a844 100644 --- a/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java +++ b/src/test/java/dev/openfeature/sdk/OpenFeatureClientTest.java @@ -3,12 +3,18 @@ import java.util.*; import dev.openfeature.sdk.fixtures.HookFixtures; + import org.junit.jupiter.api.*; import org.mockito.Mockito; import org.simplify4u.slf4jmock.LoggerMock; import org.slf4j.Logger; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; class OpenFeatureClientTest implements HookFixtures { @@ -67,4 +73,28 @@ void mergeContextTest() { assertThat(result.getValue()).isTrue(); } + + @Test + @DisplayName("addHooks should allow chaining by returning the same client instance") + void addHooksShouldAllowChaining() { + OpenFeatureAPI api = mock(OpenFeatureAPI.class); + OpenFeatureClient client = new OpenFeatureClient(api, "name", "version"); + Hook hook1 = Mockito.mock(Hook.class); + Hook hook2 = Mockito.mock(Hook.class); + + OpenFeatureClient result = client.addHooks(hook1, hook2); + assertEquals(client, result); + } + + @Test + @DisplayName("setEvaluationContext should allow chaining by returning the same client instance") + void setEvaluationContextShouldAllowChaining() { + OpenFeatureAPI api = mock(OpenFeatureAPI.class); + OpenFeatureClient client = new OpenFeatureClient(api, "name", "version"); + EvaluationContext ctx = new ImmutableContext("targeting key", new HashMap<>()); + + OpenFeatureClient result = client.setEvaluationContext(ctx); + assertEquals(client, result); + } + }