diff --git a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java index 409b85ddd..db7961436 100644 --- a/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java +++ b/providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolver.java @@ -26,7 +26,8 @@ import static dev.openfeature.contrib.providers.flagd.resolver.process.model.FeatureFlag.EMPTY_TARGETING_STRING; /** - * flagd in-process resolver. Resolves feature flags in-process. Flags are retrieved from {@link Storage}, where the + * flagd in-process resolver. Resolves feature flags in-process. Flags are + * retrieved from {@link Storage}, where the * {@link Storage} maintain flag configurations obtained from known source. */ @Slf4j @@ -97,7 +98,7 @@ public void shutdown() throws InterruptedException { * Resolve a boolean flag. */ public ProviderEvaluation booleanEvaluation(String key, Boolean defaultValue, - EvaluationContext ctx) { + EvaluationContext ctx) { return resolve(Boolean.class, key, ctx); } @@ -105,7 +106,7 @@ public ProviderEvaluation booleanEvaluation(String key, Boolean default * Resolve a string flag. */ public ProviderEvaluation stringEvaluation(String key, String defaultValue, - EvaluationContext ctx) { + EvaluationContext ctx) { return resolve(String.class, key, ctx); } @@ -113,7 +114,7 @@ public ProviderEvaluation stringEvaluation(String key, String defaultVal * Resolve a double flag. */ public ProviderEvaluation doubleEvaluation(String key, Double defaultValue, - EvaluationContext ctx) { + EvaluationContext ctx) { return resolve(Double.class, key, ctx); } @@ -121,7 +122,7 @@ public ProviderEvaluation doubleEvaluation(String key, Double defaultVal * Resolve an integer flag. */ public ProviderEvaluation integerEvaluation(String key, Integer defaultValue, - EvaluationContext ctx) { + EvaluationContext ctx) { return resolve(Integer.class, key, ctx); } @@ -142,7 +143,7 @@ public ProviderEvaluation objectEvaluation(String key, Value defaultValue } private ProviderEvaluation resolve(Class type, String key, - EvaluationContext ctx) { + EvaluationContext ctx) { final FeatureFlag flag = flagStore.getFlag(key); // missing flag @@ -155,7 +156,6 @@ private ProviderEvaluation resolve(Class type, String key, throw new FlagNotFoundError("flag: " + key + " is disabled"); } - final Object resolvedVariant; final String reason; @@ -186,7 +186,13 @@ private ProviderEvaluation resolve(Class type, String key, log.debug(message); throw new TypeMismatchError(message); } - + if (value instanceof Integer && type == Double.class) { + // if this is an integer and we are trying to resolve a double, convert + value = ((Integer) value).doubleValue(); + } else if (value instanceof Double && type == Integer.class) { + // if this is a double and we are trying to resolve an integer, convert + value = ((Double) value).intValue(); + } if (!type.isAssignableFrom(value.getClass()) || !(resolvedVariant instanceof String)) { String message = "returning default variant for flagKey: %s, type not valid"; log.debug(String.format(message, key)); diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/StepDefinitions.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/StepDefinitions.java index ae426db1a..ad11540a4 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/StepDefinitions.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/steps/StepDefinitions.java @@ -460,16 +460,15 @@ public void the_resolved_boolean_zero_value_should_be(String expected) { // float/double value @When("a zero-value float flag with key {string} is evaluated with default value {double}") public void a_zero_value_float_flag_with_key_is_evaluated_with_default_value(String flagKey, Double defaultValue) { - // TODO: There is a bug here with 0 value floats - // this.doubleFlagKey = flagKey; - // this.doubleFlagDefaultValue = defaultValue; + this.doubleFlagKey = flagKey; + this.doubleFlagDefaultValue = defaultValue; } @Then("the resolved float zero-value should be {double}") public void the_resolved_float_zero_value_should_be(Double expected) { - // FlagEvaluationDetails details = - // client.getDoubleDetails("float-zero-flag", this.doubleFlagDefaultValue); - // assertEquals(expected, details.getValue()); + FlagEvaluationDetails details = + client.getDoubleDetails("float-zero-flag", this.doubleFlagDefaultValue); + assertEquals(expected, details.getValue()); } // integer value diff --git a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java index fc668b1a5..73a91c162 100644 --- a/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java +++ b/providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/InProcessResolverTest.java @@ -47,8 +47,8 @@ public void eventHandling() throws Throwable { final BlockingQueue sender = new LinkedBlockingQueue<>(5); final BlockingQueue receiver = new LinkedBlockingQueue<>(5); - InProcessResolver inProcessResolver = - getInProcessResolverWth(new MockStorage(new HashMap<>(), sender), providerState -> { + InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(new HashMap<>(), sender), + providerState -> { receiver.offer(providerState); }); @@ -56,7 +56,8 @@ public void eventHandling() throws Throwable { Thread initThread = new Thread(() -> { try { inProcessResolver.init(); - } catch (Exception e) {} + } catch (Exception e) { + } }); initThread.start(); if (!sender.offer(StorageState.OK, 100, TimeUnit.MILLISECONDS)) { @@ -86,8 +87,8 @@ public void simpleBooleanResolving() throws Exception { }); // when - ProviderEvaluation providerEvaluation = - inProcessResolver.booleanEvaluation("booleanFlag", false, new ImmutableContext()); + ProviderEvaluation providerEvaluation = inProcessResolver.booleanEvaluation("booleanFlag", false, + new ImmutableContext()); // then assertEquals(true, providerEvaluation.getValue()); @@ -105,8 +106,8 @@ public void simpleDoubleResolving() throws Exception { }); // when - ProviderEvaluation providerEvaluation = - inProcessResolver.doubleEvaluation("doubleFlag", 0d, new ImmutableContext()); + ProviderEvaluation providerEvaluation = inProcessResolver.doubleEvaluation("doubleFlag", 0d, + new ImmutableContext()); // then assertEquals(3.141d, providerEvaluation.getValue()); @@ -114,6 +115,44 @@ public void simpleDoubleResolving() throws Exception { assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); } + @Test + public void fetchIntegerAsDouble() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("doubleFlag", DOUBLE_FLAG); + + InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> { + }); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.integerEvaluation("doubleFlag", 0, + new ImmutableContext()); + + // then + assertEquals(3, providerEvaluation.getValue()); + assertEquals("one", providerEvaluation.getVariant()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); + } + + @Test + public void fetchDoubleAsInt() throws Exception { + // given + final Map flagMap = new HashMap<>(); + flagMap.put("integerFlag", INT_FLAG); + + InProcessResolver inProcessResolver = getInProcessResolverWth(new MockStorage(flagMap), providerState -> { + }); + + // when + ProviderEvaluation providerEvaluation = inProcessResolver.doubleEvaluation("integerFlag", 0d, + new ImmutableContext()); + + // then + assertEquals(1d, providerEvaluation.getValue()); + assertEquals("one", providerEvaluation.getVariant()); + assertEquals(Reason.STATIC.toString(), providerEvaluation.getReason()); + } + @Test public void simpleIntResolving() throws Exception { // given @@ -124,8 +163,8 @@ public void simpleIntResolving() throws Exception { }); // when - ProviderEvaluation providerEvaluation = - inProcessResolver.integerEvaluation("integerFlag", 0, new ImmutableContext()); + ProviderEvaluation providerEvaluation = inProcessResolver.integerEvaluation("integerFlag", 0, + new ImmutableContext()); // then assertEquals(1, providerEvaluation.getValue()); @@ -147,8 +186,8 @@ public void simpleObjectResolving() throws Exception { typeDefault.put("date", "01.01.1990"); // when - ProviderEvaluation providerEvaluation = - inProcessResolver.objectEvaluation("objectFlag", Value.objectToValue(typeDefault), new ImmutableContext()); + ProviderEvaluation providerEvaluation = inProcessResolver.objectEvaluation("objectFlag", + Value.objectToValue(typeDefault), new ImmutableContext()); // then Value value = providerEvaluation.getValue(); @@ -230,9 +269,8 @@ public void targetingMatchedEvaluationFlag() throws Exception { }); // when - ProviderEvaluation providerEvaluation = - inProcessResolver.stringEvaluation("stringFlag", "loopAlg", - new MutableContext().add("email", "abc@faas.com")); + ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation("stringFlag", "loopAlg", + new MutableContext().add("email", "abc@faas.com")); // then assertEquals("binetAlg", providerEvaluation.getValue()); @@ -250,9 +288,8 @@ public void targetingUnmatchedEvaluationFlag() throws Exception { }); // when - ProviderEvaluation providerEvaluation = - inProcessResolver.stringEvaluation("stringFlag", "loopAlg", - new MutableContext().add("email", "abc@abc.com")); + ProviderEvaluation providerEvaluation = inProcessResolver.stringEvaluation("stringFlag", "loopAlg", + new MutableContext().add("email", "abc@abc.com")); // then assertEquals("loopAlg", providerEvaluation.getValue()); @@ -275,13 +312,13 @@ public void targetingErrorEvaluationFlag() throws Exception { }); } - private InProcessResolver getInProcessResolverWth(final MockStorage storage, Consumer stateConsumer) throws NoSuchFieldException, IllegalAccessException { Field flagStore = InProcessResolver.class.getDeclaredField("flagStore"); flagStore.setAccessible(true); - InProcessResolver resolver = new InProcessResolver(FlagdOptions.builder().deadline(1000).build(), stateConsumer); + InProcessResolver resolver = new InProcessResolver(FlagdOptions.builder().deadline(1000).build(), + stateConsumer); flagStore.set(resolver, storage); return resolver;