diff --git a/formula-rxjava3/src/main/java/com/instacart/formula/rxjava3/RxJavaRuntime.kt b/formula-rxjava3/src/main/java/com/instacart/formula/rxjava3/RxJavaRuntime.kt
index 6f99c547..81e835d1 100644
--- a/formula-rxjava3/src/main/java/com/instacart/formula/rxjava3/RxJavaRuntime.kt
+++ b/formula-rxjava3/src/main/java/com/instacart/formula/rxjava3/RxJavaRuntime.kt
@@ -11,10 +11,10 @@ import io.reactivex.rxjava3.disposables.FormulaDisposableHelper
object RxJavaRuntime {
- private var defaultErrorHandler: ((Throwable) -> Unit)? = null
+ private var defaultErrorHandler: RxJavaRuntimeErrorHandler? = null
- fun setDefaultErrorHandler(handler: ((Throwable) -> Unit)?) {
- defaultErrorHandler = handler
+ fun setDefaultErrorHandler(errorHandler: RxJavaRuntimeErrorHandler?) {
+ this.defaultErrorHandler = errorHandler
}
fun start(
@@ -29,12 +29,20 @@ object RxJavaRuntime {
type = formula.type(),
local = inspector,
)
+
+ val onError = { error: Throwable ->
+ val handled = defaultErrorHandler?.onError(error) ?: false
+ if (!handled) {
+ emitter.onError(error)
+ }
+ }
+
val runtimeFactory = {
FormulaRuntime(
threadChecker = threadChecker,
formula = formula,
onOutput = emitter::onNext,
- onError = defaultErrorHandler ?: emitter::onError,
+ onError = onError,
inspector = mergedInspector,
isValidationEnabled = isValidationEnabled,
)
@@ -52,7 +60,7 @@ object RxJavaRuntime {
runtime = runtimeFactory()
}
runtime.onInput(input)
- }, defaultErrorHandler ?: emitter::onError))
+ }, onError))
val runnable = Runnable {
threadChecker.check("Need to unsubscribe on the main thread.")
diff --git a/formula-rxjava3/src/main/java/com/instacart/formula/rxjava3/RxJavaRuntimeErrorHandler.kt b/formula-rxjava3/src/main/java/com/instacart/formula/rxjava3/RxJavaRuntimeErrorHandler.kt
new file mode 100644
index 00000000..aa5fb1ed
--- /dev/null
+++ b/formula-rxjava3/src/main/java/com/instacart/formula/rxjava3/RxJavaRuntimeErrorHandler.kt
@@ -0,0 +1,10 @@
+package com.instacart.formula.rxjava3
+
+interface RxJavaRuntimeErrorHandler {
+ /**
+ * @param error [Throwable] that occurred
+ *
+ * @return true if error was handled, false otherwise
+ */
+ fun onError(error: Throwable): Boolean
+}
diff --git a/formula/src/test/java/com/instacart/formula/RxJavaRuntimeTest.kt b/formula/src/test/java/com/instacart/formula/RxJavaRuntimeTest.kt
deleted file mode 100644
index b98c9e55..00000000
--- a/formula/src/test/java/com/instacart/formula/RxJavaRuntimeTest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.instacart.formula
-
-import com.google.common.truth.Truth.assertThat
-import com.instacart.formula.internal.ClearPluginsRule
-import com.instacart.formula.internal.Try
-import com.instacart.formula.rxjava3.RxJavaRuntime
-import com.instacart.formula.subjects.DynamicParentFormula
-import com.instacart.formula.subjects.TestKey
-import com.instacart.formula.test.RxJavaTestableRuntime
-import com.instacart.formula.test.TestableRuntime
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.RuleChain
-import org.junit.rules.TestName
-
-class RxJavaRuntimeTest {
-
- val runtime: TestableRuntime = RxJavaTestableRuntime
-
- @get:Rule
- val rule = RuleChain
- .outerRule(TestName())
- .around(ClearPluginsRule())
- .around(runtime.rule)
-
- @Test
- fun `adding duplicate child logs an exception`() {
- val logs = mutableListOf()
- RxJavaRuntime.setDefaultErrorHandler { logs.add(it.message.orEmpty()) }
-
- val result = Try {
- val formula = DynamicParentFormula()
- runtime.test(formula, Unit)
- .output { addChild(TestKey("1")) }
- .output { addChild(TestKey("1")) }
- }
-
- val error = result.errorOrNull()?.cause
- assertThat(error).isNull()
- assertThat(logs).hasSize(1)
- val log = logs.first()
- val expectedLog = "There already is a child with same key: FormulaKey(scopeKey=null, type=class com.instacart.formula.subjects.KeyFormula, key=TestKey(id=1)). Override [Formula.key] function."
- assertThat(log).isEqualTo(expectedLog)
- RxJavaRuntime.setDefaultErrorHandler(null)
- }
-}
diff --git a/formula/src/test/java/com/instacart/formula/error/RxJavaRuntimeErrorHandlerTest.kt b/formula/src/test/java/com/instacart/formula/error/RxJavaRuntimeErrorHandlerTest.kt
new file mode 100644
index 00000000..17ef2681
--- /dev/null
+++ b/formula/src/test/java/com/instacart/formula/error/RxJavaRuntimeErrorHandlerTest.kt
@@ -0,0 +1,95 @@
+package com.instacart.formula.error
+
+import com.google.common.truth.Truth.assertThat
+import com.instacart.formula.Action
+import com.instacart.formula.DuplicateKeyException
+import com.instacart.formula.internal.ClearPluginsRule
+import com.instacart.formula.internal.Try
+import com.instacart.formula.rxjava3.RxJavaRuntime
+import com.instacart.formula.rxjava3.RxJavaRuntimeErrorHandler
+import com.instacart.formula.subjects.DynamicParentFormula
+import com.instacart.formula.subjects.OnlyUpdateFormula
+import com.instacart.formula.subjects.TestKey
+import com.instacart.formula.test.RxJavaTestableRuntime
+import com.instacart.formula.test.TestableRuntime
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TestName
+import kotlin.IllegalStateException
+
+class RxJavaRuntimeErrorHandlerTest {
+
+ val runtime: TestableRuntime = RxJavaTestableRuntime
+
+ @get:Rule
+ val rule = RuleChain
+ .outerRule(TestName())
+ .around(ClearPluginsRule())
+ .around(runtime.rule)
+
+ private val errorLogs = mutableListOf()
+
+ private val duplicateKeyErrorHandler = object : RxJavaRuntimeErrorHandler {
+ override fun onError(error: Throwable): Boolean {
+ return when (error) {
+ is DuplicateKeyException -> {
+ errorLogs.add(error.message.orEmpty())
+ true
+ }
+
+ else -> {
+ false
+ }
+ }
+ }
+ }
+
+ @Before
+ fun setUp() {
+ RxJavaRuntime.setDefaultErrorHandler(duplicateKeyErrorHandler)
+ }
+
+ @After
+ fun tearDown() {
+ RxJavaRuntime.setDefaultErrorHandler(null)
+ }
+
+ @Test
+ fun `emitting a generic error throws an exception`() {
+ val result = Try {
+ val formula = OnlyUpdateFormula {
+ events(Action.onInit()) {
+ throw IllegalStateException("crashed")
+ }
+ }
+ runtime.test(formula, Unit)
+ }
+
+ val error = result.errorOrNull()?.cause
+ assertThat(error).isInstanceOf(IllegalStateException::class.java)
+ assertThat(error?.message).isEqualTo("crashed")
+
+ assertThat(errorLogs).isEmpty()
+ }
+
+ @Test
+ fun `adding duplicate child logs an exception`() {
+ val result = Try {
+ val formula = DynamicParentFormula()
+ runtime.test(formula, Unit)
+ .output { addChild(TestKey("1")) }
+ .output { addChild(TestKey("1")) }
+ }
+
+ val error = result.errorOrNull()?.cause
+ assertThat(error).isNull()
+ assertThat(errorLogs).hasSize(1)
+
+ val log = errorLogs.first()
+ val expectedLog = "There already is a child with same key: FormulaKey(scopeKey=null, type=class com.instacart.formula.subjects.KeyFormula, key=TestKey(id=1)). Override [Formula.key] function."
+ assertThat(log).isEqualTo(expectedLog)
+ }
+}