1use core::fmt;
4
5#[derive(Debug)]
7#[non_exhaustive]
8pub enum Error {
9 InvalidConfig {
11 field: &'static str,
13 reason: &'static str,
15 },
16 DeviceNotFound,
18 DeviceClosed,
20 Busy,
22 Unsupported,
24 StreamClosed {
26 reason: &'static str,
28 },
29 Usb(nusb::Error),
31 Transfer(nusb::transfer::TransferError),
33 Operation {
35 operation: &'static str,
37 source: Box<Error>,
39 },
40 Protocol {
42 operation: &'static str,
44 reason: &'static str,
46 },
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51#[non_exhaustive]
52pub enum ErrorKind {
53 InvalidConfig,
55 NotFound,
57 DeviceClosed,
59 DeviceDisconnected,
61 Busy,
63 Unsupported,
65 Usb,
67 StreamClosed,
69 Other,
71}
72
73impl Error {
74 pub(crate) const fn invalid_config(field: &'static str, reason: &'static str) -> Self {
75 Self::InvalidConfig { field, reason }
76 }
77
78 pub(crate) const fn stream_closed(reason: &'static str) -> Self {
79 Self::StreamClosed { reason }
80 }
81
82 pub(crate) const fn protocol(operation: &'static str, reason: &'static str) -> Self {
83 Self::Protocol { operation, reason }
84 }
85
86 pub(crate) fn at(self, operation: &'static str) -> Self {
87 Self::Operation {
88 operation,
89 source: Box::new(self),
90 }
91 }
92
93 pub fn kind(&self) -> ErrorKind {
95 match self {
96 Self::InvalidConfig { .. } => ErrorKind::InvalidConfig,
97 Self::DeviceNotFound => ErrorKind::NotFound,
98 Self::DeviceClosed => ErrorKind::DeviceClosed,
99 Self::Busy => ErrorKind::Busy,
100 Self::Unsupported => ErrorKind::Unsupported,
101 Self::StreamClosed { .. } => ErrorKind::StreamClosed,
102 Self::Usb(error) => match error.kind() {
103 nusb::ErrorKind::Disconnected => ErrorKind::DeviceDisconnected,
104 nusb::ErrorKind::Busy => ErrorKind::Busy,
105 nusb::ErrorKind::NotFound => ErrorKind::NotFound,
106 nusb::ErrorKind::Unsupported => ErrorKind::Unsupported,
107 _ => ErrorKind::Usb,
108 },
109 Self::Transfer(nusb::transfer::TransferError::Disconnected) => {
110 ErrorKind::DeviceDisconnected
111 }
112 Self::Transfer(_) => ErrorKind::Usb,
113 Self::Operation { source, .. } => source.kind(),
114 Self::Protocol { .. } => ErrorKind::Other,
115 }
116 }
117}
118
119impl fmt::Display for Error {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self {
122 Self::InvalidConfig { field, reason } => {
123 write!(f, "invalid configuration for {field}: {reason}")
124 }
125 Self::DeviceNotFound => f.write_str("no matching HackRF device found"),
126 Self::DeviceClosed => f.write_str("HackRF device is closed"),
127 Self::Busy => f.write_str("HackRF device or USB resource is busy"),
128 Self::Unsupported => f.write_str("operation is unsupported"),
129 Self::StreamClosed { reason } => write!(f, "stream closed: {reason}"),
130 Self::Usb(error) => write!(f, "USB error: {error}"),
131 Self::Transfer(error) => write!(f, "USB transfer error: {error}"),
132 Self::Operation { operation, source } => write!(f, "{operation}: {source}"),
133 Self::Protocol { operation, reason } => write!(f, "{operation}: {reason}"),
134 }
135 }
136}
137
138impl std::error::Error for Error {
139 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
140 match self {
141 Self::Usb(error) => Some(error),
142 Self::Transfer(error) => Some(error),
143 Self::Operation { source, .. } => Some(source),
144 _ => None,
145 }
146 }
147}
148
149impl From<nusb::Error> for Error {
150 fn from(value: nusb::Error) -> Self {
151 Self::Usb(value)
152 }
153}
154
155impl From<nusb::transfer::TransferError> for Error {
156 fn from(value: nusb::transfer::TransferError) -> Self {
157 Self::Transfer(value)
158 }
159}
160
161pub type Result<T> = core::result::Result<T, Error>;
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn operation_context_preserves_error_kind() {
170 let error = Error::from(nusb::transfer::TransferError::Fault).at("reading samples");
171 assert_eq!(error.kind(), ErrorKind::Usb);
172 assert!(error.to_string().starts_with("reading samples:"));
173 }
174}