1use crate::message::MessageId;
11use std::fmt;
12
13pub type Result<T> = std::result::Result<T, Error>;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ErrorKind {
19 Transient,
23
24 Permanent,
28}
29
30#[derive(Debug)]
32pub enum Error {
33 MessageTooLarge {
35 size: usize,
37 max_size: usize,
39 },
40
41 MessageNotFound(MessageId),
43
44 Encode(String),
46
47 Decode(String),
49
50 Send {
52 target: String,
54 reason: String,
56 },
57
58 Shutdown,
60
61 NoPeers,
63
64 Channel(String),
66
67 QueueFull {
72 dropped: usize,
74 capacity: usize,
76 },
77
78 Memberlist(String),
80
81 Config(String),
83
84 Io(std::io::Error),
86
87 Custom(String),
89}
90
91impl fmt::Display for Error {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 match self {
94 Error::MessageTooLarge { size, max_size } => {
95 write!(
96 f,
97 "message size ({} bytes) exceeds maximum ({} bytes)",
98 size, max_size
99 )
100 }
101 Error::MessageNotFound(id) => {
102 write!(f, "message not found in cache: {}", id)
103 }
104 Error::Encode(msg) => {
105 write!(f, "failed to encode message: {}", msg)
106 }
107 Error::Decode(msg) => {
108 write!(f, "failed to decode message: {}", msg)
109 }
110 Error::Send { target, reason } => {
111 write!(f, "failed to send to {}: {}", target, reason)
112 }
113 Error::Shutdown => {
114 write!(f, "plumtree instance has been shut down")
115 }
116 Error::NoPeers => {
117 write!(f, "no peers available for broadcast")
118 }
119 Error::Channel(msg) => {
120 write!(f, "channel error: {}", msg)
121 }
122 Error::QueueFull { dropped, capacity } => {
123 write!(
124 f,
125 "outgoing message queue is full ({} messages dropped, capacity {})",
126 dropped, capacity
127 )
128 }
129 Error::Memberlist(msg) => {
130 write!(f, "memberlist error: {}", msg)
131 }
132 Error::Config(msg) => {
133 write!(f, "configuration error: {}", msg)
134 }
135 Error::Io(err) => {
136 write!(f, "IO error: {}", err)
137 }
138 Error::Custom(msg) => {
139 write!(f, "{}", msg)
140 }
141 }
142 }
143}
144
145impl std::error::Error for Error {
146 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
147 match self {
148 Error::Io(err) => Some(err),
149 _ => None,
150 }
151 }
152}
153
154impl Error {
155 pub const fn kind(&self) -> ErrorKind {
160 match self {
161 Error::MessageTooLarge { .. } => ErrorKind::Permanent,
163 Error::Config(_) => ErrorKind::Permanent,
164 Error::Shutdown => ErrorKind::Permanent,
165 Error::Encode(_) => ErrorKind::Permanent,
166 Error::Decode(_) => ErrorKind::Permanent,
167
168 Error::MessageNotFound(_) => ErrorKind::Transient,
170 Error::Send { .. } => ErrorKind::Transient,
171 Error::NoPeers => ErrorKind::Transient,
172 Error::Channel(_) => ErrorKind::Transient,
173 Error::QueueFull { .. } => ErrorKind::Transient,
174 Error::Memberlist(_) => ErrorKind::Transient,
175 Error::Io(_) => ErrorKind::Transient,
176
177 Error::Custom(_) => ErrorKind::Transient,
179 }
180 }
181
182 pub const fn is_transient(&self) -> bool {
198 matches!(self.kind(), ErrorKind::Transient)
199 }
200
201 pub const fn is_permanent(&self) -> bool {
203 matches!(self.kind(), ErrorKind::Permanent)
204 }
205
206 pub const fn is_shutdown(&self) -> bool {
208 matches!(self, Error::Shutdown)
209 }
210
211 pub fn is_resource_exhausted(&self) -> bool {
213 match self {
214 Error::Channel(msg) => msg.contains("full") || msg.contains("capacity"),
215 Error::QueueFull { .. } => true,
216 Error::NoPeers => true,
217 _ => false,
218 }
219 }
220
221 pub const fn is_queue_full(&self) -> bool {
223 matches!(self, Error::QueueFull { .. })
224 }
225}
226
227impl From<std::io::Error> for Error {
228 fn from(err: std::io::Error) -> Self {
229 Error::Io(err)
230 }
231}
232
233impl<T> From<async_channel::SendError<T>> for Error {
234 fn from(err: async_channel::SendError<T>) -> Self {
235 Error::Channel(err.to_string())
236 }
237}
238
239impl From<async_channel::RecvError> for Error {
240 fn from(err: async_channel::RecvError) -> Self {
241 Error::Channel(err.to_string())
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_error_display() {
251 let err = Error::MessageTooLarge {
252 size: 100000,
253 max_size: 65536,
254 };
255 assert!(err.to_string().contains("100000"));
256 assert!(err.to_string().contains("65536"));
257 }
258
259 #[test]
260 fn test_error_from_io() {
261 let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test error");
262 let err: Error = io_err.into();
263 assert!(matches!(err, Error::Io(_)));
264 }
265
266 #[test]
267 fn test_permanent_errors() {
268 let err = Error::MessageTooLarge {
270 size: 100,
271 max_size: 50,
272 };
273 assert!(err.is_permanent());
274 assert!(!err.is_transient());
275 assert_eq!(err.kind(), ErrorKind::Permanent);
276
277 let err = Error::Config("bad config".to_string());
279 assert!(err.is_permanent());
280
281 let err = Error::Shutdown;
283 assert!(err.is_permanent());
284 assert!(err.is_shutdown());
285
286 let err = Error::Encode("bad format".to_string());
288 assert!(err.is_permanent());
289
290 let err = Error::Decode("invalid data".to_string());
291 assert!(err.is_permanent());
292 }
293
294 #[test]
295 fn test_transient_errors() {
296 let err = Error::Send {
298 target: "node1".to_string(),
299 reason: "connection refused".to_string(),
300 };
301 assert!(err.is_transient());
302 assert!(!err.is_permanent());
303 assert_eq!(err.kind(), ErrorKind::Transient);
304
305 let err = Error::NoPeers;
307 assert!(err.is_transient());
308
309 let err = Error::Channel("channel closed".to_string());
311 assert!(err.is_transient());
312
313 let err = Error::MessageNotFound(MessageId::new());
315 assert!(err.is_transient());
316
317 let io_err = std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout");
319 let err: Error = io_err.into();
320 assert!(err.is_transient());
321 }
322
323 #[test]
324 fn test_resource_exhausted() {
325 let err = Error::Channel("channel full".to_string());
326 assert!(err.is_resource_exhausted());
327
328 let err = Error::NoPeers;
329 assert!(err.is_resource_exhausted());
330
331 let err = Error::Shutdown;
332 assert!(!err.is_resource_exhausted());
333 }
334
335 #[test]
336 fn test_queue_full_error() {
337 let err = Error::QueueFull {
338 dropped: 5,
339 capacity: 1024,
340 };
341
342 assert!(err.is_transient());
344 assert!(!err.is_permanent());
345 assert_eq!(err.kind(), ErrorKind::Transient);
346
347 assert!(err.is_resource_exhausted());
349 assert!(err.is_queue_full());
350
351 let msg = err.to_string();
353 assert!(msg.contains("5"));
354 assert!(msg.contains("1024"));
355 assert!(msg.contains("queue"));
356 }
357}