Skip to content

Commit

Permalink
Add RxJavaRuntimeErrorHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisdeng2002 committed Dec 1, 2023
1 parent d014e18 commit da69558
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.FormulaDisposableHelper

object RxJavaRuntime {

private var defaultErrorHandler: RxJavaRuntimeErrorHandler? = null

fun setDefaultErrorHandler(errorHandler: RxJavaRuntimeErrorHandler?) {
this.defaultErrorHandler = errorHandler
}

fun <Input : Any, Output : Any> start(
input: Observable<Input>,
formula: IFormula<Input, Output>,
Expand All @@ -22,12 +29,18 @@ object RxJavaRuntime {
type = formula.type(),
local = inspector,
)

val onError = { error: Throwable ->
val handled = defaultErrorHandler?.onError(error) ?: false
if (!handled) emitter.onError(error)
}.takeIf { defaultErrorHandler != null } ?: emitter::onError

val runtimeFactory = {
FormulaRuntime(
threadChecker = threadChecker,
formula = formula,
onOutput = emitter::onNext,
onError = emitter::onError,
onError = onError,
inspector = mergedInspector,
isValidationEnabled = isValidationEnabled,
)
Expand All @@ -45,7 +58,7 @@ object RxJavaRuntime {
runtime = runtimeFactory()
}
runtime.onInput(input)
}, emitter::onError))
}, onError))

val runnable = Runnable {
threadChecker.check("Need to unsubscribe on the main thread.")
Expand All @@ -56,4 +69,4 @@ object RxJavaRuntime {
emitter.setDisposable(disposables)
}.distinctUntilChanged()
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 6 additions & 0 deletions formula/src/main/java/com/instacart/formula/Exceptions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.instacart.formula

/**
* Thrown when a child formula is added with a duplicate key.
*/
class DuplicateKeyException(override val message: String?) : IllegalStateException()
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.instacart.formula.internal

import com.instacart.formula.DuplicateKeyException

/**
* Holder tracks when object has already been request and throws an error if requested again. This
* is used to track duplicate requests for a particular key.
Expand All @@ -9,7 +11,7 @@ internal class SingleRequestHolder<T>(val value: T) {

inline fun requestAccess(errorMessage: () -> String): T {
if (requested) {
throw IllegalStateException(errorMessage())
throw DuplicateKeyException(errorMessage())
}

requested = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.instacart.formula.internal.ClearPluginsRule
import com.instacart.formula.internal.TestInspector
import com.instacart.formula.internal.Try
import com.instacart.formula.rxjava3.RxAction
import com.instacart.formula.rxjava3.RxJavaRuntime
import com.instacart.formula.subjects.ChildActionFiresParentEventOnStart
import com.instacart.formula.subjects.ChildMessageNoParentStateChange
import com.instacart.formula.subjects.ChildMessageTriggersEventTransitionInParent
Expand Down Expand Up @@ -1108,7 +1109,9 @@ class FormulaRuntimeTest(val runtime: TestableRuntime, val name: String) {
}

val error = result.errorOrNull()?.cause
assertThat(error).isInstanceOf(IllegalStateException::class.java)
assertThat(error).isInstanceOf(DuplicateKeyException::class.java)
val expectedMessage = "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(error?.message).isEqualTo(expectedMessage)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>()

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<Unit> {
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)
}
}

0 comments on commit da69558

Please sign in to comment.