Skip to content

Commit 3882087

Browse files
authored
feat: getConfig and getFlagType (Eppo-exp#89)
* getConfig and getFlagType * doc comments and tests * chore: update doc comments
1 parent 50b51f8 commit 3882087

File tree

4 files changed

+198
-3
lines changed

4 files changed

+198
-3
lines changed

src/main/java/cloud/eppo/BaseEppoClient.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ protected EppoValue getTypedAssignment(
203203
throwIfEmptyOrNull(flagKey, "flagKey must not be empty");
204204
throwIfEmptyOrNull(subjectKey, "subjectKey must not be empty");
205205

206-
Configuration config = configurationStore.getConfiguration();
206+
Configuration config = getConfiguration();
207207

208208
FlagConfig flag = config.getFlag(flagKey);
209209
if (flag == null) {
@@ -496,7 +496,7 @@ public BanditResult getBanditAction(
496496
Actions actions,
497497
String defaultValue) {
498498
BanditResult result = new BanditResult(defaultValue, null);
499-
final Configuration config = configurationStore.getConfiguration();
499+
final Configuration config = getConfiguration();
500500
try {
501501
String assignedVariation =
502502
getStringAssignment(
@@ -579,4 +579,20 @@ private <T> T throwIfNotGraceful(Exception e, T defaultValue) {
579579
public void setIsGracefulFailureMode(boolean isGracefulFailureMode) {
580580
this.isGracefulMode = isGracefulFailureMode;
581581
}
582+
583+
/**
584+
* Returns the configuration object used by the EppoClient for assignment and bandit evaluation.
585+
*
586+
* <p>The configuration object is for debugging (inspect the loaded config) and other advanced use
587+
* cases where flag metadata or a list of flag keys, for example, is required.
588+
*
589+
* <p>It is not recommended to use the list of keys to preload assignments as assignment
590+
* computation also logs its use which will affect your metrics.
591+
*
592+
* @see <a href="https://docs.geteppo.com/sdks/best-practices/where-to-assign/">Where To
593+
* Assign</a> for more details.
594+
*/
595+
public Configuration getConfiguration() {
596+
return configurationStore.getConfiguration();
597+
}
582598
}

src/main/java/cloud/eppo/api/Configuration.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public class Configuration {
6666

6767
private final byte[] banditParamsJson;
6868

69-
private Configuration(
69+
/** Default visibility for tests. */
70+
Configuration(
7071
Map<String, FlagConfig> flags,
7172
Map<String, BanditReference> banditReferences,
7273
Map<String, BanditParameters> bandits,
@@ -121,6 +122,19 @@ public FlagConfig getFlag(String flagKey) {
121122
return flags.get(flagKeyForLookup);
122123
}
123124

125+
/**
126+
* Returns the Variation Type for the specified flag if it exists, otherwise returns null.
127+
*
128+
* @return The flag's variation type or null.
129+
*/
130+
public @Nullable VariationType getFlagType(String flagKey) {
131+
FlagConfig flag = getFlag(flagKey);
132+
if (flag == null) {
133+
return null;
134+
}
135+
return flag.getVariationType();
136+
}
137+
124138
public String banditKeyForVariation(String flagKey, String variationValue) {
125139
// Note: In practice this double loop should be quite quick as the number of bandits and bandit
126140
// variations will be small. Should this ever change, we can optimize things.

src/test/java/cloud/eppo/BaseEppoClientTest.java

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import cloud.eppo.helpers.AssignmentTestCase;
1414
import cloud.eppo.logging.Assignment;
1515
import cloud.eppo.logging.AssignmentLogger;
16+
import cloud.eppo.ufc.dto.FlagConfig;
1617
import cloud.eppo.ufc.dto.VariationType;
1718
import com.fasterxml.jackson.core.JsonProcessingException;
1819
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -655,6 +656,103 @@ public void testPolling() {
655656
eppoClient.stopPolling();
656657
}
657658

659+
@Test
660+
public void testGetConfiguration() {
661+
// Initialize client with default settings
662+
initClient();
663+
664+
// Get configuration
665+
Configuration config = eppoClient.getConfiguration();
666+
667+
// Verify configuration is not null
668+
assertNotNull(config);
669+
670+
// Verify some known flags from the test configuration
671+
assertNotNull(config.getFlag("numeric_flag"));
672+
assertEquals(VariationType.NUMERIC, config.getFlagType("numeric_flag"));
673+
674+
// Verify a non-existent flag returns null
675+
assertNull(config.getFlag("non_existent_flag"));
676+
}
677+
678+
@Test
679+
public void testGetConfigurationWithInitialConfig() {
680+
try {
681+
// Load initial configuration from file
682+
String flagConfig = FileUtils.readFileToString(initialFlagConfigFile, "UTF8");
683+
684+
// Initialize client with initial configuration
685+
initClientWithData(immediateConfigFuture(flagConfig, false), false, true);
686+
687+
// Get configuration
688+
Configuration config = eppoClient.getConfiguration();
689+
690+
// Verify configuration is not null
691+
assertNotNull(config);
692+
693+
// Verify known flag from initial configuration
694+
FlagConfig numericFlag = config.getFlag("numeric_flag");
695+
assertNotNull(numericFlag);
696+
assertEquals(VariationType.NUMERIC, numericFlag.getVariationType());
697+
698+
// verify `no_allocations_flag` DNE
699+
assertNull(config.getFlag("no_allocations_flag"));
700+
701+
// Load new configuration
702+
eppoClient.loadConfiguration();
703+
704+
// Get updated configuration
705+
Configuration updatedConfig = eppoClient.getConfiguration();
706+
707+
// Verify numeric_flag is still there.
708+
assertNotNull(updatedConfig.getFlag("numeric_flag"));
709+
// verify `no_allocations_flag` exists now
710+
assertNotNull(updatedConfig.getFlag("no_allocations_flag"));
711+
712+
} catch (IOException e) {
713+
throw new RuntimeException(e);
714+
}
715+
}
716+
717+
@Test
718+
public void testGetConfigurationBeforeInitialization() {
719+
// Create client without loading configuration
720+
mockAssignmentLogger = mock(AssignmentLogger.class);
721+
722+
eppoClient =
723+
new BaseEppoClient(
724+
DUMMY_FLAG_API_KEY,
725+
"java",
726+
"100.1.0",
727+
null,
728+
TEST_BASE_URL,
729+
mockAssignmentLogger,
730+
null,
731+
null,
732+
false,
733+
false,
734+
true,
735+
null,
736+
null,
737+
null);
738+
739+
// Get configuration before loading
740+
Configuration config = eppoClient.getConfiguration();
741+
742+
// Verify we get an empty configuration
743+
assertNotNull(config);
744+
assertTrue(config.isEmpty());
745+
746+
eppoClient.loadConfiguration();
747+
748+
// Get configuration again after loading
749+
Configuration nextConfig = eppoClient.getConfiguration();
750+
751+
// Verify we get an empty configuration
752+
assertNotNull(nextConfig);
753+
assertFalse(nextConfig.isEmpty());
754+
}
755+
658756
@SuppressWarnings("SameParameterValue")
659757
private void sleepUninterruptedly(long sleepMs) {
660758
try {

src/test/java/cloud/eppo/api/ConfigurationBuilderTest.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package cloud.eppo.api;
22

3+
import static cloud.eppo.Utils.getMD5Hex;
34
import static org.junit.jupiter.api.Assertions.*;
45

6+
import cloud.eppo.ufc.dto.FlagConfig;
57
import cloud.eppo.ufc.dto.FlagConfigResponse;
8+
import cloud.eppo.ufc.dto.VariationType;
69
import cloud.eppo.ufc.dto.adapters.EppoModule;
710
import com.fasterxml.jackson.databind.ObjectMapper;
811
import java.io.IOException;
12+
import java.util.Collections;
13+
import java.util.Map;
914
import org.junit.jupiter.api.Test;
1015

1116
public class ConfigurationBuilderTest {
@@ -52,4 +57,66 @@ public void testBuildConfigAddsForServer_false() throws IOException {
5257

5358
assertEquals(rehydratedConfig.getFormat(), FlagConfigResponse.Format.CLIENT);
5459
}
60+
61+
@Test
62+
public void getFlagType_shouldReturnCorrectType() {
63+
// Create a flag config with a STRING variation type
64+
FlagConfig flagConfig =
65+
new FlagConfig(
66+
"test-flag",
67+
true,
68+
1,
69+
VariationType.STRING,
70+
Collections.emptyMap(),
71+
Collections.emptyList());
72+
73+
// Create configuration with this flag
74+
Map<String, FlagConfig> flags = Collections.singletonMap("test-flag", flagConfig);
75+
Configuration config =
76+
new Configuration(flags, Collections.emptyMap(), Collections.emptyMap(), false, null, null);
77+
78+
// Test successful case
79+
assertEquals(VariationType.STRING, config.getFlagType("test-flag"));
80+
81+
// Test non-existent flag
82+
assertNull(config.getFlagType("non-existent-flag"));
83+
}
84+
85+
@Test
86+
public void getFlagType_withObfuscatedConfig_shouldReturnCorrectType() {
87+
// Create a flag config with a NUMERIC variation type
88+
FlagConfig flagConfig =
89+
new FlagConfig(
90+
"test-flag",
91+
true,
92+
1,
93+
VariationType.NUMERIC,
94+
Collections.emptyMap(),
95+
Collections.emptyList());
96+
97+
// Create configuration with this flag using MD5 hash of the flag key
98+
String hashedKey = getMD5Hex("test-flag");
99+
Map<String, FlagConfig> flags = Collections.singletonMap(hashedKey, flagConfig);
100+
Configuration config =
101+
new Configuration(
102+
flags,
103+
Collections.emptyMap(),
104+
Collections.emptyMap(),
105+
true, // obfuscated
106+
null,
107+
null);
108+
109+
// Test successful case with obfuscated config
110+
assertEquals(VariationType.NUMERIC, config.getFlagType("test-flag"));
111+
112+
// Test non-existent flag
113+
assertNull(config.getFlagType("non-existent-flag"));
114+
assertNull(config.getFlagType(hashedKey));
115+
}
116+
117+
@Test
118+
public void getFlagType_withEmptyConfig_shouldReturnNull() {
119+
Configuration config = Configuration.emptyConfig();
120+
assertNull(config.getFlagType("any-flag"));
121+
}
55122
}

0 commit comments

Comments
 (0)