1use std::error::Error as StdError;
4use std::fmt;
5
6#[derive(Debug)]
15pub enum Error {
16 WouldBlock,
18
19 Stopped,
21
22 Disconnected(String),
24
25 PermissionDenied(String),
33
34 InvalidConfig(String),
36
37 Backend(Box<dyn StdError + Send + Sync>),
39}
40
41impl fmt::Display for Error {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Error::WouldBlock => write!(f, "would block: device cannot accept more data"),
45 Error::Stopped => write!(f, "stopped: stream was explicitly stopped"),
46 Error::Disconnected(msg) => write!(f, "disconnected: {}", msg),
47 Error::PermissionDenied(msg) => write!(f, "permission denied: {}", msg),
48 Error::InvalidConfig(msg) => write!(f, "invalid configuration: {}", msg),
49 Error::Backend(e) => write!(f, "backend error: {}", e),
50 }
51 }
52}
53
54impl StdError for Error {
55 fn source(&self) -> Option<&(dyn StdError + 'static)> {
56 match self {
57 Error::Backend(e) => Some(e.as_ref()),
58 _ => None,
59 }
60 }
61}
62
63impl Error {
64 pub fn disconnected(msg: impl Into<String>) -> Self {
66 Error::Disconnected(msg.into())
67 }
68
69 pub fn permission_denied(msg: impl Into<String>) -> Self {
71 Error::PermissionDenied(msg.into())
72 }
73
74 pub fn usb_permission_denied(context: impl std::fmt::Display) -> Self {
79 #[cfg(target_os = "linux")]
80 let msg = format!(
81 "{context}: USB access denied — this laser DAC needs a udev rule \
82 granting the user access to the device node (then replug the device)"
83 );
84 #[cfg(not(target_os = "linux"))]
85 let msg = format!("{context}: USB access denied");
86 Error::PermissionDenied(msg)
87 }
88
89 pub fn invalid_config(msg: impl Into<String>) -> Self {
91 Error::InvalidConfig(msg.into())
92 }
93
94 pub fn backend(err: impl StdError + Send + Sync + 'static) -> Self {
96 Error::Backend(Box::new(err))
97 }
98
99 pub fn is_would_block(&self) -> bool {
101 matches!(self, Error::WouldBlock)
102 }
103
104 pub fn is_disconnected(&self) -> bool {
106 matches!(self, Error::Disconnected(_))
107 }
108
109 pub fn is_permission_denied(&self) -> bool {
115 matches!(self, Error::PermissionDenied(_))
116 }
117
118 pub fn is_stopped(&self) -> bool {
120 matches!(self, Error::Stopped)
121 }
122}
123
124impl From<std::io::Error> for Error {
125 fn from(err: std::io::Error) -> Self {
126 if err.kind() == std::io::ErrorKind::WouldBlock {
127 Error::WouldBlock
128 } else {
129 Error::Backend(Box::new(err))
130 }
131 }
132}
133
134pub type Result<T> = std::result::Result<T, Error>;
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn permission_denied_is_detected_and_distinct_from_backend() {
143 let err = Error::permission_denied("no access");
144 assert!(err.is_permission_denied());
145 assert!(!err.is_disconnected());
146 assert!(!matches!(err, Error::Backend(_)));
147 }
148
149 #[test]
150 fn usb_permission_denied_carries_context() {
151 let err = Error::usb_permission_denied("helios open");
152 assert!(err.is_permission_denied());
153 let msg = err.to_string();
154 assert!(
155 msg.contains("helios open"),
156 "message keeps the context: {msg}"
157 );
158 #[cfg(target_os = "linux")]
161 assert!(
162 msg.contains("udev"),
163 "linux message names the udev fix: {msg}"
164 );
165 }
166}