| 1 | //! Every way budctl can fail, as one typed error. |
| 2 | //! |
| 3 | //! Front ends decide what to do with each [`Kind`]; the CLI maps them to its exit codes. |
| 4 | |
| 5 | use std::fmt; |
| 6 | use std::io; |
| 7 | |
| 8 | pub type Result<T> = std::result::Result<T, Error>; |
| 9 | |
| 10 | #[derive(Debug)] |
| 11 | pub enum Error { |
| 12 | /// The request makes no sense for this device (bad value, several devices and no choice made…). |
| 13 | Usage(String), |
| 14 | /// The buds do not offer this feature. |
| 15 | Unsupported(String), |
| 16 | /// No paired device matches, or it is not a model budctl supports. |
| 17 | NoDevice(String), |
| 18 | /// The buds are paired but not connected, or the link dropped. |
| 19 | NotConnected(String), |
| 20 | /// The buds' SDP record has none of the model's control services. |
| 21 | NoService(String), |
| 22 | /// The buds identified themselves as a different model. |
| 23 | WrongModel { expected: &'static str, found: String }, |
| 24 | /// BlueZ could not be reached over D-Bus. |
| 25 | Bluez(String), |
| 26 | /// Another client holds the control channel. |
| 27 | ChannelBusy(String), |
| 28 | /// The buds did not answer in time. |
| 29 | Timeout(String), |
| 30 | /// The buds answered with a non-zero status byte. |
| 31 | Rejected { what: String, status: u8 }, |
| 32 | /// The buds sent something budctl cannot parse. |
| 33 | Protocol(String), |
| 34 | /// Any other operating-system error. |
| 35 | Os { context: &'static str, source: io::Error }, |
| 36 | } |
| 37 | |
| 38 | /// How a front end should treat an error. |
| 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 40 | pub enum Kind { |
| 41 | /// The user asked for something invalid. (CLI exit code 1) |
| 42 | Usage, |
| 43 | /// The device was not found or is not reachable. (CLI exit code 2) |
| 44 | NotFound, |
| 45 | /// Another app holds the control channel. (CLI exit code 3) |
| 46 | Busy, |
| 47 | /// The buds rejected the command, timed out or sent garbage. (CLI exit code 4) |
| 48 | Failed, |
| 49 | } |
| 50 | |
| 51 | impl Error { |
| 52 | pub fn kind(&self) -> Kind { |
| 53 | match self { |
| 54 | Error::Usage(_) | Error::Unsupported(_) => Kind::Usage, |
| 55 | Error::NoDevice(_) |
| 56 | | Error::NotConnected(_) |
| 57 | | Error::NoService(_) |
| 58 | | Error::WrongModel { .. } |
| 59 | | Error::Bluez(_) |
| 60 | | Error::Os { .. } => Kind::NotFound, |
| 61 | Error::ChannelBusy(_) => Kind::Busy, |
| 62 | Error::Timeout(_) | Error::Rejected { .. } | Error::Protocol(_) => Kind::Failed, |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | impl fmt::Display for Error { |
| 68 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 69 | match self { |
| 70 | Error::Usage(msg) | Error::Unsupported(msg) | Error::NoDevice(msg) => f.write_str(msg), |
| 71 | Error::NotConnected(msg) => f.write_str(msg), |
| 72 | Error::NoService(address) => write!( |
| 73 | f, |
| 74 | "{address} offers no control service budctl knows (checked its SDP record)" |
| 75 | ), |
| 76 | Error::WrongModel { expected, found } => { |
| 77 | write!(f, "these buds report product id {found}, which is not a {expected}") |
| 78 | } |
| 79 | Error::Bluez(msg) => write!(f, "cannot reach BlueZ over D-Bus: {msg} (is bluetoothd running?)"), |
| 80 | Error::ChannelBusy(detail) => { |
| 81 | write!(f, "control channel busy — is another app connected? ({detail})") |
| 82 | } |
| 83 | Error::Timeout(what) => write!(f, "no answer from the buds: {what}"), |
| 84 | Error::Rejected { what, status } => { |
| 85 | write!(f, "the buds rejected {what} (status 0x{status:02X})") |
| 86 | } |
| 87 | Error::Protocol(msg) => write!(f, "unexpected data from the buds: {msg}"), |
| 88 | Error::Os { context, source } => write!(f, "{context}: {source}"), |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | impl std::error::Error for Error { |
| 94 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| 95 | match self { |
| 96 | Error::Os { source, .. } => Some(source), |
| 97 | _ => None, |
| 98 | } |
| 99 | } |
| 100 | } |