From cf389fc513726367a5b8b1aafca10bc79dd727ea Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 15 Mar 2023 00:48:01 -0700 Subject: [PATCH 01/14] Start to an inline evaluating provider based on jsonlogic.com Signed-off-by: Justin Abrahms --- pom.xml | 1 + .../inlineeval/InlineEvaluatingProvider.java | 74 +++++++++++++++++++ .../providers/inlineeval/RuleFetcher.java | 20 +++++ providers/inline-evaluating-provider/pom.xml | 42 +++++++++++ .../providers/inlineeval/ExampleFetcher.java | 23 ++++++ .../InlineEvaluatingProviderTest.java | 71 ++++++++++++++++++ release-please-config.json | 10 +++ 7 files changed, 241 insertions(+) create mode 100644 providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java create mode 100644 providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java create mode 100644 providers/inline-evaluating-provider/pom.xml create mode 100644 providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java create mode 100644 providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java diff --git a/pom.xml b/pom.xml index f635ee0ac..04ff62aad 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ providers/flagd providers/flagsmith providers/go-feature-flag + providers/inline-evaluating-provider providers/env-var diff --git a/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java b/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java new file mode 100644 index 000000000..e3d19df62 --- /dev/null +++ b/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java @@ -0,0 +1,74 @@ +package dev.openfeature.contrib.providers.inlineeval; + +import dev.openfeature.sdk.*; +import io.github.jamsesso.jsonlogic.JsonLogic; +import io.github.jamsesso.jsonlogic.JsonLogicException; + +import java.util.function.Function; + +public class InlineEvaluatingProvider implements FeatureProvider { + private final JsonLogic logic; + private final RuleFetcher fetcher; + + void initialize(EvaluationContext initialContext) { + fetcher.initialize(initialContext); + } + + public InlineEvaluatingProvider(JsonLogic logic, RuleFetcher fetcher) { + this.logic = logic; + this.fetcher = fetcher; + } + + @Override + public Metadata getMetadata() { + return null; + } + + @Override + public ProviderEvaluation getBooleanEvaluation(String key, Boolean _default, EvaluationContext ctx) { + return evalRuleForKey(key, _default, ctx); + } + + @Override + public ProviderEvaluation getStringEvaluation(String key, String _default, EvaluationContext ctx) { + return evalRuleForKey(key, _default, ctx); + } + + @Override + public ProviderEvaluation getIntegerEvaluation(String key, Integer _default, EvaluationContext ctx) { + // jsonlogic only returns doubles, not integers. + return evalRuleForKey(key, _default, ctx, (o) -> ((Double) o).intValue()); + } + + @Override + public ProviderEvaluation getDoubleEvaluation(String key, Double _default, EvaluationContext ctx) { + return evalRuleForKey(key, _default, ctx); + } + + @Override + public ProviderEvaluation getObjectEvaluation(String s, Value value, EvaluationContext evaluationContext) { + // we can't use the common implementation because we need to convert to-and-from Value objects. + throw new UnsupportedOperationException("Haven't gotten there yet."); + } + + private ProviderEvaluation evalRuleForKey(String key, T _default, EvaluationContext ctx) { + return evalRuleForKey(key, _default, ctx, (o) -> (T) o); + } + private ProviderEvaluation evalRuleForKey(String key, T _default, EvaluationContext ctx, Function resultToType) { + var rule = fetcher.getRuleForKey(key); + if (rule == null) { + return ProviderEvaluation.builder() + .value(_default) + .reason("Unable to find rules for the given key") + .build(); + } + + try { + return ProviderEvaluation.builder() + .value(resultToType.apply(this.logic.apply(rule, ctx.asObjectMap()))) + .build(); + } catch (JsonLogicException e) { + throw new RuntimeException(e); + } + } +} diff --git a/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java b/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java new file mode 100644 index 000000000..fe685cbce --- /dev/null +++ b/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java @@ -0,0 +1,20 @@ +package dev.openfeature.contrib.providers.inlineeval; + +import dev.openfeature.sdk.EvaluationContext; + +public interface RuleFetcher { + + /** + * Called to set up the client initially. This is used to pre-fetch initial data as well as setup mechanisms to stay up + * to date. + * @param initialContext application context known thus far + */ + void initialize(EvaluationContext initialContext); + + /** + * Given a key name, return the JSONLogic rules for it + * @param key The key to fetch logic for + * @return json logic rules or null + */ + String getRuleForKey(String key); +} diff --git a/providers/inline-evaluating-provider/pom.xml b/providers/inline-evaluating-provider/pom.xml new file mode 100644 index 000000000..47ed62ffc --- /dev/null +++ b/providers/inline-evaluating-provider/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + dev.openfeature.contrib + parent + 0.1.0 + ../../pom.xml + + + dev.openfeature.contrib.providers + inline-evaluating-provider + 0.0.1 + + inline-evaluating-provider + Allows for evaluating rules on the client without synchronous calls to a backend + https://openfeature.dev + + + + justinabrahms + Justin Abrahms + OpenFeature + https://openfeature.dev/ + + + + + + io.github.jamsesso + json-logic-java + 1.0.7 + + + + + + + + + + \ No newline at end of file diff --git a/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java b/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java new file mode 100644 index 000000000..5fcb6a69b --- /dev/null +++ b/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java @@ -0,0 +1,23 @@ +package dev.openfeature.contrib.providers.inlineeval; + +import dev.openfeature.sdk.EvaluationContext; + +public class ExampleFetcher implements RuleFetcher { + + @Override + public String getRuleForKey(String key) { + // In a real version of this, these rules should be cached. + return """ + {"if": [ + {"or": [ + {"in": [{"var": "userId"}, [1,2,3,4]]}, + {"in": [{"var": "category"}, ["pies", "cakes"]]}, + ]}, + true, + false + ]} + """; + } + + @Override public void initialize(EvaluationContext initialContext) {} +} diff --git a/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java b/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java new file mode 100644 index 000000000..3ac4dfaa3 --- /dev/null +++ b/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java @@ -0,0 +1,71 @@ +package dev.openfeature.contrib.providers.inlineeval; + +import dev.openfeature.sdk.ImmutableContext; +import dev.openfeature.sdk.Value; +import io.github.jamsesso.jsonlogic.JsonLogic; +import io.github.jamsesso.jsonlogic.JsonLogicException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + +import java.util.Map; + +class InlineEvaluatingProviderTest { + @Test + public void demonstrateJsonLogic() throws JsonLogicException { + // if specific id matches or category is in valid set, yes. Otherwise, no. + var rule = """ + {"if": [ + {"or": [ + {"in": [{"var": "userId"}, [1,2,3,4]]}, + {"in": [{"var": "category"}, ["pies", "cakes"]]}, + ]}, + true, + false + ]} + """; + var logic = new JsonLogic(); + assertEquals(false, logic.apply(rule, null)); + assertEquals(true, logic.apply(rule, Map.of("userId", 2))); + assertEquals(false, logic.apply(rule, Map.of("userId", 5))); + assertEquals(true, logic.apply(rule, Map.of("category", "pies"))); + assertEquals(false, logic.apply(rule, Map.of("category", "muffins"))); + } + + + @Test + public void jsonlogicReturnTypes() throws JsonLogicException { + // if specific id matches or category is in valid set, yes. Otherwise, no. + var rule = """ + {"if": [{"var": "bool"}, true, + {"if": [{"var": "string"}, "yes", + {"if": [{"var": "double"}, 4.2, + 2 + ]} + ]} + ]} + """; + var logic = new JsonLogic(); + assertEquals(2D, logic.apply(rule, null)); + assertEquals(4.2D, logic.apply(rule, Map.of("double", true))); + assertEquals("yes", logic.apply(rule, Map.of("string", true))); + assertEquals(true, logic.apply(rule, Map.of("bool", "true"))); + } + + @Test public void providerTest() { + var iep = new InlineEvaluatingProvider(new JsonLogic(), new ExampleFetcher()); + var evalCtx = new ImmutableContext(Map.of("userId", new Value(2))); + + var result = iep.getBooleanEvaluation("test-key", false, evalCtx); + assertTrue(result.getValue(), result.getReason()); + } + + @Test public void callsFetcherInitialize() { + var mockFetcher = mock(RuleFetcher.class); + var iep = new InlineEvaluatingProvider(new JsonLogic(), mockFetcher); + iep.initialize(null); + verify(mockFetcher).initialize(any()); + } +} \ No newline at end of file diff --git a/release-please-config.json b/release-please-config.json index c2f27753d..e8531d33d 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -34,6 +34,16 @@ "pom.xml" ] }, + "providers/inline-evaluating-provider": { + "package-name": "dev.openfeature.contrib.providers.inlineeval", + "release-type": "simple", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "versioning": "default", + "extra-files": [ + "pom.xml" + ] + }, "providers/env-var": { "package-name": "dev.openfeature.contrib.providers.env-var", "release-type": "simple", From 37fb1179dbdbab973bbaee231f5c76a16784dc36 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Mon, 20 Mar 2023 15:16:28 -0700 Subject: [PATCH 02/14] File based fetcher, fix file structure Signed-off-by: Justin Abrahms --- providers/inline-evaluating-provider/pom.xml | 5 ++ .../inlineeval/FileBasedFetcher.java | 26 +++++++ .../inlineeval/InlineEvaluatingProvider.java | 3 +- .../providers/inlineeval/RuleFetcher.java | 0 .../InlineEvaluatingProviderTest.java | 71 +++++++++++++++++++ .../src/test/resources/dessert-decider.json | 8 +++ .../src/test/resources/many-types.json | 7 ++ .../src/test/resources/test-rules.json | 16 +++++ .../providers/inlineeval/ExampleFetcher.java | 23 ------ .../InlineEvaluatingProviderTest.java | 71 ------------------- 10 files changed, 135 insertions(+), 95 deletions(-) create mode 100644 providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/FileBasedFetcher.java rename providers/inline-evaluating-provider/{ => src}/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java (95%) rename providers/inline-evaluating-provider/{ => src}/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java (100%) create mode 100644 providers/inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java create mode 100644 providers/inline-evaluating-provider/src/test/resources/dessert-decider.json create mode 100644 providers/inline-evaluating-provider/src/test/resources/many-types.json create mode 100644 providers/inline-evaluating-provider/src/test/resources/test-rules.json delete mode 100644 providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java delete mode 100644 providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java diff --git a/providers/inline-evaluating-provider/pom.xml b/providers/inline-evaluating-provider/pom.xml index 47ed62ffc..473421245 100644 --- a/providers/inline-evaluating-provider/pom.xml +++ b/providers/inline-evaluating-provider/pom.xml @@ -31,6 +31,11 @@ json-logic-java 1.0.7 + + org.json + json + 20230227 + diff --git a/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/FileBasedFetcher.java b/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/FileBasedFetcher.java new file mode 100644 index 000000000..92b2d3fbc --- /dev/null +++ b/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/FileBasedFetcher.java @@ -0,0 +1,26 @@ +package dev.openfeature.contrib.providers.inlineeval; + +import dev.openfeature.sdk.EvaluationContext; +import lombok.SneakyThrows; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class FileBasedFetcher implements RuleFetcher { + JSONObject rules; + + public FileBasedFetcher(URI filename) throws IOException { + String jsonData = String.join("", Files.readAllLines(Paths.get(filename))); + rules = new JSONObject(jsonData); + } + + @Override + public String getRuleForKey(String key) { + return rules.getJSONObject(key).toString(); + } + + @Override public void initialize(EvaluationContext initialContext) {} +} diff --git a/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java b/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java similarity index 95% rename from providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java rename to providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java index e3d19df62..d26288d7c 100644 --- a/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java +++ b/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java @@ -49,13 +49,14 @@ public ProviderEvaluation getDoubleEvaluation(String key, Double _defaul public ProviderEvaluation getObjectEvaluation(String s, Value value, EvaluationContext evaluationContext) { // we can't use the common implementation because we need to convert to-and-from Value objects. throw new UnsupportedOperationException("Haven't gotten there yet."); +// return evalRuleForKey(key, _default, ctx, (o) -> o); } private ProviderEvaluation evalRuleForKey(String key, T _default, EvaluationContext ctx) { return evalRuleForKey(key, _default, ctx, (o) -> (T) o); } private ProviderEvaluation evalRuleForKey(String key, T _default, EvaluationContext ctx, Function resultToType) { - var rule = fetcher.getRuleForKey(key); + String rule = fetcher.getRuleForKey(key); if (rule == null) { return ProviderEvaluation.builder() .value(_default) diff --git a/providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java b/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java similarity index 100% rename from providers/inline-evaluating-provider/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java rename to providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java diff --git a/providers/inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java b/providers/inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java new file mode 100644 index 000000000..34cf96b00 --- /dev/null +++ b/providers/inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java @@ -0,0 +1,71 @@ +package dev.openfeature.contrib.providers.inlineeval; + +import dev.openfeature.sdk.ImmutableContext; +import dev.openfeature.sdk.ProviderEvaluation; +import dev.openfeature.sdk.Value; +import io.github.jamsesso.jsonlogic.JsonLogic; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + +class InlineEvaluatingProviderTest { + @Test + public void demonstrateJsonLogic() throws Exception { + // if specific id matches or category is in valid set, yes. Otherwise, no. + String rule = readTestResource("/dessert-decider.json"); + + JsonLogic logic = new JsonLogic(); + assertEquals(false, logic.apply(rule, new HashMap())); + assertEquals(true, logic.apply(rule, Collections.singletonMap("userId", 2))); + assertEquals(false, logic.apply(rule, Collections.singletonMap("userId", 5))); + assertEquals(true, logic.apply(rule, Collections.singletonMap("category", "pies"))); + assertEquals(false, logic.apply(rule, Collections.singletonMap("category", "muffins"))); + } + + private String readTestResource(String name) throws IOException, URISyntaxException { + URL url = this.getClass().getResource(name); + if (url == null) { + return null; + } + return String.join("", Files.readAllLines(Paths.get(url.toURI()))); + } + + + @Test + public void jsonlogicReturnTypes() throws Exception { + // if specific id matches or category is in valid set, yes. Otherwise, no. + + String rule = readTestResource("/many-types.json"); + JsonLogic logic = new JsonLogic(); + assertEquals(2D, logic.apply(rule, Collections.emptyMap())); + assertEquals(4.2D, logic.apply(rule, Collections.singletonMap("double", true))); + assertEquals("yes", logic.apply(rule, Collections.singletonMap("string", true))); + assertEquals(true, logic.apply(rule, Collections.singletonMap("bool", "true"))); + } + + @Test public void providerTest() throws Exception { + URL v = this.getClass().getResource("/test-rules.json"); + InlineEvaluatingProvider iep = new InlineEvaluatingProvider(new JsonLogic(), new FileBasedFetcher(v.toURI())); + ImmutableContext evalCtx = new ImmutableContext(Collections.singletonMap("userId", new Value(2))); + + ProviderEvaluation result = iep.getBooleanEvaluation("should-have-dessert?", false, evalCtx); + assertTrue(result.getValue(), result.getReason()); + } + + @Test public void callsFetcherInitialize() { + RuleFetcher mockFetcher = mock(RuleFetcher.class); + InlineEvaluatingProvider iep = new InlineEvaluatingProvider(new JsonLogic(), mockFetcher); + iep.initialize(null); + verify(mockFetcher).initialize(any()); + } +} \ No newline at end of file diff --git a/providers/inline-evaluating-provider/src/test/resources/dessert-decider.json b/providers/inline-evaluating-provider/src/test/resources/dessert-decider.json new file mode 100644 index 000000000..2515fe7a6 --- /dev/null +++ b/providers/inline-evaluating-provider/src/test/resources/dessert-decider.json @@ -0,0 +1,8 @@ +{"if": [ + {"or": [ + {"in": [{"var": "userId"}, [1,2,3,4]]}, + {"in": [{"var": "category"}, ["pies", "cakes"]]}, + ]}, + true, + false +]} \ No newline at end of file diff --git a/providers/inline-evaluating-provider/src/test/resources/many-types.json b/providers/inline-evaluating-provider/src/test/resources/many-types.json new file mode 100644 index 000000000..6d01b0999 --- /dev/null +++ b/providers/inline-evaluating-provider/src/test/resources/many-types.json @@ -0,0 +1,7 @@ +{"if": [{"var": "bool"}, true, + {"if": [{"var": "string"}, "yes", + {"if": [{"var": "double"}, 4.2, + 2 + ]} + ]} +]} \ No newline at end of file diff --git a/providers/inline-evaluating-provider/src/test/resources/test-rules.json b/providers/inline-evaluating-provider/src/test/resources/test-rules.json new file mode 100644 index 000000000..f357989cd --- /dev/null +++ b/providers/inline-evaluating-provider/src/test/resources/test-rules.json @@ -0,0 +1,16 @@ +{ + "should-have-dessert?": {"if": [ + {"or": [ + {"in": [{"var": "userId"}, [1,2,3,4]]}, + {"in": [{"var": "category"}, ["pies", "cakes"]]}, + ]}, + true, + false + ]}, + "many-types": {"if": [ + {"var": "bool"}, true, + {"var": "string"}, "yes", + {"var": "double"}, 4.2, + 2 + ]} +} \ No newline at end of file diff --git a/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java b/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java deleted file mode 100644 index 5fcb6a69b..000000000 --- a/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/ExampleFetcher.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.openfeature.contrib.providers.inlineeval; - -import dev.openfeature.sdk.EvaluationContext; - -public class ExampleFetcher implements RuleFetcher { - - @Override - public String getRuleForKey(String key) { - // In a real version of this, these rules should be cached. - return """ - {"if": [ - {"or": [ - {"in": [{"var": "userId"}, [1,2,3,4]]}, - {"in": [{"var": "category"}, ["pies", "cakes"]]}, - ]}, - true, - false - ]} - """; - } - - @Override public void initialize(EvaluationContext initialContext) {} -} diff --git a/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java b/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java deleted file mode 100644 index 3ac4dfaa3..000000000 --- a/providers/inline-evaluating-provider/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.openfeature.contrib.providers.inlineeval; - -import dev.openfeature.sdk.ImmutableContext; -import dev.openfeature.sdk.Value; -import io.github.jamsesso.jsonlogic.JsonLogic; -import io.github.jamsesso.jsonlogic.JsonLogicException; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.*; - -import java.util.Map; - -class InlineEvaluatingProviderTest { - @Test - public void demonstrateJsonLogic() throws JsonLogicException { - // if specific id matches or category is in valid set, yes. Otherwise, no. - var rule = """ - {"if": [ - {"or": [ - {"in": [{"var": "userId"}, [1,2,3,4]]}, - {"in": [{"var": "category"}, ["pies", "cakes"]]}, - ]}, - true, - false - ]} - """; - var logic = new JsonLogic(); - assertEquals(false, logic.apply(rule, null)); - assertEquals(true, logic.apply(rule, Map.of("userId", 2))); - assertEquals(false, logic.apply(rule, Map.of("userId", 5))); - assertEquals(true, logic.apply(rule, Map.of("category", "pies"))); - assertEquals(false, logic.apply(rule, Map.of("category", "muffins"))); - } - - - @Test - public void jsonlogicReturnTypes() throws JsonLogicException { - // if specific id matches or category is in valid set, yes. Otherwise, no. - var rule = """ - {"if": [{"var": "bool"}, true, - {"if": [{"var": "string"}, "yes", - {"if": [{"var": "double"}, 4.2, - 2 - ]} - ]} - ]} - """; - var logic = new JsonLogic(); - assertEquals(2D, logic.apply(rule, null)); - assertEquals(4.2D, logic.apply(rule, Map.of("double", true))); - assertEquals("yes", logic.apply(rule, Map.of("string", true))); - assertEquals(true, logic.apply(rule, Map.of("bool", "true"))); - } - - @Test public void providerTest() { - var iep = new InlineEvaluatingProvider(new JsonLogic(), new ExampleFetcher()); - var evalCtx = new ImmutableContext(Map.of("userId", new Value(2))); - - var result = iep.getBooleanEvaluation("test-key", false, evalCtx); - assertTrue(result.getValue(), result.getReason()); - } - - @Test public void callsFetcherInitialize() { - var mockFetcher = mock(RuleFetcher.class); - var iep = new InlineEvaluatingProvider(new JsonLogic(), mockFetcher); - iep.initialize(null); - verify(mockFetcher).initialize(any()); - } -} \ No newline at end of file From bf2a133f936d20666d6aa77c02cfa173127048bc Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Sat, 25 Mar 2023 10:46:07 -0700 Subject: [PATCH 03/14] Rename files, add tests, handle null checks. Signed-off-by: Justin Abrahms --- pom.xml | 2 +- .../pom.xml | 2 +- .../jsonlogic}/FileBasedFetcher.java | 14 +++++++--- .../jsonlogic}/InlineEvaluatingProvider.java | 8 +++--- .../providers/jsonlogic}/RuleFetcher.java | 5 +++- .../jsonlogic/FileBasedFetcherTest.java | 16 +++++++++++ .../InlineEvaluatingProviderTest.java | 27 ++++++++----------- .../contrib/providers/jsonlogic/Utils.java | 17 ++++++++++++ .../src/test/resources/dessert-decider.json | 2 +- .../src/test/resources/many-types.json | 0 .../src/test/resources/test-rules.json | 5 ++-- 11 files changed, 70 insertions(+), 28 deletions(-) rename providers/{inline-evaluating-provider => jsonlogic-eval-provider}/pom.xml (96%) rename providers/{inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval => jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic}/FileBasedFetcher.java (56%) rename providers/{inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval => jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic}/InlineEvaluatingProvider.java (92%) rename providers/{inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval => jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic}/RuleFetcher.java (84%) create mode 100644 providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcherTest.java rename providers/{inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval => jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic}/InlineEvaluatingProviderTest.java (80%) create mode 100644 providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/Utils.java rename providers/{inline-evaluating-provider => jsonlogic-eval-provider}/src/test/resources/dessert-decider.json (61%) rename providers/{inline-evaluating-provider => jsonlogic-eval-provider}/src/test/resources/many-types.json (100%) rename providers/{inline-evaluating-provider => jsonlogic-eval-provider}/src/test/resources/test-rules.json (75%) diff --git a/pom.xml b/pom.xml index 04ff62aad..73b897f6c 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ providers/flagd providers/flagsmith providers/go-feature-flag - providers/inline-evaluating-provider + providers/jsonlogic-eval-provider providers/env-var diff --git a/providers/inline-evaluating-provider/pom.xml b/providers/jsonlogic-eval-provider/pom.xml similarity index 96% rename from providers/inline-evaluating-provider/pom.xml rename to providers/jsonlogic-eval-provider/pom.xml index 473421245..a2587ae18 100644 --- a/providers/inline-evaluating-provider/pom.xml +++ b/providers/jsonlogic-eval-provider/pom.xml @@ -9,7 +9,7 @@ dev.openfeature.contrib.providers - inline-evaluating-provider + jsonlogic-eval-provider 0.0.1 inline-evaluating-provider diff --git a/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/FileBasedFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java similarity index 56% rename from providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/FileBasedFetcher.java rename to providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java index 92b2d3fbc..e0c68c904 100644 --- a/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/FileBasedFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java @@ -1,25 +1,33 @@ -package dev.openfeature.contrib.providers.inlineeval; +package dev.openfeature.contrib.providers.jsonlogic; import dev.openfeature.sdk.EvaluationContext; -import lombok.SneakyThrows; +import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.logging.Logger; public class FileBasedFetcher implements RuleFetcher { + private final Logger log; JSONObject rules; public FileBasedFetcher(URI filename) throws IOException { + this.log = Logger.getLogger(String.valueOf(FileBasedFetcher.class)); String jsonData = String.join("", Files.readAllLines(Paths.get(filename))); rules = new JSONObject(jsonData); } @Override public String getRuleForKey(String key) { - return rules.getJSONObject(key).toString(); + try { + return rules.getJSONObject(key).toString(); + } catch (JSONException e) { + log.warning(String.format("Unable to deserialize rule for %s due to exception %s", key, e)); + } + return null; } @Override public void initialize(EvaluationContext initialContext) {} diff --git a/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProvider.java similarity index 92% rename from providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java rename to providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProvider.java index d26288d7c..e1f3dc4d6 100644 --- a/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProvider.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProvider.java @@ -1,6 +1,7 @@ -package dev.openfeature.contrib.providers.inlineeval; +package dev.openfeature.contrib.providers.jsonlogic; import dev.openfeature.sdk.*; +import dev.openfeature.sdk.exceptions.ParseError; import io.github.jamsesso.jsonlogic.JsonLogic; import io.github.jamsesso.jsonlogic.JsonLogicException; @@ -10,7 +11,8 @@ public class InlineEvaluatingProvider implements FeatureProvider { private final JsonLogic logic; private final RuleFetcher fetcher; - void initialize(EvaluationContext initialContext) { + + public void initialize(EvaluationContext initialContext) { fetcher.initialize(initialContext); } @@ -69,7 +71,7 @@ private ProviderEvaluation evalRuleForKey(String key, T _default, Evaluat .value(resultToType.apply(this.logic.apply(rule, ctx.asObjectMap()))) .build(); } catch (JsonLogicException e) { - throw new RuntimeException(e); + throw new ParseError(e); } } } diff --git a/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/RuleFetcher.java similarity index 84% rename from providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java rename to providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/RuleFetcher.java index fe685cbce..478fb89c1 100644 --- a/providers/inline-evaluating-provider/src/main/java/dev/openfeature/contrib/providers/inlineeval/RuleFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/RuleFetcher.java @@ -1,7 +1,9 @@ -package dev.openfeature.contrib.providers.inlineeval; +package dev.openfeature.contrib.providers.jsonlogic; import dev.openfeature.sdk.EvaluationContext; +import javax.annotation.Nullable; + public interface RuleFetcher { /** @@ -16,5 +18,6 @@ public interface RuleFetcher { * @param key The key to fetch logic for * @return json logic rules or null */ + @Nullable String getRuleForKey(String key); } diff --git a/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcherTest.java b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcherTest.java new file mode 100644 index 000000000..46a0204d3 --- /dev/null +++ b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcherTest.java @@ -0,0 +1,16 @@ +package dev.openfeature.contrib.providers.jsonlogic; + +import org.junit.jupiter.api.Test; + +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertNull; + +class FileBasedFetcherTest { + + @Test public void testNullValueForRule() throws Exception { + URI uri = this.getClass().getResource("/test-rules.json").toURI(); + FileBasedFetcher f = new FileBasedFetcher(uri); + assertNull(f.getRuleForKey("malformed")); + } +} \ No newline at end of file diff --git a/providers/inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProviderTest.java similarity index 80% rename from providers/inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java rename to providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProviderTest.java index 34cf96b00..45c95adc9 100644 --- a/providers/inline-evaluating-provider/src/test/java/dev/openfeature/contrib/providers/inlineeval/InlineEvaluatingProviderTest.java +++ b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProviderTest.java @@ -1,4 +1,4 @@ -package dev.openfeature.contrib.providers.inlineeval; +package dev.openfeature.contrib.providers.jsonlogic; import dev.openfeature.sdk.ImmutableContext; import dev.openfeature.sdk.ProviderEvaluation; @@ -6,11 +6,7 @@ import io.github.jamsesso.jsonlogic.JsonLogic; import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Collections; import java.util.HashMap; @@ -22,7 +18,7 @@ class InlineEvaluatingProviderTest { @Test public void demonstrateJsonLogic() throws Exception { // if specific id matches or category is in valid set, yes. Otherwise, no. - String rule = readTestResource("/dessert-decider.json"); + String rule = Utils.readTestResource("/dessert-decider.json"); JsonLogic logic = new JsonLogic(); assertEquals(false, logic.apply(rule, new HashMap())); @@ -32,20 +28,11 @@ public void demonstrateJsonLogic() throws Exception { assertEquals(false, logic.apply(rule, Collections.singletonMap("category", "muffins"))); } - private String readTestResource(String name) throws IOException, URISyntaxException { - URL url = this.getClass().getResource(name); - if (url == null) { - return null; - } - return String.join("", Files.readAllLines(Paths.get(url.toURI()))); - } - - @Test public void jsonlogicReturnTypes() throws Exception { // if specific id matches or category is in valid set, yes. Otherwise, no. - String rule = readTestResource("/many-types.json"); + String rule = Utils.readTestResource("/many-types.json"); JsonLogic logic = new JsonLogic(); assertEquals(2D, logic.apply(rule, Collections.emptyMap())); assertEquals(4.2D, logic.apply(rule, Collections.singletonMap("double", true))); @@ -62,6 +49,14 @@ public void jsonlogicReturnTypes() throws Exception { assertTrue(result.getValue(), result.getReason()); } + @Test public void missingKey() throws Exception { + URL v = this.getClass().getResource("/test-rules.json"); + InlineEvaluatingProvider iep = new InlineEvaluatingProvider(new JsonLogic(), new FileBasedFetcher(v.toURI())); + + ProviderEvaluation result = iep.getBooleanEvaluation("missingKey", false, null); + assertEquals("Unable to find rules for the given key", result.getReason()); + } + @Test public void callsFetcherInitialize() { RuleFetcher mockFetcher = mock(RuleFetcher.class); InlineEvaluatingProvider iep = new InlineEvaluatingProvider(new JsonLogic(), mockFetcher); diff --git a/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/Utils.java b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/Utils.java new file mode 100644 index 000000000..bd3b2423d --- /dev/null +++ b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/Utils.java @@ -0,0 +1,17 @@ +package dev.openfeature.contrib.providers.jsonlogic; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class Utils { + public static String readTestResource(String name) throws IOException, URISyntaxException { + URL url = Utils.class.getResource(name); + if (url == null) { + return null; + } + return String.join("", Files.readAllLines(Paths.get(url.toURI()))); + } +} diff --git a/providers/inline-evaluating-provider/src/test/resources/dessert-decider.json b/providers/jsonlogic-eval-provider/src/test/resources/dessert-decider.json similarity index 61% rename from providers/inline-evaluating-provider/src/test/resources/dessert-decider.json rename to providers/jsonlogic-eval-provider/src/test/resources/dessert-decider.json index 2515fe7a6..6cf5675a8 100644 --- a/providers/inline-evaluating-provider/src/test/resources/dessert-decider.json +++ b/providers/jsonlogic-eval-provider/src/test/resources/dessert-decider.json @@ -1,7 +1,7 @@ {"if": [ {"or": [ {"in": [{"var": "userId"}, [1,2,3,4]]}, - {"in": [{"var": "category"}, ["pies", "cakes"]]}, + {"in": [{"var": "category"}, ["pies", "cakes"]]} ]}, true, false diff --git a/providers/inline-evaluating-provider/src/test/resources/many-types.json b/providers/jsonlogic-eval-provider/src/test/resources/many-types.json similarity index 100% rename from providers/inline-evaluating-provider/src/test/resources/many-types.json rename to providers/jsonlogic-eval-provider/src/test/resources/many-types.json diff --git a/providers/inline-evaluating-provider/src/test/resources/test-rules.json b/providers/jsonlogic-eval-provider/src/test/resources/test-rules.json similarity index 75% rename from providers/inline-evaluating-provider/src/test/resources/test-rules.json rename to providers/jsonlogic-eval-provider/src/test/resources/test-rules.json index f357989cd..de9cbd34e 100644 --- a/providers/inline-evaluating-provider/src/test/resources/test-rules.json +++ b/providers/jsonlogic-eval-provider/src/test/resources/test-rules.json @@ -2,7 +2,7 @@ "should-have-dessert?": {"if": [ {"or": [ {"in": [{"var": "userId"}, [1,2,3,4]]}, - {"in": [{"var": "category"}, ["pies", "cakes"]]}, + {"in": [{"var": "category"}, ["pies", "cakes"]]} ]}, true, false @@ -12,5 +12,6 @@ {"var": "string"}, "yes", {"var": "double"}, 4.2, 2 - ]} + ]}, + "malformed": null } \ No newline at end of file From 5e96e3d9b0990d88e2822aba1f4fc5a162b7f3ca Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Sat, 25 Mar 2023 11:03:19 -0700 Subject: [PATCH 04/14] Simplified constructor, if you don't need to specify a JSONLogic instance. Signed-off-by: Justin Abrahms --- ...ineEvaluatingProvider.java => JsonlogicProvider.java} | 9 +++++++-- ...atingProviderTest.java => JsonlogicProviderTest.java} | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) rename providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/{InlineEvaluatingProvider.java => JsonlogicProvider.java} (91%) rename providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/{InlineEvaluatingProviderTest.java => JsonlogicProviderTest.java} (87%) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProvider.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java similarity index 91% rename from providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProvider.java rename to providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java index e1f3dc4d6..0cf12b570 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProvider.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java @@ -7,7 +7,7 @@ import java.util.function.Function; -public class InlineEvaluatingProvider implements FeatureProvider { +public class JsonlogicProvider implements FeatureProvider { private final JsonLogic logic; private final RuleFetcher fetcher; @@ -16,7 +16,12 @@ public void initialize(EvaluationContext initialContext) { fetcher.initialize(initialContext); } - public InlineEvaluatingProvider(JsonLogic logic, RuleFetcher fetcher) { + public JsonlogicProvider(RuleFetcher fetcher) { + this.logic = new JsonLogic(); + this.fetcher = fetcher; + } + + public JsonlogicProvider(JsonLogic logic, RuleFetcher fetcher) { this.logic = logic; this.fetcher = fetcher; } diff --git a/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProviderTest.java b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProviderTest.java similarity index 87% rename from providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProviderTest.java rename to providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProviderTest.java index 45c95adc9..665c09009 100644 --- a/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/InlineEvaluatingProviderTest.java +++ b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProviderTest.java @@ -14,7 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; -class InlineEvaluatingProviderTest { +class JsonlogicProviderTest { @Test public void demonstrateJsonLogic() throws Exception { // if specific id matches or category is in valid set, yes. Otherwise, no. @@ -42,7 +42,7 @@ public void jsonlogicReturnTypes() throws Exception { @Test public void providerTest() throws Exception { URL v = this.getClass().getResource("/test-rules.json"); - InlineEvaluatingProvider iep = new InlineEvaluatingProvider(new JsonLogic(), new FileBasedFetcher(v.toURI())); + JsonlogicProvider iep = new JsonlogicProvider(new FileBasedFetcher(v.toURI())); ImmutableContext evalCtx = new ImmutableContext(Collections.singletonMap("userId", new Value(2))); ProviderEvaluation result = iep.getBooleanEvaluation("should-have-dessert?", false, evalCtx); @@ -51,7 +51,7 @@ public void jsonlogicReturnTypes() throws Exception { @Test public void missingKey() throws Exception { URL v = this.getClass().getResource("/test-rules.json"); - InlineEvaluatingProvider iep = new InlineEvaluatingProvider(new JsonLogic(), new FileBasedFetcher(v.toURI())); + JsonlogicProvider iep = new JsonlogicProvider(new FileBasedFetcher(v.toURI())); ProviderEvaluation result = iep.getBooleanEvaluation("missingKey", false, null); assertEquals("Unable to find rules for the given key", result.getReason()); @@ -59,7 +59,7 @@ public void jsonlogicReturnTypes() throws Exception { @Test public void callsFetcherInitialize() { RuleFetcher mockFetcher = mock(RuleFetcher.class); - InlineEvaluatingProvider iep = new InlineEvaluatingProvider(new JsonLogic(), mockFetcher); + JsonlogicProvider iep = new JsonlogicProvider(mockFetcher); iep.initialize(null); verify(mockFetcher).initialize(any()); } From 910af59bb1f49eac44fa511445f0dd59c8cfc3f4 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Sat, 25 Mar 2023 11:03:50 -0700 Subject: [PATCH 05/14] README & release-please config. Signed-off-by: Justin Abrahms --- providers/jsonlogic-eval-provider/README.md | 41 +++++++++++++++++++ providers/jsonlogic-eval-provider/version.txt | 1 + release-please-config.json | 7 ++-- 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 providers/jsonlogic-eval-provider/README.md create mode 100644 providers/jsonlogic-eval-provider/version.txt diff --git a/providers/jsonlogic-eval-provider/README.md b/providers/jsonlogic-eval-provider/README.md new file mode 100644 index 000000000..2555841dc --- /dev/null +++ b/providers/jsonlogic-eval-provider/README.md @@ -0,0 +1,41 @@ +# JSONLogic Evaluation Provider + +This provider does inline evaluation (e.g. no hot-path remote calls) based on JSONLogic. This should allow you to +achieve low latency flag evaluation. + +## Installation + + +```xml + + + dev.openfeature.contrib.providers + jsonlogic-eval-provider + 0.0.1 + +``` + + +## Usage + +You will need to create a custom class which implements the `RuleFetcher` interface. This code should cache your +rules locally. During the `initialization` method, it should also set up a mechanism to stay up to date with remote +flag changes. You can see `FileBasedFetcher` as a simplified example. + +```java +JsonlogicProvider jlp = new JsonlogicProvider(new RuleFetcher() { + @Override + public void initialize(EvaluationContext initialContext) { + // setup initial fetch & stay-up-to-date logic + } + + @Nullable + @Override + public String getRuleForKey(String key) { + // return the jsonlogic rule in string format for a given flag key + return null; + } +}) + +OpenFeature.setProvider(jlp); +``` \ No newline at end of file diff --git a/providers/jsonlogic-eval-provider/version.txt b/providers/jsonlogic-eval-provider/version.txt new file mode 100644 index 000000000..8a9ecc2ea --- /dev/null +++ b/providers/jsonlogic-eval-provider/version.txt @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/release-please-config.json b/release-please-config.json index e8531d33d..f4c12abfe 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -34,14 +34,15 @@ "pom.xml" ] }, - "providers/inline-evaluating-provider": { - "package-name": "dev.openfeature.contrib.providers.inlineeval", + "providers/jsonlogic-eval-provider": { + "package-name": "dev.openfeature.contrib.providers.jsonlogic", "release-type": "simple", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, "versioning": "default", "extra-files": [ - "pom.xml" + "pom.xml", + "README.md" ] }, "providers/env-var": { From 8ac2efcd3b9c8c31dffdfc3cac0de35867d5c874 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Mon, 20 Mar 2023 16:11:28 -0700 Subject: [PATCH 06/14] Checkstyle Signed-off-by: Justin Abrahms --- .../providers/jsonlogic/FileBasedFetcher.java | 7 ++++- .../jsonlogic/JsonlogicProvider.java | 30 +++++++++++-------- .../providers/jsonlogic/RuleFetcher.java | 9 ++++-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java index e0c68c904..18fe1f58d 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java @@ -10,6 +10,10 @@ import java.nio.file.Paths; import java.util.logging.Logger; +/** + * A {@link RuleFetcher} which reads in the rules from a file. It assumes that the keys are the flag keys and the + * values are the json logic rules. + */ public class FileBasedFetcher implements RuleFetcher { private final Logger log; JSONObject rules; @@ -30,5 +34,6 @@ public String getRuleForKey(String key) { return null; } - @Override public void initialize(EvaluationContext initialContext) {} + @Override public void initialize(EvaluationContext initialContext) { + } } diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java index 0cf12b570..03891810f 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java @@ -7,6 +7,9 @@ import java.util.function.Function; +/** + * A provider which evaluates JsonLogic rules provided by a {@link RuleFetcher}. + */ public class JsonlogicProvider implements FeatureProvider { private final JsonLogic logic; private final RuleFetcher fetcher; @@ -32,41 +35,42 @@ public Metadata getMetadata() { } @Override - public ProviderEvaluation getBooleanEvaluation(String key, Boolean _default, EvaluationContext ctx) { - return evalRuleForKey(key, _default, ctx); + public ProviderEvaluation getBooleanEvaluation(String key, Boolean defaultValue, EvaluationContext ctx) { + return evalRuleForKey(key, defaultValue, ctx); } @Override - public ProviderEvaluation getStringEvaluation(String key, String _default, EvaluationContext ctx) { - return evalRuleForKey(key, _default, ctx); + public ProviderEvaluation getStringEvaluation(String key, String defaultValue, EvaluationContext ctx) { + return evalRuleForKey(key, defaultValue, ctx); } @Override - public ProviderEvaluation getIntegerEvaluation(String key, Integer _default, EvaluationContext ctx) { + public ProviderEvaluation getIntegerEvaluation(String key, Integer defaultValue, EvaluationContext ctx) { // jsonlogic only returns doubles, not integers. - return evalRuleForKey(key, _default, ctx, (o) -> ((Double) o).intValue()); + return evalRuleForKey(key, defaultValue, ctx, (o) -> ((Double) o).intValue()); } @Override - public ProviderEvaluation getDoubleEvaluation(String key, Double _default, EvaluationContext ctx) { - return evalRuleForKey(key, _default, ctx); + public ProviderEvaluation getDoubleEvaluation(String key, Double defaultValue, EvaluationContext ctx) { + return evalRuleForKey(key, defaultValue, ctx); } @Override public ProviderEvaluation getObjectEvaluation(String s, Value value, EvaluationContext evaluationContext) { // we can't use the common implementation because we need to convert to-and-from Value objects. throw new UnsupportedOperationException("Haven't gotten there yet."); -// return evalRuleForKey(key, _default, ctx, (o) -> o); } - private ProviderEvaluation evalRuleForKey(String key, T _default, EvaluationContext ctx) { - return evalRuleForKey(key, _default, ctx, (o) -> (T) o); + private ProviderEvaluation evalRuleForKey(String key, T defaultValue, EvaluationContext ctx) { + return evalRuleForKey(key, defaultValue, ctx, (o) -> (T) o); } - private ProviderEvaluation evalRuleForKey(String key, T _default, EvaluationContext ctx, Function resultToType) { + + private ProviderEvaluation evalRuleForKey( + String key, T defaultValue, EvaluationContext ctx, Function resultToType) { String rule = fetcher.getRuleForKey(key); if (rule == null) { return ProviderEvaluation.builder() - .value(_default) + .value(defaultValue) .reason("Unable to find rules for the given key") .build(); } diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/RuleFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/RuleFetcher.java index 478fb89c1..b802e4046 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/RuleFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/RuleFetcher.java @@ -4,17 +4,20 @@ import javax.annotation.Nullable; +/** + * A RuleFetcher exists to fetch rules from a likely remote location which will be used for local evaluation. + */ public interface RuleFetcher { /** - * Called to set up the client initially. This is used to pre-fetch initial data as well as setup mechanisms to stay up - * to date. + * Called to set up the client initially. This is used to pre-fetch initial data as well as setup mechanisms + * to stay up to date. * @param initialContext application context known thus far */ void initialize(EvaluationContext initialContext); /** - * Given a key name, return the JSONLogic rules for it + * Given a key name, return the JSONLogic rules for it. * @param key The key to fetch logic for * @return json logic rules or null */ From f634f0b0a386e94425e0ef78174d7b73ac453d7a Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Fri, 7 Apr 2023 11:22:23 -0700 Subject: [PATCH 07/14] Javadoc Signed-off-by: Justin Abrahms --- .../contrib/providers/jsonlogic/FileBasedFetcher.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java index 18fe1f58d..87e397a11 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java @@ -18,6 +18,11 @@ public class FileBasedFetcher implements RuleFetcher { private final Logger log; JSONObject rules; + /** + * Create a file based fetcher give a file URI + * @param filename URI to a given file. + * @throws IOException + */ public FileBasedFetcher(URI filename) throws IOException { this.log = Logger.getLogger(String.valueOf(FileBasedFetcher.class)); String jsonData = String.join("", Files.readAllLines(Paths.get(filename))); From 3c54fb4b5b72d4c92a9056acda8f0725fb1e7388 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Fri, 7 Apr 2023 11:27:05 -0700 Subject: [PATCH 08/14] Now even more valid javadoc Signed-off-by: Justin Abrahms --- .../contrib/providers/jsonlogic/FileBasedFetcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java index 87e397a11..6908a40d1 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java @@ -19,9 +19,9 @@ public class FileBasedFetcher implements RuleFetcher { JSONObject rules; /** - * Create a file based fetcher give a file URI + * Create a file based fetcher give a file URI. * @param filename URI to a given file. - * @throws IOException + * @throws IOException when we can't load the file correctly */ public FileBasedFetcher(URI filename) throws IOException { this.log = Logger.getLogger(String.valueOf(FileBasedFetcher.class)); From 1519df7513f3f930565c2405cab414df50eae9a8 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Fri, 7 Apr 2023 12:10:33 -0700 Subject: [PATCH 09/14] Acknowledge that you can open files in the file-based fetcher Signed-off-by: Justin Abrahms --- providers/jsonlogic-eval-provider/pom.xml | 6 ++++++ .../contrib/providers/jsonlogic/FileBasedFetcher.java | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/providers/jsonlogic-eval-provider/pom.xml b/providers/jsonlogic-eval-provider/pom.xml index a2587ae18..ad5fce5ec 100644 --- a/providers/jsonlogic-eval-provider/pom.xml +++ b/providers/jsonlogic-eval-provider/pom.xml @@ -36,6 +36,12 @@ json 20230227 + + com.github.spotbugs + spotbugs-annotations + 4.7.3 + compile + diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java index 6908a40d1..0409a268a 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java @@ -9,11 +9,16 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.logging.Logger; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * A {@link RuleFetcher} which reads in the rules from a file. It assumes that the keys are the flag keys and the * values are the json logic rules. */ +@SuppressFBWarnings( + value = "PATH_TRAVERSAL_IN", + justification = "This is expected to read files based on user input" +) public class FileBasedFetcher implements RuleFetcher { private final Logger log; JSONObject rules; From d609277c63a98b508b6dcdf7836fbba1a91c3b81 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Wed, 12 Apr 2023 21:48:54 -0700 Subject: [PATCH 10/14] Update providers/jsonlogic-eval-provider/README.md Co-authored-by: Kavindu Dodanduwa Signed-off-by: Justin Abrahms --- providers/jsonlogic-eval-provider/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/jsonlogic-eval-provider/README.md b/providers/jsonlogic-eval-provider/README.md index 2555841dc..25b146c8b 100644 --- a/providers/jsonlogic-eval-provider/README.md +++ b/providers/jsonlogic-eval-provider/README.md @@ -1,6 +1,6 @@ # JSONLogic Evaluation Provider -This provider does inline evaluation (e.g. no hot-path remote calls) based on JSONLogic. This should allow you to +This provider does inline evaluation (e.g. no hot-path remote calls) based on [JSONLogic](https://jsonlogic.com/). This should allow you to achieve low latency flag evaluation. ## Installation From 439b15c947eff193c9da28ad88861f518479c488 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 13 Apr 2023 04:46:25 -0700 Subject: [PATCH 11/14] Update field visbility / lifetime Signed-off-by: Justin Abrahms --- .../contrib/providers/jsonlogic/FileBasedFetcher.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java index 0409a268a..a463254eb 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java @@ -20,8 +20,8 @@ justification = "This is expected to read files based on user input" ) public class FileBasedFetcher implements RuleFetcher { - private final Logger log; - JSONObject rules; + private final static Logger log = Logger.getLogger(String.valueOf(FileBasedFetcher.class)); + private final JSONObject rules; /** * Create a file based fetcher give a file URI. @@ -29,7 +29,6 @@ public class FileBasedFetcher implements RuleFetcher { * @throws IOException when we can't load the file correctly */ public FileBasedFetcher(URI filename) throws IOException { - this.log = Logger.getLogger(String.valueOf(FileBasedFetcher.class)); String jsonData = String.join("", Files.readAllLines(Paths.get(filename))); rules = new JSONObject(jsonData); } From a3a34116361e581abe677472af47f6c19678ce86 Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 13 Apr 2023 04:46:49 -0700 Subject: [PATCH 12/14] Return non-null metadata Signed-off-by: Justin Abrahms --- .../contrib/providers/jsonlogic/JsonlogicProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java index 03891810f..ddec3fea0 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java @@ -31,7 +31,7 @@ public JsonlogicProvider(JsonLogic logic, RuleFetcher fetcher) { @Override public Metadata getMetadata() { - return null; + return () -> "JsonLogicProvider(" + this.fetcher.getClass().getName() + ")"; } @Override From 8dc5587fa48cbeb40a6d5143ef312fd91e4437ac Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 13 Apr 2023 04:47:06 -0700 Subject: [PATCH 13/14] Move error message to the error message field. Signed-off-by: Justin Abrahms --- .../contrib/providers/jsonlogic/JsonlogicProvider.java | 3 ++- .../contrib/providers/jsonlogic/JsonlogicProviderTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java index ddec3fea0..8617ac691 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProvider.java @@ -71,7 +71,8 @@ private ProviderEvaluation evalRuleForKey( if (rule == null) { return ProviderEvaluation.builder() .value(defaultValue) - .reason("Unable to find rules for the given key") + .reason(Reason.ERROR.toString()) + .errorMessage("Unable to find rules for the given key") .build(); } diff --git a/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProviderTest.java b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProviderTest.java index 665c09009..5a0f51e00 100644 --- a/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProviderTest.java +++ b/providers/jsonlogic-eval-provider/src/test/java/dev/openfeature/contrib/providers/jsonlogic/JsonlogicProviderTest.java @@ -54,7 +54,8 @@ public void jsonlogicReturnTypes() throws Exception { JsonlogicProvider iep = new JsonlogicProvider(new FileBasedFetcher(v.toURI())); ProviderEvaluation result = iep.getBooleanEvaluation("missingKey", false, null); - assertEquals("Unable to find rules for the given key", result.getReason()); + assertEquals("Unable to find rules for the given key", result.getErrorMessage()); + assertEquals("ERROR", result.getReason()); } @Test public void callsFetcherInitialize() { From e5ef2d49ed1ecb787f770b66d6366aaaf8bab05f Mon Sep 17 00:00:00 2001 From: Justin Abrahms Date: Thu, 13 Apr 2023 04:56:05 -0700 Subject: [PATCH 14/14] Flip order of final/static. Signed-off-by: Justin Abrahms --- .../contrib/providers/jsonlogic/FileBasedFetcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java index a463254eb..7def40764 100644 --- a/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java +++ b/providers/jsonlogic-eval-provider/src/main/java/dev/openfeature/contrib/providers/jsonlogic/FileBasedFetcher.java @@ -1,6 +1,7 @@ package dev.openfeature.contrib.providers.jsonlogic; import dev.openfeature.sdk.EvaluationContext; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.json.JSONException; import org.json.JSONObject; @@ -9,7 +10,6 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.logging.Logger; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * A {@link RuleFetcher} which reads in the rules from a file. It assumes that the keys are the flag keys and the @@ -20,7 +20,7 @@ justification = "This is expected to read files based on user input" ) public class FileBasedFetcher implements RuleFetcher { - private final static Logger log = Logger.getLogger(String.valueOf(FileBasedFetcher.class)); + private static final Logger log = Logger.getLogger(String.valueOf(FileBasedFetcher.class)); private final JSONObject rules; /**