1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum Error {
12 #[error("characteristic not found: {0}")]
14 CharacteristicNotFound(&'static str),
15
16 #[error("service not found: {0}")]
18 ServiceNotFound(&'static str),
19
20 #[error("insufficient authentication - pairing required")]
22 InsufficientAuthentication,
23
24 #[error("device not connected")]
26 NotConnected,
27
28 #[error("transport error: {0}")]
30 Transport(String),
31
32 #[error("invalid response: {0}")]
34 InvalidResponse(String),
35
36 #[error("operation timed out")]
38 Timeout,
39
40 #[error("io error: {0}")]
42 Io(#[from] std::io::Error),
43
44 #[cfg(feature = "btleplug-support")]
46 #[error("btleplug error: {0}")]
47 Btleplug(#[from] btleplug::Error),
48}
49
50impl Error {
51 #[must_use]
53 pub const fn requires_pairing(&self) -> bool {
54 matches!(self, Self::InsufficientAuthentication)
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn error_display_messages() {
64 let cases = [
65 (Error::CharacteristicNotFound("lock_state"), "characteristic not found: lock_state"),
66 (Error::ServiceNotFound("battery"), "service not found: battery"),
67 (Error::InsufficientAuthentication, "insufficient authentication - pairing required"),
68 (Error::NotConnected, "device not connected"),
69 (Error::Transport("timeout".into()), "transport error: timeout"),
70 (Error::InvalidResponse("empty".into()), "invalid response: empty"),
71 (Error::Timeout, "operation timed out"),
72 ];
73 for (error, expected) in cases {
74 assert_eq!(error.to_string(), expected);
75 }
76 }
77
78 #[test]
79 fn requires_pairing_returns_true_for_auth_error() {
80 assert!(Error::InsufficientAuthentication.requires_pairing());
81 }
82
83 #[test]
84 fn requires_pairing_returns_false_for_other_errors() {
85 let non_auth_errors = [
86 Error::CharacteristicNotFound("test"),
87 Error::ServiceNotFound("test"),
88 Error::NotConnected,
89 Error::Transport("test".into()),
90 Error::InvalidResponse("test".into()),
91 Error::Timeout,
92 ];
93 for error in non_auth_errors {
94 assert!(!error.requires_pairing(), "unexpected true for {error:?}");
95 }
96 }
97
98 #[test]
99 fn io_error_conversion() {
100 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
101 let err: Error = io_err.into();
102 assert!(matches!(err, Error::Io(_)));
103 }
104
105 #[test]
106 fn error_is_send_and_sync() {
107 fn assert_send_sync<T: Send + Sync>() {}
108 assert_send_sync::<Error>();
110 }
111}