diff --git a/README.md b/README.md index 0cb9599..9e9226f 100644 --- a/README.md +++ b/README.md @@ -117,23 +117,26 @@ 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.types.DatafileContent; +import com.featurevisor.sdk.Featurevisor; // 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 -Instance f = new Instance( - new Instance.InstanceOptions() - .datafile(datafileContent) +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/): @@ -170,8 +173,8 @@ Map initialContext = new HashMap<>(); initialContext.put("deviceId", "123"); initialContext.put("country", "nl"); -Instance f = new Instance(new Instance.InstanceOptions() - .datafile(datafile) +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() + .datafile(datafileContent) .context(initialContext)); ``` @@ -357,7 +360,7 @@ Map anotherFeatureSticky = new HashMap<>(); anotherFeatureSticky.put("enabled", false); stickyFeatures.put("anotherFeatureKey", anotherFeatureSticky); -Instance f = new Instance(new Instance.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .sticky(stickyFeatures)); ``` @@ -433,7 +436,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() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logLevel(Logger.LogLevel.DEBUG)); ``` @@ -457,7 +460,7 @@ Logger customLogger = Logger.createLogger(new Logger.CreateLoggerOptions() System.out.println("[" + level + "] " + message); })); -Instance f = new Instance(new Instance.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logger(customLogger)); ``` @@ -470,7 +473,7 @@ Logger customLogger = new Logger(Logger.LogLevel.INFO, (level, message, details) System.out.println("[" + level + "] " + message); }); -Instance f = new Instance(new Instance.InstanceOptions() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .logger(customLogger)); ``` @@ -637,7 +640,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() +Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); ``` @@ -662,7 +665,7 @@ That's where child instances come in handy: Map childContext = new HashMap<>(); childContext.put("userId", "123"); -Instance 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 57d3daa..8e379cc 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.Instance; 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 Instance) { - ((Instance) f).setContext(context, true); - ((Instance) 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")) { - Instance.OverrideOptions options = new Instance.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, } } - Instance.OverrideOptions options = new Instance.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"); - Instance.OverrideOptions options = new Instance.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(); - Instance.OverrideOptions options = new Instance.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 Instance) { - return ((Instance) 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, Instance.OverrideOptions options) { - if (f instanceof Instance) { - return ((Instance) 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, 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, 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 Instance) { - return ((Instance) 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, 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, 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, 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, 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 Instance) { - return ((Instance) 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; @@ -577,11 +576,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() + Featurevisor instance = Featurevisor.createInstance(new Featurevisor.Options() .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"); - Instance 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 Instance.InstanceOptions() + f = Featurevisor.createInstance(new Featurevisor.Options() .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)); - Instance f = Featurevisor.createInstance(new Instance.InstanceOptions() + Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .datafile(datafilesByEnvironment.get(environment)) .logLevel(level)); @@ -773,7 +772,7 @@ private void assessDistribution() { Map datafilesByEnvironment = buildDatafiles(rootDirectoryPath, Arrays.asList(environment)); - Instance f = Featurevisor.createInstance(new Instance.InstanceOptions() + Featurevisor f = Featurevisor.createInstance(new Featurevisor.Options() .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..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 Instance parent; + private Featurevisor 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(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, Instance.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, Instance.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, Instance.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, Instance.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, Instance.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, Instance.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, Instance.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, Instance.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, Instance.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, Instance.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, Instance.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 Instance.OverrideOptions mergeOverrideOptions(Instance.OverrideOptions options) { + private Featurevisor.OverrideOptions mergeOverrideOptions(Featurevisor.OverrideOptions options) { if (options == null) { - options = new Instance.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 702e451..be60fb5 100644 --- a/src/main/java/com/featurevisor/sdk/Featurevisor.java +++ b/src/main/java/com/featurevisor/sdk/Featurevisor.java @@ -1,89 +1,720 @@ 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<>()); + } /** - * Create a new Featurevisor instance - * @param options The instance options - * @return A new Featurevisor instance + * Factory methods */ - public static Instance createInstance(Instance.InstanceOptions options) { + public static Featurevisor createInstance(Options options) { if (options == null) { - options = new Instance.InstanceOptions(); + options = new Options(); + } + return new Featurevisor(options); + } + + public static Featurevisor createInstance() { + return createInstance(new Options()); + } + + public static Featurevisor createInstance(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 Options { + private DatafileContent datafile; + private String datafileString; + private Map context; + private Logger.LogLevel logLevel; + private Logger logger; + private Map sticky; + private List hooks; + + public Options() {} + + // 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 Options datafile(DatafileContent datafile) { + this.datafile = datafile; + return this; + } + + public Options datafileString(String datafileString) { + this.datafileString = datafileString; + return this; + } + + public Options context(Map context) { + this.context = context; + return this; + } + + public Options logLevel(Logger.LogLevel logLevel) { + this.logLevel = logLevel; + return this; + } + + public Options logger(Logger logger) { + this.logger = logger; + return this; + } + + public Options sticky(Map sticky) { + this.sticky = sticky; + return this; + } + + public Options 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(Options 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 { + DatafileContent datafile = DatafileContent.fromJson(options.getDatafileString()); + 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())); } - return new Instance(options); } /** - * Create a new Featurevisor instance with default options - * @return A new Featurevisor instance + * Set datafile from string */ - public static Instance createInstance() { - return createInstance(new Instance.InstanceOptions()); + public void setDatafile(String datafileString) { + try { + DatafileContent datafile = DatafileContent.fromJson(datafileString); + 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 Instance createInstance(com.featurevisor.types.DatafileContent datafile) { - return createInstance(new Instance.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 Instance createInstance(String datafileString) { - return createInstance(new Instance.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 Instance createInstance(Map context) { - return createInstance(new Instance.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 Instance createInstance(Logger.LogLevel logLevel) { - return createInstance(new Instance.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 Instance createInstance(Logger logger) { - return createInstance(new Instance.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 Instance createInstance(Map sticky, boolean isSticky) { - if (isSticky) { - return createInstance(new Instance.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 Instance.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/Instance.java b/src/main/java/com/featurevisor/sdk/Instance.java deleted file mode 100644 index 60a10d4..0000000 --- a/src/main/java/com/featurevisor/sdk/Instance.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 Instance { - // 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 Instance(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 ee0145b..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"); - Instance parentInstance = new Instance(new Instance.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/FeaturevisorTest.java similarity index 83% rename from src/test/java/com/featurevisor/sdk/InstanceTest.java rename to src/test/java/com/featurevisor/sdk/FeaturevisorTest.java index 8a9ca06..b71b544 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; @@ -35,8 +35,19 @@ public void setUp() { @Test public void testCreateInstanceIsFunction() { - // This test verifies that Instance constructor exists - assertNotNull(Instance.class.getConstructors()); + // This test verifies that Featurevisor constructor exists + 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 @@ -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; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + // Test createInstance with DatafileContent + Featurevisor sdk = Featurevisor.createInstance(datafile); - // Test that the SDK was created successfully assertNotNull(sdk); - // Test that getVariation returns null for non-existent feature (which is expected behavior) + 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); + + assertNotNull(sdk); + // 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 = {""}; @@ -127,7 +385,7 @@ public void testConfigurePlainBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -202,7 +460,7 @@ public void testConfigureAndBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -279,7 +537,7 @@ public void testConfigureOrBucketBy() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -365,7 +623,7 @@ public void testInterceptContextBeforeHook() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -450,7 +708,7 @@ public void testInterceptValueAfterHook() { List hooks = new ArrayList<>(); hooks.add(hook); - Instance sdk = new Instance(new Instance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -530,7 +788,7 @@ public void testInitializeWithStickyFeatures() throws InterruptedException { sticky.put("test", testSticky); - Instance sdk = new Instance(new Instance.InstanceOptions().sticky(sticky)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().sticky(sticky)); // initially control Map context = Map.of( @@ -597,7 +855,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - Instance sdk = new Instance(new Instance.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 +903,7 @@ public void testHonourSimpleRequiredFeatures() { return; } - Instance sdk2 = new Instance(new Instance.InstanceOptions().datafile(datafileEnabled)); + Featurevisor sdk2 = new Featurevisor(new Featurevisor.Options().datafile(datafileEnabled)); assertTrue(sdk2.isEnabled("myKey")); } @@ -716,7 +974,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); assertFalse(sdk.isEnabled("myKey")); @@ -785,7 +1043,7 @@ public void testHonourRequiredFeaturesWithVariation() { return; } - Instance sdk2 = new Instance(new Instance.InstanceOptions().datafile(datafileDesired)); + Featurevisor sdk2 = new Featurevisor(new Featurevisor.Options().datafile(datafileDesired)); assertTrue(sdk2.isEnabled("myKey")); } @@ -877,7 +1135,7 @@ public void testEmitWarningsForDeprecatedFeature() { } })); - Instance sdk = new Instance(new Instance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .logger(customLogger)); @@ -954,7 +1212,7 @@ public void testGetVariation() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1033,7 +1291,7 @@ public void testGetVariable() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1095,7 +1353,7 @@ public void testCheckIfEnabledForOverriddenFlagsFromRules() { return; } - Instance sdk = new Instance(new Instance.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 +1450,7 @@ public void testGetVariationWithForceRules() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1263,7 +1521,7 @@ public void testCheckIfEnabledForMutuallyExclusiveFeatures() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions() + Featurevisor sdk = new Featurevisor(new Featurevisor.Options() .datafile(datafile) .hooks(hooks)); @@ -1457,7 +1715,7 @@ public void testGetVariableComprehensive() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map context = Map.of( "userId", "123" @@ -1576,7 +1834,7 @@ public void testGetVariablesWithoutAnyVariationsWithSegments() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); Map defaultContext = Map.of( "userId", "123" @@ -1643,7 +1901,7 @@ public void testCheckIfEnabledForIndividuallyNamedSegments() { return; } - Instance sdk = new Instance(new Instance.InstanceOptions().datafile(datafile)); + Featurevisor sdk = new Featurevisor(new Featurevisor.Options().datafile(datafile)); // Test with no context (should be disabled) assertFalse(sdk.isEnabled("test"));