Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Commit

Permalink
Adjust Exception hierarchy (#69)
Browse files Browse the repository at this point in the history
- Changed all but `NotReady` to extend from `IOException`
- Marked all Exception constructors as `internal`
- Removed `GattResponseFailure`
    - Throwing `ConnectionLost` instead (as described in KDoc `@throws`)
- Renamed `GattStatusFailure` to `GattErrorStatus`
  • Loading branch information
twyatt authored Jun 2, 2020
1 parent 4e18e40 commit 95d393c
Show file tree
Hide file tree
Showing 9 changed files with 33 additions and 33 deletions.
14 changes: 3 additions & 11 deletions core/src/main/java/gatt/CoroutinesGatt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED
import android.os.RemoteException
import com.juul.able.Able
import java.io.IOException
import java.util.UUID
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.ExecutorCoroutineDispatcher
Expand All @@ -20,12 +21,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext

class OutOfOrderGattCallback(message: String) : IllegalStateException(message)

class GattResponseFailure(
message: String,
cause: Throwable
) : IllegalStateException(message, cause)
class OutOfOrderGattCallback internal constructor(message: String) : IOException(message)

class CoroutinesGatt internal constructor(
private val bluetoothGatt: BluetoothGatt,
Expand Down Expand Up @@ -89,10 +85,6 @@ class CoroutinesGatt internal constructor(
bluetoothGatt.requestMtu(mtu)
}

/**
* @throws [RemoteException] if underlying [BluetoothGatt.requestMtu] returns `false`.
* @throws [ConnectionLost] if [Gatt] disconnects while method is executing.
*/
override suspend fun readRemoteRssi(): OnReadRemoteRssi =
performBluetoothAction("readRemoteRssi") {
bluetoothGatt.readRemoteRssi()
Expand Down Expand Up @@ -123,7 +115,7 @@ class CoroutinesGatt internal constructor(
} catch (e: CancellationException) {
throw CancellationException("Waiting on response for $methodName was cancelled", e)
} catch (e: Exception) {
throw GattResponseFailure("Failed to receive response for $methodName", e)
throw ConnectionLost("Failed to receive response for $methodName", e)
}
Able.info { "$methodName$response" }

Expand Down
12 changes: 8 additions & 4 deletions core/src/main/java/gatt/GattConnection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.bluetooth.BluetoothGatt.GATT_SUCCESS
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED
import com.juul.able.Able
import java.io.IOException
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
Expand Down Expand Up @@ -48,18 +49,21 @@ interface GattConnection {
suspend fun disconnect(): Unit
}

class GattStatusFailure(
class GattErrorStatus internal constructor(
val event: OnConnectionStateChange
) : IllegalStateException("Received $event")
) : IOException("Received $event")

class ConnectionLost : Exception()
class ConnectionLost internal constructor(
message: String? = null,
cause: Throwable? = null
) : IOException(message, cause)

internal suspend fun GattConnection.suspendUntilConnectionState(state: GattConnectionState) {
Able.debug { "Suspending until ${state.asGattConnectionStateString()}" }
onConnectionStateChange
.onEach { event ->
Able.verbose { "← Received $event while waiting for ${state.asGattConnectionStateString()}" }
if (event.status != GATT_SUCCESS) throw GattStatusFailure(event)
if (event.status != GATT_SUCCESS) throw GattErrorStatus(event)
if (event.newState == STATE_DISCONNECTED && state != STATE_DISCONNECTED) throw ConnectionLost()
}
.first { (_, newState) -> newState == state }
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/gatt/GattIo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ interface GattIo {
enable: Boolean
): Boolean

/**
* @throws [RemoteException] if underlying [BluetoothGatt.requestMtu] returns `false`.
* @throws [ConnectionLost] if [Gatt] disconnects while method is executing.
*/
suspend fun readRemoteRssi(): OnReadRemoteRssi
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/test/java/device/CoroutinesDeviceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import com.juul.able.device.CoroutinesDevice
import com.juul.able.gatt.ConnectionLost
import com.juul.able.gatt.GATT_CONN_CANCEL
import com.juul.able.gatt.GattCallback
import com.juul.able.gatt.GattStatusFailure
import com.juul.able.gatt.GattErrorStatus
import com.juul.able.gatt.OnConnectionStateChange
import com.juul.able.test.logger.ConsoleLoggerTestRule
import io.mockk.every
Expand Down Expand Up @@ -65,7 +65,7 @@ class CoroutinesDeviceTest {

assertEquals(
expected = OnConnectionStateChange(GATT_CONN_CANCEL, STATE_CONNECTED),
actual = (failure.cause as GattStatusFailure).event
actual = (failure.cause as GattErrorStatus).event
)
verify(exactly = 1) { bluetoothGatt.close() }
}
Expand Down
5 changes: 2 additions & 3 deletions core/src/test/java/gatt/CoroutinesGattTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import android.os.RemoteException
import com.juul.able.gatt.ConnectionLost
import com.juul.able.gatt.CoroutinesGatt
import com.juul.able.gatt.GattCallback
import com.juul.able.gatt.GattResponseFailure
import com.juul.able.gatt.OnCharacteristicChanged
import com.juul.able.gatt.OnCharacteristicRead
import com.juul.able.gatt.OnCharacteristicWrite
Expand Down Expand Up @@ -476,7 +475,7 @@ class CoroutinesGattTest {
}

@Test
fun `Gatt action throws GattResponseFailure if connection drops while executing request`() {
fun `Gatt action throws ConnectionLost if connection drops while executing request`() {
createDispatcher().use { dispatcher ->
val callback = GattCallback(dispatcher)
val bluetoothGatt = mockk<BluetoothGatt> {
Expand All @@ -490,7 +489,7 @@ class CoroutinesGattTest {

val gatt = CoroutinesGatt(bluetoothGatt, dispatcher, callback)
runBlocking {
val cause = assertFailsWith<GattResponseFailure> {
val cause = assertFailsWith<ConnectionLost> {
gatt.readCharacteristic(createCharacteristic())
}.cause

Expand Down
5 changes: 3 additions & 2 deletions keep-alive/src/main/java/KeepAliveGatt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.juul.able.keepalive.State.Cancelled
import com.juul.able.keepalive.State.Connected
import com.juul.able.keepalive.State.Connecting
import com.juul.able.keepalive.State.Disconnected
import java.io.IOException
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -54,8 +55,8 @@ import kotlinx.coroutines.withTimeoutOrNull

typealias ConnectAction = suspend GattIo.() -> Unit

class NotReady(message: String) : IllegalStateException(message)
class ConnectionRejected(cause: Throwable) : IllegalStateException(cause)
class NotReady internal constructor(message: String) : IllegalStateException(message)
class ConnectionRejected internal constructor(cause: Throwable) : IOException(cause)

sealed class State {
object Connecting : State()
Expand Down
2 changes: 1 addition & 1 deletion keep-alive/src/test/java/KeepAliveGattTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ class KeepAliveGattTest {
bluetoothDevice.connectGatt(any())
} answers {
if (++attempt >= connectionAttempts) throw EndOfTest()
Failure.Connection(ConnectionLost())
Failure.Connection(mockk<ConnectionLost>())
}

val keepAlive = scope.keepAliveGatt(
Expand Down
7 changes: 4 additions & 3 deletions throw/src/main/java/android/BluetoothDeviceOrThrow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ package com.juul.able.throwable.android

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt.GATT_SUCCESS
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED
import android.content.Context
import android.os.RemoteException
import com.juul.able.android.connectGatt
import com.juul.able.device.ConnectGattResult.Failure
import com.juul.able.device.ConnectGattResult.Success
import com.juul.able.gatt.ConnectionLost
import com.juul.able.gatt.Gatt
import com.juul.able.gatt.GattStatusFailure
import com.juul.able.gatt.GattErrorStatus

/**
* @throws RemoteException if underlying [BluetoothDevice.connectGatt] returns `null`.
* @throws GattStatusFailure if non-[GATT_SUCCESS] status occurs during connection process.
* @throws ConnectionLost if disconnect is requested during connection process.
* @throws GattErrorStatus if non-[GATT_SUCCESS] status is received during connection process.
* @throws ConnectionLost if [STATE_DISCONNECTED] is received during connection process.
*/
suspend fun BluetoothDevice.connectGattOrThrow(
context: Context
Expand Down
13 changes: 6 additions & 7 deletions throw/src/test/java/android/BluetoothDeviceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
package com.juul.able.throwable.test.android

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt.GATT_FAILURE
import android.bluetooth.BluetoothProfile.STATE_DISCONNECTED
import android.content.Context
import com.juul.able.android.connectGatt
import com.juul.able.device.ConnectGattResult.Failure
import com.juul.able.device.ConnectGattResult.Success
import com.juul.able.gatt.Gatt
import com.juul.able.gatt.GattStatusFailure
import com.juul.able.gatt.OnConnectionStateChange
import com.juul.able.throwable.android.connectGattOrThrow
import io.mockk.coEvery
import io.mockk.mockk
Expand Down Expand Up @@ -49,16 +45,19 @@ class BluetoothDeviceTest {
fun `connectGattOrThrow throws result cause on failure`() {
val context = mockk<Context>()
val bluetoothDevice = mockk<BluetoothDevice>()
val cause = GattStatusFailure(OnConnectionStateChange(GATT_FAILURE, STATE_DISCONNECTED))

mockkStatic("com.juul.able.android.BluetoothDeviceKt") {
coEvery { bluetoothDevice.connectGatt(any()) } returns Failure.Connection(cause)
coEvery {
bluetoothDevice.connectGatt(any())
} returns Failure.Connection(TestException())

assertFailsWith<GattStatusFailure> {
assertFailsWith<TestException> {
runBlocking {
bluetoothDevice.connectGattOrThrow(context)
}
}
}
}
}

private class TestException : Exception()

0 comments on commit 95d393c

Please sign in to comment.