rust_ethernet_ip/
error.rs1use std::io;
3use std::time::Duration;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, EtherNetIpError>;
8
9#[derive(Debug, Error)]
11#[non_exhaustive]
12pub enum EtherNetIpError {
13 #[error("IO error: {0}")]
15 Io(#[from] io::Error),
16
17 #[error("Protocol error: {0}")]
19 Protocol(String),
20
21 #[error("Tag not found: {0}")]
23 TagNotFound(String),
24
25 #[error("Data type mismatch: expected {expected}, got {actual}")]
27 DataTypeMismatch { expected: String, actual: String },
28
29 #[error("Write error: {message} (status: {status})")]
31 WriteError { status: u8, message: String },
32
33 #[error("Read error: {message} (status: {status})")]
35 ReadError { status: u8, message: String },
36
37 #[error("Invalid response: {reason}")]
39 InvalidResponse { reason: String },
40
41 #[error("Operation timed out after {0:?}")]
43 Timeout(Duration),
44
45 #[error("UDT error: {0}")]
47 Udt(String),
48
49 #[error("Connection error: {0}")]
51 Connection(String),
52
53 #[error("Connection lost: {0}")]
55 ConnectionLost(String),
56
57 #[error("CIP error 0x{code:02X}: {message}")]
59 CipError { code: u8, message: String },
60
61 #[error("String too long: max length is {max_length}, but got {actual_length}")]
63 StringTooLong {
64 max_length: usize,
65 actual_length: usize,
66 },
67
68 #[error("Invalid string: {reason}")]
70 InvalidString { reason: String },
71
72 #[error("Tag error: {0}")]
74 Tag(String),
75
76 #[error("Permission denied: {0}")]
78 Permission(String),
79
80 #[error("UTF-8 error: {0}")]
82 Utf8(#[from] std::string::FromUtf8Error),
83
84 #[error("Other error: {0}")]
86 Other(String),
87
88 #[error("Subscription error: {0}")]
90 Subscription(String),
91
92 #[error("Unsupported API `{api}`: {reason}")]
94 Unsupported {
95 api: &'static str,
97 reason: &'static str,
99 },
100}
101
102impl EtherNetIpError {
103 #[must_use]
106 pub fn is_retriable(&self) -> bool {
107 matches!(
108 self,
109 EtherNetIpError::Timeout(_)
110 | EtherNetIpError::Connection(_)
111 | EtherNetIpError::ConnectionLost(_)
112 | EtherNetIpError::Io(_)
113 )
114 }
115}
116
117impl<T> From<std::sync::PoisonError<T>> for EtherNetIpError {
118 fn from(_: std::sync::PoisonError<T>) -> Self {
119 EtherNetIpError::Other("lock poisoned".to_string())
120 }
121}
122
123impl From<rust_ethernet_ip_tag_path::TagPathError> for EtherNetIpError {
124 fn from(error: rust_ethernet_ip_tag_path::TagPathError) -> Self {
125 EtherNetIpError::Protocol(error.to_string())
126 }
127}
128
129impl From<rust_ethernet_ip_protocol::ProtocolError> for EtherNetIpError {
130 fn from(error: rust_ethernet_ip_protocol::ProtocolError) -> Self {
131 EtherNetIpError::Protocol(error.to_string())
132 }
133}
134
135impl From<rust_ethernet_ip_types::TypeError> for EtherNetIpError {
136 fn from(error: rust_ethernet_ip_types::TypeError) -> Self {
137 EtherNetIpError::Protocol(error.to_string())
138 }
139}
140
141impl From<rust_ethernet_ip_udt::UdtError> for EtherNetIpError {
142 fn from(error: rust_ethernet_ip_udt::UdtError) -> Self {
143 match error {
144 rust_ethernet_ip_udt::UdtError::Protocol(message) => EtherNetIpError::Protocol(message),
145 rust_ethernet_ip_udt::UdtError::TagNotFound(tag) => EtherNetIpError::TagNotFound(tag),
146 rust_ethernet_ip_udt::UdtError::DataTypeMismatch { expected, actual } => {
147 EtherNetIpError::DataTypeMismatch { expected, actual }
148 }
149 }
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use std::assert_matches;
157 use std::sync::Mutex;
158
159 fn convert_poison_error() -> Result<()> {
160 let lock = Mutex::new(());
161 std::thread::scope(|scope| {
162 let handle = scope.spawn(|| {
163 let _guard = lock.lock().expect("test lock should not be poisoned yet");
164 panic!("poison test mutex");
165 });
166 assert!(handle.join().is_err());
167 });
168
169 let _guard = lock.lock()?;
170 Ok(())
171 }
172
173 #[test]
174 fn poison_error_converts_to_other_variant() {
175 let err = convert_poison_error().expect_err("poisoned mutex should convert into an error");
176 assert_matches!(err, EtherNetIpError::Other(message) if message == "lock poisoned");
177 }
178
179 #[test]
180 fn unsupported_error_names_api_and_reason() {
181 let err = EtherNetIpError::Unsupported {
182 api: "old_api",
183 reason: "use new_api",
184 };
185
186 assert_eq!(err.to_string(), "Unsupported API `old_api`: use new_api");
187 }
188}