Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 4 additions & 19 deletions src/main/java/cloud/eppo/BaseEppoClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
import cloud.eppo.logging.BanditAssignment;
import cloud.eppo.logging.BanditLogger;
import cloud.eppo.ufc.dto.*;
import cloud.eppo.ufc.dto.adapters.EppoModule;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
Expand All @@ -27,12 +24,7 @@

public class BaseEppoClient {
private static final Logger log = LoggerFactory.getLogger(BaseEppoClient.class);
private final ObjectMapper mapper =
new ObjectMapper()
.registerModule(EppoModule.eppoModule()); // TODO: is this the best place for this?

protected final ConfigurationRequestor requestor;

private final IConfigurationStore configurationStore;
private final AssignmentLogger assignmentLogger;
private final BanditLogger banditLogger;
Expand Down Expand Up @@ -311,8 +303,9 @@ private boolean valueTypeMatchesExpected(VariationType expectedType, EppoValue v
case JSON:
typeMatch =
value.isString()
// Eppo leaves JSON as a JSON string; to verify it's valid we attempt to parse
&& parseJsonString(value.stringValue()) != null;
// Eppo leaves JSON as a JSON string; to verify it's valid we attempt to parse (via
// unwrapping)
&& value.unwrap(VariationType.JSON) != null;
break;
default:
throw new IllegalArgumentException("Unexpected type for type checking: " + expectedType);
Expand Down Expand Up @@ -435,7 +428,7 @@ public JsonNode getJSONAssignment(
subjectAttributes,
EppoValue.valueOf(defaultValue.toString()),
VariationType.JSON);
return parseJsonString(value.stringValue());
return value.unwrap(VariationType.JSON);
} catch (Exception e) {
return throwIfNotGraceful(e, defaultValue);
}
Expand Down Expand Up @@ -481,14 +474,6 @@ public String getJSONStringAssignment(String flagKey, String subjectKey, String
return this.getJSONStringAssignment(flagKey, subjectKey, new Attributes(), defaultValue);
}

private JsonNode parseJsonString(String jsonString) {
try {
return mapper.readTree(jsonString);
} catch (JsonProcessingException e) {
return null;
}
}

public BanditResult getBanditAction(
String flagKey,
String subjectKey,
Expand Down
35 changes: 34 additions & 1 deletion src/main/java/cloud/eppo/api/EppoValue.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cloud.eppo.api;

import cloud.eppo.ufc.dto.EppoValueType;
import cloud.eppo.ufc.dto.VariationType;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -96,6 +99,36 @@ public EppoValueType getType() {
return type;
}

/**
* Unwraps this EppoValue to the appropriate Java type based on the variation type.
*
* @param expectedType the expected variation type
* @param <T> the target type (Boolean, Integer, Double, String, or JsonNode)
* @return the unwrapped value
*/
@SuppressWarnings("unchecked")
public <T> T unwrap(VariationType expectedType) {
switch (expectedType) {
case BOOLEAN:
return (T) Boolean.valueOf(booleanValue());
case INTEGER:
return (T) Integer.valueOf(Double.valueOf(doubleValue()).intValue());
case NUMERIC:
return (T) Double.valueOf(doubleValue());
case STRING:
return (T) stringValue();
case JSON:
String jsonString = stringValue();
try {
ObjectMapper mapper = new ObjectMapper();
return (T) mapper.readTree(jsonString);
} catch (JsonProcessingException e) {
return null;
}
}
throw new IllegalArgumentException("Unknown variation type: " + expectedType);
}

@Override
public String toString() {
switch (this.type) {
Expand All @@ -106,7 +139,7 @@ public String toString() {
case STRING:
return this.stringValue;
case ARRAY_OF_STRING:
// Android21 back-compatability
// Android21 back-compatibility
return joinStringArray(this.stringArrayValue);
case NULL:
return "";
Expand Down
123 changes: 123 additions & 0 deletions src/test/java/cloud/eppo/api/EppoValueTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package cloud.eppo.api;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import cloud.eppo.ufc.dto.VariationType;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -65,4 +69,123 @@ public void testToStringConsistencyAcrossTypes() {
EppoValue arrayValue = EppoValue.valueOf(array);
assertEquals("test1, test2", arrayValue.toString());
}

@Test
public void testUnwrapBoolean() {
EppoValue boolValue = EppoValue.valueOf(true);
Boolean result = boolValue.unwrap(VariationType.BOOLEAN);
assertEquals(Boolean.TRUE, result);

EppoValue falseValue = EppoValue.valueOf(false);
Boolean falseResult = falseValue.unwrap(VariationType.BOOLEAN);
assertEquals(Boolean.FALSE, falseResult);
}

@Test
public void testUnwrapInteger() {
EppoValue numValue = EppoValue.valueOf(42.0);
Integer result = numValue.unwrap(VariationType.INTEGER);
assertEquals(Integer.valueOf(42), result);

EppoValue negativeValue = EppoValue.valueOf(-17.0);
Integer negativeResult = negativeValue.unwrap(VariationType.INTEGER);
assertEquals(Integer.valueOf(-17), negativeResult);
}

@Test
public void testUnwrapNumeric() {
EppoValue numValue = EppoValue.valueOf(123.456);
Double result = numValue.unwrap(VariationType.NUMERIC);
assertEquals(Double.valueOf(123.456), result);

EppoValue intValue = EppoValue.valueOf(100.0);
Double intResult = intValue.unwrap(VariationType.NUMERIC);
assertEquals(Double.valueOf(100.0), intResult);
}

@Test
public void testUnwrapString() {
EppoValue strValue = EppoValue.valueOf("hello world");
String result = strValue.unwrap(VariationType.STRING);
assertEquals("hello world", result);

EppoValue emptyValue = EppoValue.valueOf("");
String emptyResult = emptyValue.unwrap(VariationType.STRING);
assertEquals("", emptyResult);
}

@Test
public void testUnwrapJsonValid() {
String jsonString = "{\"foo\":\"bar\",\"count\":42}";
EppoValue jsonValue = EppoValue.valueOf(jsonString);
JsonNode result = jsonValue.unwrap(VariationType.JSON);

assertTrue(result.isObject());
assertEquals("bar", result.get("foo").asText());
assertEquals(42, result.get("count").asInt());
}

@Test
public void testUnwrapJsonArray() {
String jsonArrayString = "[1,2,3,4,5]";
EppoValue jsonValue = EppoValue.valueOf(jsonArrayString);
JsonNode result = jsonValue.unwrap(VariationType.JSON);

assertTrue(result.isArray());
assertEquals(5, result.size());
assertEquals(1, result.get(0).asInt());
assertEquals(5, result.get(4).asInt());
}

@Test
public void testUnwrapJsonWithSpecialCharacters() {
String jsonString = "{\"a\":\"kümmert\",\"b\":\"schön\"}";
EppoValue jsonValue = EppoValue.valueOf(jsonString);
JsonNode result = jsonValue.unwrap(VariationType.JSON);

assertTrue(result.isObject());
assertEquals("kümmert", result.get("a").asText());
assertEquals("schön", result.get("b").asText());
}

@Test
public void testUnwrapJsonWithEmojis() {
String jsonString = "{\"a\":\"🤗\",\"b\":\"🌸\"}";
EppoValue jsonValue = EppoValue.valueOf(jsonString);
JsonNode result = jsonValue.unwrap(VariationType.JSON);

assertTrue(result.isObject());
assertEquals("🤗", result.get("a").asText());
assertEquals("🌸", result.get("b").asText());
}

@Test
public void testUnwrapJsonWithWhitespace() {
String jsonString = "{ \"key\": \"value\", \"number\": 123 }";
EppoValue jsonValue = EppoValue.valueOf(jsonString);
JsonNode result = jsonValue.unwrap(VariationType.JSON);

assertTrue(result.isObject());
assertEquals("value", result.get("key").asText());
assertEquals(123, result.get("number").asInt());
}

@Test
public void testUnwrapJsonInvalid() {
String invalidJson = "not valid json {";
EppoValue jsonValue = EppoValue.valueOf(invalidJson);
JsonNode result = jsonValue.unwrap(VariationType.JSON);

assertNull(result, "Invalid JSON should return null");
}

@Test
public void testUnwrapJsonEmpty() {
String emptyJson = "{}";
EppoValue jsonValue = EppoValue.valueOf(emptyJson);
JsonNode result = jsonValue.unwrap(VariationType.JSON);

assertTrue(result.isObject());
assertEquals(0, result.size());
}
}