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 {
28 expected: String,
30 actual: String,
32 },
33
34 #[error("Write error: {message} (status: {status})")]
36 WriteError {
37 status: u8,
39 message: String,
41 },
42
43 #[error("Read error: {message} (status: {status})")]
45 ReadError {
46 status: u8,
48 message: String,
50 },
51
52 #[error("Invalid response: {reason}")]
54 InvalidResponse {
55 reason: String,
57 },
58
59 #[error("Operation timed out after {0:?}")]
61 Timeout(Duration),
62
63 #[error("UDT error: {0}")]
65 Udt(String),
66
67 #[error("Connection error: {0}")]
69 Connection(String),
70
71 #[error("Connection lost: {0}")]
73 ConnectionLost(String),
74
75 #[error("CIP error 0x{code:02X}: {message}")]
77 CipError {
78 code: u8,
80 message: String,
82 },
83
84 #[error("String too long: max length is {max_length}, but got {actual_length}")]
86 StringTooLong {
87 max_length: usize,
89 actual_length: usize,
91 },
92
93 #[error("Invalid string: {reason}")]
95 InvalidString {
96 reason: String,
98 },
99
100 #[error("Tag error: {0}")]
102 Tag(String),
103
104 #[error("Permission denied: {0}")]
106 Permission(String),
107
108 #[error("UTF-8 error: {0}")]
110 Utf8(#[from] std::string::FromUtf8Error),
111
112 #[error("Other error: {0}")]
114 Other(String),
115
116 #[error("Subscription error: {0}")]
118 Subscription(String),
119
120 #[error("Unsupported API `{api}`: {reason}")]
122 Unsupported {
123 api: &'static str,
125 reason: &'static str,
127 },
128}
129
130impl EtherNetIpError {
131 #[must_use]
134 pub fn is_retriable(&self) -> bool {
135 matches!(
136 self,
137 EtherNetIpError::Timeout(_)
138 | EtherNetIpError::Connection(_)
139 | EtherNetIpError::ConnectionLost(_)
140 | EtherNetIpError::Io(_)
141 )
142 }
143}
144
145impl<T> From<std::sync::PoisonError<T>> for EtherNetIpError {
146 fn from(_: std::sync::PoisonError<T>) -> Self {
147 EtherNetIpError::Other("lock poisoned".to_string())
148 }
149}
150
151impl From<rust_ethernet_ip_tag_path::TagPathError> for EtherNetIpError {
152 fn from(error: rust_ethernet_ip_tag_path::TagPathError) -> Self {
153 EtherNetIpError::Protocol(error.to_string())
154 }
155}
156
157impl From<rust_ethernet_ip_protocol::ProtocolError> for EtherNetIpError {
158 fn from(error: rust_ethernet_ip_protocol::ProtocolError) -> Self {
159 EtherNetIpError::Protocol(error.to_string())
160 }
161}
162
163impl From<rust_ethernet_ip_types::TypeError> for EtherNetIpError {
164 fn from(error: rust_ethernet_ip_types::TypeError) -> Self {
165 EtherNetIpError::Protocol(error.to_string())
166 }
167}
168
169impl From<rust_ethernet_ip_udt::UdtError> for EtherNetIpError {
170 fn from(error: rust_ethernet_ip_udt::UdtError) -> Self {
171 match error {
172 rust_ethernet_ip_udt::UdtError::Protocol(message) => EtherNetIpError::Protocol(message),
173 rust_ethernet_ip_udt::UdtError::TagNotFound(tag) => EtherNetIpError::TagNotFound(tag),
174 rust_ethernet_ip_udt::UdtError::DataTypeMismatch { expected, actual } => {
175 EtherNetIpError::DataTypeMismatch { expected, actual }
176 }
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use std::sync::Mutex;
185
186 fn convert_poison_error() -> Result<()> {
187 let lock = Mutex::new(());
188 std::thread::scope(|scope| {
189 let handle = scope.spawn(|| {
190 let _guard = lock.lock().expect("test lock should not be poisoned yet");
191 panic!("poison test mutex");
192 });
193 assert!(handle.join().is_err());
194 });
195
196 let _guard = lock.lock()?;
197 Ok(())
198 }
199
200 #[test]
201 fn poison_error_converts_to_other_variant() {
202 let err = convert_poison_error().expect_err("poisoned mutex should convert into an error");
203 assert!(matches!(
204 err,
205 EtherNetIpError::Other(message) if message == "lock poisoned"
206 ));
207 }
208
209 #[test]
210 fn unsupported_error_names_api_and_reason() {
211 let err = EtherNetIpError::Unsupported {
212 api: "old_api",
213 reason: "use new_api",
214 };
215
216 assert_eq!(err.to_string(), "Unsupported API `old_api`: use new_api");
217 }
218}