Skip to main content

foxtive_worker/
error.rs

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