Skip to content

Commit d952b52

Browse files
dbottillomichaelbull
authored andcommitted
Propagate nested coroutineBinding failures
Closes #128 and closes #133
1 parent d0dfb0c commit d952b52

File tree

3 files changed

+161
-9
lines changed

3 files changed

+161
-9
lines changed

kotlin-result-coroutines/src/commonMain/kotlin/com/github/michaelbull/result/coroutines/CoroutineBinding.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public suspend inline fun <V, E> coroutineBinding(crossinline block: suspend Cor
5656
}
5757
}
5858
} catch (ex: BindCancellationException) {
59-
receiver.result!!
59+
receiver.result ?: throw ex
6060
}
6161
}
6262

kotlin-result-coroutines/src/commonTest/kotlin/com/github/michaelbull/result/coroutines/AsyncCoroutineBindingTest.kt

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,128 @@ class AsyncCoroutineBindingTest {
164164
assertTrue(yStateChange)
165165
assertFalse(zStateChange)
166166
}
167+
168+
@Test
169+
fun shouldHandleNestedBindings() = runTest {
170+
var xStateChange = false
171+
var yStateChange = false
172+
var zStateChange = false
173+
174+
suspend fun provideX(): Result<Int, BindingError> {
175+
delay(1)
176+
xStateChange = true
177+
return Ok(1)
178+
}
179+
180+
suspend fun provideXWrapped() = coroutineBinding {
181+
provideX().bind()
182+
}
183+
184+
suspend fun provideY(): Result<Int, BindingError.BindingErrorA> {
185+
delay(20)
186+
yStateChange = true
187+
return Ok(1)
188+
}
189+
190+
suspend fun provideYWrapped() = coroutineBinding {
191+
provideY().bind()
192+
}
193+
194+
suspend fun provideZ(): Result<Int, BindingError.BindingErrorB> {
195+
delay(100)
196+
zStateChange = true
197+
return Ok(1)
198+
}
199+
200+
suspend fun provideZWrapped() = coroutineBinding {
201+
provideZ().bind()
202+
}
203+
204+
val dispatcherA = StandardTestDispatcher(testScheduler)
205+
val dispatcherB = StandardTestDispatcher(testScheduler)
206+
val dispatcherC = StandardTestDispatcher(testScheduler)
207+
208+
val result: Result<Int, BindingError> = coroutineBinding {
209+
val x = async(dispatcherA) { provideXWrapped().bind() }
210+
val y = async(dispatcherB) { provideYWrapped().bind() }
211+
212+
testScheduler.advanceTimeBy(2)
213+
testScheduler.runCurrent()
214+
215+
val z = async(dispatcherC) { provideZWrapped().bind() }
216+
217+
x.await() + y.await() + z.await()
218+
}
219+
220+
assertEquals(
221+
expected = Ok(3),
222+
actual = result
223+
)
224+
225+
assertTrue(xStateChange)
226+
assertTrue(yStateChange)
227+
assertTrue(zStateChange)
228+
}
229+
230+
@Test
231+
fun shouldHandleExceptionsWithNestedBindings() = runTest {
232+
var xStateChange = false
233+
var yStateChange = false
234+
var zStateChange = false
235+
236+
suspend fun provideX(): Result<Int, BindingError> {
237+
delay(1)
238+
xStateChange = true
239+
return Ok(1)
240+
}
241+
242+
suspend fun provideXWrapped() = coroutineBinding {
243+
provideX().bind()
244+
}
245+
246+
suspend fun provideY(): Result<Int, BindingError.BindingErrorA> {
247+
delay(20)
248+
yStateChange = true
249+
return Err(BindingError.BindingErrorA)
250+
}
251+
252+
suspend fun provideYWrapped() = coroutineBinding {
253+
provideY().bind()
254+
}
255+
256+
suspend fun provideZ(): Result<Int, BindingError.BindingErrorB> {
257+
delay(100)
258+
zStateChange = true
259+
return Ok(1)
260+
}
261+
262+
suspend fun provideZWrapped() = coroutineBinding {
263+
provideZ().bind()
264+
}
265+
266+
val dispatcherA = StandardTestDispatcher(testScheduler)
267+
val dispatcherB = StandardTestDispatcher(testScheduler)
268+
val dispatcherC = StandardTestDispatcher(testScheduler)
269+
270+
val result: Result<Int, BindingError> = coroutineBinding {
271+
val x = async(dispatcherA) { provideXWrapped().bind() }
272+
val y = async(dispatcherB) { provideYWrapped().bind() }
273+
274+
testScheduler.advanceTimeBy(2)
275+
testScheduler.runCurrent()
276+
277+
val z = async(dispatcherC) { provideZWrapped().bind() }
278+
279+
x.await() + y.await() + z.await()
280+
}
281+
282+
assertEquals(
283+
expected = Err(BindingError.BindingErrorA),
284+
actual = result
285+
)
286+
287+
assertTrue(xStateChange)
288+
assertTrue(yStateChange)
289+
assertFalse(zStateChange)
290+
}
167291
}

