Skip to main content

foxtive_worker/
error.rs

1use thiserror::Error;
2use std::time::Duration;
3
4/// Error types for the foxtive-worker crate.
5#[derive(Debug, Error)]
6pub enum WorkerError {
7    #[error("Message processing failed: {0}")]
8    ProcessingFailed(String),
9
10    #[error("Processing error: {0}")]
11    ProcessingError(String),
12
13    #[error("Backend error: {0}")]
14    BackendError(String),
15
16    #[error("Acknowledgment failed: {0}")]
17    AcknowledgmentFailed(String),
18
19    #[error("Worker {id} panicked: {panic_info}")]
20    WorkerPanic {
21        id: String,
22        panic_info: String,
23    },
24
25    #[error("Pool exhausted: no available workers")]
26    PoolExhausted,
27
28    #[error("Configuration error: {0}")]
29    ConfigError(String),
30
31    #[error("Shutdown requested")]
32    Shutdown,
33
34    #[error("Serialization error: {0}")]
35    SerializationError(#[from] serde_json::Error),
36
37    #[error("Timeout error: {0}")]
38    Timeout(String),
39
40    #[error("Channel error: {0}")]
41    ChannelError(String),
42
43    #[error("Retryable failure: {source}. Retrying in {delay_ms:?}ms")]
44    RetryableFailure {
45        source: Box<WorkerError>,
46        delay_ms: Duration,
47    },
48
49    #[error("Retries exhausted for message: {source}")]
50    RetriesExhausted {
51        source: Box<WorkerError>,
52    },
53
54    #[error("Message already acknowledged by middleware")]
55    AlreadyAcknowledged,
56
57    #[error("Application error: {0}")]
58    AppError(#[from] anyhow::Error),
59}
60
61/// A type alias for results returned by worker operations.
62pub type WorkerResult<T> = Result<T, WorkerError>;
63
64// Note: Removed generic From<anyhow::Error> implementation to avoid conflict with AppError variant.
65// Use WorkerError::AppError(err) or the ? operator with anyhow::Error directly.
66// The AppError variant preserves the full anyhow::Error with context chain and backtrace.
67
68impl From<std::io::Error> for WorkerError {
69    fn from(err: std::io::Error) -> Self {
70        WorkerError::BackendError(err.to_string())
71    }
72}
73
74impl From<tokio::task::JoinError> for WorkerError {
75    fn from(err: tokio::task::JoinError) -> Self {
76        // Preserve panic information if available
77        match err.try_into_panic() {
78            Ok(reason) => {
79                // Task panicked
80                if let Some(s) = reason.downcast_ref::<String>() {
81                    WorkerError::ProcessingFailed(format!("Task panicked: {}", s))
82                } else if let Some(s) = reason.downcast_ref::<&str>() {
83                    WorkerError::ProcessingFailed(format!("Task panicked: {}", s))
84                } else {
85                    WorkerError::ProcessingFailed("Task panicked with unknown reason".to_string())
86                }
87            }
88            Err(cancelled_err) => {
89                // Task was cancelled
90                WorkerError::ProcessingFailed(format!("Task cancelled: {}", cancelled_err))
91            }
92        }
93    }
94}