From 311590658e8f8ace2f6a9e0e08f1de4540f46ba6 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 2 Mar 2026 19:24:13 -0300 Subject: [PATCH] Fallback treatments module --- build.gradle | 2 +- fallback/.gitignore | 4 ++ fallback/README.md | 10 +++ fallback/build.gradle | 23 +++++++ fallback/src/main/AndroidManifest.xml | 3 + .../client/fallback/FallbackTreatment.java | 0 .../FallbackTreatmentsCalculator.java | 0 .../FallbackTreatmentsCalculatorImpl.java | 10 ++- .../FallbackTreatmentsConfiguration.java | 0 .../client/fallback/FallbacksSanitizer.java | 0 .../fallback/FallbacksSanitizerImpl.java | 0 .../fallback/FallbackTreatmentTest.java | 0 .../FallbackTreatmentsCalculatorTest.java | 4 +- .../FallbackTreatmentsConfigurationTest.java | 0 .../fallback/FallbacksSanitizerImplTest.java | 0 .../client/utils/logger/LogPrinterStub.java | 66 +++++++++++++++++++ main/build.gradle | 1 + settings.gradle | 1 + 18 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 fallback/.gitignore create mode 100644 fallback/README.md create mode 100644 fallback/build.gradle create mode 100644 fallback/src/main/AndroidManifest.xml rename {main => fallback}/src/main/java/io/split/android/client/fallback/FallbackTreatment.java (100%) rename {main => fallback}/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculator.java (100%) rename {main => fallback}/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorImpl.java (86%) rename {main => fallback}/src/main/java/io/split/android/client/fallback/FallbackTreatmentsConfiguration.java (100%) rename {main => fallback}/src/main/java/io/split/android/client/fallback/FallbacksSanitizer.java (100%) rename {main => fallback}/src/main/java/io/split/android/client/fallback/FallbacksSanitizerImpl.java (100%) rename {main => fallback}/src/test/java/io/split/android/client/fallback/FallbackTreatmentTest.java (100%) rename {main => fallback}/src/test/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorTest.java (98%) rename {main => fallback}/src/test/java/io/split/android/client/fallback/FallbackTreatmentsConfigurationTest.java (100%) rename {main => fallback}/src/test/java/io/split/android/client/fallback/FallbacksSanitizerImplTest.java (100%) create mode 100644 fallback/src/test/java/io/split/android/client/utils/logger/LogPrinterStub.java diff --git a/build.gradle b/build.gradle index 5cd9478e0..20adf0399 100644 --- a/build.gradle +++ b/build.gradle @@ -145,7 +145,7 @@ dependencies { return candidates.find { findProject(it) != null } } - ['main', 'logger', 'events', 'events-domain', 'api', 'http-api', 'http'].each { moduleName -> + ['main', 'logger', 'events', 'events-domain', 'api', 'http-api', 'http', 'fallback'].each { moduleName -> def resolvedPath = resolveProjectPath(moduleName) if (resolvedPath != null) { include project(resolvedPath) diff --git a/fallback/.gitignore b/fallback/.gitignore new file mode 100644 index 000000000..3a11ced48 --- /dev/null +++ b/fallback/.gitignore @@ -0,0 +1,4 @@ +/build +.gradle +*.iml +.DS_Store diff --git a/fallback/README.md b/fallback/README.md new file mode 100644 index 000000000..b9bad4614 --- /dev/null +++ b/fallback/README.md @@ -0,0 +1,10 @@ +# Fallback module + +This module contains the fallback treatment logic for the Split SDK. + +It provides the types and resolution strategy used when the SDK is unable to evaluate a feature flag normally (e.g. during initialization or on error), returning a configured fallback treatment instead of the default "control". + +Key types: +- `FallbackTreatmentsConfiguration` — builder for configuring global and per-flag fallbacks +- `FallbackTreatment` — represents a fallback treatment value with optional config and label +- `FallbackTreatmentsCalculator` — resolves the applicable fallback for a given flag name diff --git a/fallback/build.gradle b/fallback/build.gradle new file mode 100644 index 000000000..fc30c3b42 --- /dev/null +++ b/fallback/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'com.android.library' +} + +apply from: "$projectDir/../gradle/common-android-library.gradle" + +android { + namespace 'io.split.android.client.fallback' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api project(':logger') + + implementation libs.annotation + + testImplementation libs.junit4 + testImplementation libs.mockitoCore +} diff --git a/fallback/src/main/AndroidManifest.xml b/fallback/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9a40236b9 --- /dev/null +++ b/fallback/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/main/src/main/java/io/split/android/client/fallback/FallbackTreatment.java b/fallback/src/main/java/io/split/android/client/fallback/FallbackTreatment.java similarity index 100% rename from main/src/main/java/io/split/android/client/fallback/FallbackTreatment.java rename to fallback/src/main/java/io/split/android/client/fallback/FallbackTreatment.java diff --git a/main/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculator.java b/fallback/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculator.java similarity index 100% rename from main/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculator.java rename to fallback/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculator.java diff --git a/main/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorImpl.java b/fallback/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorImpl.java similarity index 86% rename from main/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorImpl.java rename to fallback/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorImpl.java index 0eb727a1e..d67a7c084 100644 --- a/main/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorImpl.java +++ b/fallback/src/main/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorImpl.java @@ -1,23 +1,21 @@ package io.split.android.client.fallback; -import static io.split.android.client.utils.Utils.checkNotNull; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.Map; - -import io.split.android.grammar.Treatments; +import java.util.Objects; public final class FallbackTreatmentsCalculatorImpl implements FallbackTreatmentsCalculator { private static final String LABEL_PREFIX = "fallback - "; + private static final String CONTROL = "control"; @NonNull private final FallbackTreatmentsConfiguration mConfig; public FallbackTreatmentsCalculatorImpl(@NonNull FallbackTreatmentsConfiguration config) { - mConfig = checkNotNull(config); + mConfig = Objects.requireNonNull(config); } @NonNull @@ -40,7 +38,7 @@ public FallbackTreatment resolve(@NonNull String flagName, @Nullable String labe if (global != null) { return global.copyWithLabel(resolveLabel(label)); } - return new FallbackTreatment(Treatments.CONTROL, null, label); + return new FallbackTreatment(CONTROL, null, label); } @Nullable diff --git a/main/src/main/java/io/split/android/client/fallback/FallbackTreatmentsConfiguration.java b/fallback/src/main/java/io/split/android/client/fallback/FallbackTreatmentsConfiguration.java similarity index 100% rename from main/src/main/java/io/split/android/client/fallback/FallbackTreatmentsConfiguration.java rename to fallback/src/main/java/io/split/android/client/fallback/FallbackTreatmentsConfiguration.java diff --git a/main/src/main/java/io/split/android/client/fallback/FallbacksSanitizer.java b/fallback/src/main/java/io/split/android/client/fallback/FallbacksSanitizer.java similarity index 100% rename from main/src/main/java/io/split/android/client/fallback/FallbacksSanitizer.java rename to fallback/src/main/java/io/split/android/client/fallback/FallbacksSanitizer.java diff --git a/main/src/main/java/io/split/android/client/fallback/FallbacksSanitizerImpl.java b/fallback/src/main/java/io/split/android/client/fallback/FallbacksSanitizerImpl.java similarity index 100% rename from main/src/main/java/io/split/android/client/fallback/FallbacksSanitizerImpl.java rename to fallback/src/main/java/io/split/android/client/fallback/FallbacksSanitizerImpl.java diff --git a/main/src/test/java/io/split/android/client/fallback/FallbackTreatmentTest.java b/fallback/src/test/java/io/split/android/client/fallback/FallbackTreatmentTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/fallback/FallbackTreatmentTest.java rename to fallback/src/test/java/io/split/android/client/fallback/FallbackTreatmentTest.java diff --git a/main/src/test/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorTest.java b/fallback/src/test/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorTest.java similarity index 98% rename from main/src/test/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorTest.java rename to fallback/src/test/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorTest.java index 37c5353c0..c122f4257 100644 --- a/main/src/test/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorTest.java +++ b/fallback/src/test/java/io/split/android/client/fallback/FallbackTreatmentsCalculatorTest.java @@ -10,8 +10,6 @@ import java.util.HashMap; import java.util.Map; -import io.split.android.client.TreatmentLabels; - public class FallbackTreatmentsCalculatorTest { @Test @@ -103,7 +101,7 @@ public void labelIsPrefixed() { .build(); FallbackTreatmentsCalculator calculator = new FallbackTreatmentsCalculatorImpl(config); - FallbackTreatment resolved = calculator.resolve("flagA", TreatmentLabels.EXCEPTION); + FallbackTreatment resolved = calculator.resolve("flagA", "exception"); assertNotNull(resolved); assertEquals("fallback - exception", resolved.getLabel()); diff --git a/main/src/test/java/io/split/android/client/fallback/FallbackTreatmentsConfigurationTest.java b/fallback/src/test/java/io/split/android/client/fallback/FallbackTreatmentsConfigurationTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/fallback/FallbackTreatmentsConfigurationTest.java rename to fallback/src/test/java/io/split/android/client/fallback/FallbackTreatmentsConfigurationTest.java diff --git a/main/src/test/java/io/split/android/client/fallback/FallbacksSanitizerImplTest.java b/fallback/src/test/java/io/split/android/client/fallback/FallbacksSanitizerImplTest.java similarity index 100% rename from main/src/test/java/io/split/android/client/fallback/FallbacksSanitizerImplTest.java rename to fallback/src/test/java/io/split/android/client/fallback/FallbacksSanitizerImplTest.java diff --git a/fallback/src/test/java/io/split/android/client/utils/logger/LogPrinterStub.java b/fallback/src/test/java/io/split/android/client/utils/logger/LogPrinterStub.java new file mode 100644 index 000000000..e8c53ef3d --- /dev/null +++ b/fallback/src/test/java/io/split/android/client/utils/logger/LogPrinterStub.java @@ -0,0 +1,66 @@ +package io.split.android.client.utils.logger; + +import android.util.Log; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; + +public class LogPrinterStub implements LogPrinter { + private final Set calls = new HashSet<>(); + private final Map> logs = new ConcurrentHashMap<>(); + + public LogPrinterStub() { + // Initialize for all Android log levels: VERBOSE(2) .. ASSERT(7) + for (int level = Log.VERBOSE; level <= Log.ASSERT; level++) { + logs.put(level, new ConcurrentLinkedDeque<>()); + } + } + + @Override + public void v(String tag, String msg, Throwable tr) { + logs.get(Log.VERBOSE).add(msg); + calls.add(Log.VERBOSE); + } + + @Override + public void d(String tag, String msg, Throwable tr) { + logs.get(Log.DEBUG).add(msg); + calls.add(Log.DEBUG); + } + + @Override + public void i(String tag, String msg, Throwable tr) { + logs.get(Log.INFO).add(msg); + calls.add(Log.INFO); + } + + @Override + public void w(String tag, String msg, Throwable tr) { + logs.get(Log.WARN).add(msg); + calls.add(Log.WARN); + } + + @Override + public void e(String tag, String msg, Throwable tr) { + logs.get(Log.ERROR).add(msg); + calls.add(Log.ERROR); + } + + @Override + public void wtf(String tag, String msg, Throwable tr) { + logs.get(Log.ASSERT).add(msg); + calls.add(Log.ASSERT); + } + + public boolean isCalled(Integer type) { + return calls.contains(type); + } + + public Map> getLoggedMessages() { + return new HashMap<>(logs); + } +} diff --git a/main/build.gradle b/main/build.gradle index 362b7508f..d79d1518f 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -53,6 +53,7 @@ dependencies { api project(':logger') api project(':api') api project(':http-api') + api project(':fallback') // Internal module dependencies implementation project(':http') implementation project(':events-domain') diff --git a/settings.gradle b/settings.gradle index 9ddd50403..ecfd8fb57 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ include ':api' include ':logger' include ':http-api' include ':http' +include ':fallback' include ':main' include ':events' include ':events-domain'