From cb434c6909dd934d40ca6896a399381b938842dd Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 16:22:43 +0200 Subject: [PATCH 1/8] rename from Instance to FeaturevisorInstance --- README.md | 20 +++--- src/main/java/com/featurevisor/cli/CLI.java | 66 +++++++++---------- .../com/featurevisor/sdk/ChildInstance.java | 30 ++++----- .../com/featurevisor/sdk/Featurevisor.java | 36 +++++----- ...nstance.java => FeaturevisorInstance.java} | 4 +- .../java/com/featurevisor/sdk/ChildTest.java | 2 +- .../com/featurevisor/sdk/InstanceTest.java | 44 ++++++------- 7 files changed, 101 insertions(+), 101 deletions(-) rename src/main/java/com/featurevisor/sdk/{Instance.java => FeaturevisorInstance.java} (99%) diff --git a/README.md b/README.md index 0cb9599..65840c8 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ See example application here: [https://github.com/featurevisor/featurevisor-exam The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly: ```java -import com.featurevisor.sdk.Instance; +import com.featurevisor.sdk.FeaturevisorInstance; import com.featurevisor.types.DatafileContent; // Load datafile content @@ -128,8 +128,8 @@ String datafileContent = "..." // ... load your datafile content DatafileContent datafile = DatafileContent.fromJson(datafileContent); // Create SDK instance -Instance f = new Instance( - new Instance.InstanceOptions() +FeaturevisorInstance f = new FeaturevisorInstance( + new FeaturevisorInstance.InstanceOptions() .datafile(datafileContent) ); ``` @@ -170,7 +170,7 @@ Map initialContext = new HashMap<>(); initialContext.put("deviceId", "123"); initialContext.put("country", "nl"); -Instance f = new Instance(new Instance.InstanceOptions() +FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .context(initialContext)); ``` @@ -357,7 +357,7 @@ Map anotherFeatureSticky = new HashMap<>(); anotherFeatureSticky.put("enabled", false); stickyFeatures.put("anotherFeatureKey", anotherFeatureSticky); -Instance f = new Instance(new Instance.InstanceOptions() +FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .sticky(stickyFeatures)); ``` @@ -433,7 +433,7 @@ Setting `debug` level will print out all logs, including `info`, `warn`, and `er ```java import com.featurevisor.sdk.Logger; -Instance f = new Instance(new Instance.InstanceOptions() +FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .logLevel(Logger.LogLevel.DEBUG)); ``` @@ -457,7 +457,7 @@ Logger customLogger = Logger.createLogger(new Logger.CreateLoggerOptions() System.out.println("[" + level + "] " + message); })); -Instance f = new Instance(new Instance.InstanceOptions() +FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .logger(customLogger)); ``` @@ -470,7 +470,7 @@ Logger customLogger = new Logger(Logger.LogLevel.INFO, (level, message, details) System.out.println("[" + level + "] " + message); }); -Instance f = new Instance(new Instance.InstanceOptions() +FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .logger(customLogger)); ``` @@ -637,7 +637,7 @@ You can register hooks at the time of SDK initialization: List> hooks = new ArrayList<>(); hooks.add(myCustomHook); -Instance f = new Instance(new Instance.InstanceOptions() +FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .hooks(hooks)); ``` @@ -662,7 +662,7 @@ That's where child instances come in handy: Map childContext = new HashMap<>(); childContext.put("userId", "123"); -Instance childF = f.spawn(childContext); +FeaturevisorInstance childF = f.spawn(childContext); ``` Now you can pass the child instance where your individual request is being handled, and you can continue to evaluate features targeting that specific user alone: diff --git a/src/main/java/com/featurevisor/cli/CLI.java b/src/main/java/com/featurevisor/cli/CLI.java index 57d3daa..e40b625 100644 --- a/src/main/java/com/featurevisor/cli/CLI.java +++ b/src/main/java/com/featurevisor/cli/CLI.java @@ -6,7 +6,7 @@ import picocli.CommandLine.Parameters; import com.featurevisor.sdk.Featurevisor; -import com.featurevisor.sdk.Instance; +import com.featurevisor.sdk.FeaturevisorInstance; import com.featurevisor.sdk.Logger; import com.featurevisor.sdk.DatafileReader; import com.featurevisor.types.DatafileContent; @@ -250,9 +250,9 @@ private TestResult testFeature(Map assertion, String featureKey, Map sticky = (Map) assertion.getOrDefault("sticky", new HashMap<>()); // Update the SDK instance context and sticky values for this assertion - if (f instanceof Instance) { - ((Instance) f).setContext(context, true); - ((Instance) f).setSticky(sticky, true); + if (f instanceof FeaturevisorInstance) { + ((FeaturevisorInstance) f).setContext(context, true); + ((FeaturevisorInstance) f).setSticky(sticky, true); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { ((com.featurevisor.sdk.ChildInstance) f).setContext(context, true); ((com.featurevisor.sdk.ChildInstance) f).setSticky(sticky, true); @@ -274,7 +274,7 @@ private TestResult testFeature(Map assertion, String featureKey, // Test expectedVariation if (assertion.containsKey("expectedVariation")) { - Instance.OverrideOptions options = new Instance.OverrideOptions(); + FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); if (assertion.containsKey("defaultVariationValue")) { options.setDefaultVariationValue(assertion.get("defaultVariationValue").toString()); } @@ -308,7 +308,7 @@ private TestResult testFeature(Map assertion, String featureKey, } } - Instance.OverrideOptions options = new Instance.OverrideOptions(); + FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); if (defaultVariableValues.containsKey(variableKey)) { options.setDefaultVariableValue(defaultVariableValues.get(variableKey)); } @@ -347,7 +347,7 @@ private TestResult testFeature(Map assertion, String featureKey, if (expectedEvaluations.containsKey("variation")) { @SuppressWarnings("unchecked") Map variationEvaluation = (Map) expectedEvaluations.get("variation"); - Instance.OverrideOptions options = new Instance.OverrideOptions(); + FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); if (assertion.containsKey("defaultVariationValue")) { options.setDefaultVariationValue(assertion.get("defaultVariationValue").toString()); } @@ -378,7 +378,7 @@ private TestResult testFeature(Map assertion, String featureKey, @SuppressWarnings("unchecked") Map expectedEvaluation = (Map) entry.getValue(); - Instance.OverrideOptions options = new Instance.OverrideOptions(); + FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); if (defaultVariableValues.containsKey(variableKey)) { options.setDefaultVariableValue(defaultVariableValues.get(variableKey)); } @@ -427,26 +427,26 @@ private TestResult testFeature(Map assertion, String featureKey, * Helper methods to work with both Instance and ChildInstance */ private boolean isEnabled(Object f, String featureKey, Map context) { - if (f instanceof Instance) { - return ((Instance) f).isEnabled(featureKey, context); + if (f instanceof FeaturevisorInstance) { + return ((FeaturevisorInstance) f).isEnabled(featureKey, context); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { return ((com.featurevisor.sdk.ChildInstance) f).isEnabled(featureKey, context); } return false; } - private Object getVariation(Object f, String featureKey, Map context, Instance.OverrideOptions options) { - if (f instanceof Instance) { - return ((Instance) f).getVariation(featureKey, context, options); + private Object getVariation(Object f, String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { + if (f instanceof FeaturevisorInstance) { + return ((FeaturevisorInstance) f).getVariation(featureKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { return ((com.featurevisor.sdk.ChildInstance) f).getVariation(featureKey, context, options); } return null; } - private Object getVariable(Object f, String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { - if (f instanceof Instance) { - return ((Instance) f).getVariable(featureKey, variableKey, context, options); + private Object getVariable(Object f, String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + if (f instanceof FeaturevisorInstance) { + return ((FeaturevisorInstance) f).getVariable(featureKey, variableKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { return ((com.featurevisor.sdk.ChildInstance) f).getVariable(featureKey, variableKey, context, options); } @@ -454,8 +454,8 @@ private Object getVariable(Object f, String featureKey, String variableKey, Map< } private com.featurevisor.sdk.Evaluation evaluateFlag(Object f, String featureKey, Map context) { - if (f instanceof Instance) { - return ((Instance) f).evaluateFlag(featureKey, context); + if (f instanceof FeaturevisorInstance) { + return ((FeaturevisorInstance) f).evaluateFlag(featureKey, context); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have evaluateFlag, so we'll skip this test for child instances return null; @@ -463,9 +463,9 @@ private com.featurevisor.sdk.Evaluation evaluateFlag(Object f, String featureKey return null; } - private com.featurevisor.sdk.Evaluation evaluateVariation(Object f, String featureKey, Map context, Instance.OverrideOptions options) { - if (f instanceof Instance) { - return ((Instance) f).evaluateVariation(featureKey, context, options); + private com.featurevisor.sdk.Evaluation evaluateVariation(Object f, String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { + if (f instanceof FeaturevisorInstance) { + return ((FeaturevisorInstance) f).evaluateVariation(featureKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have evaluateVariation, so we'll skip this test for child instances return null; @@ -473,9 +473,9 @@ private com.featurevisor.sdk.Evaluation evaluateVariation(Object f, String featu return null; } - private com.featurevisor.sdk.Evaluation evaluateVariable(Object f, String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { - if (f instanceof Instance) { - return ((Instance) f).evaluateVariable(featureKey, variableKey, context, options); + private com.featurevisor.sdk.Evaluation evaluateVariable(Object f, String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + if (f instanceof FeaturevisorInstance) { + return ((FeaturevisorInstance) f).evaluateVariable(featureKey, variableKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have evaluateVariable, so we'll skip this test for child instances return null; @@ -484,8 +484,8 @@ private com.featurevisor.sdk.Evaluation evaluateVariable(Object f, String featur } private com.featurevisor.sdk.ChildInstance spawn(Object f, Map context) { - if (f instanceof Instance) { - return ((Instance) f).spawn(context); + if (f instanceof FeaturevisorInstance) { + return ((FeaturevisorInstance) f).spawn(context); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have spawn, so we'll return null return null; @@ -577,11 +577,11 @@ private void test() { return; } - // Create SDK instances for each environment - Map sdkInstancesByEnvironment = new HashMap<>(); + // Create SDK instances for each environment + Map sdkInstancesByEnvironment = new HashMap<>(); for (String environment : environments) { DatafileContent datafile = datafilesByEnvironment.get(environment); - Instance instance = Featurevisor.createInstance(new Instance.InstanceOptions() + FeaturevisorInstance instance = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .logLevel(level)); sdkInstancesByEnvironment.put(environment, instance); @@ -610,7 +610,7 @@ private void test() { if (test.containsKey("feature")) { String environment = (String) assertion.get("environment"); - Instance f = sdkInstancesByEnvironment.get(environment); + FeaturevisorInstance f = sdkInstancesByEnvironment.get(environment); // If "at" parameter is provided, create a new SDK instance with the specific hook if (assertion.containsKey("at")) { @@ -631,7 +631,7 @@ private void test() { hooksManager.add(new HooksManager.Hook("at-parameter") .bucketValue((options) -> (int) (atValue * 1000))); - f = Featurevisor.createInstance(new Instance.InstanceOptions() + f = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .logLevel(level) .hooks(hooksManager.getAll())); @@ -710,7 +710,7 @@ private void benchmark() { Logger.LogLevel level = getLoggerLevel(); Map datafilesByEnvironment = buildDatafiles(rootDirectoryPath, Arrays.asList(environment)); - Instance f = Featurevisor.createInstance(new Instance.InstanceOptions() + FeaturevisorInstance f = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafilesByEnvironment.get(environment)) .logLevel(level)); @@ -773,7 +773,7 @@ private void assessDistribution() { Map datafilesByEnvironment = buildDatafiles(rootDirectoryPath, Arrays.asList(environment)); - Instance f = Featurevisor.createInstance(new Instance.InstanceOptions() + FeaturevisorInstance f = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafilesByEnvironment.get(environment)) .logLevel(getLoggerLevel())); diff --git a/src/main/java/com/featurevisor/sdk/ChildInstance.java b/src/main/java/com/featurevisor/sdk/ChildInstance.java index aedf6c0..0ce35f3 100644 --- a/src/main/java/com/featurevisor/sdk/ChildInstance.java +++ b/src/main/java/com/featurevisor/sdk/ChildInstance.java @@ -10,7 +10,7 @@ * Provides isolated context for individual requests/users */ public class ChildInstance { - private Instance parent; + private FeaturevisorInstance parent; private Map context; private Map sticky; private Emitter emitter; @@ -18,7 +18,7 @@ public class ChildInstance { /** * Constructor */ - public ChildInstance(Instance parent, Map context, Map sticky) { + public ChildInstance(FeaturevisorInstance parent, Map context, Map sticky) { this.parent = parent; this.context = context != null ? new HashMap<>(context) : new HashMap<>(); this.sticky = sticky; @@ -103,7 +103,7 @@ public void setSticky(Map sticky) { /** * Flag */ - public boolean isEnabled(String featureKey, Map context, Instance.OverrideOptions options) { + public boolean isEnabled(String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.isEnabled( featureKey, mergeContexts(this.context, context), @@ -122,7 +122,7 @@ public boolean isEnabled(String featureKey) { /** * Variation */ - public String getVariation(String featureKey, Map context, Instance.OverrideOptions options) { + public String getVariation(String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariation( featureKey, mergeContexts(this.context, context), @@ -141,7 +141,7 @@ public String getVariation(String featureKey) { /** * Variable */ - public Object getVariable(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public Object getVariable(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariable( featureKey, variableKey, @@ -158,7 +158,7 @@ public Object getVariable(String featureKey, String variableKey) { return getVariable(featureKey, variableKey, null, null); } - public Boolean getVariableBoolean(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public Boolean getVariableBoolean(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariableBoolean( featureKey, variableKey, @@ -175,7 +175,7 @@ public Boolean getVariableBoolean(String featureKey, String variableKey) { return getVariableBoolean(featureKey, variableKey, null, null); } - public String getVariableString(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public String getVariableString(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariableString( featureKey, variableKey, @@ -192,7 +192,7 @@ public String getVariableString(String featureKey, String variableKey) { return getVariableString(featureKey, variableKey, null, null); } - public Integer getVariableInteger(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public Integer getVariableInteger(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariableInteger( featureKey, variableKey, @@ -209,7 +209,7 @@ public Integer getVariableInteger(String featureKey, String variableKey) { return getVariableInteger(featureKey, variableKey, null, null); } - public Double getVariableDouble(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public Double getVariableDouble(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariableDouble( featureKey, variableKey, @@ -226,7 +226,7 @@ public Double getVariableDouble(String featureKey, String variableKey) { return getVariableDouble(featureKey, variableKey, null, null); } - public List getVariableArray(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public List getVariableArray(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariableArray( featureKey, variableKey, @@ -243,7 +243,7 @@ public List getVariableArray(String featureKey, String variableKey) { return getVariableArray(featureKey, variableKey, null, null); } - public T getVariableObject(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public T getVariableObject(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariableObject( featureKey, variableKey, @@ -260,7 +260,7 @@ public T getVariableObject(String featureKey, String variableKey) { return getVariableObject(featureKey, variableKey, null, null); } - public T getVariableJSON(String featureKey, String variableKey, Map context, Instance.OverrideOptions options) { + public T getVariableJSON(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { return this.parent.getVariableJSON( featureKey, variableKey, @@ -280,7 +280,7 @@ public T getVariableJSON(String featureKey, String variableKey) { /** * Get all evaluations */ - public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys, Instance.OverrideOptions options) { + public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys, FeaturevisorInstance.OverrideOptions options) { return this.parent.getAllEvaluations( mergeContexts(this.context, context), featureKeys, @@ -313,9 +313,9 @@ private Map mergeContexts(Map childContext, Map< return merged; } - private Instance.OverrideOptions mergeOverrideOptions(Instance.OverrideOptions options) { + private FeaturevisorInstance.OverrideOptions mergeOverrideOptions(FeaturevisorInstance.OverrideOptions options) { if (options == null) { - options = new Instance.OverrideOptions(); + options = new FeaturevisorInstance.OverrideOptions(); } if (this.sticky != null) { diff --git a/src/main/java/com/featurevisor/sdk/Featurevisor.java b/src/main/java/com/featurevisor/sdk/Featurevisor.java index 702e451..b14e590 100644 --- a/src/main/java/com/featurevisor/sdk/Featurevisor.java +++ b/src/main/java/com/featurevisor/sdk/Featurevisor.java @@ -14,19 +14,19 @@ public class Featurevisor { * @param options The instance options * @return A new Featurevisor instance */ - public static Instance createInstance(Instance.InstanceOptions options) { + public static FeaturevisorInstance createInstance(FeaturevisorInstance.InstanceOptions options) { if (options == null) { - options = new Instance.InstanceOptions(); + options = new FeaturevisorInstance.InstanceOptions(); } - return new Instance(options); + return new FeaturevisorInstance(options); } /** * Create a new Featurevisor instance with default options * @return A new Featurevisor instance */ - public static Instance createInstance() { - return createInstance(new Instance.InstanceOptions()); + public static FeaturevisorInstance createInstance() { + return createInstance(new FeaturevisorInstance.InstanceOptions()); } /** @@ -34,8 +34,8 @@ public static Instance createInstance() { * @param datafile The datafile content * @return A new Featurevisor instance */ - public static Instance createInstance(com.featurevisor.types.DatafileContent datafile) { - return createInstance(new Instance.InstanceOptions().datafile(datafile)); + public static FeaturevisorInstance createInstance(com.featurevisor.types.DatafileContent datafile) { + return createInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); } /** @@ -43,8 +43,8 @@ public static Instance createInstance(com.featurevisor.types.DatafileContent dat * @param datafileString The datafile as JSON string * @return A new Featurevisor instance */ - public static Instance createInstance(String datafileString) { - return createInstance(new Instance.InstanceOptions().datafileString(datafileString)); + public static FeaturevisorInstance createInstance(String datafileString) { + return createInstance(new FeaturevisorInstance.InstanceOptions().datafileString(datafileString)); } /** @@ -52,8 +52,8 @@ public static Instance createInstance(String datafileString) { * @param context The context map * @return A new Featurevisor instance */ - public static Instance createInstance(Map context) { - return createInstance(new Instance.InstanceOptions().context(context)); + public static FeaturevisorInstance createInstance(Map context) { + return createInstance(new FeaturevisorInstance.InstanceOptions().context(context)); } /** @@ -61,8 +61,8 @@ public static Instance createInstance(Map context) { * @param logLevel The log level * @return A new Featurevisor instance */ - public static Instance createInstance(Logger.LogLevel logLevel) { - return createInstance(new Instance.InstanceOptions().logLevel(logLevel)); + public static FeaturevisorInstance createInstance(Logger.LogLevel logLevel) { + return createInstance(new FeaturevisorInstance.InstanceOptions().logLevel(logLevel)); } /** @@ -70,8 +70,8 @@ public static Instance createInstance(Logger.LogLevel logLevel) { * @param logger The logger instance * @return A new Featurevisor instance */ - public static Instance createInstance(Logger logger) { - return createInstance(new Instance.InstanceOptions().logger(logger)); + public static FeaturevisorInstance createInstance(Logger logger) { + return createInstance(new FeaturevisorInstance.InstanceOptions().logger(logger)); } /** @@ -79,11 +79,11 @@ public static Instance createInstance(Logger logger) { * @param sticky The sticky features map * @return A new Featurevisor instance */ - public static Instance createInstance(Map sticky, boolean isSticky) { + public static FeaturevisorInstance createInstance(Map sticky, boolean isSticky) { if (isSticky) { - return createInstance(new Instance.InstanceOptions().sticky(sticky)); + return createInstance(new FeaturevisorInstance.InstanceOptions().sticky(sticky)); } else { - return createInstance(new Instance.InstanceOptions().context(sticky)); + return createInstance(new FeaturevisorInstance.InstanceOptions().context(sticky)); } } } diff --git a/src/main/java/com/featurevisor/sdk/Instance.java b/src/main/java/com/featurevisor/sdk/FeaturevisorInstance.java similarity index 99% rename from src/main/java/com/featurevisor/sdk/Instance.java rename to src/main/java/com/featurevisor/sdk/FeaturevisorInstance.java index 60a10d4..360d5ac 100644 --- a/src/main/java/com/featurevisor/sdk/Instance.java +++ b/src/main/java/com/featurevisor/sdk/FeaturevisorInstance.java @@ -14,7 +14,7 @@ * Main Featurevisor SDK instance * Provides the primary interface for feature flag evaluation */ -public class Instance { +public class FeaturevisorInstance { // from options private Map context = new HashMap<>(); private Logger logger; @@ -152,7 +152,7 @@ public OverrideOptions flagEvaluation(Evaluation flagEvaluation) { /** * Constructor */ - public Instance(InstanceOptions options) { + public FeaturevisorInstance(InstanceOptions options) { // from options if (options.getContext() != null) { this.context = new HashMap<>(options.getContext()); diff --git a/src/test/java/com/featurevisor/sdk/ChildTest.java b/src/test/java/com/featurevisor/sdk/ChildTest.java index ee0145b..eadfca1 100644 --- a/src/test/java/com/featurevisor/sdk/ChildTest.java +++ b/src/test/java/com/featurevisor/sdk/ChildTest.java @@ -249,7 +249,7 @@ public void testCreateChildInstance() { Map parentContext = new HashMap<>(); parentContext.put("appVersion", "1.0.0"); - Instance parentInstance = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance parentInstance = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .context(parentContext)); diff --git a/src/test/java/com/featurevisor/sdk/InstanceTest.java b/src/test/java/com/featurevisor/sdk/InstanceTest.java index 8a9ca06..7bef2ae 100644 --- a/src/test/java/com/featurevisor/sdk/InstanceTest.java +++ b/src/test/java/com/featurevisor/sdk/InstanceTest.java @@ -35,8 +35,8 @@ public void setUp() { @Test public void testCreateInstanceIsFunction() { - // This test verifies that Instance constructor exists - assertNotNull(Instance.class.getConstructors()); + // This test verifies that FeaturevisorInstance constructor exists + assertNotNull(FeaturevisorInstance.class.getConstructors()); } @Test @@ -58,7 +58,7 @@ public void testCreateInstanceWithDatafileContent() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); // Test that the SDK was created successfully assertNotNull(sdk); @@ -127,7 +127,7 @@ public void testConfigurePlainBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -202,7 +202,7 @@ public void testConfigureAndBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -279,7 +279,7 @@ public void testConfigureOrBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -365,7 +365,7 @@ public void testInterceptContextBeforeHook() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -450,7 +450,7 @@ public void testInterceptValueAfterHook() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -530,7 +530,7 @@ public void testInitializeWithStickyFeatures() throws InterruptedException { sticky.put("test", testSticky); - Instance sdk = new Instance(new Instance.InstanceOptions().sticky(sticky)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().sticky(sticky)); // initially control Map context = Map.of( @@ -597,7 +597,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); // should be disabled because required is disabled assertFalse(sdk.isEnabled("myKey")); @@ -645,7 +645,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - Instance sdk2 = new Instance(new Instance.InstanceOptions().datafile(datafileEnabled)); + FeaturevisorInstance sdk2 = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafileEnabled)); assertTrue(sdk2.isEnabled("myKey")); } @@ -716,7 +716,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); assertFalse(sdk.isEnabled("myKey")); @@ -785,7 +785,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - Instance sdk2 = new Instance(new Instance.InstanceOptions().datafile(datafileDesired)); + FeaturevisorInstance sdk2 = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafileDesired)); assertTrue(sdk2.isEnabled("myKey")); } @@ -877,7 +877,7 @@ public void testEmitWarningsForDeprecatedFeature() { } })); - Instance sdk = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .logger(customLogger)); @@ -954,7 +954,7 @@ public void testGetVariation() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1033,7 +1033,7 @@ public void testGetVariable() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1095,7 +1095,7 @@ public void testCheckIfEnabledForOverriddenFlagsFromRules() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); // Test with German user (should be enabled) Map context1 = new HashMap<>(); @@ -1192,7 +1192,7 @@ public void testGetVariationWithForceRules() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1263,7 +1263,7 @@ public void testCheckIfEnabledForMutuallyExclusiveFeatures() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions() + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -1457,7 +1457,7 @@ public void testGetVariableComprehensive() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1576,7 +1576,7 @@ public void testGetVariablesWithoutAnyVariationsWithSegments() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); Map defaultContext = Map.of( "userId", "123" @@ -1643,7 +1643,7 @@ public void testCheckIfEnabledForIndividuallyNamedSegments() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); // Test with no context (should be disabled) assertFalse(sdk.isEnabled("test")); From 275045e1c93f7fcc213999fc9c186a3f9aa2520f Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 16:32:11 +0200 Subject: [PATCH 2/8] rename FeaturevisorInstance to Featurevisor --- README.md | 20 +- src/main/java/com/featurevisor/cli/CLI.java | 63 +- .../com/featurevisor/sdk/ChildInstance.java | 30 +- .../com/featurevisor/sdk/Featurevisor.java | 721 ++++++++++++++++-- .../sdk/FeaturevisorInstance.java | 682 ----------------- .../java/com/featurevisor/sdk/ChildTest.java | 2 +- .../com/featurevisor/sdk/InstanceTest.java | 44 +- 7 files changed, 756 insertions(+), 806 deletions(-) delete mode 100644 src/main/java/com/featurevisor/sdk/FeaturevisorInstance.java diff --git a/README.md b/README.md index 65840c8..3400f79 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ See example application here: [https://github.com/featurevisor/featurevisor-exam The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly: ```java -import com.featurevisor.sdk.FeaturevisorInstance; +import com.featurevisor.sdk.Featurevisor; import com.featurevisor.types.DatafileContent; // Load datafile content @@ -128,8 +128,8 @@ String datafileContent = "..." // ... load your datafile content DatafileContent datafile = DatafileContent.fromJson(datafileContent); // Create SDK instance -FeaturevisorInstance f = new FeaturevisorInstance( - new FeaturevisorInstance.InstanceOptions() +Featurevisor f = new Featurevisor( + new Featurevisor.InstanceOptions() .datafile(datafileContent) ); ``` @@ -170,7 +170,7 @@ Map initialContext = new HashMap<>(); initialContext.put("deviceId", "123"); initialContext.put("country", "nl"); -FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() +Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .context(initialContext)); ``` @@ -357,7 +357,7 @@ Map anotherFeatureSticky = new HashMap<>(); anotherFeatureSticky.put("enabled", false); stickyFeatures.put("anotherFeatureKey", anotherFeatureSticky); -FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() +Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .sticky(stickyFeatures)); ``` @@ -433,7 +433,7 @@ Setting `debug` level will print out all logs, including `info`, `warn`, and `er ```java import com.featurevisor.sdk.Logger; -FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() +Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .logLevel(Logger.LogLevel.DEBUG)); ``` @@ -457,7 +457,7 @@ Logger customLogger = Logger.createLogger(new Logger.CreateLoggerOptions() System.out.println("[" + level + "] " + message); })); -FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() +Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .logger(customLogger)); ``` @@ -470,7 +470,7 @@ Logger customLogger = new Logger(Logger.LogLevel.INFO, (level, message, details) System.out.println("[" + level + "] " + message); }); -FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() +Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .logger(customLogger)); ``` @@ -637,7 +637,7 @@ You can register hooks at the time of SDK initialization: List> hooks = new ArrayList<>(); hooks.add(myCustomHook); -FeaturevisorInstance f = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() +Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .hooks(hooks)); ``` @@ -662,7 +662,7 @@ That's where child instances come in handy: Map childContext = new HashMap<>(); childContext.put("userId", "123"); -FeaturevisorInstance childF = f.spawn(childContext); +Featurevisor childF = f.spawn(childContext); ``` Now you can pass the child instance where your individual request is being handled, and you can continue to evaluate features targeting that specific user alone: diff --git a/src/main/java/com/featurevisor/cli/CLI.java b/src/main/java/com/featurevisor/cli/CLI.java index e40b625..318c1e9 100644 --- a/src/main/java/com/featurevisor/cli/CLI.java +++ b/src/main/java/com/featurevisor/cli/CLI.java @@ -6,7 +6,6 @@ import picocli.CommandLine.Parameters; import com.featurevisor.sdk.Featurevisor; -import com.featurevisor.sdk.FeaturevisorInstance; import com.featurevisor.sdk.Logger; import com.featurevisor.sdk.DatafileReader; import com.featurevisor.types.DatafileContent; @@ -250,9 +249,9 @@ private TestResult testFeature(Map assertion, String featureKey, Map sticky = (Map) assertion.getOrDefault("sticky", new HashMap<>()); // Update the SDK instance context and sticky values for this assertion - if (f instanceof FeaturevisorInstance) { - ((FeaturevisorInstance) f).setContext(context, true); - ((FeaturevisorInstance) f).setSticky(sticky, true); + if (f instanceof Featurevisor) { + ((Featurevisor) f).setContext(context, true); + ((Featurevisor) f).setSticky(sticky, true); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { ((com.featurevisor.sdk.ChildInstance) f).setContext(context, true); ((com.featurevisor.sdk.ChildInstance) f).setSticky(sticky, true); @@ -274,7 +273,7 @@ private TestResult testFeature(Map assertion, String featureKey, // Test expectedVariation if (assertion.containsKey("expectedVariation")) { - FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); + Featurevisor.OverrideOptions options = new Featurevisor.OverrideOptions(); if (assertion.containsKey("defaultVariationValue")) { options.setDefaultVariationValue(assertion.get("defaultVariationValue").toString()); } @@ -308,7 +307,7 @@ private TestResult testFeature(Map assertion, String featureKey, } } - FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); + Featurevisor.OverrideOptions options = new Featurevisor.OverrideOptions(); if (defaultVariableValues.containsKey(variableKey)) { options.setDefaultVariableValue(defaultVariableValues.get(variableKey)); } @@ -347,7 +346,7 @@ private TestResult testFeature(Map assertion, String featureKey, if (expectedEvaluations.containsKey("variation")) { @SuppressWarnings("unchecked") Map variationEvaluation = (Map) expectedEvaluations.get("variation"); - FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); + Featurevisor.OverrideOptions options = new Featurevisor.OverrideOptions(); if (assertion.containsKey("defaultVariationValue")) { options.setDefaultVariationValue(assertion.get("defaultVariationValue").toString()); } @@ -378,7 +377,7 @@ private TestResult testFeature(Map assertion, String featureKey, @SuppressWarnings("unchecked") Map expectedEvaluation = (Map) entry.getValue(); - FeaturevisorInstance.OverrideOptions options = new FeaturevisorInstance.OverrideOptions(); + Featurevisor.OverrideOptions options = new Featurevisor.OverrideOptions(); if (defaultVariableValues.containsKey(variableKey)) { options.setDefaultVariableValue(defaultVariableValues.get(variableKey)); } @@ -427,26 +426,26 @@ private TestResult testFeature(Map assertion, String featureKey, * Helper methods to work with both Instance and ChildInstance */ private boolean isEnabled(Object f, String featureKey, Map context) { - if (f instanceof FeaturevisorInstance) { - return ((FeaturevisorInstance) f).isEnabled(featureKey, context); + if (f instanceof Featurevisor) { + return ((Featurevisor) f).isEnabled(featureKey, context); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { return ((com.featurevisor.sdk.ChildInstance) f).isEnabled(featureKey, context); } return false; } - private Object getVariation(Object f, String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { - if (f instanceof FeaturevisorInstance) { - return ((FeaturevisorInstance) f).getVariation(featureKey, context, options); + private Object getVariation(Object f, String featureKey, Map context, Featurevisor.OverrideOptions options) { + if (f instanceof Featurevisor) { + return ((Featurevisor) f).getVariation(featureKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { return ((com.featurevisor.sdk.ChildInstance) f).getVariation(featureKey, context, options); } return null; } - private Object getVariable(Object f, String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { - if (f instanceof FeaturevisorInstance) { - return ((FeaturevisorInstance) f).getVariable(featureKey, variableKey, context, options); + private Object getVariable(Object f, String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { + if (f instanceof Featurevisor) { + return ((Featurevisor) f).getVariable(featureKey, variableKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { return ((com.featurevisor.sdk.ChildInstance) f).getVariable(featureKey, variableKey, context, options); } @@ -454,8 +453,8 @@ private Object getVariable(Object f, String featureKey, String variableKey, Map< } private com.featurevisor.sdk.Evaluation evaluateFlag(Object f, String featureKey, Map context) { - if (f instanceof FeaturevisorInstance) { - return ((FeaturevisorInstance) f).evaluateFlag(featureKey, context); + if (f instanceof Featurevisor) { + return ((Featurevisor) f).evaluateFlag(featureKey, context); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have evaluateFlag, so we'll skip this test for child instances return null; @@ -463,9 +462,9 @@ private com.featurevisor.sdk.Evaluation evaluateFlag(Object f, String featureKey return null; } - private com.featurevisor.sdk.Evaluation evaluateVariation(Object f, String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { - if (f instanceof FeaturevisorInstance) { - return ((FeaturevisorInstance) f).evaluateVariation(featureKey, context, options); + private com.featurevisor.sdk.Evaluation evaluateVariation(Object f, String featureKey, Map context, Featurevisor.OverrideOptions options) { + if (f instanceof Featurevisor) { + return ((Featurevisor) f).evaluateVariation(featureKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have evaluateVariation, so we'll skip this test for child instances return null; @@ -473,9 +472,9 @@ private com.featurevisor.sdk.Evaluation evaluateVariation(Object f, String featu return null; } - private com.featurevisor.sdk.Evaluation evaluateVariable(Object f, String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { - if (f instanceof FeaturevisorInstance) { - return ((FeaturevisorInstance) f).evaluateVariable(featureKey, variableKey, context, options); + private com.featurevisor.sdk.Evaluation evaluateVariable(Object f, String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { + if (f instanceof Featurevisor) { + return ((Featurevisor) f).evaluateVariable(featureKey, variableKey, context, options); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have evaluateVariable, so we'll skip this test for child instances return null; @@ -484,8 +483,8 @@ private com.featurevisor.sdk.Evaluation evaluateVariable(Object f, String featur } private com.featurevisor.sdk.ChildInstance spawn(Object f, Map context) { - if (f instanceof FeaturevisorInstance) { - return ((FeaturevisorInstance) f).spawn(context); + if (f instanceof Featurevisor) { + return ((Featurevisor) f).spawn(context); } else if (f instanceof com.featurevisor.sdk.ChildInstance) { // ChildInstance doesn't have spawn, so we'll return null return null; @@ -578,10 +577,10 @@ private void test() { } // Create SDK instances for each environment - Map sdkInstancesByEnvironment = new HashMap<>(); + Map sdkInstancesByEnvironment = new HashMap<>(); for (String environment : environments) { DatafileContent datafile = datafilesByEnvironment.get(environment); - FeaturevisorInstance instance = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor instance = Featurevisor.createInstance(new Featurevisor.InstanceOptions() .datafile(datafile) .logLevel(level)); sdkInstancesByEnvironment.put(environment, instance); @@ -610,7 +609,7 @@ private void test() { if (test.containsKey("feature")) { String environment = (String) assertion.get("environment"); - FeaturevisorInstance f = sdkInstancesByEnvironment.get(environment); + Featurevisor f = sdkInstancesByEnvironment.get(environment); // If "at" parameter is provided, create a new SDK instance with the specific hook if (assertion.containsKey("at")) { @@ -631,7 +630,7 @@ private void test() { hooksManager.add(new HooksManager.Hook("at-parameter") .bucketValue((options) -> (int) (atValue * 1000))); - f = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() + f = Featurevisor.createInstance(new Featurevisor.InstanceOptions() .datafile(datafile) .logLevel(level) .hooks(hooksManager.getAll())); @@ -710,7 +709,7 @@ private void benchmark() { Logger.LogLevel level = getLoggerLevel(); Map datafilesByEnvironment = buildDatafiles(rootDirectoryPath, Arrays.asList(environment)); - FeaturevisorInstance f = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor f = Featurevisor.createInstance(new Featurevisor.InstanceOptions() .datafile(datafilesByEnvironment.get(environment)) .logLevel(level)); @@ -773,7 +772,7 @@ private void assessDistribution() { Map datafilesByEnvironment = buildDatafiles(rootDirectoryPath, Arrays.asList(environment)); - FeaturevisorInstance f = Featurevisor.createInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor f = Featurevisor.createInstance(new Featurevisor.InstanceOptions() .datafile(datafilesByEnvironment.get(environment)) .logLevel(getLoggerLevel())); diff --git a/src/main/java/com/featurevisor/sdk/ChildInstance.java b/src/main/java/com/featurevisor/sdk/ChildInstance.java index 0ce35f3..374b212 100644 --- a/src/main/java/com/featurevisor/sdk/ChildInstance.java +++ b/src/main/java/com/featurevisor/sdk/ChildInstance.java @@ -10,7 +10,7 @@ * Provides isolated context for individual requests/users */ public class ChildInstance { - private FeaturevisorInstance parent; + private Featurevisor parent; private Map context; private Map sticky; private Emitter emitter; @@ -18,7 +18,7 @@ public class ChildInstance { /** * Constructor */ - public ChildInstance(FeaturevisorInstance parent, Map context, Map sticky) { + public ChildInstance(Featurevisor parent, Map context, Map sticky) { this.parent = parent; this.context = context != null ? new HashMap<>(context) : new HashMap<>(); this.sticky = sticky; @@ -103,7 +103,7 @@ public void setSticky(Map sticky) { /** * Flag */ - public boolean isEnabled(String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public boolean isEnabled(String featureKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.isEnabled( featureKey, mergeContexts(this.context, context), @@ -122,7 +122,7 @@ public boolean isEnabled(String featureKey) { /** * Variation */ - public String getVariation(String featureKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public String getVariation(String featureKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariation( featureKey, mergeContexts(this.context, context), @@ -141,7 +141,7 @@ public String getVariation(String featureKey) { /** * Variable */ - public Object getVariable(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public Object getVariable(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariable( featureKey, variableKey, @@ -158,7 +158,7 @@ public Object getVariable(String featureKey, String variableKey) { return getVariable(featureKey, variableKey, null, null); } - public Boolean getVariableBoolean(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public Boolean getVariableBoolean(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariableBoolean( featureKey, variableKey, @@ -175,7 +175,7 @@ public Boolean getVariableBoolean(String featureKey, String variableKey) { return getVariableBoolean(featureKey, variableKey, null, null); } - public String getVariableString(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public String getVariableString(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariableString( featureKey, variableKey, @@ -192,7 +192,7 @@ public String getVariableString(String featureKey, String variableKey) { return getVariableString(featureKey, variableKey, null, null); } - public Integer getVariableInteger(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public Integer getVariableInteger(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariableInteger( featureKey, variableKey, @@ -209,7 +209,7 @@ public Integer getVariableInteger(String featureKey, String variableKey) { return getVariableInteger(featureKey, variableKey, null, null); } - public Double getVariableDouble(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public Double getVariableDouble(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariableDouble( featureKey, variableKey, @@ -226,7 +226,7 @@ public Double getVariableDouble(String featureKey, String variableKey) { return getVariableDouble(featureKey, variableKey, null, null); } - public List getVariableArray(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public List getVariableArray(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariableArray( featureKey, variableKey, @@ -243,7 +243,7 @@ public List getVariableArray(String featureKey, String variableKey) { return getVariableArray(featureKey, variableKey, null, null); } - public T getVariableObject(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public T getVariableObject(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariableObject( featureKey, variableKey, @@ -260,7 +260,7 @@ public T getVariableObject(String featureKey, String variableKey) { return getVariableObject(featureKey, variableKey, null, null); } - public T getVariableJSON(String featureKey, String variableKey, Map context, FeaturevisorInstance.OverrideOptions options) { + public T getVariableJSON(String featureKey, String variableKey, Map context, Featurevisor.OverrideOptions options) { return this.parent.getVariableJSON( featureKey, variableKey, @@ -280,7 +280,7 @@ public T getVariableJSON(String featureKey, String variableKey) { /** * Get all evaluations */ - public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys, FeaturevisorInstance.OverrideOptions options) { + public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys, Featurevisor.OverrideOptions options) { return this.parent.getAllEvaluations( mergeContexts(this.context, context), featureKeys, @@ -313,9 +313,9 @@ private Map mergeContexts(Map childContext, Map< return merged; } - private FeaturevisorInstance.OverrideOptions mergeOverrideOptions(FeaturevisorInstance.OverrideOptions options) { + private Featurevisor.OverrideOptions mergeOverrideOptions(Featurevisor.OverrideOptions options) { if (options == null) { - options = new FeaturevisorInstance.OverrideOptions(); + options = new Featurevisor.OverrideOptions(); } if (this.sticky != null) { diff --git a/src/main/java/com/featurevisor/sdk/Featurevisor.java b/src/main/java/com/featurevisor/sdk/Featurevisor.java index b14e590..753c419 100644 --- a/src/main/java/com/featurevisor/sdk/Featurevisor.java +++ b/src/main/java/com/featurevisor/sdk/Featurevisor.java @@ -1,89 +1,722 @@ package com.featurevisor.sdk; +import com.featurevisor.types.DatafileContent; +import com.featurevisor.types.Feature; +import com.featurevisor.types.EvaluatedFeature; +import com.featurevisor.types.EvaluatedFeatures; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; /** - * Main entry point for Featurevisor SDK - * Provides factory methods for creating SDK instances + * Main Featurevisor SDK class + * Provides the primary interface for feature flag evaluation and factory methods */ public class Featurevisor { + // from options + private Map context = new HashMap<>(); + private Logger logger; + private Map sticky; + + // internally created + private DatafileReader datafileReader; + private HooksManager hooksManager; + private Emitter emitter; + + private static final DatafileContent emptyDatafile; + + static { + emptyDatafile = new DatafileContent(); + emptyDatafile.setSchemaVersion("2"); + emptyDatafile.setRevision("unknown"); + emptyDatafile.setSegments(new HashMap<>()); + emptyDatafile.setFeatures(new HashMap<>()); + } + + /** + * Options for creating an instance + */ + public static class InstanceOptions { + private DatafileContent datafile; + private String datafileString; + private Map context; + private Logger.LogLevel logLevel; + private Logger logger; + private Map sticky; + private List hooks; + + public InstanceOptions() {} + + // Getters + public DatafileContent getDatafile() { return datafile; } + public String getDatafileString() { return datafileString; } + public Map getContext() { return context; } + public Logger.LogLevel getLogLevel() { return logLevel; } + public Logger getLogger() { return logger; } + public Map getSticky() { return sticky; } + public List getHooks() { return hooks; } + + // Setters + public void setDatafile(DatafileContent datafile) { this.datafile = datafile; } + public void setDatafileString(String datafileString) { this.datafileString = datafileString; } + public void setContext(Map context) { this.context = context; } + public void setLogLevel(Logger.LogLevel logLevel) { this.logLevel = logLevel; } + public void setLogger(Logger logger) { this.logger = logger; } + public void setSticky(Map sticky) { this.sticky = sticky; } + public void setHooks(List hooks) { this.hooks = hooks; } + + // Builder pattern methods + public InstanceOptions datafile(DatafileContent datafile) { + this.datafile = datafile; + return this; + } + + public InstanceOptions datafileString(String datafileString) { + this.datafileString = datafileString; + return this; + } + + public InstanceOptions context(Map context) { + this.context = context; + return this; + } + + public InstanceOptions logLevel(Logger.LogLevel logLevel) { + this.logLevel = logLevel; + return this; + } + + public InstanceOptions logger(Logger logger) { + this.logger = logger; + return this; + } + + public InstanceOptions sticky(Map sticky) { + this.sticky = sticky; + return this; + } + + public InstanceOptions hooks(List hooks) { + this.hooks = hooks; + return this; + } + } + + /** + * Options for overriding evaluation behavior + */ + public static class OverrideOptions { + private Map sticky; + private String defaultVariationValue; + private Object defaultVariableValue; + private Evaluation flagEvaluation; + + public OverrideOptions() {} + + // Getters + public Map getSticky() { return sticky; } + public String getDefaultVariationValue() { return defaultVariationValue; } + public Object getDefaultVariableValue() { return defaultVariableValue; } + public Evaluation getFlagEvaluation() { return flagEvaluation; } + + // Setters + public void setSticky(Map sticky) { this.sticky = sticky; } + public void setDefaultVariationValue(String defaultVariationValue) { this.defaultVariationValue = defaultVariationValue; } + public void setDefaultVariableValue(Object defaultVariableValue) { this.defaultVariableValue = defaultVariableValue; } + public void setFlagEvaluation(Evaluation flagEvaluation) { this.flagEvaluation = flagEvaluation; } + + // Builder pattern methods + public OverrideOptions sticky(Map sticky) { + this.sticky = sticky; + return this; + } + + public OverrideOptions defaultVariationValue(String defaultVariationValue) { + this.defaultVariationValue = defaultVariationValue; + return this; + } + + public OverrideOptions defaultVariableValue(Object defaultVariableValue) { + this.defaultVariableValue = defaultVariableValue; + return this; + } + + public OverrideOptions flagEvaluation(Evaluation flagEvaluation) { + this.flagEvaluation = flagEvaluation; + return this; + } + } + + /** + * Constructor + */ + public Featurevisor(InstanceOptions options) { + // from options + if (options.getContext() != null) { + this.context = new HashMap<>(options.getContext()); + } + + this.logger = options.getLogger() != null ? + options.getLogger() : + Logger.createLogger(new Logger.CreateLoggerOptions().level( + options.getLogLevel() != null ? options.getLogLevel() : Logger.LogLevel.INFO + )); + + this.hooksManager = new HooksManager(new HooksManager.HooksManagerOptions(this.logger) + .hooks(options.getHooks() != null ? options.getHooks() : new ArrayList<>())); + + this.emitter = new Emitter(); + this.sticky = options.getSticky(); + + // datafile + this.datafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() + .datafile(emptyDatafile) + .logger(this.logger)); + + if (options.getDatafile() != null) { + this.datafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() + .datafile(options.getDatafile()) + .logger(this.logger)); + } else if (options.getDatafileString() != null) { + try { + ObjectMapper mapper = new ObjectMapper(); + DatafileContent datafile = mapper.readValue(options.getDatafileString(), DatafileContent.class); + this.datafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() + .datafile(datafile) + .logger(this.logger)); + } catch (Exception e) { + this.logger.error("could not parse datafile string", Map.of("error", e.getMessage())); + } + } + + this.logger.info("Featurevisor SDK initialized", null); + } /** - * Create a new Featurevisor instance - * @param options The instance options - * @return A new Featurevisor instance + * Factory methods */ - public static FeaturevisorInstance createInstance(FeaturevisorInstance.InstanceOptions options) { + public static Featurevisor createInstance(InstanceOptions options) { if (options == null) { - options = new FeaturevisorInstance.InstanceOptions(); + options = new InstanceOptions(); + } + return new Featurevisor(options); + } + + public static Featurevisor createInstance() { + return createInstance(new InstanceOptions()); + } + + public static Featurevisor createInstance(com.featurevisor.types.DatafileContent datafile) { + return createInstance(new InstanceOptions().datafile(datafile)); + } + + public static Featurevisor createInstance(String datafileString) { + return createInstance(new InstanceOptions().datafileString(datafileString)); + } + + public static Featurevisor createInstance(Map context) { + return createInstance(new InstanceOptions().context(context)); + } + + public static Featurevisor createInstance(Logger.LogLevel logLevel) { + return createInstance(new InstanceOptions().logLevel(logLevel)); + } + + public static Featurevisor createInstance(Logger logger) { + return createInstance(new InstanceOptions().logger(logger)); + } + + public static Featurevisor createInstance(Map sticky, boolean isSticky) { + if (isSticky) { + return createInstance(new InstanceOptions().sticky(sticky)); + } else { + return createInstance(new InstanceOptions().context(sticky)); + } + } + + /** + * Set log level + */ + public void setLogLevel(Logger.LogLevel level) { + this.logger.setLevel(level); + } + + /** + * Set datafile + */ + public void setDatafile(DatafileContent datafile) { + try { + DatafileReader newDatafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() + .datafile(datafile) + .logger(this.logger)); + + Emitter.EventDetails details = Events.getParamsForDatafileSetEvent( + this.datafileReader.getDatafile(), newDatafileReader.getDatafile()); + + this.datafileReader = newDatafileReader; + + this.logger.info("datafile set", details); + this.emitter.trigger(Emitter.EventName.DATAFILE_SET, details); + } catch (Exception e) { + this.logger.error("could not parse datafile", Map.of("error", e.getMessage())); } - return new FeaturevisorInstance(options); } /** - * Create a new Featurevisor instance with default options - * @return A new Featurevisor instance + * Set datafile from string */ - public static FeaturevisorInstance createInstance() { - return createInstance(new FeaturevisorInstance.InstanceOptions()); + public void setDatafile(String datafileString) { + try { + ObjectMapper mapper = new ObjectMapper(); + DatafileContent datafile = mapper.readValue(datafileString, DatafileContent.class); + setDatafile(datafile); + } catch (Exception e) { + this.logger.error("could not parse datafile string", Map.of("error", e.getMessage())); + } } /** - * Create a new Featurevisor instance with datafile - * @param datafile The datafile content - * @return A new Featurevisor instance + * Set sticky features */ - public static FeaturevisorInstance createInstance(com.featurevisor.types.DatafileContent datafile) { - return createInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + public void setSticky(Map sticky, boolean replace) { + Map previousStickyFeatures = this.sticky != null ? + new HashMap<>(this.sticky) : new HashMap<>(); + + if (replace) { + this.sticky = new HashMap<>(sticky); + } else { + this.sticky = new HashMap<>(this.sticky != null ? this.sticky : new HashMap<>()); + this.sticky.putAll(sticky); + } + + Emitter.EventDetails params = Events.getParamsForStickySetEvent( + previousStickyFeatures, this.sticky, replace); + + this.logger.info("sticky features set", params); + this.emitter.trigger(Emitter.EventName.STICKY_SET, params); } /** - * Create a new Featurevisor instance with datafile string - * @param datafileString The datafile as JSON string - * @return A new Featurevisor instance + * Get revision */ - public static FeaturevisorInstance createInstance(String datafileString) { - return createInstance(new FeaturevisorInstance.InstanceOptions().datafileString(datafileString)); + public String getRevision() { + return this.datafileReader.getRevision(); } /** - * Create a new Featurevisor instance with context - * @param context The context map - * @return A new Featurevisor instance + * Get feature */ - public static FeaturevisorInstance createInstance(Map context) { - return createInstance(new FeaturevisorInstance.InstanceOptions().context(context)); + public Feature getFeature(String featureKey) { + return this.datafileReader.getFeature(featureKey); } /** - * Create a new Featurevisor instance with log level - * @param logLevel The log level - * @return A new Featurevisor instance + * Add hook */ - public static FeaturevisorInstance createInstance(Logger.LogLevel logLevel) { - return createInstance(new FeaturevisorInstance.InstanceOptions().logLevel(logLevel)); + public Runnable addHook(HooksManager.Hook hook) { + return this.hooksManager.add(hook); } /** - * Create a new Featurevisor instance with logger - * @param logger The logger instance - * @return A new Featurevisor instance + * Subscribe to event */ - public static FeaturevisorInstance createInstance(Logger logger) { - return createInstance(new FeaturevisorInstance.InstanceOptions().logger(logger)); + public Emitter.UnsubscribeFunction on(Emitter.EventName eventName, Emitter.EventCallback callback) { + return this.emitter.on(eventName, callback); } /** - * Create a new Featurevisor instance with sticky features - * @param sticky The sticky features map - * @return A new Featurevisor instance + * Close instance */ - public static FeaturevisorInstance createInstance(Map sticky, boolean isSticky) { - if (isSticky) { - return createInstance(new FeaturevisorInstance.InstanceOptions().sticky(sticky)); + public void close() { + this.emitter.clearAll(); + } + + /** + * Context + */ + public void setContext(Map context, boolean replace) { + if (replace) { + this.context = new HashMap<>(context); } else { - return createInstance(new FeaturevisorInstance.InstanceOptions().context(sticky)); + this.context = new HashMap<>(this.context); + this.context.putAll(context); + } + + Emitter.EventDetails eventDetails = new Emitter.EventDetails(); + eventDetails.put("context", this.context); + eventDetails.put("replaced", replace); + + this.emitter.trigger(Emitter.EventName.CONTEXT_SET, eventDetails); + this.logger.debug(replace ? "context replaced" : "context updated", eventDetails); + } + + public Map getContext(Map context) { + if (context != null && !context.isEmpty()) { + Map mergedContext = new HashMap<>(this.context); + mergedContext.putAll(context); + return mergedContext; + } + return new HashMap<>(this.context); + } + + public Map getContext() { + return new HashMap<>(this.context); + } + + /** + * Spawn child instance + */ + public ChildInstance spawn(Map context, OverrideOptions options) { + if (context == null) { + context = new HashMap<>(); + } + if (options == null) { + options = new OverrideOptions(); + } + + return new ChildInstance(this, getContext(context), options.getSticky()); + } + + public ChildInstance spawn(Map context) { + return spawn(context, null); + } + + public ChildInstance spawn() { + return spawn(null, null); + } + + /** + * Flag + */ + private EvaluateOptions getEvaluationDependencies(Map context, OverrideOptions options) { + if (context == null) { + context = new HashMap<>(); + } + if (options == null) { + options = new OverrideOptions(); + } + + Map mergedSticky = this.sticky; + if (options.getSticky() != null) { + if (this.sticky != null) { + mergedSticky = new HashMap<>(this.sticky); + mergedSticky.putAll(options.getSticky()); + } else { + mergedSticky = options.getSticky(); + } } + + return new EvaluateOptions() + .context(getContext(context)) + .logger(this.logger) + .hooksManager(this.hooksManager) + .datafileReader(this.datafileReader) + .sticky(mergedSticky) + .defaultVariationValue(options.getDefaultVariationValue()) + .defaultVariableValue(options.getDefaultVariableValue()) + .flagEvaluation(options.getFlagEvaluation()); + } + + public Evaluation evaluateFlag(String featureKey, Map context, OverrideOptions options) { + EvaluateOptions evaluateOptions = getEvaluationDependencies(context, options) + .type(Evaluation.TYPE_FLAG) + .featureKey(featureKey); + + return Evaluate.evaluateWithHooks(evaluateOptions); + } + + public Evaluation evaluateFlag(String featureKey, Map context) { + return evaluateFlag(featureKey, context, null); + } + + public Evaluation evaluateFlag(String featureKey) { + return evaluateFlag(featureKey, null, null); + } + + public boolean isEnabled(String featureKey, Map context, OverrideOptions options) { + try { + Evaluation evaluation = evaluateFlag(featureKey, context, options); + return Boolean.TRUE.equals(evaluation.getEnabled()); + } catch (Exception e) { + this.logger.error("isEnabled", Map.of("featureKey", featureKey, "error", e.getMessage())); + return false; + } + } + + public boolean isEnabled(String featureKey, Map context) { + return isEnabled(featureKey, context, null); + } + + public boolean isEnabled(String featureKey) { + return isEnabled(featureKey, null, null); + } + + /** + * Variation + */ + public Evaluation evaluateVariation(String featureKey, Map context, OverrideOptions options) { + EvaluateOptions evaluateOptions = getEvaluationDependencies(context, options) + .type(Evaluation.TYPE_VARIATION) + .featureKey(featureKey); + + return Evaluate.evaluateWithHooks(evaluateOptions); + } + + public Evaluation evaluateVariation(String featureKey, Map context) { + return evaluateVariation(featureKey, context, null); + } + + public Evaluation evaluateVariation(String featureKey) { + return evaluateVariation(featureKey, null, null); + } + + public String getVariation(String featureKey, Map context, OverrideOptions options) { + try { + Evaluation evaluation = evaluateVariation(featureKey, context, options); + + if (evaluation.getVariationValue() != null) { + return evaluation.getVariationValue(); + } + + if (evaluation.getVariation() != null) { + return evaluation.getVariation().getValue(); + } + + return null; + } catch (Exception e) { + this.logger.error("getVariation", Map.of("featureKey", featureKey, "error", e.getMessage())); + return null; + } + } + + public String getVariation(String featureKey, Map context) { + return getVariation(featureKey, context, null); + } + + public String getVariation(String featureKey) { + return getVariation(featureKey, null, null); + } + + /** + * Variable + */ + public Evaluation evaluateVariable(String featureKey, String variableKey, Map context, OverrideOptions options) { + EvaluateOptions evaluateOptions = getEvaluationDependencies(context, options) + .type(Evaluation.TYPE_VARIABLE) + .featureKey(featureKey) + .variableKey(variableKey); + + return Evaluate.evaluateWithHooks(evaluateOptions); + } + + public Evaluation evaluateVariable(String featureKey, String variableKey, Map context) { + return evaluateVariable(featureKey, variableKey, context, null); + } + + public Evaluation evaluateVariable(String featureKey, String variableKey) { + return evaluateVariable(featureKey, variableKey, null, null); + } + + public Object getVariable(String featureKey, String variableKey, Map context, OverrideOptions options) { + try { + Evaluation evaluation = evaluateVariable(featureKey, variableKey, context, options); + + if (evaluation.getVariableValue() != null) { + Object value = evaluation.getVariableValue(); + if (value instanceof String) { + String strValue = (String) value; + boolean looksLikeJson = (strValue.startsWith("{") && strValue.endsWith("}")) || + (strValue.startsWith("[") && strValue.endsWith("]")); + boolean isJsonType = evaluation.getVariableSchema() != null && + "json".equals(evaluation.getVariableSchema().getType()); + if (isJsonType || looksLikeJson) { + try { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(strValue, Object.class); + } catch (Exception e) { + // fallback to string + } + } + } + return value; + } + + return null; + } catch (Exception e) { + this.logger.error("getVariable", Map.of("featureKey", featureKey, "variableKey", variableKey, "error", e.getMessage())); + return null; + } + } + + public Object getVariable(String featureKey, String variableKey, Map context) { + return getVariable(featureKey, variableKey, context, null); + } + + public Object getVariable(String featureKey, String variableKey) { + return getVariable(featureKey, variableKey, null, null); + } + + public Boolean getVariableBoolean(String featureKey, String variableKey, Map context, OverrideOptions options) { + Object variableValue = getVariable(featureKey, variableKey, context, options); + return Helpers.getValueByType(variableValue, "boolean"); + } + + public Boolean getVariableBoolean(String featureKey, String variableKey, Map context) { + return getVariableBoolean(featureKey, variableKey, context, null); + } + + public Boolean getVariableBoolean(String featureKey, String variableKey) { + return getVariableBoolean(featureKey, variableKey, null, null); + } + + public String getVariableString(String featureKey, String variableKey, Map context, OverrideOptions options) { + Object variableValue = getVariable(featureKey, variableKey, context, options); + return Helpers.getValueByType(variableValue, "string"); + } + + public String getVariableString(String featureKey, String variableKey, Map context) { + return getVariableString(featureKey, variableKey, context, null); + } + + public String getVariableString(String featureKey, String variableKey) { + return getVariableString(featureKey, variableKey, null, null); + } + + public Integer getVariableInteger(String featureKey, String variableKey, Map context, OverrideOptions options) { + Object variableValue = getVariable(featureKey, variableKey, context, options); + return (Integer) Helpers.getValueByType(variableValue, "integer"); + } + + public Integer getVariableInteger(String featureKey, String variableKey, Map context) { + return getVariableInteger(featureKey, variableKey, context, null); + } + + public Integer getVariableInteger(String featureKey, String variableKey) { + return getVariableInteger(featureKey, variableKey, null, null); + } + + public Double getVariableDouble(String featureKey, String variableKey, Map context, OverrideOptions options) { + Object variableValue = getVariable(featureKey, variableKey, context, options); + return (Double) Helpers.getValueByType(variableValue, "double"); + } + + public Double getVariableDouble(String featureKey, String variableKey, Map context) { + return getVariableDouble(featureKey, variableKey, context, null); + } + + public Double getVariableDouble(String featureKey, String variableKey) { + return getVariableDouble(featureKey, variableKey, null, null); + } + + @SuppressWarnings("unchecked") + public List getVariableArray(String featureKey, String variableKey, Map context, OverrideOptions options) { + Object variableValue = getVariable(featureKey, variableKey, context, options); + return Helpers.getValueByType(variableValue, "array"); + } + + public List getVariableArray(String featureKey, String variableKey, Map context) { + return getVariableArray(featureKey, variableKey, context, null); + } + + public List getVariableArray(String featureKey, String variableKey) { + return getVariableArray(featureKey, variableKey, null, null); + } + + @SuppressWarnings("unchecked") + public T getVariableObject(String featureKey, String variableKey, Map context, OverrideOptions options) { + Object variableValue = getVariable(featureKey, variableKey, context, options); + return Helpers.getValueByType(variableValue, "object"); + } + + public T getVariableObject(String featureKey, String variableKey, Map context) { + return getVariableObject(featureKey, variableKey, context, null); + } + + public T getVariableObject(String featureKey, String variableKey) { + return getVariableObject(featureKey, variableKey, null, null); + } + + @SuppressWarnings("unchecked") + public T getVariableJSON(String featureKey, String variableKey, Map context, OverrideOptions options) { + Object variableValue = getVariable(featureKey, variableKey, context, options); + return Helpers.getValueByType(variableValue, "json"); + } + + public T getVariableJSON(String featureKey, String variableKey, Map context) { + return getVariableJSON(featureKey, variableKey, context, null); + } + + public T getVariableJSON(String featureKey, String variableKey) { + return getVariableJSON(featureKey, variableKey, null, null); + } + + /** + * Get all evaluations + */ + public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys, OverrideOptions options) { + if (context == null) { + context = new HashMap<>(); + } + if (featureKeys == null) { + featureKeys = new ArrayList<>(); + } + if (options == null) { + options = new OverrideOptions(); + } + + Map result = new HashMap<>(); + + List keys = featureKeys.isEmpty() ? this.datafileReader.getFeatureKeys() : featureKeys; + for (String featureKey : keys) { + // isEnabled + Evaluation flagEvaluation = evaluateFlag(featureKey, context, options); + + EvaluatedFeature evaluatedFeature = new EvaluatedFeature(); + evaluatedFeature.setEnabled(Boolean.TRUE.equals(flagEvaluation.getEnabled())); + + OverrideOptions opts = new OverrideOptions() + .sticky(options.getSticky()) + .defaultVariationValue(options.getDefaultVariationValue()) + .defaultVariableValue(options.getDefaultVariableValue()) + .flagEvaluation(flagEvaluation); + + // variation + if (this.datafileReader.hasVariations(featureKey)) { + Object variation = getVariation(featureKey, context, opts); + if (variation != null) { + evaluatedFeature.setVariation(variation.toString()); + } + } + + // variables + List variableKeys = this.datafileReader.getVariableKeys(featureKey); + if (!variableKeys.isEmpty()) { + Map variables = new HashMap<>(); + + for (String variableKey : variableKeys) { + variables.put(variableKey, getVariable(featureKey, variableKey, context, opts)); + } + + evaluatedFeature.setVariables(variables); + } + + result.put(featureKey, evaluatedFeature); + } + + return EvaluatedFeatures.of(result); + } + + public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys) { + return getAllEvaluations(context, featureKeys, null); + } + + public EvaluatedFeatures getAllEvaluations(Map context) { + return getAllEvaluations(context, null, null); + } + + public EvaluatedFeatures getAllEvaluations() { + return getAllEvaluations(null, null, null); } } diff --git a/src/main/java/com/featurevisor/sdk/FeaturevisorInstance.java b/src/main/java/com/featurevisor/sdk/FeaturevisorInstance.java deleted file mode 100644 index 360d5ac..0000000 --- a/src/main/java/com/featurevisor/sdk/FeaturevisorInstance.java +++ /dev/null @@ -1,682 +0,0 @@ -package com.featurevisor.sdk; - -import com.featurevisor.types.DatafileContent; -import com.featurevisor.types.Feature; -import com.featurevisor.types.EvaluatedFeature; -import com.featurevisor.types.EvaluatedFeatures; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.Map; -import java.util.HashMap; -import java.util.List; -import java.util.ArrayList; - -/** - * Main Featurevisor SDK instance - * Provides the primary interface for feature flag evaluation - */ -public class FeaturevisorInstance { - // from options - private Map context = new HashMap<>(); - private Logger logger; - private Map sticky; - - // internally created - private DatafileReader datafileReader; - private HooksManager hooksManager; - private Emitter emitter; - - private static final DatafileContent emptyDatafile; - - static { - emptyDatafile = new DatafileContent(); - emptyDatafile.setSchemaVersion("2"); - emptyDatafile.setRevision("unknown"); - emptyDatafile.setSegments(new HashMap<>()); - emptyDatafile.setFeatures(new HashMap<>()); - } - - /** - * Options for creating an instance - */ - public static class InstanceOptions { - private DatafileContent datafile; - private String datafileString; - private Map context; - private Logger.LogLevel logLevel; - private Logger logger; - private Map sticky; - private List hooks; - - public InstanceOptions() {} - - // Getters - public DatafileContent getDatafile() { return datafile; } - public String getDatafileString() { return datafileString; } - public Map getContext() { return context; } - public Logger.LogLevel getLogLevel() { return logLevel; } - public Logger getLogger() { return logger; } - public Map getSticky() { return sticky; } - public List getHooks() { return hooks; } - - // Setters - public void setDatafile(DatafileContent datafile) { this.datafile = datafile; } - public void setDatafileString(String datafileString) { this.datafileString = datafileString; } - public void setContext(Map context) { this.context = context; } - public void setLogLevel(Logger.LogLevel logLevel) { this.logLevel = logLevel; } - public void setLogger(Logger logger) { this.logger = logger; } - public void setSticky(Map sticky) { this.sticky = sticky; } - public void setHooks(List hooks) { this.hooks = hooks; } - - // Builder pattern methods - public InstanceOptions datafile(DatafileContent datafile) { - this.datafile = datafile; - return this; - } - - public InstanceOptions datafileString(String datafileString) { - this.datafileString = datafileString; - return this; - } - - public InstanceOptions context(Map context) { - this.context = context; - return this; - } - - public InstanceOptions logLevel(Logger.LogLevel logLevel) { - this.logLevel = logLevel; - return this; - } - - public InstanceOptions logger(Logger logger) { - this.logger = logger; - return this; - } - - public InstanceOptions sticky(Map sticky) { - this.sticky = sticky; - return this; - } - - public InstanceOptions hooks(List hooks) { - this.hooks = hooks; - return this; - } - } - - /** - * Options for overriding evaluation behavior - */ - public static class OverrideOptions { - private Map sticky; - private String defaultVariationValue; - private Object defaultVariableValue; - private Evaluation flagEvaluation; - - public OverrideOptions() {} - - // Getters - public Map getSticky() { return sticky; } - public String getDefaultVariationValue() { return defaultVariationValue; } - public Object getDefaultVariableValue() { return defaultVariableValue; } - public Evaluation getFlagEvaluation() { return flagEvaluation; } - - // Setters - public void setSticky(Map sticky) { this.sticky = sticky; } - public void setDefaultVariationValue(String defaultVariationValue) { this.defaultVariationValue = defaultVariationValue; } - public void setDefaultVariableValue(Object defaultVariableValue) { this.defaultVariableValue = defaultVariableValue; } - public void setFlagEvaluation(Evaluation flagEvaluation) { this.flagEvaluation = flagEvaluation; } - - // Builder pattern methods - public OverrideOptions sticky(Map sticky) { - this.sticky = sticky; - return this; - } - - public OverrideOptions defaultVariationValue(String defaultVariationValue) { - this.defaultVariationValue = defaultVariationValue; - return this; - } - - public OverrideOptions defaultVariableValue(Object defaultVariableValue) { - this.defaultVariableValue = defaultVariableValue; - return this; - } - - public OverrideOptions flagEvaluation(Evaluation flagEvaluation) { - this.flagEvaluation = flagEvaluation; - return this; - } - } - - /** - * Constructor - */ - public FeaturevisorInstance(InstanceOptions options) { - // from options - if (options.getContext() != null) { - this.context = new HashMap<>(options.getContext()); - } - - this.logger = options.getLogger() != null ? - options.getLogger() : - Logger.createLogger(new Logger.CreateLoggerOptions().level( - options.getLogLevel() != null ? options.getLogLevel() : Logger.LogLevel.INFO - )); - - this.hooksManager = new HooksManager(new HooksManager.HooksManagerOptions(this.logger) - .hooks(options.getHooks() != null ? options.getHooks() : new ArrayList<>())); - - this.emitter = new Emitter(); - this.sticky = options.getSticky(); - - // datafile - this.datafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() - .datafile(emptyDatafile) - .logger(this.logger)); - - if (options.getDatafile() != null) { - this.datafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() - .datafile(options.getDatafile()) - .logger(this.logger)); - } else if (options.getDatafileString() != null) { - try { - ObjectMapper mapper = new ObjectMapper(); - DatafileContent datafile = mapper.readValue(options.getDatafileString(), DatafileContent.class); - this.datafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() - .datafile(datafile) - .logger(this.logger)); - } catch (Exception e) { - this.logger.error("could not parse datafile string", Map.of("error", e.getMessage())); - } - } - - this.logger.info("Featurevisor SDK initialized", null); - } - - /** - * Set log level - */ - public void setLogLevel(Logger.LogLevel level) { - this.logger.setLevel(level); - } - - /** - * Set datafile - */ - public void setDatafile(DatafileContent datafile) { - try { - DatafileReader newDatafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() - .datafile(datafile) - .logger(this.logger)); - - Emitter.EventDetails details = Events.getParamsForDatafileSetEvent( - this.datafileReader.getDatafile(), newDatafileReader.getDatafile()); - - this.datafileReader = newDatafileReader; - - this.logger.info("datafile set", details); - this.emitter.trigger(Emitter.EventName.DATAFILE_SET, details); - } catch (Exception e) { - this.logger.error("could not parse datafile", Map.of("error", e.getMessage())); - } - } - - /** - * Set datafile from string - */ - public void setDatafile(String datafileString) { - try { - ObjectMapper mapper = new ObjectMapper(); - DatafileContent datafile = mapper.readValue(datafileString, DatafileContent.class); - setDatafile(datafile); - } catch (Exception e) { - this.logger.error("could not parse datafile string", Map.of("error", e.getMessage())); - } - } - - /** - * Set sticky features - */ - public void setSticky(Map sticky, boolean replace) { - Map previousStickyFeatures = this.sticky != null ? - new HashMap<>(this.sticky) : new HashMap<>(); - - if (replace) { - this.sticky = new HashMap<>(sticky); - } else { - this.sticky = new HashMap<>(this.sticky != null ? this.sticky : new HashMap<>()); - this.sticky.putAll(sticky); - } - - Emitter.EventDetails params = Events.getParamsForStickySetEvent( - previousStickyFeatures, this.sticky, replace); - - this.logger.info("sticky features set", params); - this.emitter.trigger(Emitter.EventName.STICKY_SET, params); - } - - /** - * Get revision - */ - public String getRevision() { - return this.datafileReader.getRevision(); - } - - /** - * Get feature - */ - public Feature getFeature(String featureKey) { - return this.datafileReader.getFeature(featureKey); - } - - /** - * Add hook - */ - public Runnable addHook(HooksManager.Hook hook) { - return this.hooksManager.add(hook); - } - - /** - * Subscribe to event - */ - public Emitter.UnsubscribeFunction on(Emitter.EventName eventName, Emitter.EventCallback callback) { - return this.emitter.on(eventName, callback); - } - - /** - * Close instance - */ - public void close() { - this.emitter.clearAll(); - } - - /** - * Context - */ - public void setContext(Map context, boolean replace) { - if (replace) { - this.context = new HashMap<>(context); - } else { - this.context = new HashMap<>(this.context); - this.context.putAll(context); - } - - Emitter.EventDetails eventDetails = new Emitter.EventDetails(); - eventDetails.put("context", this.context); - eventDetails.put("replaced", replace); - - this.emitter.trigger(Emitter.EventName.CONTEXT_SET, eventDetails); - this.logger.debug(replace ? "context replaced" : "context updated", eventDetails); - } - - public Map getContext(Map context) { - if (context != null && !context.isEmpty()) { - Map mergedContext = new HashMap<>(this.context); - mergedContext.putAll(context); - return mergedContext; - } - return new HashMap<>(this.context); - } - - public Map getContext() { - return new HashMap<>(this.context); - } - - /** - * Spawn child instance - */ - public ChildInstance spawn(Map context, OverrideOptions options) { - if (context == null) { - context = new HashMap<>(); - } - if (options == null) { - options = new OverrideOptions(); - } - - return new ChildInstance(this, getContext(context), options.getSticky()); - } - - public ChildInstance spawn(Map context) { - return spawn(context, null); - } - - public ChildInstance spawn() { - return spawn(null, null); - } - - /** - * Flag - */ - private EvaluateOptions getEvaluationDependencies(Map context, OverrideOptions options) { - if (context == null) { - context = new HashMap<>(); - } - if (options == null) { - options = new OverrideOptions(); - } - - Map mergedSticky = this.sticky; - if (options.getSticky() != null) { - if (this.sticky != null) { - mergedSticky = new HashMap<>(this.sticky); - mergedSticky.putAll(options.getSticky()); - } else { - mergedSticky = options.getSticky(); - } - } - - return new EvaluateOptions() - .context(getContext(context)) - .logger(this.logger) - .hooksManager(this.hooksManager) - .datafileReader(this.datafileReader) - .sticky(mergedSticky) - .defaultVariationValue(options.getDefaultVariationValue()) - .defaultVariableValue(options.getDefaultVariableValue()) - .flagEvaluation(options.getFlagEvaluation()); - } - - public Evaluation evaluateFlag(String featureKey, Map context, OverrideOptions options) { - EvaluateOptions evaluateOptions = getEvaluationDependencies(context, options) - .type(Evaluation.TYPE_FLAG) - .featureKey(featureKey); - - return Evaluate.evaluateWithHooks(evaluateOptions); - } - - public Evaluation evaluateFlag(String featureKey, Map context) { - return evaluateFlag(featureKey, context, null); - } - - public Evaluation evaluateFlag(String featureKey) { - return evaluateFlag(featureKey, null, null); - } - - public boolean isEnabled(String featureKey, Map context, OverrideOptions options) { - try { - Evaluation evaluation = evaluateFlag(featureKey, context, options); - return Boolean.TRUE.equals(evaluation.getEnabled()); - } catch (Exception e) { - this.logger.error("isEnabled", Map.of("featureKey", featureKey, "error", e.getMessage())); - return false; - } - } - - public boolean isEnabled(String featureKey, Map context) { - return isEnabled(featureKey, context, null); - } - - public boolean isEnabled(String featureKey) { - return isEnabled(featureKey, null, null); - } - - /** - * Variation - */ - public Evaluation evaluateVariation(String featureKey, Map context, OverrideOptions options) { - EvaluateOptions evaluateOptions = getEvaluationDependencies(context, options) - .type(Evaluation.TYPE_VARIATION) - .featureKey(featureKey); - - return Evaluate.evaluateWithHooks(evaluateOptions); - } - - public Evaluation evaluateVariation(String featureKey, Map context) { - return evaluateVariation(featureKey, context, null); - } - - public Evaluation evaluateVariation(String featureKey) { - return evaluateVariation(featureKey, null, null); - } - - public String getVariation(String featureKey, Map context, OverrideOptions options) { - try { - Evaluation evaluation = evaluateVariation(featureKey, context, options); - - if (evaluation.getVariationValue() != null) { - return evaluation.getVariationValue(); - } - - if (evaluation.getVariation() != null) { - return evaluation.getVariation().getValue(); - } - - return null; - } catch (Exception e) { - this.logger.error("getVariation", Map.of("featureKey", featureKey, "error", e.getMessage())); - return null; - } - } - - public String getVariation(String featureKey, Map context) { - return getVariation(featureKey, context, null); - } - - public String getVariation(String featureKey) { - return getVariation(featureKey, null, null); - } - - /** - * Variable - */ - public Evaluation evaluateVariable(String featureKey, String variableKey, Map context, OverrideOptions options) { - EvaluateOptions evaluateOptions = getEvaluationDependencies(context, options) - .type(Evaluation.TYPE_VARIABLE) - .featureKey(featureKey) - .variableKey(variableKey); - - return Evaluate.evaluateWithHooks(evaluateOptions); - } - - public Evaluation evaluateVariable(String featureKey, String variableKey, Map context) { - return evaluateVariable(featureKey, variableKey, context, null); - } - - public Evaluation evaluateVariable(String featureKey, String variableKey) { - return evaluateVariable(featureKey, variableKey, null, null); - } - - public Object getVariable(String featureKey, String variableKey, Map context, OverrideOptions options) { - try { - Evaluation evaluation = evaluateVariable(featureKey, variableKey, context, options); - - - - if (evaluation.getVariableValue() != null) { - Object value = evaluation.getVariableValue(); - if (value instanceof String) { - String strValue = (String) value; - boolean looksLikeJson = (strValue.startsWith("{") && strValue.endsWith("}")) || - (strValue.startsWith("[") && strValue.endsWith("]")); - boolean isJsonType = evaluation.getVariableSchema() != null && - "json".equals(evaluation.getVariableSchema().getType()); - if (isJsonType || looksLikeJson) { - try { - ObjectMapper mapper = new ObjectMapper(); - return mapper.readValue(strValue, Object.class); - } catch (Exception e) { - // fallback to string - } - } - } - return value; - } - - return null; - } catch (Exception e) { - this.logger.error("getVariable", Map.of("featureKey", featureKey, "variableKey", variableKey, "error", e.getMessage())); - return null; - } - } - - public Object getVariable(String featureKey, String variableKey, Map context) { - return getVariable(featureKey, variableKey, context, null); - } - - public Object getVariable(String featureKey, String variableKey) { - return getVariable(featureKey, variableKey, null, null); - } - - public Boolean getVariableBoolean(String featureKey, String variableKey, Map context, OverrideOptions options) { - Object variableValue = getVariable(featureKey, variableKey, context, options); - return Helpers.getValueByType(variableValue, "boolean"); - } - - public Boolean getVariableBoolean(String featureKey, String variableKey, Map context) { - return getVariableBoolean(featureKey, variableKey, context, null); - } - - public Boolean getVariableBoolean(String featureKey, String variableKey) { - return getVariableBoolean(featureKey, variableKey, null, null); - } - - public String getVariableString(String featureKey, String variableKey, Map context, OverrideOptions options) { - Object variableValue = getVariable(featureKey, variableKey, context, options); - return Helpers.getValueByType(variableValue, "string"); - } - - public String getVariableString(String featureKey, String variableKey, Map context) { - return getVariableString(featureKey, variableKey, context, null); - } - - public String getVariableString(String featureKey, String variableKey) { - return getVariableString(featureKey, variableKey, null, null); - } - - public Integer getVariableInteger(String featureKey, String variableKey, Map context, OverrideOptions options) { - Object variableValue = getVariable(featureKey, variableKey, context, options); - return (Integer) Helpers.getValueByType(variableValue, "integer"); - } - - public Integer getVariableInteger(String featureKey, String variableKey, Map context) { - return getVariableInteger(featureKey, variableKey, context, null); - } - - public Integer getVariableInteger(String featureKey, String variableKey) { - return getVariableInteger(featureKey, variableKey, null, null); - } - - public Double getVariableDouble(String featureKey, String variableKey, Map context, OverrideOptions options) { - Object variableValue = getVariable(featureKey, variableKey, context, options); - return (Double) Helpers.getValueByType(variableValue, "double"); - } - - public Double getVariableDouble(String featureKey, String variableKey, Map context) { - return getVariableDouble(featureKey, variableKey, context, null); - } - - public Double getVariableDouble(String featureKey, String variableKey) { - return getVariableDouble(featureKey, variableKey, null, null); - } - - @SuppressWarnings("unchecked") - public List getVariableArray(String featureKey, String variableKey, Map context, OverrideOptions options) { - Object variableValue = getVariable(featureKey, variableKey, context, options); - return Helpers.getValueByType(variableValue, "array"); - } - - public List getVariableArray(String featureKey, String variableKey, Map context) { - return getVariableArray(featureKey, variableKey, context, null); - } - - public List getVariableArray(String featureKey, String variableKey) { - return getVariableArray(featureKey, variableKey, null, null); - } - - @SuppressWarnings("unchecked") - public T getVariableObject(String featureKey, String variableKey, Map context, OverrideOptions options) { - Object variableValue = getVariable(featureKey, variableKey, context, options); - return Helpers.getValueByType(variableValue, "object"); - } - - public T getVariableObject(String featureKey, String variableKey, Map context) { - return getVariableObject(featureKey, variableKey, context, null); - } - - public T getVariableObject(String featureKey, String variableKey) { - return getVariableObject(featureKey, variableKey, null, null); - } - - @SuppressWarnings("unchecked") - public T getVariableJSON(String featureKey, String variableKey, Map context, OverrideOptions options) { - Object variableValue = getVariable(featureKey, variableKey, context, options); - return Helpers.getValueByType(variableValue, "json"); - } - - public T getVariableJSON(String featureKey, String variableKey, Map context) { - return getVariableJSON(featureKey, variableKey, context, null); - } - - public T getVariableJSON(String featureKey, String variableKey) { - return getVariableJSON(featureKey, variableKey, null, null); - } - - /** - * Get all evaluations - */ - public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys, OverrideOptions options) { - if (context == null) { - context = new HashMap<>(); - } - if (featureKeys == null) { - featureKeys = new ArrayList<>(); - } - if (options == null) { - options = new OverrideOptions(); - } - - Map result = new HashMap<>(); - - List keys = featureKeys.isEmpty() ? this.datafileReader.getFeatureKeys() : featureKeys; - for (String featureKey : keys) { - // isEnabled - Evaluation flagEvaluation = evaluateFlag(featureKey, context, options); - - EvaluatedFeature evaluatedFeature = new EvaluatedFeature(); - evaluatedFeature.setEnabled(Boolean.TRUE.equals(flagEvaluation.getEnabled())); - - OverrideOptions opts = new OverrideOptions() - .sticky(options.getSticky()) - .defaultVariationValue(options.getDefaultVariationValue()) - .defaultVariableValue(options.getDefaultVariableValue()) - .flagEvaluation(flagEvaluation); - - // variation - if (this.datafileReader.hasVariations(featureKey)) { - Object variation = getVariation(featureKey, context, opts); - if (variation != null) { - evaluatedFeature.setVariation(variation.toString()); - } - } - - // variables - List variableKeys = this.datafileReader.getVariableKeys(featureKey); - if (!variableKeys.isEmpty()) { - Map variables = new HashMap<>(); - - for (String variableKey : variableKeys) { - variables.put(variableKey, getVariable(featureKey, variableKey, context, opts)); - } - - evaluatedFeature.setVariables(variables); - } - - result.put(featureKey, evaluatedFeature); - } - - return EvaluatedFeatures.of(result); - } - - public EvaluatedFeatures getAllEvaluations(Map context, List featureKeys) { - return getAllEvaluations(context, featureKeys, null); - } - - public EvaluatedFeatures getAllEvaluations(Map context) { - return getAllEvaluations(context, null, null); - } - - public EvaluatedFeatures getAllEvaluations() { - return getAllEvaluations(null, null, null); - } -} diff --git a/src/test/java/com/featurevisor/sdk/ChildTest.java b/src/test/java/com/featurevisor/sdk/ChildTest.java index eadfca1..828bf10 100644 --- a/src/test/java/com/featurevisor/sdk/ChildTest.java +++ b/src/test/java/com/featurevisor/sdk/ChildTest.java @@ -249,7 +249,7 @@ public void testCreateChildInstance() { Map parentContext = new HashMap<>(); parentContext.put("appVersion", "1.0.0"); - FeaturevisorInstance parentInstance = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor parentInstance = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .context(parentContext)); diff --git a/src/test/java/com/featurevisor/sdk/InstanceTest.java b/src/test/java/com/featurevisor/sdk/InstanceTest.java index 7bef2ae..3d41871 100644 --- a/src/test/java/com/featurevisor/sdk/InstanceTest.java +++ b/src/test/java/com/featurevisor/sdk/InstanceTest.java @@ -35,8 +35,8 @@ public void setUp() { @Test public void testCreateInstanceIsFunction() { - // This test verifies that FeaturevisorInstance constructor exists - assertNotNull(FeaturevisorInstance.class.getConstructors()); + // This test verifies that Featurevisor constructor exists + assertNotNull(Featurevisor.class.getConstructors()); } @Test @@ -58,7 +58,7 @@ public void testCreateInstanceWithDatafileContent() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); // Test that the SDK was created successfully assertNotNull(sdk); @@ -127,7 +127,7 @@ public void testConfigurePlainBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -202,7 +202,7 @@ public void testConfigureAndBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -279,7 +279,7 @@ public void testConfigureOrBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -365,7 +365,7 @@ public void testInterceptContextBeforeHook() { List hooks = new ArrayList<>(); hooks.add(hook); - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -450,7 +450,7 @@ public void testInterceptValueAfterHook() { List hooks = new ArrayList<>(); hooks.add(hook); - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -530,7 +530,7 @@ public void testInitializeWithStickyFeatures() throws InterruptedException { sticky.put("test", testSticky); - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().sticky(sticky)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().sticky(sticky)); // initially control Map context = Map.of( @@ -597,7 +597,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); // should be disabled because required is disabled assertFalse(sdk.isEnabled("myKey")); @@ -645,7 +645,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - FeaturevisorInstance sdk2 = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafileEnabled)); + Featurevisor sdk2 = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafileEnabled)); assertTrue(sdk2.isEnabled("myKey")); } @@ -716,7 +716,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); assertFalse(sdk.isEnabled("myKey")); @@ -785,7 +785,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - FeaturevisorInstance sdk2 = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafileDesired)); + Featurevisor sdk2 = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafileDesired)); assertTrue(sdk2.isEnabled("myKey")); } @@ -877,7 +877,7 @@ public void testEmitWarningsForDeprecatedFeature() { } })); - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .logger(customLogger)); @@ -954,7 +954,7 @@ public void testGetVariation() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1033,7 +1033,7 @@ public void testGetVariable() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1095,7 +1095,7 @@ public void testCheckIfEnabledForOverriddenFlagsFromRules() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); // Test with German user (should be enabled) Map context1 = new HashMap<>(); @@ -1192,7 +1192,7 @@ public void testGetVariationWithForceRules() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1263,7 +1263,7 @@ public void testCheckIfEnabledForMutuallyExclusiveFeatures() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() .datafile(datafile) .hooks(hooks)); @@ -1457,7 +1457,7 @@ public void testGetVariableComprehensive() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1576,7 +1576,7 @@ public void testGetVariablesWithoutAnyVariationsWithSegments() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); Map defaultContext = Map.of( "userId", "123" @@ -1643,7 +1643,7 @@ public void testCheckIfEnabledForIndividuallyNamedSegments() { return; } - FeaturevisorInstance sdk = new FeaturevisorInstance(new FeaturevisorInstance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); // Test with no context (should be disabled) assertFalse(sdk.isEnabled("test")); From 19f3286bf269958714b11e2bffd4541f3b625820 Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 16:39:13 +0200 Subject: [PATCH 3/8] rename InstanceOptions to Options --- README.md | 19 ++-- src/main/java/com/featurevisor/cli/CLI.java | 8 +- .../com/featurevisor/sdk/Featurevisor.java | 104 +++++++++--------- .../java/com/featurevisor/sdk/ChildTest.java | 2 +- .../com/featurevisor/sdk/InstanceTest.java | 40 +++---- 5 files changed, 87 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 3400f79..62e32cf 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/b ```java import com.featurevisor.sdk.Featurevisor; +import com.featurevisor.sdk.ChildInstance; import com.featurevisor.types.DatafileContent; // Load datafile content @@ -128,8 +129,8 @@ String datafileContent = "..." // ... load your datafile content DatafileContent datafile = DatafileContent.fromJson(datafileContent); // Create SDK instance -Featurevisor f = new Featurevisor( - new Featurevisor.InstanceOptions() +Featurevisor f = Featurevisor.createInstance( + new Featurevisor.Options() .datafile(datafileContent) ); ``` @@ -170,7 +171,7 @@ Map initialContext = new HashMap<>(); initialContext.put("deviceId", "123"); initialContext.put("country", "nl"); -Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .context(initialContext)); ``` @@ -357,7 +358,7 @@ Map anotherFeatureSticky = new HashMap<>(); anotherFeatureSticky.put("enabled", false); stickyFeatures.put("anotherFeatureKey", anotherFeatureSticky); -Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .sticky(stickyFeatures)); ``` @@ -433,7 +434,7 @@ Setting `debug` level will print out all logs, including `info`, `warn`, and `er ```java import com.featurevisor.sdk.Logger; -Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logLevel(Logger.LogLevel.DEBUG)); ``` @@ -457,7 +458,7 @@ Logger customLogger = Logger.createLogger(new Logger.CreateLoggerOptions() System.out.println("[" + level + "] " + message); })); -Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logger(customLogger)); ``` @@ -470,7 +471,7 @@ Logger customLogger = new Logger(Logger.LogLevel.INFO, (level, message, details) System.out.println("[" + level + "] " + message); }); -Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logger(customLogger)); ``` @@ -637,7 +638,7 @@ You can register hooks at the time of SDK initialization: List> hooks = new ArrayList<>(); hooks.add(myCustomHook); -Featurevisor f = new Featurevisor(new Featurevisor.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); ``` @@ -662,7 +663,7 @@ That's where child instances come in handy: Map childContext = new HashMap<>(); childContext.put("userId", "123"); -Featurevisor childF = f.spawn(childContext); +ChildInstance childF = f.spawn(childContext); ``` Now you can pass the child instance where your individual request is being handled, and you can continue to evaluate features targeting that specific user alone: diff --git a/src/main/java/com/featurevisor/cli/CLI.java b/src/main/java/com/featurevisor/cli/CLI.java index 318c1e9..8e379cc 100644 --- a/src/main/java/com/featurevisor/cli/CLI.java +++ b/src/main/java/com/featurevisor/cli/CLI.java @@ -580,7 +580,7 @@ private void test() { Map sdkInstancesByEnvironment = new HashMap<>(); for (String environment : environments) { DatafileContent datafile = datafilesByEnvironment.get(environment); - Featurevisor instance = Featurevisor.createInstance(new Featurevisor.InstanceOptions() + Featurevisor instance = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logLevel(level)); sdkInstancesByEnvironment.put(environment, instance); @@ -630,7 +630,7 @@ private void test() { hooksManager.add(new HooksManager.Hook("at-parameter") .bucketValue((options) -> (int) (atValue * 1000))); - f = Featurevisor.createInstance(new Featurevisor.InstanceOptions() + f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logLevel(level) .hooks(hooksManager.getAll())); @@ -709,7 +709,7 @@ private void benchmark() { Logger.LogLevel level = getLoggerLevel(); Map datafilesByEnvironment = buildDatafiles(rootDirectoryPath, Arrays.asList(environment)); - Featurevisor f = Featurevisor.createInstance(new Featurevisor.InstanceOptions() + Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafilesByEnvironment.get(environment)) .logLevel(level)); @@ -772,7 +772,7 @@ private void assessDistribution() { Map datafilesByEnvironment = buildDatafiles(rootDirectoryPath, Arrays.asList(environment)); - Featurevisor f = Featurevisor.createInstance(new Featurevisor.InstanceOptions() + Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafilesByEnvironment.get(environment)) .logLevel(getLoggerLevel())); diff --git a/src/main/java/com/featurevisor/sdk/Featurevisor.java b/src/main/java/com/featurevisor/sdk/Featurevisor.java index 753c419..6d96816 100644 --- a/src/main/java/com/featurevisor/sdk/Featurevisor.java +++ b/src/main/java/com/featurevisor/sdk/Featurevisor.java @@ -35,10 +35,52 @@ public class Featurevisor { emptyDatafile.setFeatures(new HashMap<>()); } + /** + * Factory methods + */ + public static Featurevisor createInstance(Options options) { + if (options == null) { + options = new Options(); + } + return new Featurevisor(options); + } + + public static Featurevisor createInstance() { + return createInstance(new Options()); + } + + public static Featurevisor createInstance(com.featurevisor.types.DatafileContent datafile) { + return createInstance(new Options().datafile(datafile)); + } + + public static Featurevisor createInstance(String datafileString) { + return createInstance(new Options().datafileString(datafileString)); + } + + public static Featurevisor createInstance(Map context) { + return createInstance(new Options().context(context)); + } + + public static Featurevisor createInstance(Logger.LogLevel logLevel) { + return createInstance(new Options().logLevel(logLevel)); + } + + public static Featurevisor createInstance(Logger logger) { + return createInstance(new Options().logger(logger)); + } + + public static Featurevisor createInstance(Map sticky, boolean isSticky) { + if (isSticky) { + return createInstance(new Options().sticky(sticky)); + } else { + return createInstance(new Options().context(sticky)); + } + } + /** * Options for creating an instance */ - public static class InstanceOptions { + public static class Options { private DatafileContent datafile; private String datafileString; private Map context; @@ -47,7 +89,7 @@ public static class InstanceOptions { private Map sticky; private List hooks; - public InstanceOptions() {} + public Options() {} // Getters public DatafileContent getDatafile() { return datafile; } @@ -68,37 +110,37 @@ public InstanceOptions() {} public void setHooks(List hooks) { this.hooks = hooks; } // Builder pattern methods - public InstanceOptions datafile(DatafileContent datafile) { + public Options datafile(DatafileContent datafile) { this.datafile = datafile; return this; } - public InstanceOptions datafileString(String datafileString) { + public Options datafileString(String datafileString) { this.datafileString = datafileString; return this; } - public InstanceOptions context(Map context) { + public Options context(Map context) { this.context = context; return this; } - public InstanceOptions logLevel(Logger.LogLevel logLevel) { + public Options logLevel(Logger.LogLevel logLevel) { this.logLevel = logLevel; return this; } - public InstanceOptions logger(Logger logger) { + public Options logger(Logger logger) { this.logger = logger; return this; } - public InstanceOptions sticky(Map sticky) { + public Options sticky(Map sticky) { this.sticky = sticky; return this; } - public InstanceOptions hooks(List hooks) { + public Options hooks(List hooks) { this.hooks = hooks; return this; } @@ -152,7 +194,7 @@ public OverrideOptions flagEvaluation(Evaluation flagEvaluation) { /** * Constructor */ - public Featurevisor(InstanceOptions options) { + public Featurevisor(Options options) { // from options if (options.getContext() != null) { this.context = new HashMap<>(options.getContext()); @@ -194,48 +236,6 @@ public Featurevisor(InstanceOptions options) { this.logger.info("Featurevisor SDK initialized", null); } - /** - * Factory methods - */ - public static Featurevisor createInstance(InstanceOptions options) { - if (options == null) { - options = new InstanceOptions(); - } - return new Featurevisor(options); - } - - public static Featurevisor createInstance() { - return createInstance(new InstanceOptions()); - } - - public static Featurevisor createInstance(com.featurevisor.types.DatafileContent datafile) { - return createInstance(new InstanceOptions().datafile(datafile)); - } - - public static Featurevisor createInstance(String datafileString) { - return createInstance(new InstanceOptions().datafileString(datafileString)); - } - - public static Featurevisor createInstance(Map context) { - return createInstance(new InstanceOptions().context(context)); - } - - public static Featurevisor createInstance(Logger.LogLevel logLevel) { - return createInstance(new InstanceOptions().logLevel(logLevel)); - } - - public static Featurevisor createInstance(Logger logger) { - return createInstance(new InstanceOptions().logger(logger)); - } - - public static Featurevisor createInstance(Map sticky, boolean isSticky) { - if (isSticky) { - return createInstance(new InstanceOptions().sticky(sticky)); - } else { - return createInstance(new InstanceOptions().context(sticky)); - } - } - /** * Set log level */ diff --git a/src/test/java/com/featurevisor/sdk/ChildTest.java b/src/test/java/com/featurevisor/sdk/ChildTest.java index 828bf10..81665d7 100644 --- a/src/test/java/com/featurevisor/sdk/ChildTest.java +++ b/src/test/java/com/featurevisor/sdk/ChildTest.java @@ -249,7 +249,7 @@ public void testCreateChildInstance() { Map parentContext = new HashMap<>(); parentContext.put("appVersion", "1.0.0"); - Featurevisor parentInstance = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor parentInstance = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .context(parentContext)); diff --git a/src/test/java/com/featurevisor/sdk/InstanceTest.java b/src/test/java/com/featurevisor/sdk/InstanceTest.java index 3d41871..4cb67af 100644 --- a/src/test/java/com/featurevisor/sdk/InstanceTest.java +++ b/src/test/java/com/featurevisor/sdk/InstanceTest.java @@ -58,7 +58,7 @@ public void testCreateInstanceWithDatafileContent() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); // Test that the SDK was created successfully assertNotNull(sdk); @@ -127,7 +127,7 @@ public void testConfigurePlainBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -202,7 +202,7 @@ public void testConfigureAndBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -279,7 +279,7 @@ public void testConfigureOrBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -365,7 +365,7 @@ public void testInterceptContextBeforeHook() { List hooks = new ArrayList<>(); hooks.add(hook); - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -450,7 +450,7 @@ public void testInterceptValueAfterHook() { List hooks = new ArrayList<>(); hooks.add(hook); - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -530,7 +530,7 @@ public void testInitializeWithStickyFeatures() throws InterruptedException { sticky.put("test", testSticky); - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().sticky(sticky)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().sticky(sticky)); // initially control Map context = Map.of( @@ -597,7 +597,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); // should be disabled because required is disabled assertFalse(sdk.isEnabled("myKey")); @@ -645,7 +645,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - Featurevisor sdk2 = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafileEnabled)); + Featurevisor sdk2 = new Featurevisor(new Featurevisor.Options().datafile(datafileEnabled)); assertTrue(sdk2.isEnabled("myKey")); } @@ -716,7 +716,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); assertFalse(sdk.isEnabled("myKey")); @@ -785,7 +785,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - Featurevisor sdk2 = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafileDesired)); + Featurevisor sdk2 = new Featurevisor(new Featurevisor.Options().datafile(datafileDesired)); assertTrue(sdk2.isEnabled("myKey")); } @@ -877,7 +877,7 @@ public void testEmitWarningsForDeprecatedFeature() { } })); - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .logger(customLogger)); @@ -954,7 +954,7 @@ public void testGetVariation() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1033,7 +1033,7 @@ public void testGetVariable() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1095,7 +1095,7 @@ public void testCheckIfEnabledForOverriddenFlagsFromRules() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); // Test with German user (should be enabled) Map context1 = new HashMap<>(); @@ -1192,7 +1192,7 @@ public void testGetVariationWithForceRules() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1263,7 +1263,7 @@ public void testCheckIfEnabledForMutuallyExclusiveFeatures() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -1457,7 +1457,7 @@ public void testGetVariableComprehensive() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1576,7 +1576,7 @@ public void testGetVariablesWithoutAnyVariationsWithSegments() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map defaultContext = Map.of( "userId", "123" @@ -1643,7 +1643,7 @@ public void testCheckIfEnabledForIndividuallyNamedSegments() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); // Test with no context (should be disabled) assertFalse(sdk.isEnabled("test")); From 9089b9ed1f1c31623faa699b6088b2e74acfa498 Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 16:44:35 +0200 Subject: [PATCH 4/8] updates --- README.md | 7 +------ src/main/java/com/featurevisor/sdk/Featurevisor.java | 8 +++----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 62e32cf..ac5ef06 100644 --- a/README.md +++ b/README.md @@ -118,15 +118,10 @@ The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/b ```java import com.featurevisor.sdk.Featurevisor; -import com.featurevisor.sdk.ChildInstance; -import com.featurevisor.types.DatafileContent; // Load datafile content String datafileUrl = "https://cdn.yoursite.com/datafile.json"; -String datafileContent = "..." // ... load your datafile content - -// Parse the JSON into DatafileContent object -DatafileContent datafile = DatafileContent.fromJson(datafileContent); +String datafileContent = "..." // load your datafile content // Create SDK instance Featurevisor f = Featurevisor.createInstance( diff --git a/src/main/java/com/featurevisor/sdk/Featurevisor.java b/src/main/java/com/featurevisor/sdk/Featurevisor.java index 6d96816..be60fb5 100644 --- a/src/main/java/com/featurevisor/sdk/Featurevisor.java +++ b/src/main/java/com/featurevisor/sdk/Featurevisor.java @@ -49,7 +49,7 @@ public static Featurevisor createInstance() { return createInstance(new Options()); } - public static Featurevisor createInstance(com.featurevisor.types.DatafileContent datafile) { + public static Featurevisor createInstance(DatafileContent datafile) { return createInstance(new Options().datafile(datafile)); } @@ -223,8 +223,7 @@ public Featurevisor(Options options) { .logger(this.logger)); } else if (options.getDatafileString() != null) { try { - ObjectMapper mapper = new ObjectMapper(); - DatafileContent datafile = mapper.readValue(options.getDatafileString(), DatafileContent.class); + DatafileContent datafile = DatafileContent.fromJson(options.getDatafileString()); this.datafileReader = new DatafileReader(new DatafileReader.DatafileReaderOptions() .datafile(datafile) .logger(this.logger)); @@ -269,8 +268,7 @@ public void setDatafile(DatafileContent datafile) { */ public void setDatafile(String datafileString) { try { - ObjectMapper mapper = new ObjectMapper(); - DatafileContent datafile = mapper.readValue(datafileString, DatafileContent.class); + DatafileContent datafile = DatafileContent.fromJson(datafileString); setDatafile(datafile); } catch (Exception e) { this.logger.error("could not parse datafile string", Map.of("error", e.getMessage())); From f2e8279e0106fcf0454ebadc8ca2210402768fe9 Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 16:46:08 +0200 Subject: [PATCH 5/8] rename test --- .../sdk/{InstanceTest.java => FeaturevisorTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/featurevisor/sdk/{InstanceTest.java => FeaturevisorTest.java} (99%) diff --git a/src/test/java/com/featurevisor/sdk/InstanceTest.java b/src/test/java/com/featurevisor/sdk/FeaturevisorTest.java similarity index 99% rename from src/test/java/com/featurevisor/sdk/InstanceTest.java rename to src/test/java/com/featurevisor/sdk/FeaturevisorTest.java index 4cb67af..cc0e500 100644 --- a/src/test/java/com/featurevisor/sdk/InstanceTest.java +++ b/src/test/java/com/featurevisor/sdk/FeaturevisorTest.java @@ -24,7 +24,7 @@ import java.util.Arrays; import com.fasterxml.jackson.databind.ObjectMapper; -public class InstanceTest { +public class FeaturevisorTest { private Logger logger; From 0444fdcea8c5eb63753502d4d01a2f5402cee565 Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 16:49:13 +0200 Subject: [PATCH 6/8] tests for static methods --- .../featurevisor/sdk/FeaturevisorTest.java | 266 +++++++++++++++++- 1 file changed, 262 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/featurevisor/sdk/FeaturevisorTest.java b/src/test/java/com/featurevisor/sdk/FeaturevisorTest.java index cc0e500..b71b544 100644 --- a/src/test/java/com/featurevisor/sdk/FeaturevisorTest.java +++ b/src/test/java/com/featurevisor/sdk/FeaturevisorTest.java @@ -39,6 +39,17 @@ public void testCreateInstanceIsFunction() { assertNotNull(Featurevisor.class.getConstructors()); } + @Test + public void testCreateInstanceWithNoParameters() { + // Test the simplest createInstance() method + Featurevisor sdk = Featurevisor.createInstance(); + + assertNotNull(sdk); + // Should have default logger and empty datafile + assertNotNull(sdk.getRevision()); + assertNull(sdk.getVariation("nonExistentFeature")); + } + @Test public void testCreateInstanceWithDatafileContent() { // Create datafile content using JSON string for better readability @@ -46,7 +57,37 @@ public void testCreateInstanceWithDatafileContent() { { "schemaVersion": "2", "revision": "1.0", - "features": {}, + "features": { + "test": { + "key": "test", + "bucketBy": "userId", + "variations": [ + { + "value": "control" + }, + { + "value": "treatment" + } + ], + "traffic": [ + { + "key": "1", + "segments": "*", + "percentage": 100000, + "allocation": [ + { + "variation": "control", + "range": [0, 100000] + }, + { + "variation": "treatment", + "range": [0, 0] + } + ] + } + ] + } + }, "segments": {} }"""; @@ -58,14 +99,231 @@ public void testCreateInstanceWithDatafileContent() { return; } - Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); + // Test createInstance with DatafileContent + Featurevisor sdk = Featurevisor.createInstance(datafile); + + assertNotNull(sdk); + assertEquals("1.0", sdk.getRevision()); + assertEquals("control", sdk.getVariation("test", Map.of("userId", "123"))); + } + + @Test + public void testCreateInstanceWithDatafileString() { + // Test createInstance with datafile string + String datafileJson = """ + { + "schemaVersion": "2", + "revision": "2.0", + "features": { + "test": { + "key": "test", + "bucketBy": "userId", + "variations": [ + { + "value": "control" + }, + { + "value": "treatment" + } + ], + "traffic": [ + { + "key": "1", + "segments": "*", + "percentage": 100000, + "allocation": [ + { + "variation": "control", + "range": [0, 0] + }, + { + "variation": "treatment", + "range": [0, 100000] + } + ] + } + ] + } + }, + "segments": {} + }"""; + + // Test createInstance with datafile string + Featurevisor sdk = Featurevisor.createInstance(datafileJson); + + assertNotNull(sdk); + assertEquals("2.0", sdk.getRevision()); + assertEquals("treatment", sdk.getVariation("test", Map.of("userId", "123"))); + } + + @Test + public void testCreateInstanceWithContext() { + // Test createInstance with context + Map context = Map.of( + "userId", "123", + "country", "us" + ); + + Featurevisor sdk = Featurevisor.createInstance(context); + + assertNotNull(sdk); + // Context should be set + Map retrievedContext = sdk.getContext(); + assertEquals("123", retrievedContext.get("userId")); + assertEquals("us", retrievedContext.get("country")); + } + + @Test + public void testCreateInstanceWithLogLevel() { + // Test createInstance with log level + Featurevisor sdk = Featurevisor.createInstance(Logger.LogLevel.DEBUG); + + assertNotNull(sdk); + // The logger should be set with DEBUG level + // We can't directly access the logger level, but we can verify the instance was created + assertNotNull(sdk.getRevision()); + } + + @Test + public void testCreateInstanceWithLogger() { + // Test createInstance with custom logger + Logger customLogger = Logger.createLogger(new Logger.CreateLoggerOptions() + .level(Logger.LogLevel.ERROR) + .handler((level, message, details) -> { + // Custom handler + })); + + Featurevisor sdk = Featurevisor.createInstance(customLogger); + + assertNotNull(sdk); + // The custom logger should be used + assertNotNull(sdk.getRevision()); + } + + @Test + public void testCreateInstanceWithStickyFeatures() { + // Test createInstance with sticky features (isSticky = true) + Map sticky = new HashMap<>(); + Map testSticky = new HashMap<>(); + testSticky.put("enabled", true); + testSticky.put("variation", "control"); + sticky.put("test", testSticky); + + Featurevisor sdk = Featurevisor.createInstance(sticky, true); + + assertNotNull(sdk); + // Sticky features should be set + assertEquals("control", sdk.getVariation("test", Map.of("userId", "123"))); + } + + @Test + public void testCreateInstanceWithContextAsSticky() { + // Test createInstance with context as sticky (isSticky = false) + Map context = Map.of( + "userId", "123", + "country", "us" + ); + + Featurevisor sdk = Featurevisor.createInstance(context, false); + + assertNotNull(sdk); + // Context should be set (not sticky) + Map retrievedContext = sdk.getContext(); + assertEquals("123", retrievedContext.get("userId")); + assertEquals("us", retrievedContext.get("country")); + } + + @Test + public void testCreateInstanceWithNullOptions() { + // Test createInstance with null options (should use defaults) + Featurevisor sdk = Featurevisor.createInstance((Featurevisor.Options) null); + + assertNotNull(sdk); + assertNotNull(sdk.getRevision()); + } + + @Test + public void testCreateInstanceWithInvalidDatafileString() { + // Test createInstance with invalid datafile string + String invalidJson = "{ invalid json }"; + + // Should not throw exception, but should log error + Featurevisor sdk = Featurevisor.createInstance(invalidJson); - // Test that the SDK was created successfully assertNotNull(sdk); - // Test that getVariation returns null for non-existent feature (which is expected behavior) + // Should have default empty datafile assertNull(sdk.getVariation("test")); } + @Test + public void testCreateInstanceWithOptionsBuilder() { + // Test createInstance with Options builder pattern + String datafileJson = """ + { + "schemaVersion": "2", + "revision": "3.0", + "features": { + "test": { + "key": "test", + "bucketBy": "userId", + "variations": [ + { + "value": "control" + }, + { + "value": "treatment" + } + ], + "traffic": [ + { + "key": "1", + "segments": "*", + "percentage": 100000, + "allocation": [ + { + "variation": "control", + "range": [0, 100000] + }, + { + "variation": "treatment", + "range": [0, 0] + } + ] + } + ] + } + }, + "segments": {} + }"""; + + DatafileContent datafile; + try { + datafile = DatafileContent.fromJson(datafileJson); + } catch (Exception e) { + fail("Failed to parse datafile JSON: " + e.getMessage()); + return; + } + + Map context = Map.of("userId", "123"); + Logger customLogger = Logger.createLogger(new Logger.CreateLoggerOptions().level(Logger.LogLevel.INFO)); + + // Test with Options builder + Featurevisor.Options options = new Featurevisor.Options() + .datafile(datafile) + .context(context) + .logger(customLogger); + + Featurevisor sdk = Featurevisor.createInstance(options); + + assertNotNull(sdk); + assertEquals("3.0", sdk.getRevision()); + assertEquals("control", sdk.getVariation("test", context)); + + // Context should be set + Map retrievedContext = sdk.getContext(); + assertEquals("123", retrievedContext.get("userId")); + } + @Test public void testConfigurePlainBucketBy() { final String[] capturedBucketKey = {""}; From 82999eae6a5a18c01c86c0da54e83c4995345aee Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 16:54:41 +0200 Subject: [PATCH 7/8] updates --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ac5ef06..5cdcade 100644 --- a/README.md +++ b/README.md @@ -124,10 +124,7 @@ String datafileUrl = "https://cdn.yoursite.com/datafile.json"; String datafileContent = "..." // load your datafile content // Create SDK instance -Featurevisor f = Featurevisor.createInstance( - new Featurevisor.Options() - .datafile(datafileContent) -); +Featurevisor f = Featurevisor.createInstance(datafileContent); ``` ## Evaluation types @@ -167,7 +164,7 @@ initialContext.put("deviceId", "123"); initialContext.put("country", "nl"); Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() - .datafile(datafile) + .datafile(datafileContent) .context(initialContext)); ``` From da8370b956d2a71f42e7fd358ff9d006edd80421 Mon Sep 17 00:00:00 2001 From: Fahad Heylaal Date: Tue, 5 Aug 2025 17:06:28 +0200 Subject: [PATCH 8/8] updates --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 5cdcade..9e9226f 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,16 @@ String datafileContent = "..." // load your datafile content Featurevisor f = Featurevisor.createInstance(datafileContent); ``` +or by constructing a `Featurevisor.Options` object: + +```java +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() + .datafile(datafileContent) +); +``` + +We will learn about several different options in the next sections. + ## Evaluation types We can evaluate 3 types of values against a particular [feature](https://featurevisor.com/docs/features/):