diff --git a/pom.xml b/pom.xml index d355560..d839f8d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ cloud.eppo eppo-server-sdk - 2.0.2 + 2.1.0 ${project.groupId}:${project.artifactId} Eppo Server-Side SDK for Java diff --git a/src/main/java/com/eppo/sdk/EppoClient.java b/src/main/java/com/eppo/sdk/EppoClient.java index fc5a4f0..db146a2 100644 --- a/src/main/java/com/eppo/sdk/EppoClient.java +++ b/src/main/java/com/eppo/sdk/EppoClient.java @@ -22,6 +22,7 @@ import com.eppo.sdk.helpers.InputValidator; import com.eppo.sdk.helpers.RuleValidator; import com.eppo.sdk.helpers.Shard; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; @@ -60,8 +61,7 @@ private EppoClient(ConfigurationStore configurationStore, Timer poller, EppoClie private Optional getAssignmentValue( String subjectKey, String flagKey, - SubjectAttributes subjectAttributes - ) { + SubjectAttributes subjectAttributes) { // Validate Input Values InputValidator.validateNotBlank(subjectKey, "Invalid argument: subjectKey cannot be blank"); InputValidator.validateNotBlank(flagKey, "Invalid argument: flagKey cannot be blank"); @@ -142,6 +142,8 @@ private Optional getTypedAssignment(String subjectKey, String experimentKey, return Optional.of(value.get().boolValue()); case NUMBER: return Optional.of(value.get().doubleValue()); + case JSON_NODE: + return Optional.of(value.get().jsonNodeValue()); default: return Optional.of(value.get().stringValue()); } @@ -258,7 +260,7 @@ public Optional getDoubleAssignment(String subjectKey, String experiment * @param subjectAttributes * @return */ - public Optional getJSONAssignment(String subjectKey, String experimentKey, + public Optional getJSONStringAssignment(String subjectKey, String experimentKey, SubjectAttributes subjectAttributes) { return this.getStringAssignment(subjectKey, experimentKey, subjectAttributes); } @@ -271,8 +273,34 @@ public Optional getJSONAssignment(String subjectKey, String experimentKe * @param experimentKey * @return */ - public Optional getJSONAssignment(String subjectKey, String experimentKey) { - return this.getJSONAssignment(subjectKey, experimentKey, new SubjectAttributes()); + public Optional getJSONStringAssignment(String subjectKey, String experimentKey) { + return this.getJSONStringAssignment(subjectKey, experimentKey, new SubjectAttributes()); + } + + /** + * This function will return JSON assignment value + * + * @param subjectKey + * @param experimentKey + * @param subjectAttributes + * @return + */ + public Optional getParsedJSONAssignment(String subjectKey, String experimentKey, + SubjectAttributes subjectAttributes) { + return (Optional) this.getTypedAssignment(subjectKey, experimentKey, EppoValueType.JSON_NODE, + subjectAttributes); + } + + /** + * This function will return JSON assignment value without passing + * subjectAttributes + * + * @param subjectKey + * @param experimentKey + * @return + */ + public Optional getParsedJSONAssignment(String subjectKey, String experimentKey) { + return this.getParsedJSONAssignment(subjectKey, experimentKey, new SubjectAttributes()); } /** diff --git a/src/test/java/com/eppo/sdk/EppoClientTest.java b/src/test/java/com/eppo/sdk/EppoClientTest.java index 3243b55..0000ebf 100644 --- a/src/test/java/com/eppo/sdk/EppoClientTest.java +++ b/src/test/java/com/eppo/sdk/EppoClientTest.java @@ -11,7 +11,6 @@ import com.eppo.sdk.dto.*; import com.eppo.sdk.helpers.Converter; -import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -31,7 +30,6 @@ import lombok.Data; - @ExtendWith(WireMockExtension.class) public class EppoClientTest { @@ -120,15 +118,15 @@ public AssignmentValueType deserialize(JsonParser jsonParser, DeserializationCon void init() { setupMockRacServer(); EppoClientConfig config = EppoClientConfig.builder() - .apiKey("mock-api-key") - .baseURL("http://localhost:4001") - .assignmentLogger(new IAssignmentLogger() { - @Override - public void logAssignment(AssignmentLogData logData) { - // Auto-generated method stub - } - }) - .build(); + .apiKey("mock-api-key") + .baseURL("http://localhost:4001") + .assignmentLogger(new IAssignmentLogger() { + @Override + public void logAssignment(AssignmentLogData logData) { + // Auto-generated method stub + } + }) + .build(); EppoClient.init(config); } @@ -136,7 +134,8 @@ private void setupMockRacServer() { this.mockServer = new WireMockServer(TEST_PORT); this.mockServer.start(); String racResponseJson = getMockRandomizedAssignmentResponse(); - this.mockServer.stubFor(WireMock.get(WireMock.urlMatching(".*randomized_assignment.*")).willReturn(WireMock.okJson(racResponseJson))); + this.mockServer.stubFor( + WireMock.get(WireMock.urlMatching(".*randomized_assignment.*")).willReturn(WireMock.okJson(racResponseJson))); } @AfterEach @@ -159,8 +158,14 @@ void testAssignments(AssignmentTestCase testCase) throws IOException { assertEquals(expectedBooleanAssignments, actualBooleanAssignments); break; case JSON: - List actualJSONAssignments = this.getJSONAssignments(testCase); - assertEquals(testCase.expectedAssignments, actualJSONAssignments); + List actualJSONAssignments = this.getJSONAssignments(testCase); + for (JsonNode node : actualJSONAssignments) { + assertEquals(JsonNodeType.OBJECT, node.getNodeType()); + } + + assertEquals(testCase.expectedAssignments, actualJSONAssignments.stream() + .map(node -> node.toString()) + .collect(Collectors.toList())); break; default: List actualStringAssignments = this.getStringAssignments(testCase); @@ -173,48 +178,49 @@ private List getAssignments(AssignmentTestCase testCase, AssignmentValueType EppoClient client = EppoClient.getInstance(); if (testCase.subjectsWithAttributes != null) { return testCase.subjectsWithAttributes.stream() + .map(subject -> { + try { + switch (valueType) { + case NUMERIC: + return client.getDoubleAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) + .orElse(null); + case BOOLEAN: + return client.getBooleanAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) + .orElse(null); + case JSON: + return client + .getParsedJSONAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) + .orElse(null); + default: + return client.getStringAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) + .orElse(null); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } + return testCase.subjects.stream() .map(subject -> { try { switch (valueType) { case NUMERIC: - return client.getDoubleAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) - .orElse(null); + return client.getDoubleAssignment(subject, testCase.experiment) + .orElse(null); case BOOLEAN: - return client.getBooleanAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) - .orElse(null); + return client.getBooleanAssignment(subject, testCase.experiment) + .orElse(null); case JSON: - return client.getJSONAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) - .orElse(null); + return client.getParsedJSONAssignment(subject, testCase.experiment) + .orElse(null); default: - return client.getStringAssignment(subject.subjectKey, testCase.experiment, subject.subjectAttributes) - .orElse(null); + return client.getStringAssignment(subject, testCase.experiment) + .orElse(null); } } catch (Exception e) { throw new RuntimeException(e); } }).collect(Collectors.toList()); - } - return testCase.subjects.stream() - .map(subject -> { - try { - switch (valueType) { - case NUMERIC: - return client.getDoubleAssignment(subject, testCase.experiment) - .orElse(null); - case BOOLEAN: - return client.getBooleanAssignment(subject, testCase.experiment) - .orElse(null); - case JSON: - return client.getJSONAssignment(subject, testCase.experiment) - .orElse(null); - default: - return client.getStringAssignment(subject, testCase.experiment) - .orElse(null); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }).collect(Collectors.toList()); } private List getStringAssignments(AssignmentTestCase testCase) { @@ -229,8 +235,8 @@ private List getBooleanAssignments(AssignmentTestCase testCase) { return (List) this.getAssignments(testCase, AssignmentValueType.BOOLEAN); } - private List getJSONAssignments(AssignmentTestCase testCase) { - return (List) this.getAssignments(testCase, AssignmentValueType.JSON); + private List getJSONAssignments(AssignmentTestCase testCase) { + return (List) this.getAssignments(testCase, AssignmentValueType.JSON); } private static Stream getAssignmentTestData() throws IOException { @@ -248,7 +254,7 @@ private static Stream getAssignmentTestData() throws IOException { private static String getMockRandomizedAssignmentResponse() { File mockRacResponse = new File("src/test/resources/rac-experiments-v3.json"); try { - return FileUtils.readFileToString(mockRacResponse, "UTF8"); + return FileUtils.readFileToString(mockRacResponse, "UTF8"); } catch (Exception e) { throw new RuntimeException("Error reading mock RAC data", e); }