diff --git a/CHANGES.txt b/CHANGES.txt index 4a60a24..0cd9583 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,2 +1,4 @@ 1.0.0 -- First release. Up to date with spec v0.1.0 +- First release. Up to date with java sdk v0.1.0 +1.1.0 +- Up tp date with spec v0.5.0 and java sdk v0.3.1 diff --git a/README.md b/README.md index 9d8fa1d..19beb5e 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,20 @@ This Provider is designed to allow the use of OpenFeature with Split, the platfo This SDK is compatible with Java 11 and higher. ## Getting started +### Add it to your maven build +```java + + io.split.openfeature + split-openfeature-provider + 1.1.0 + +``` +### Configure it Below is a simple example that describes the instantiation of the Split Provider. Please see the [OpenFeature Documentation](https://docs.openfeature.dev/docs/reference/concepts/evaluation-api) for details on how to use the OpenFeature SDK. ```java -import dev.openfeature.javasdk.OpenFeatureAPI; -import io.split.openfeature.SplitProvider +import dev.openfeature.sdk.OpenFeatureAPI; +import io.split.openfeature.SplitProvider; OpenFeatureAPI api = OpenFeatureAPI.getInstance(); api.setProvider(new SplitProvider("YOUR_API_KEY")); @@ -20,8 +29,8 @@ api.setProvider(new SplitProvider("YOUR_API_KEY")); If you are more familiar with Split or want access to other initialization options, you can provide a `SplitClient` to the constructor. See the [Split Java SDK Documentation](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK) for more information. ```java -import dev.openfeature.javasdk.OpenFeatureAPI; -import io.split.openfeature.SplitProvider +import dev.openfeature.sdk.OpenFeatureAPI; +import io.split.openfeature.SplitProvider; import io.split.client.SplitClient; import io.split.client.SplitClientConfig; import io.split.client.SplitFactoryBuilder; @@ -43,18 +52,18 @@ One important note is that the Split Provider **requires a targeting key** to be ```java Client client = api.getClient("CLIENT_NAME"); -EvaluationContext context = new EvaluationContext("TARGETING_KEY"); +EvaluationContext context = new MutableContext("TARGETING_KEY"); Boolean boolValue = client.getBooleanValue("boolFlag", false, context); ``` If the same targeting key is used repeatedly, the evaluation context may be set at the client level ```java -EvaluationContext context = new EvaluationContext("TARGETING_KEY"); +EvaluationContext context = new MutableContext("TARGETING_KEY"); client.setEvaluationContext(context) ``` or at the OpenFeatureAPI level ```java -EvaluationContext context = new EvaluationContext("TARGETING_KEY"); -OpenFeatureAPI.getInstance().setCtx(context) +EvaluationContext context = new MutableContext("TARGETING_KEY"); +OpenFeatureAPI.getInstance().setEvaluationContext(context) ```` If the context was set at the client or api level, it is not required to provide it during flag evaluation. diff --git a/pom.xml b/pom.xml index 77c5449..66c81c7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.split.openfeature split-openfeature-provider - 1.0.0 + 1.1.0 split-openfeature-provider-java Split OpenFeature Java Provider @@ -74,9 +74,8 @@ dev.openfeature - javasdk - - 0.1.0 + sdk + 0.3.1 diff --git a/src/main/java/io/split/openfeature/SplitModule.java b/src/main/java/io/split/openfeature/SplitModule.java index 066caf2..fad7fa8 100644 --- a/src/main/java/io/split/openfeature/SplitModule.java +++ b/src/main/java/io/split/openfeature/SplitModule.java @@ -1,6 +1,6 @@ package io.split.openfeature; -import dev.openfeature.javasdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.GeneralError; import io.split.client.SplitClient; import io.split.client.SplitClientConfig; import io.split.client.SplitFactory; diff --git a/src/main/java/io/split/openfeature/SplitProvider.java b/src/main/java/io/split/openfeature/SplitProvider.java index 1b41cd5..c2d1437 100644 --- a/src/main/java/io/split/openfeature/SplitProvider.java +++ b/src/main/java/io/split/openfeature/SplitProvider.java @@ -1,20 +1,20 @@ package io.split.openfeature; -import dev.openfeature.javasdk.ErrorCode; -import dev.openfeature.javasdk.EvaluationContext; -import dev.openfeature.javasdk.FeatureProvider; -import dev.openfeature.javasdk.Metadata; -import dev.openfeature.javasdk.ProviderEvaluation; -import dev.openfeature.javasdk.Reason; -import dev.openfeature.javasdk.Structure; -import dev.openfeature.javasdk.Value; -import dev.openfeature.javasdk.exceptions.GeneralError; -import dev.openfeature.javasdk.exceptions.OpenFeatureError; -import dev.openfeature.javasdk.exceptions.ParseError; +import dev.openfeature.sdk.ErrorCode; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.FeatureProvider; +import dev.openfeature.sdk.Metadata; +import dev.openfeature.sdk.MutableStructure; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Value; +import dev.openfeature.sdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.OpenFeatureError; +import dev.openfeature.sdk.exceptions.ParseError; +import dev.openfeature.sdk.exceptions.TargetingKeyMissingError; import io.split.client.SplitClient; import io.split.openfeature.utils.Serialization; - -import java.time.ZonedDateTime; +import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.List; import java.util.Map; @@ -48,7 +48,7 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa try { String evaluated = evaluateTreatment(key, evaluationContext); if (noTreatment(evaluated)) { - return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND.name()); + return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND); } // if treatment is "on" or "true" we treat that as true // if it is "off" or "false" we treat it as false @@ -59,7 +59,7 @@ public ProviderEvaluation getBooleanEvaluation(String key, Boolean defa } else if (evaluated.equalsIgnoreCase("false") || evaluated.equals("off")) { value = false; } else { - throw new ParseError(ErrorCode.PARSE_ERROR.name()); + throw new ParseError(); } return constructProviderEvaluation(value, evaluated); } catch (OpenFeatureError e) { @@ -75,7 +75,7 @@ public ProviderEvaluation getStringEvaluation(String key, String default try { String evaluated = evaluateTreatment(key, evaluationContext); if (noTreatment(evaluated)) { - return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND.name()); + return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND); } return constructProviderEvaluation(evaluated, evaluated); } catch (OpenFeatureError e) { @@ -90,14 +90,14 @@ public ProviderEvaluation getIntegerEvaluation(String key, Integer defa try { String evaluated = evaluateTreatment(key, evaluationContext); if (noTreatment(evaluated)) { - return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND.name()); + return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND); } Integer value = Integer.valueOf(evaluated); return constructProviderEvaluation(value, evaluated); } catch (OpenFeatureError e) { throw e; } catch (NumberFormatException e) { - throw new ParseError(ErrorCode.PARSE_ERROR.name()); + throw new ParseError(); } catch (Exception e) { throw new GeneralError("Error getting Integer evaluation", e); } @@ -108,29 +108,29 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double default try { String evaluated = evaluateTreatment(key, evaluationContext); if (noTreatment(evaluated)) { - return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND.name()); + return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND); } Double value = Double.valueOf(evaluated); return constructProviderEvaluation(value, evaluated); } catch (OpenFeatureError e) { throw e; } catch (NumberFormatException e) { - throw new ParseError(ErrorCode.PARSE_ERROR.name()); + throw new ParseError(); } catch (Exception e) { throw new GeneralError("Error getting Double evaluation", e); } } @Override - public ProviderEvaluation getObjectEvaluation(String key, Structure defaultTreatment, EvaluationContext evaluationContext) { + public ProviderEvaluation getObjectEvaluation(String key, Value defaultTreatment, EvaluationContext evaluationContext) { try { String evaluated = evaluateTreatment(key, evaluationContext); if (noTreatment(evaluated)) { - return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND.name()); + return constructProviderEvaluation(defaultTreatment, evaluated, Reason.DEFAULT, ErrorCode.FLAG_NOT_FOUND); } Map rawMap = Serialization.stringToMap(evaluated); - Structure structure = mapToStructure(rawMap); - return constructProviderEvaluation(structure, evaluated); + Value value = mapToValue(rawMap); + return constructProviderEvaluation(value, evaluated); } catch (OpenFeatureError e) { throw e; } catch (Exception e) { @@ -139,14 +139,14 @@ public ProviderEvaluation getObjectEvaluation(String key, Structure d } public Map transformContext(EvaluationContext context) { - return getMapFromStructMap(context.asMap()); + return context.asObjectMap(); } private String evaluateTreatment(String key, EvaluationContext evaluationContext) { String id = evaluationContext.getTargetingKey(); if (id == null || id.isEmpty()) { // targeting key is always required - throw new GeneralError("TARGETING_KEY_MISSING"); + throw new TargetingKeyMissingError(); } Map attributes = transformContext(evaluationContext); return client.getTreatment(id, key, attributes); @@ -160,87 +160,47 @@ private ProviderEvaluation constructProviderEvaluation(T value, String va return constructProviderEvaluation(value, variant, Reason.TARGETING_MATCH, null); } - private ProviderEvaluation constructProviderEvaluation(T value, String variant, Reason reason, String errorCode) { + private ProviderEvaluation constructProviderEvaluation(T value, String variant, Reason reason, ErrorCode errorCode) { ProviderEvaluation.ProviderEvaluationBuilder builder = ProviderEvaluation.builder(); return builder .value(value) - .reason(reason) + .reason(reason.name()) .variant(variant) .errorCode(errorCode) .build(); } - private Map getMapFromStructMap(Map structMap) { - return structMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> getInnerValue(e.getValue()))); - } - - private Structure mapToStructure(Map map) { - return new Structure( - map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue())))); + /** + * Turn map String->Object into a Value. + * @param map a Map String->Object, where object is NOT Value or Structure + * @return Value representing the map passed in + */ + private Value mapToValue(Map map) { + return new Value( + new MutableStructure( + map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> objectToValue(e.getValue()))))); } - private Object getInnerValue(Value value) { - Object object = value.asBoolean(); - if (object != null) { - return object; - } - object = value.asDouble(); - if (object != null) { - return object; - } - object = value.asInteger(); - if (object != null) { - return object; - } - object = value.asString(); - if (object != null) { - return object; - } - object = value.asZonedDateTime(); - if (object != null) { - return object; - } - object = value.asStructure(); - if (object != null) { - // must return a map - return getMapFromStructMap(((Structure) object).asMap()); - } - object = value.asList(); - if (object != null) { - // must return a list of inner objects - List values = (List) object; - return values.stream().map(this::getInnerValue).collect(Collectors.toList()); - } - throw new ClassCastException("Could not get inner value from Value object."); - } private Value objectToValue(Object object) { - if (object instanceof Value) { - return (Value) object; - } else if (object instanceof String) { - // try to parse to zoned date time, otherwise use as string + if (object instanceof String) { + // try to parse as instant, otherwise use as string try { - return new Value(ZonedDateTime.parse((String) object)); + return new Value(Instant.parse((String) object)); } catch (DateTimeParseException e) { return new Value((String) object); } - } else if (object instanceof Boolean) { - return new Value((Boolean) object); - } else if (object instanceof Integer) { - return new Value((Integer) object); - } else if (object instanceof Double) { - return new Value((Double) object); - } else if (object instanceof Structure) { - return new Value((Structure) object); } else if (object instanceof List) { // need to translate each elem in list to a value return new Value(((List) object).stream().map(this::objectToValue).collect(Collectors.toList())); - } else if (object instanceof ZonedDateTime) { - return new Value((ZonedDateTime) object); } else if (object instanceof Map) { - return new Value(mapToStructure((Map) object)); + return mapToValue((Map) object); } else { - throw new ClassCastException("Could not cast Object to Value"); + try { + return new Value(object); + } catch (InstantiationException e) { + throw new ClassCastException("Could not cast Object to Value"); + } } } } diff --git a/src/main/java/io/split/openfeature/utils/Serialization.java b/src/main/java/io/split/openfeature/utils/Serialization.java index 0c687e4..d3f23f6 100644 --- a/src/main/java/io/split/openfeature/utils/Serialization.java +++ b/src/main/java/io/split/openfeature/utils/Serialization.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import dev.openfeature.javasdk.ErrorCode; -import dev.openfeature.javasdk.exceptions.ParseError; +import dev.openfeature.sdk.ErrorCode; +import dev.openfeature.sdk.exceptions.ParseError; import java.util.Map; diff --git a/src/test/java/io/split/openfeature/ClientTest.java b/src/test/java/io/split/openfeature/ClientTest.java index 9bbd322..88a0f63 100644 --- a/src/test/java/io/split/openfeature/ClientTest.java +++ b/src/test/java/io/split/openfeature/ClientTest.java @@ -1,13 +1,14 @@ package io.split.openfeature; -import dev.openfeature.javasdk.Client; -import dev.openfeature.javasdk.ErrorCode; -import dev.openfeature.javasdk.EvaluationContext; -import dev.openfeature.javasdk.FlagEvaluationDetails; -import dev.openfeature.javasdk.OpenFeatureAPI; -import dev.openfeature.javasdk.Reason; -import dev.openfeature.javasdk.Structure; -import dev.openfeature.javasdk.Value; +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.ErrorCode; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.FlagEvaluationDetails; +import dev.openfeature.sdk.MutableContext; +import dev.openfeature.sdk.MutableStructure; +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Reason; +import dev.openfeature.sdk.Value; import io.split.client.SplitClient; import io.split.client.SplitClientConfig; import io.split.client.SplitFactoryBuilder; @@ -41,7 +42,7 @@ public void init() { System.out.println("Unexpected Exception occurred initializing Split Provider."); } client = openFeatureAPI.getClient("Split Client"); - EvaluationContext evaluationContext = new EvaluationContext(); + EvaluationContext evaluationContext = new MutableContext(); String targetingKey = "key"; evaluationContext.setTargetingKey(targetingKey); client.setEvaluationContext(evaluationContext); @@ -65,18 +66,18 @@ public void useDefaultTest() { Integer resultInt = client.getIntegerValue(flagName, defaultInt); assertEquals(defaultInt, resultInt); - Structure defaultStructure = new Structure(Map.of("foo", new Value("bar"))); - Structure resultStructure = client.getObjectValue(flagName, defaultStructure); + Value defaultStructure = mapToValue(Map.of("foo", new Value("bar"))); + Value resultStructure = client.getObjectValue(flagName, defaultStructure); assertEquals(defaultStructure, resultStructure); } @Test public void missingTargetingKeyTest() { // Split requires a targeting key and should return the default treatment and throw an error if not provided - client.setEvaluationContext(new EvaluationContext()); + client.setEvaluationContext(new MutableContext()); FlagEvaluationDetails details = client.getBooleanDetails("non-existent-feature", false); assertFalse(details.getValue()); - assertEquals("TARGETING_KEY_MISSING", details.getErrorCode()); + assertEquals(ErrorCode.TARGETING_KEY_MISSING, details.getErrorCode()); } @Test @@ -87,7 +88,7 @@ public void getControlVariantNonExistentSplit() { FlagEvaluationDetails details = client.getBooleanDetails("non-existent-feature", false); assertFalse(details.getValue()); assertEquals("control", details.getVariant()); - assertEquals(ErrorCode.FLAG_NOT_FOUND.name(), details.getErrorCode()); + assertEquals(ErrorCode.FLAG_NOT_FOUND, details.getErrorCode()); } @Test @@ -104,8 +105,8 @@ public void getBooleanSplitWithKeyTest() { assertTrue(result); // if we override the evaluation context for this check to use a different key, - // this should take priority and therefore we should receive a treatment of off - EvaluationContext evaluationContext = new EvaluationContext(); + // this should take priority, and therefore we should receive a treatment of off + EvaluationContext evaluationContext = new MutableContext(); evaluationContext.setTargetingKey("randomKey"); result = client.getBooleanValue("my_feature", true, evaluationContext); assertFalse(result); @@ -125,8 +126,8 @@ public void getIntegerSplitTest() { @Test public void getObjectSplitTest() { - Structure result = client.getObjectValue("obj_feature", new Structure()); - assertEquals(new Structure(Map.of("key", new Value("value"))), result); + Value result = client.getObjectValue("obj_feature", new Value()); + assertEquals(mapToValue(Map.of("key", new Value("value"))), result); } @Test @@ -145,7 +146,7 @@ public void getMetadataNameTest() { public void getBooleanDetailsTest() { FlagEvaluationDetails details = client.getBooleanDetails("some_other_feature", true); assertEquals("some_other_feature", details.getFlagKey()); - assertEquals(Reason.TARGETING_MATCH, details.getReason()); + assertEquals(Reason.TARGETING_MATCH.name(), details.getReason()); assertFalse(details.getValue()); // the flag has a treatment of "off", this is returned as a value of false but the variant is still "off" assertEquals("off", details.getVariant()); @@ -156,7 +157,7 @@ public void getBooleanDetailsTest() { public void getIntegerDetailsTest() { FlagEvaluationDetails details = client.getIntegerDetails("int_feature", 0); assertEquals("int_feature", details.getFlagKey()); - assertEquals(Reason.TARGETING_MATCH, details.getReason()); + assertEquals(Reason.TARGETING_MATCH.name(), details.getReason()); assertEquals(32, details.getValue()); // the flag has a treatment of "32", this is resolved to an integer but the variant is still "32" assertEquals("32", details.getVariant()); @@ -167,7 +168,7 @@ public void getIntegerDetailsTest() { public void getStringDetailsTest() { FlagEvaluationDetails details = client.getStringDetails("some_other_feature", "blah"); assertEquals("some_other_feature", details.getFlagKey()); - assertEquals(Reason.TARGETING_MATCH, details.getReason()); + assertEquals(Reason.TARGETING_MATCH.name(), details.getReason()); assertEquals("off", details.getValue()); // the flag has a treatment of "off", since this is a string the variant is the same as the value assertEquals("off", details.getVariant()); @@ -176,10 +177,10 @@ public void getStringDetailsTest() { @Test public void getObjectDetailsTest() { - FlagEvaluationDetails details = client.getObjectDetails("obj_feature", new Structure()); + FlagEvaluationDetails details = client.getObjectDetails("obj_feature", new Value()); assertEquals("obj_feature", details.getFlagKey()); - assertEquals(Reason.TARGETING_MATCH, details.getReason()); - assertEquals(new Structure(Map.of("key", new Value("value"))), details.getValue()); + assertEquals(Reason.TARGETING_MATCH.name(), details.getReason()); + assertEquals(mapToValue(Map.of("key", new Value("value"))), details.getValue()); // the flag's treatment is stored as a string, and the variant is that raw string assertEquals("{\"key\": \"value\"}", details.getVariant()); assertNull(details.getErrorCode()); @@ -189,7 +190,7 @@ public void getObjectDetailsTest() { public void getDoubleDetailsTest() { FlagEvaluationDetails details = client.getDoubleDetails("int_feature", 0D); assertEquals("int_feature", details.getFlagKey()); - assertEquals(Reason.TARGETING_MATCH, details.getReason()); + assertEquals(Reason.TARGETING_MATCH.name(), details.getReason()); assertEquals(32D, details.getValue()); // the flag has a treatment of "32", this is resolved to a double but the variant is still "32" assertEquals("32", details.getVariant()); @@ -198,14 +199,14 @@ public void getDoubleDetailsTest() { @Test public void getBooleanFailTest() { - // attempt to fetch an object treatment as a boolean. Should result in the default + // attempt to fetch an object treatment as a Boolean. Should result in the default Boolean value = client.getBooleanValue("obj_feature", false); assertFalse(value); FlagEvaluationDetails details = client.getBooleanDetails("obj_feature", false); assertFalse(details.getValue()); - assertEquals(ErrorCode.PARSE_ERROR.name(), details.getErrorCode()); - assertEquals(Reason.ERROR, details.getReason()); + assertEquals(ErrorCode.PARSE_ERROR, details.getErrorCode()); + assertEquals(Reason.ERROR.name(), details.getReason()); assertNull(details.getVariant()); } @@ -217,8 +218,8 @@ public void getIntegerFailTest() { FlagEvaluationDetails details = client.getIntegerDetails("obj_feature", 10); assertEquals(10, details.getValue()); - assertEquals(ErrorCode.PARSE_ERROR.name(), details.getErrorCode()); - assertEquals(Reason.ERROR, details.getReason()); + assertEquals(ErrorCode.PARSE_ERROR, details.getErrorCode()); + assertEquals(Reason.ERROR.name(), details.getReason()); assertNull(details.getVariant()); } @@ -230,22 +231,26 @@ public void getDoubleFailTest() { FlagEvaluationDetails details = client.getDoubleDetails("obj_feature", 10D); assertEquals(10D, details.getValue()); - assertEquals(ErrorCode.PARSE_ERROR.name(), details.getErrorCode()); - assertEquals(Reason.ERROR, details.getReason()); + assertEquals(ErrorCode.PARSE_ERROR, details.getErrorCode()); + assertEquals(Reason.ERROR.name(), details.getReason()); assertNull(details.getVariant()); } @Test public void getObjectFailTest() { // attempt to fetch an int as an object. Should result in the default - Structure defaultStruct = new Structure(Map.of("foo", new Value("bar"))); - Structure value = client.getObjectValue("int_feature", defaultStruct); - assertEquals(defaultStruct, value); - - FlagEvaluationDetails details = client.getObjectDetails("int_feature", defaultStruct); - assertEquals(defaultStruct, details.getValue()); - assertEquals(ErrorCode.PARSE_ERROR.name(), details.getErrorCode()); - assertEquals(Reason.ERROR, details.getReason()); + Value defaultValue = mapToValue(Map.of("foo", new Value("bar"))); + Value value = client.getObjectValue("int_feature", defaultValue); + assertEquals(defaultValue, value); + + FlagEvaluationDetails details = client.getObjectDetails("int_feature", defaultValue); + assertEquals(defaultValue, details.getValue()); + assertEquals(ErrorCode.PARSE_ERROR, details.getErrorCode()); + assertEquals(Reason.ERROR.name(), details.getReason()); assertNull(details.getVariant()); } + + private Value mapToValue(Map map) { + return new Value(new MutableStructure(map)); + } } diff --git a/src/test/java/io/split/openfeature/SplitProviderTest.java b/src/test/java/io/split/openfeature/SplitProviderTest.java index 7e6ce40..64ff453 100644 --- a/src/test/java/io/split/openfeature/SplitProviderTest.java +++ b/src/test/java/io/split/openfeature/SplitProviderTest.java @@ -1,20 +1,20 @@ package io.split.openfeature; -import dev.openfeature.javasdk.ErrorCode; -import dev.openfeature.javasdk.EvaluationContext; -import dev.openfeature.javasdk.ProviderEvaluation; -import dev.openfeature.javasdk.Structure; -import dev.openfeature.javasdk.Value; -import dev.openfeature.javasdk.exceptions.GeneralError; -import dev.openfeature.javasdk.exceptions.OpenFeatureError; +import dev.openfeature.sdk.ErrorCode; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.MutableContext; +import dev.openfeature.sdk.MutableStructure; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.Value; +import dev.openfeature.sdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.OpenFeatureError; import io.split.client.SplitClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.time.ZoneId; -import java.time.ZonedDateTime; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -31,18 +31,18 @@ public class SplitProviderTest { EvaluationContext evaluationContext; String key; + @Mock + private SplitClient mockSplitClient; + @BeforeEach private void init() { MockitoAnnotations.openMocks(this); key = "key"; - evaluationContext = new EvaluationContext(); + evaluationContext = new MutableContext(); evaluationContext.setTargetingKey(key); } - @Mock - private SplitClient mockSplitClient; - @Test public void shouldFailWithBadApiKeyTest() { String apiKey = "someKey"; @@ -155,9 +155,9 @@ public void evalBooleanErrorTest() { when(mockSplitClient.getTreatment(eq(key), eq(flagName), anyMap())).thenReturn("a random string"); try { splitProvider.getBooleanEvaluation(flagName, false, evaluationContext); - fail("Should have thrown an exception casting string to integer"); + fail("Should have thrown an exception casting string to boolean"); } catch (OpenFeatureError e) { - assertEquals(ErrorCode.PARSE_ERROR.name(), e.getMessage()); + assertEquals(ErrorCode.PARSE_ERROR, e.getErrorCode()); } catch (Exception e) { fail("Unexpected exception occurred", e); } @@ -276,7 +276,7 @@ public void evalIntErrorTest() { splitProvider.getIntegerEvaluation(flagName, 10, evaluationContext); fail("Should have thrown an exception casting string to integer"); } catch (OpenFeatureError e) { - assertEquals(ErrorCode.PARSE_ERROR.name(), e.getMessage()); + assertEquals(ErrorCode.PARSE_ERROR, e.getErrorCode()); } catch (Exception e) { fail("Unexpected exception occurred", e); } @@ -346,7 +346,7 @@ public void evalDoubleErrorTest() { splitProvider.getDoubleEvaluation(flagName, 10D, evaluationContext); fail("Should have thrown an exception casting string to integer"); } catch (OpenFeatureError e) { - assertEquals(ErrorCode.PARSE_ERROR.name(), e.getMessage()); + assertEquals(ErrorCode.PARSE_ERROR, e.getErrorCode()); } catch (Exception e) { fail("Unexpected exception occurred", e); } @@ -360,11 +360,11 @@ public void evalStructureNullEmptyTest() { SplitProvider splitProvider = new SplitProvider(mockSplitClient); String flagName = "flagName"; - Structure defaultTreatment = new Structure(Map.of("foo", new Value("bar"))); + Value defaultTreatment = mapToValue(Map.of("foo", new Value("bar"))); when(mockSplitClient.getTreatment(eq(key), eq(flagName), anyMap())).thenReturn(null); - ProviderEvaluation response = splitProvider.getObjectEvaluation(flagName, defaultTreatment, evaluationContext); + ProviderEvaluation response = splitProvider.getObjectEvaluation(flagName, defaultTreatment, evaluationContext); assertEquals(defaultTreatment, response.getValue()); when(mockSplitClient.getTreatment(eq(key), eq(flagName), anyMap())).thenReturn(""); @@ -379,11 +379,11 @@ public void evalStructureControlTest() { SplitProvider splitProvider = new SplitProvider(mockSplitClient); String flagName = "flagName"; - Structure defaultTreatment = new Structure(Map.of("foo", new Value("bar"))); + Value defaultTreatment = mapToValue(Map.of("foo", new Value("bar"))); when(mockSplitClient.getTreatment(eq(key), eq(flagName), anyMap())).thenReturn("control"); - ProviderEvaluation response = splitProvider.getObjectEvaluation(flagName, defaultTreatment, evaluationContext); + ProviderEvaluation response = splitProvider.getObjectEvaluation(flagName, defaultTreatment, evaluationContext); assertEquals(defaultTreatment, response.getValue()); } @@ -393,13 +393,13 @@ public void evalStructureRegularTest() { SplitProvider splitProvider = new SplitProvider(mockSplitClient); String flagName = "flagName"; - Structure treatment = new Structure(Map.of("abc", new Value("def"))); + Value treatment = mapToValue(Map.of("abc", new Value("def"))); String treatmentAsString = "{\"abc\":\"def\"}"; when(mockSplitClient.getTreatment(eq(key), eq(flagName), anyMap())).thenReturn(treatmentAsString); - ProviderEvaluation response = - splitProvider.getObjectEvaluation(flagName, new Structure(Map.of("foo", new Value("bar"))), evaluationContext); + ProviderEvaluation response = + splitProvider.getObjectEvaluation(flagName, mapToValue(Map.of("foo", new Value("bar"))), evaluationContext); assertEquals(treatment, response.getValue()); } @@ -409,39 +409,35 @@ public void evalStructureComplexTest() { SplitProvider splitProvider = new SplitProvider(mockSplitClient); String flagName = "flagName"; - ZonedDateTime zonedDateTime = ZonedDateTime.of(2020, 1, 10, 0, 0, 0, 0, ZoneId.of("UTC")); - Structure treatment = new Structure(Map.of( + Instant instant = Instant.ofEpochMilli(1665698754828L); + Value treatment = mapToValue(Map.of( "string", new Value("blah"), "int", new Value(10), "double", new Value(100D), "bool", new Value(true), - "struct", new Value( - new Structure(Map.of( + "struct", mapToValue(Map.of( "foo", new Value("bar"), "baz", new Value(10), - "innerMap", new Value( - new Structure(Map.of( - "aa", new Value("bb"))))))), + "innerMap", mapToValue(Map.of( + "aa", new Value("bb"))))), "list", new Value( List.of( new Value(1), new Value(true), - new Value( - new Structure(Map.of( + mapToValue(Map.of( "cc", new Value("dd") - ))), - new Value( - new Structure(Map.of( + )), + mapToValue(Map.of( "ee", new Value(1) - ))))), - "dateTime", new Value(zonedDateTime) + )))), + "dateTime", new Value(instant) )); - String treatmentAsString = "{\"string\":\"blah\",\"int\":10,\"double\":100.0,\"bool\":true, \"struct\":{\"foo\":\"bar\",\"baz\":10,\"innerMap\":{\"aa\":\"bb\"}},\"list\":[1,true,{\"cc\":\"dd\"},{\"ee\":1}],\"dateTime\":\"2020-01-10T00:00Z[UTC]\"}"; + String treatmentAsString = "{\"string\":\"blah\",\"int\":10,\"double\":100.0,\"bool\":true, \"struct\":{\"foo\":\"bar\",\"baz\":10,\"innerMap\":{\"aa\":\"bb\"}},\"list\":[1,true,{\"cc\":\"dd\"},{\"ee\":1}],\"dateTime\":\"2022-10-13T22:05:54.828Z\"}"; when(mockSplitClient.getTreatment(eq(key), eq(flagName), anyMap())).thenReturn(treatmentAsString); - ProviderEvaluation response = - splitProvider.getObjectEvaluation(flagName, new Structure(Map.of("foo", new Value("bar"))), evaluationContext); + ProviderEvaluation response = + splitProvider.getObjectEvaluation(flagName, mapToValue(Map.of("foo", new Value("bar"))), evaluationContext); assertEquals(treatment, response.getValue()); } @@ -456,7 +452,7 @@ public void evalStructureErrorTest() { when(mockSplitClient.getTreatment(eq(key), eq(flagName), anyMap())).thenReturn(treatment); try { - splitProvider.getObjectEvaluation(flagName, new Structure(Map.of("foo", new Value("bar"))), evaluationContext); + splitProvider.getObjectEvaluation(flagName, mapToValue(Map.of("foo", new Value("bar"))), evaluationContext); fail("Should have thrown an exception casting string to an object"); } catch (OpenFeatureError e) { assertEquals(ErrorCode.PARSE_ERROR.name(), e.getMessage()); @@ -464,4 +460,8 @@ public void evalStructureErrorTest() { fail("Unexpected exception occurred", e); } } + + private Value mapToValue(Map map) { + return new Value(new MutableStructure(map)); + } }