kotlin-result-coroutines/src/commonTest/kotlin/com/github/michaelbull/result/coroutines/CoroutineBindingTest.kt

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import kotlin.test.assertTrue
1212

1313
class CoroutineBindingTest {
1414

15-
private object BindingError
15+
private sealed interface BindingError {
16+
data object BindingErrorA : BindingError
17+
data object BindingErrorB : BindingError
18+
data object BindingErrorC : BindingError
19+
}
1620

1721
@Test
1822
fun returnsOkIfAllBindsSuccessful() = runTest {
@@ -71,7 +75,7 @@ class CoroutineBindingTest {
7175

7276
suspend fun provideY(): Result<Int, BindingError> {
7377
delay(1)
74-
return Err(BindingError)
78+
return Err(BindingError.BindingErrorA)
7579
}
7680

7781
suspend fun provideZ(): Result<Int, BindingError> {
@@ -87,7 +91,7 @@ class CoroutineBindingTest {
8791
}
8892

8993
assertEquals(
90-
expected = Err(BindingError),
94+
expected = Err(BindingError.BindingErrorA),
9195
actual = result,
9296
)
9397
}
@@ -107,13 +111,13 @@ class CoroutineBindingTest {
107111
suspend fun provideY(): Result<Int, BindingError> {
108112
delay(10)
109113
yStateChange = true
110-
return Err(BindingError)
114+
return Err(BindingError.BindingErrorA)
111115
}
112116

113117
suspend fun provideZ(): Result<Int, BindingError> {
114118
delay(1)
115119
zStateChange = true
116-
return Err(BindingError)
120+
return Err(BindingError.BindingErrorA)
117121
}
118122

119123
val result: Result<Int, BindingError> = coroutineBinding {
@@ -124,7 +128,7 @@ class CoroutineBindingTest {
124128
}
125129

126130
assertEquals(
127-
expected = Err(BindingError),
131+
expected = Err(BindingError.BindingErrorA),
128132
actual = result,
129133
)
130134

@@ -142,7 +146,7 @@ class CoroutineBindingTest {
142146

143147
suspend fun provideY(): Result<String, BindingError> {
144148
delay(1)
145-
return Err(BindingError)
149+
return Err(BindingError.BindingErrorA)
146150
}
147151

148152
suspend fun provideZ(): Result<Int, BindingError> {
@@ -158,8 +162,32 @@ class CoroutineBindingTest {
158162
}
159163

160164
assertEquals(
161-
expected = Err(BindingError),
165+
expected = Err(BindingError.BindingErrorA),
162166
actual = result,
163167
)
164168
}
169+
170+
@Test
171+
fun shouldHandleExceptionsWithMultipleNestedBindings() = runTest {
172+
val result: Result<Int, BindingError> = coroutineBinding {
173+
val b: Result<Int, BindingError> = coroutineBinding {
174+
val c: Result<Int, BindingError> = coroutineBinding {
175+
Err(BindingError.BindingErrorC).bind()
176+
}
177+
178+
assertEquals(Err(BindingError.BindingErrorC), c)
179+
180+
Ok(2).bind()
181+
}
182+
183+
assertEquals(Ok(2), b)
184+
185+
Err(BindingError.BindingErrorB).bind()
186+
}
187+
188+
assertEquals(
189+
expected = Err(BindingError.BindingErrorB),
190+
actual = result
191+
)
192+
}
165193
}

0 commit comments

Comments
 (0)