From f8d4907c41350a4ac27beb8313e525650de7402e Mon Sep 17 00:00:00 2001 From: Roger Floriano <31597636+petruki@users.noreply.github.com> Date: Fri, 10 Oct 2025 15:05:26 -0700 Subject: [PATCH] Closes #368 - allow diff Switcher Key constant name (#369) --- README.md | 13 +++-- .../client/SwitcherContextBase.java | 23 ++++++-- src/test/java/com/switcherapi/Switchers.java | 4 ++ .../client/SwitcherContextBuilderTest.java | 6 +-- .../SwitcherContextRemoteExecutorTest.java | 4 +- .../switcherapi/client/SwitcherFail3Test.java | 54 +++++++++++++++++++ .../client/SwitcherLocal1Test.java | 11 ++++ .../SwitcherSnapshotAutoUpdateTest.java | 12 ++--- .../local/SwitcherLocalServiceTest.java | 2 +- .../utils/SnapshotWatcherContextTest.java | 2 +- .../utils/SnapshotWatcherErrorTest.java | 2 +- .../client/utils/SnapshotWatcherTest.java | 2 +- .../utils/SnapshotWatcherWorkerTest.java | 2 +- .../com/switcherapi/playground/Features.java | 2 +- src/test/resources/snapshot/fixture1.json | 8 +++ 15 files changed, 123 insertions(+), 24 deletions(-) create mode 100644 src/test/java/com/switcherapi/client/SwitcherFail3Test.java diff --git a/README.md b/README.md index 594463c..21adb6e 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Instead of using SwitcherContext, which is used to automatically load from the s ```java MyAppFeatures.configure(ContextBuilder.builder() - .contextLocation("com.switcherapi.playground.Features") + .context(Features.class.getName()) .apiKey("API_KEY") .url("https://switcher-api.com") .domain("Playground") @@ -130,8 +130,15 @@ class MySwitcherClientConfig extends SwitcherContextBase { ### Defining your features -Create a class that extends SwitcherContext if you are loading the configuration from the switcherapi.properties file. -Or use SwitcherContextBase to define the configuration using the ContextBuilder or SwitcherConfig. +Create a class that extends `SwitcherContext` if you are loading the configuration from the switcherapi.properties file.
+Or use `SwitcherContextBase` to define the configuration using the ContextBuilder or SwitcherConfig. + +Switcher Keys are defined using the @SwitcherKey annotation and must be public static final String.
+- Public because you will need to access it from other places of your code. +- Static because you will access it without instantiating the class. +- Final because the value must not be changed during runtime. + +The attribute name can be defined as you want (e.g. FEATURE01, FEATURE_01, myFeatureOne, etc), but the value must be the exact Switcher Key defined in Switcher Management or snapshot files. ```java public class MyAppFeatures extends SwitcherContext { diff --git a/src/main/java/com/switcherapi/client/SwitcherContextBase.java b/src/main/java/com/switcherapi/client/SwitcherContextBase.java index 59bbee0..2769a5f 100644 --- a/src/main/java/com/switcherapi/client/SwitcherContextBase.java +++ b/src/main/java/com/switcherapi/client/SwitcherContextBase.java @@ -65,7 +65,7 @@ * // Initialize the Switcher Client using ContextBuilder * public void configureClient() { * Features.configure(ContextBuilder.builder() - * .context("com.business.config.Features") + * .context(Features.class.getName()) * .apiKey("API_KEY") * .domain("Playground") * .component("switcher-playground") @@ -244,12 +244,22 @@ private static void registerSwitcherKeys() { * @param fields to be registered */ private static void registerSwitcherKey(Field[] fields) { - switcherKeys = Optional.ofNullable(switcherKeys).orElse(new HashSet<>()); + Set switcherKeys = new HashSet<>(); + for (Field field : fields) { if (field.isAnnotationPresent(SwitcherKey.class)) { - switcherKeys.add(field.getName()); + try { + switcherKeys.add(field.get(null).toString()); + } catch (Exception e) { + throw new SwitcherContextException( + String.format("Error retrieving Switcher Key value from field %s", field.getName())); + } } } + + if (!switcherKeys.isEmpty()) { + setSwitcherKeys(switcherKeys); + } } /** @@ -539,8 +549,13 @@ private static synchronized void setContextBase(SwitcherContextBase contextBase) SwitcherContextBase.contextBase = contextBase; } - private static synchronized void setSwitcherKeys(Set switcherKeys) { + private static synchronized void setSwitcherKeys(Set switcherKeys) + throws SwitcherContextException { SwitcherContextBase.switcherKeys = switcherKeys; + + if (switcherKeys.stream().anyMatch(StringUtils::isBlank)) { + throw new SwitcherContextException("One or more Switcher Keys are empty"); + } } } diff --git a/src/test/java/com/switcherapi/Switchers.java b/src/test/java/com/switcherapi/Switchers.java index 4109c69..9b92ff8 100644 --- a/src/test/java/com/switcherapi/Switchers.java +++ b/src/test/java/com/switcherapi/Switchers.java @@ -142,4 +142,8 @@ public class Switchers extends SwitcherContext { @SwitcherKey public static final String NOT_FOUND_KEY = "NOT_FOUND_KEY"; + + @SwitcherKey + public static final String friendlyFeatureName = "USECASE1"; + } diff --git a/src/test/java/com/switcherapi/client/SwitcherContextBuilderTest.java b/src/test/java/com/switcherapi/client/SwitcherContextBuilderTest.java index 6b799a3..c7a46f1 100644 --- a/src/test/java/com/switcherapi/client/SwitcherContextBuilderTest.java +++ b/src/test/java/com/switcherapi/client/SwitcherContextBuilderTest.java @@ -21,7 +21,7 @@ class SwitcherContextBuilderTest { void shouldReturnSuccess() { //given configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .url("http://localhost:3000") .apiKey("API_KEY") .domain("switcher-domain") @@ -41,7 +41,7 @@ void shouldReturnSuccess() { void shouldReturnError_snapshotNotLoaded() { //given configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .url("http://localhost:3000") .apiKey("API_KEY") .domain("switcher-domain") @@ -59,7 +59,7 @@ void shouldReturnError_snapshotNotLoaded() { void shouldThrowError_wrongContextKeyTypeUsage() { //given configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .domain("switcher-domain") .snapshotLocation(SNAPSHOTS_LOCAL) .local(true)); diff --git a/src/test/java/com/switcherapi/client/SwitcherContextRemoteExecutorTest.java b/src/test/java/com/switcherapi/client/SwitcherContextRemoteExecutorTest.java index 8a50881..32c46e9 100644 --- a/src/test/java/com/switcherapi/client/SwitcherContextRemoteExecutorTest.java +++ b/src/test/java/com/switcherapi/client/SwitcherContextRemoteExecutorTest.java @@ -35,7 +35,7 @@ void shouldConfigureRemotePoolSize() { //given SwitchersBase.configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("API_KEY") .domain("switcher-domain") @@ -61,7 +61,7 @@ void shouldConfigureRemoteTimeout() { //given SwitchersBase.configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("API_KEY") .domain("switcher-domain") diff --git a/src/test/java/com/switcherapi/client/SwitcherFail3Test.java b/src/test/java/com/switcherapi/client/SwitcherFail3Test.java new file mode 100644 index 0000000..9f846f0 --- /dev/null +++ b/src/test/java/com/switcherapi/client/SwitcherFail3Test.java @@ -0,0 +1,54 @@ +package com.switcherapi.client; + +import com.switcherapi.client.exception.SwitcherContextException; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; + +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class SwitcherFail3Test { + + private static final String SNAPSHOTS_LOCAL = Paths.get(StringUtils.EMPTY).toAbsolutePath() + "/src/test/resources"; + + @Test + void shouldNotRegisterSwitcher_nullKey() { + //given + TestCaseNull.configure(ContextBuilder.builder() + .context(TestCaseNull.class.getName()) + .snapshotLocation(SNAPSHOTS_LOCAL) + .local(true)); + + //test + Exception exception = assertThrows(SwitcherContextException.class, TestCaseNull::initializeClient); + assertEquals("Something went wrong: Context has errors - Error retrieving Switcher Key value from field NULL_KEY", + exception.getMessage()); + } + + @Test + void shouldNotRegisterSwitcher_emptyKey() { + //given + TestCaseEmpty.configure(ContextBuilder.builder() + .context(TestCaseEmpty.class.getName()) + .snapshotLocation(SNAPSHOTS_LOCAL) + .local(true)); + + //test + Exception exception = assertThrows(SwitcherContextException.class, TestCaseEmpty::initializeClient); + assertEquals("Something went wrong: Context has errors - One or more Switcher Keys are empty", + exception.getMessage()); + } + + static class TestCaseNull extends SwitcherContextBase { + @SwitcherKey + public static final String NULL_KEY = null; + } + + static class TestCaseEmpty extends SwitcherContextBase { + @SwitcherKey + public static final String EMPTY_KEY = ""; + } + +} diff --git a/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java b/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java index 9c5f184..1e83778 100644 --- a/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java +++ b/src/test/java/com/switcherapi/client/SwitcherLocal1Test.java @@ -57,6 +57,17 @@ void localShouldReturnTrue() { // check result history assertTrue(switcher.getLastExecutionResult().isItOn()); } + + @Test + void localShouldReturnTrueUsingFriendlyConstantName() { + SwitcherRequest switcher = Switchers.getSwitcher(Switchers.friendlyFeatureName, true); + + assertNull(switcher.getLastExecutionResult()); + assertTrue(switcher.isItOn()); + + // check result history + assertTrue(switcher.getLastExecutionResult().isItOn()); + } @Test void localShouldReturnFalse() { diff --git a/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java b/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java index 1423a2f..c8a5f6f 100644 --- a/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java +++ b/src/test/java/com/switcherapi/client/SwitcherSnapshotAutoUpdateTest.java @@ -85,7 +85,7 @@ void shouldUpdateSnapshot_local() { //that Switchers.configure(ContextBuilder.builder(true) - .context(Switchers.class.getCanonicalName()) + .context(Switchers.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("[API_KEY]") .snapshotLocation(SNAPSHOTS_LOCAL) @@ -110,7 +110,7 @@ void shouldUpdateSnapshot_remote() { //that Switchers.configure(ContextBuilder.builder(true) - .context(Switchers.class.getCanonicalName()) + .context(Switchers.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("[API_KEY]") .domain("Test") @@ -137,7 +137,7 @@ void shouldNotUpdateSnapshot_whenNoUpdateAvailable() { //that Switchers.configure(ContextBuilder.builder(true) - .context(Switchers.class.getCanonicalName()) + .context(Switchers.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("[API_KEY]") .snapshotLocation(SNAPSHOTS_LOCAL) @@ -165,7 +165,7 @@ void shouldUpdateSnapshot_remote_inMemory() { //that Switchers.configure(ContextBuilder.builder(true) - .context(Switchers.class.getCanonicalName()) + .context(Switchers.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("[API_KEY]") .environment("generated_mock_default_5") @@ -192,7 +192,7 @@ void shouldNotKillThread_whenAPI_wentLocal() { //that Switchers.configure(ContextBuilder.builder(true) - .context(Switchers.class.getCanonicalName()) + .context(Switchers.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("[API_KEY]") .environment("generated_mock_default_6") @@ -227,7 +227,7 @@ void shouldRestartSnapshotAutoUpdate_whenAlreadySetup() { //that Switchers.configure(ContextBuilder.builder(true) - .context(Switchers.class.getCanonicalName()) + .context(Switchers.class.getName()) .url(String.format("http://localhost:%s", mockBackEnd.getPort())) .apiKey("[API_KEY]") .environment("generated_mock_default_6") diff --git a/src/test/java/com/switcherapi/client/service/local/SwitcherLocalServiceTest.java b/src/test/java/com/switcherapi/client/service/local/SwitcherLocalServiceTest.java index fbe07c3..a6a55be 100644 --- a/src/test/java/com/switcherapi/client/service/local/SwitcherLocalServiceTest.java +++ b/src/test/java/com/switcherapi/client/service/local/SwitcherLocalServiceTest.java @@ -37,7 +37,7 @@ class SwitcherLocalServiceTest { static void init() { executorService = Executors.newSingleThreadExecutor(); SwitchersBase.configure(ContextBuilder.builder() - .context("com.switcherapi.SwitchersBase") + .context(SwitchersBase.class.getName()) .snapshotLocation(SNAPSHOTS_LOCAL) .environment("default") .local(true)); diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherContextTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherContextTest.java index 10f57d0..463e9b2 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherContextTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherContextTest.java @@ -19,7 +19,7 @@ static void setupContext() throws IOException { generateFixture(); SwitchersBase.configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .environment("generated_watcher_default") .snapshotLocation(SNAPSHOTS_LOCAL) .snapshotWatcher(true) diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherErrorTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherErrorTest.java index 286fb4f..1b00ab1 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherErrorTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherErrorTest.java @@ -14,7 +14,7 @@ class SnapshotWatcherErrorTest { void shouldNotWatchSnapshotWhenRemote() { //given SwitchersBase.configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .url("https://api.switcherapi.com") .apiKey("[API_KEY]") .domain("Test") diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java index 7a04c6a..b145f79 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherTest.java @@ -22,7 +22,7 @@ static void setupContext() throws IOException { generateFixture(); SwitchersBase.configure(ContextBuilder.builder() - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .environment("generated_watcher_default") .snapshotLocation(SNAPSHOTS_LOCAL) .local(true)); diff --git a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java index 011afb3..c17b40a 100644 --- a/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java +++ b/src/test/java/com/switcherapi/client/utils/SnapshotWatcherWorkerTest.java @@ -11,7 +11,7 @@ class SnapshotWatcherWorkerTest extends SnapshotTest { @BeforeAll static void setupContext() { SwitchersBase.configure(ContextBuilder.builder(true) - .context(SwitchersBase.class.getCanonicalName()) + .context(SwitchersBase.class.getName()) .snapshotLocation(SNAPSHOTS_LOCAL) .environment("default") .local(true)); diff --git a/src/test/java/com/switcherapi/playground/Features.java b/src/test/java/com/switcherapi/playground/Features.java index 2fe36ca..a2aa417 100644 --- a/src/test/java/com/switcherapi/playground/Features.java +++ b/src/test/java/com/switcherapi/playground/Features.java @@ -12,7 +12,7 @@ public class Features extends SwitcherContextBase { @Override protected void configureClient() { configure(ContextBuilder.builder() - .context(Features.class.getCanonicalName()) + .context(Features.class.getName()) .url("https://api.switcherapi.com") .apiKey(System.getenv("switcher.api.key")) .component(System.getenv("switcher.component")) diff --git a/src/test/resources/snapshot/fixture1.json b/src/test/resources/snapshot/fixture1.json index 7212a52..abafff9 100644 --- a/src/test/resources/snapshot/fixture1.json +++ b/src/test/resources/snapshot/fixture1.json @@ -10,6 +10,14 @@ "description": "Simple test", "activated": true, "config": [ + { + "key": "USECASE1", + "description": "Simple test - Config enabled", + "activated": true, + "components": [ + "switcher-client" + ] + }, { "key": "USECASE11", "description": "Simple test - Config enabled",