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
93impl EtherNetIpError {
94 #[must_use]
97 pub fn is_retriable(&self) -> bool {
98 matches!(
99 self,
100 EtherNetIpError::Timeout(_)
101 | EtherNetIpError::Connection(_)
102 | EtherNetIpError::ConnectionLost(_)
103 | EtherNetIpError::Io(_)
104 )
105 }
106}
107
108impl<T> From<std::sync::PoisonError<T>> for EtherNetIpError {
109 fn from(_: std::sync::PoisonError<T>) -> Self {
110 EtherNetIpError::Other("lock poisoned".to_string())
111 }
112}
113
114impl From<rust_ethernet_ip_tag_path::TagPathError> for EtherNetIpError {
115 fn from(error: rust_ethernet_ip_tag_path::TagPathError) -> Self {
116 EtherNetIpError::Protocol(error.to_string())
117 }
118}
119
120impl From<rust_ethernet_ip_protocol::ProtocolError> for EtherNetIpError {
121 fn from(error: rust_ethernet_ip_protocol::ProtocolError) -> Self {
122 EtherNetIpError::Protocol(error.to_string())
123 }
124}
125
126impl From<rust_ethernet_ip_types::TypeError> for EtherNetIpError {
127 fn from(error: rust_ethernet_ip_types::TypeError) -> Self {
128 EtherNetIpError::Protocol(error.to_string())
129 }
130}
131
132impl From<rust_ethernet_ip_udt::UdtError> for EtherNetIpError {
133 fn from(error: rust_ethernet_ip_udt::UdtError) -> Self {
134 match error {
135 rust_ethernet_ip_udt::UdtError::Protocol(message) => EtherNetIpError::Protocol(message),
136 rust_ethernet_ip_udt::UdtError::TagNotFound(tag) => EtherNetIpError::TagNotFound(tag),
137 rust_ethernet_ip_udt::UdtError::DataTypeMismatch { expected, actual } => {
138 EtherNetIpError::DataTypeMismatch { expected, actual }
139 }
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use std::sync::Mutex;
148
149 fn convert_poison_error() -> Result<()> {
150 let lock = Mutex::new(());
151 std::thread::scope(|scope| {
152 let handle = scope.spawn(|| {
153 let _guard = lock.lock().expect("test lock should not be poisoned yet");
154 panic!("poison test mutex");
155 });
156 assert!(handle.join().is_err());
157 });
158
159 let _guard = lock.lock()?;
160 Ok(())
161 }
162
163 #[test]
164 fn poison_error_converts_to_other_variant() {
165 let err = convert_poison_error().expect_err("poisoned mutex should convert into an error");
166 assert!(matches!(err, EtherNetIpError::Other(message) if message == "lock poisoned"));
167 }
168}