From 9ed2461064d158297b7450722e0883332f623751 Mon Sep 17 00:00:00 2001 From: Robert Manson Date: Wed, 27 Dec 2023 06:44:03 -0800 Subject: [PATCH] [Feature] Add BluetoothError error reasons (#42) --- Sources/Peripheral/BluetoothError.swift | 9 ++++- Sources/Utils/CentralManagerUtils.swift | 34 ++++++++++--------- Tests/CentralManagerUtilsTests.swift | 45 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 Tests/CentralManagerUtilsTests.swift diff --git a/Sources/Peripheral/BluetoothError.swift b/Sources/Peripheral/BluetoothError.swift index f55879e..d3f668e 100644 --- a/Sources/Peripheral/BluetoothError.swift +++ b/Sources/Peripheral/BluetoothError.swift @@ -2,8 +2,15 @@ import Foundation +public enum BluetoothUnavailableReason { + case poweredOff + case unauthorized + case unsupported + case unknown +} + public enum BluetoothError: Error { - case bluetoothUnavailable + case bluetoothUnavailable(BluetoothUnavailableReason) case connectingInProgress case disconnectingInProgress case cancelledConnectionToPeripheral diff --git a/Sources/Utils/CentralManagerUtils.swift b/Sources/Utils/CentralManagerUtils.swift index 1b3ab8f..e3787c5 100644 --- a/Sources/Utils/CentralManagerUtils.swift +++ b/Sources/Utils/CentralManagerUtils.swift @@ -10,28 +10,32 @@ struct CentralManagerUtils { /// - Returns: success when `poweredOn`; failure when `unsupported`, `unauthorized` or `poweredOff`; and /// nil for `unknown` or `resetting`. static func isBluetoothReady(_ bluetoothState: CBManagerState) -> Result? { - guard let isBluetoothReady: Bool = Self.isBluetoothReady(bluetoothState) else { + guard bluetoothState != .poweredOn else { + return .success(()) + } + + guard let reason = BluetoothUnavailableReason(bluetoothState) else { return nil } - return isBluetoothReady - ? .success(()) - : .failure(BluetoothError.bluetoothUnavailable) + + return .failure(BluetoothError.bluetoothUnavailable(reason)) } - - /// Whether Bluetooth is ready to be used or not given a bluetoothState. - /// - Returns: true when `poweredOn`; false when `unsupported`, `unauthorized` or `poweredOff`; and - /// nil for `unknown` or `resetting`. - private static func isBluetoothReady(_ bluetoothState: CBManagerState) -> Bool? { +} + +private extension BluetoothUnavailableReason { + init?(_ bluetoothState: CBManagerState) { switch bluetoothState { - case .poweredOn: - return true - case .unsupported, .unauthorized, .poweredOff: - return false - case .unknown, .resetting: + case .unauthorized: + self = .unauthorized + case .unsupported: + self = .unsupported + case .poweredOff: + self = .poweredOff + case .poweredOn, .unknown, .resetting: return nil @unknown default: AsyncBluetooth.commonLogger.error("Unsupported CBManagerState received with raw value of \(bluetoothState.rawValue)") - return false + self = .unknown } } } diff --git a/Tests/CentralManagerUtilsTests.swift b/Tests/CentralManagerUtilsTests.swift new file mode 100644 index 0000000..aaaf5c9 --- /dev/null +++ b/Tests/CentralManagerUtilsTests.swift @@ -0,0 +1,45 @@ +import Foundation +import XCTest +@testable import AsyncBluetooth + +class CentralManagerUtilsTests: XCTestCase { + func testIsBluetoothReady() throws { + XCTAssertNotNil( + try CentralManagerUtils.isBluetoothReady(.poweredOn)?.get() + ) + + XCTAssertNil(CentralManagerUtils.isBluetoothReady(.resetting)) + + XCTAssertEqual( + CentralManagerUtils.isBluetoothReady(.poweredOff)?.error?.extractReason, + .poweredOff + ) + + XCTAssertEqual( + CentralManagerUtils.isBluetoothReady(.unauthorized)?.error?.extractReason, + .unauthorized + ) + } +} + +private extension Result<(), Error> { + var error: Error? { + switch self { + case .success: return nil + case .failure(let error): return error + } + } +} + +private extension Error { + var extractReason: BluetoothUnavailableReason? { + guard let error = self as? BluetoothError else { + return nil + } + + switch error { + case .bluetoothUnavailable(let reason): return reason + default: return nil + } + } +}