use core::fmt;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
InvalidConfig {
field: &'static str,
reason: &'static str,
},
DeviceNotFound,
DeviceClosed,
Busy,
Unsupported,
StreamClosed {
reason: &'static str,
},
Usb(nusb::Error),
Transfer(nusb::transfer::TransferError),
Operation {
operation: &'static str,
source: Box<Error>,
},
Protocol {
operation: &'static str,
reason: &'static str,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ErrorKind {
InvalidConfig,
NotFound,
DeviceClosed,
DeviceDisconnected,
Busy,
Unsupported,
Usb,
StreamClosed,
Other,
}
impl Error {
pub(crate) const fn invalid_config(field: &'static str, reason: &'static str) -> Self {
Self::InvalidConfig { field, reason }
}
pub(crate) const fn stream_closed(reason: &'static str) -> Self {
Self::StreamClosed { reason }
}
pub(crate) const fn protocol(operation: &'static str, reason: &'static str) -> Self {
Self::Protocol { operation, reason }
}
pub(crate) fn at(self, operation: &'static str) -> Self {
Self::Operation {
operation,
source: Box::new(self),
}
}
pub fn kind(&self) -> ErrorKind {
match self {
Self::InvalidConfig { .. } => ErrorKind::InvalidConfig,
Self::DeviceNotFound => ErrorKind::NotFound,
Self::DeviceClosed => ErrorKind::DeviceClosed,
Self::Busy => ErrorKind::Busy,
Self::Unsupported => ErrorKind::Unsupported,
Self::StreamClosed { .. } => ErrorKind::StreamClosed,
Self::Usb(error) => match error.kind() {
nusb::ErrorKind::Disconnected => ErrorKind::DeviceDisconnected,
nusb::ErrorKind::Busy => ErrorKind::Busy,
nusb::ErrorKind::NotFound => ErrorKind::NotFound,
nusb::ErrorKind::Unsupported => ErrorKind::Unsupported,
_ => ErrorKind::Usb,
},
Self::Transfer(nusb::transfer::TransferError::Disconnected) => {
ErrorKind::DeviceDisconnected
}
Self::Transfer(_) => ErrorKind::Usb,
Self::Operation { source, .. } => source.kind(),
Self::Protocol { .. } => ErrorKind::Other,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidConfig { field, reason } => {
write!(f, "invalid configuration for {field}: {reason}")
}
Self::DeviceNotFound => f.write_str("no matching HackRF device found"),
Self::DeviceClosed => f.write_str("HackRF device is closed"),
Self::Busy => f.write_str("HackRF device or USB resource is busy"),
Self::Unsupported => f.write_str("operation is unsupported"),
Self::StreamClosed { reason } => write!(f, "stream closed: {reason}"),
Self::Usb(error) => write!(f, "USB error: {error}"),
Self::Transfer(error) => write!(f, "USB transfer error: {error}"),
Self::Operation { operation, source } => write!(f, "{operation}: {source}"),
Self::Protocol { operation, reason } => write!(f, "{operation}: {reason}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Usb(error) => Some(error),
Self::Transfer(error) => Some(error),
Self::Operation { source, .. } => Some(source),
_ => None,
}
}
}
impl From<nusb::Error> for Error {
fn from(value: nusb::Error) -> Self {
Self::Usb(value)
}
}
impl From<nusb::transfer::TransferError> for Error {
fn from(value: nusb::transfer::TransferError) -> Self {
Self::Transfer(value)
}
}
pub type Result<T> = core::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn operation_context_preserves_error_kind() {
let error = Error::from(nusb::transfer::TransferError::Fault).at("reading samples");
assert_eq!(error.kind(), ErrorKind::Usb);
assert!(error.to_string().starts_with("reading samples:"));
}
}