Skip to content

Commit be97d24

Browse files
committed
Client side exceptions now disposes all subscriptions managed by Kelm
1 parent 5529e82 commit be97d24

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

kelm-core/src/main/kotlin/kelm/exceptions.kt

+6
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ data class SubscriptionException(val subscription: Any, override val cause: Thro
1414

1515
data class CmdException(val cmd: Any, override val cause: Throwable) :
1616
ExternalException("The command [$cmd] threw an error", cause)
17+
18+
data class UnhandledException(override val cause: Throwable) :
19+
ExternalException(
20+
"An unhandled exception was caught. Could be caused by a client side function.",
21+
cause
22+
)

kelm-core/src/main/kotlin/kelm/internal/core.kt

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import kelm.Sub
1717
import kelm.SubContext
1818
import kelm.SubFactoryNotImplementedException
1919
import kelm.SubscriptionException
20+
import kelm.UnhandledException
2021
import kelm.UpdateContext
2122
import kelm.UpdateF
2223
import kelm.toNullable
@@ -264,6 +265,12 @@ internal fun <ModelT, MsgT, CmdT : Cmd, SubT : Sub> build(
264265
subDisposables.values.forEach(Disposable::dispose)
265266
loggerDisposables.forEach(Disposable::dispose)
266267
}
268+
.doOnError {
269+
errorToMsg(UnhandledException(it))
270+
cmdDisposables.values.forEach(Disposable::dispose)
271+
subDisposables.values.forEach(Disposable::dispose)
272+
loggerDisposables.forEach(Disposable::dispose)
273+
}
267274
}
268275

269276
private fun <SubT : Sub> computeSubsDiff(old: List<SubT>, new: List<SubT>): List<SubsDiffOp<SubT>> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package clientSideError
2+
3+
import clientSideError.AElement.Cmd
4+
import clientSideError.AElement.Model
5+
import clientSideError.AElement.Msg
6+
import clientSideError.AElement.Sub
7+
import io.kotlintest.matchers.types.shouldBeInstanceOf
8+
import io.kotlintest.shouldBe
9+
import io.reactivex.Observable
10+
import kelm.ExternalException
11+
import kelm.Kelm
12+
import kelm.SubContext
13+
import kelm.UnhandledException
14+
import kelm.UpdateContext
15+
import org.spekframework.spek2.Spek
16+
17+
object AElement : Kelm.Element<Model, Msg, Cmd, Sub>() {
18+
var errorToMsgSaved: ExternalException? = null
19+
20+
object Model
21+
object Msg
22+
object Cmd : kelm.Cmd()
23+
object Sub : kelm.Sub("sub")
24+
25+
override fun UpdateContext<Model, Msg, Cmd, Sub>.update(model: Model, msg: Msg): Model? {
26+
+Cmd
27+
return null
28+
}
29+
30+
override fun SubContext<Sub>.subscriptions(model: Model) {
31+
+Sub
32+
}
33+
34+
override fun errorToMsg(error: ExternalException): Msg? {
35+
errorToMsgSaved = error
36+
return null
37+
}
38+
}
39+
40+
object ClientSideErrorTest : Spek({
41+
group("given an element with faulty client side functions") {
42+
val cmdToMaybe = { cmd: Cmd -> error("ops") }
43+
var disposedSub = false
44+
val subToObs = { _: Sub, _: Observable<Msg>, _: Observable<Model> ->
45+
Observable.never<Msg>()
46+
.doOnDispose { disposedSub = true }
47+
}
48+
49+
test("make sure in case of faulty client-side error, Kelm disposes all subs") {
50+
val ts = AElement
51+
.start(
52+
initModel = Model,
53+
msgInput = Observable.just(Msg),
54+
cmdToMaybe = cmdToMaybe,
55+
subToObs = subToObs
56+
)
57+
.test()
58+
59+
ts.assertError { it is IllegalStateException && it.localizedMessage == "ops" }
60+
disposedSub shouldBe true
61+
AElement.errorToMsgSaved.shouldBeInstanceOf<UnhandledException>()
62+
}
63+
}
64+
})

0 commit comments

Comments
 (0)