1use thiserror::Error;
2
3pub type Result<T> = std::result::Result<T, CelersError>;
4
5#[derive(Error, Debug)]
6pub enum CelersError {
7 #[error("Serialization error: {0}")]
8 Serialization(String),
9
10 #[error("Deserialization error: {0}")]
11 Deserialization(String),
12
13 #[error("Broker error: {0}")]
14 Broker(String),
15
16 #[error("Task not found: {0}")]
17 TaskNotFound(String),
18
19 #[error("Task execution failed: {0}")]
20 TaskExecution(String),
21
22 #[error("Task was revoked: {0}")]
23 TaskRevoked(crate::TaskId),
24
25 #[error("Task timeout: {0}")]
26 Timeout(String),
27
28 #[error("Invalid task state transition from {from:?} to {to:?}")]
29 InvalidStateTransition { from: String, to: String },
30
31 #[error("Configuration error: {0}")]
32 Configuration(String),
33
34 #[error("IO error: {0}")]
35 Io(#[from] std::io::Error),
36
37 #[error("Other error: {0}")]
38 Other(String),
39}
40
41impl CelersError {
42 #[inline]
44 #[must_use]
45 pub const fn is_serialization(&self) -> bool {
46 matches!(self, CelersError::Serialization(_))
47 }
48
49 #[inline]
51 #[must_use]
52 pub const fn is_deserialization(&self) -> bool {
53 matches!(self, CelersError::Deserialization(_))
54 }
55
56 #[inline]
58 #[must_use]
59 pub const fn is_broker(&self) -> bool {
60 matches!(self, CelersError::Broker(_))
61 }
62
63 #[inline]
65 #[must_use]
66 pub const fn is_task_not_found(&self) -> bool {
67 matches!(self, CelersError::TaskNotFound(_))
68 }
69
70 #[inline]
72 #[must_use]
73 pub const fn is_task_execution(&self) -> bool {
74 matches!(self, CelersError::TaskExecution(_))
75 }
76
77 #[inline]
79 #[must_use]
80 pub const fn is_task_revoked(&self) -> bool {
81 matches!(self, CelersError::TaskRevoked(_))
82 }
83
84 #[inline]
86 #[must_use]
87 pub const fn is_timeout(&self) -> bool {
88 matches!(self, CelersError::Timeout(_))
89 }
90
91 #[inline]
93 #[must_use]
94 pub const fn is_configuration(&self) -> bool {
95 matches!(self, CelersError::Configuration(_))
96 }
97
98 #[inline]
100 #[must_use]
101 pub const fn is_io(&self) -> bool {
102 matches!(self, CelersError::Io(_))
103 }
104
105 #[inline]
107 #[must_use]
108 pub const fn is_invalid_state_transition(&self) -> bool {
109 matches!(self, CelersError::InvalidStateTransition { .. })
110 }
111
112 #[inline]
117 #[must_use]
118 pub const fn is_retryable(&self) -> bool {
119 matches!(
120 self,
121 CelersError::Broker(_) | CelersError::Io(_) | CelersError::TaskExecution(_)
122 )
123 }
124
125 #[inline]
127 #[must_use]
128 pub const fn category(&self) -> &'static str {
129 match self {
130 CelersError::Serialization(_) => "serialization",
131 CelersError::Deserialization(_) => "deserialization",
132 CelersError::Broker(_) => "broker",
133 CelersError::TaskNotFound(_) => "task_not_found",
134 CelersError::TaskExecution(_) => "task_execution",
135 CelersError::TaskRevoked(_) => "task_revoked",
136 CelersError::Timeout(_) => "timeout",
137 CelersError::InvalidStateTransition { .. } => "invalid_state_transition",
138 CelersError::Configuration(_) => "configuration",
139 CelersError::Io(_) => "io",
140 CelersError::Other(_) => "other",
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_serialization_error() {
151 let err = CelersError::Serialization("test".to_string());
152 assert!(err.is_serialization());
153 assert!(!err.is_deserialization());
154 assert!(!err.is_broker());
155 assert!(!err.is_retryable());
156 assert_eq!(err.category(), "serialization");
157 assert_eq!(err.to_string(), "Serialization error: test");
158 }
159
160 #[test]
161 fn test_deserialization_error() {
162 let err = CelersError::Deserialization("invalid json".to_string());
163 assert!(err.is_deserialization());
164 assert!(!err.is_serialization());
165 assert!(!err.is_retryable());
166 assert_eq!(err.category(), "deserialization");
167 }
168
169 #[test]
170 fn test_broker_error() {
171 let err = CelersError::Broker("connection lost".to_string());
172 assert!(err.is_broker());
173 assert!(!err.is_serialization());
174 assert!(err.is_retryable()); assert_eq!(err.category(), "broker");
176 }
177
178 #[test]
179 fn test_task_not_found_error() {
180 let err = CelersError::TaskNotFound("task123".to_string());
181 assert!(err.is_task_not_found());
182 assert!(!err.is_task_execution());
183 assert!(!err.is_retryable());
184 assert_eq!(err.category(), "task_not_found");
185 }
186
187 #[test]
188 fn test_task_execution_error() {
189 let err = CelersError::TaskExecution("panic occurred".to_string());
190 assert!(err.is_task_execution());
191 assert!(!err.is_task_not_found());
192 assert!(err.is_retryable()); assert_eq!(err.category(), "task_execution");
194 }
195
196 #[test]
197 fn test_invalid_state_transition_error() {
198 let err = CelersError::InvalidStateTransition {
199 from: "pending".to_string(),
200 to: "success".to_string(),
201 };
202 assert!(err.is_invalid_state_transition());
203 assert!(!err.is_retryable());
204 assert_eq!(err.category(), "invalid_state_transition");
205 assert!(err
206 .to_string()
207 .contains("Invalid task state transition from"));
208 }
209
210 #[test]
211 fn test_configuration_error() {
212 let err = CelersError::Configuration("missing redis url".to_string());
213 assert!(err.is_configuration());
214 assert!(!err.is_retryable());
215 assert_eq!(err.category(), "configuration");
216 }
217
218 #[test]
219 fn test_io_error() {
220 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
221 let err = CelersError::from(io_err);
222 assert!(err.is_io());
223 assert!(err.is_retryable()); assert_eq!(err.category(), "io");
225 }
226
227 #[test]
228 fn test_other_error() {
229 let err = CelersError::Other("unknown error".to_string());
230 assert!(!err.is_serialization());
231 assert!(!err.is_broker());
232 assert!(!err.is_retryable());
233 assert_eq!(err.category(), "other");
234 }
235
236 #[test]
237 fn test_is_retryable_logic() {
238 assert!(CelersError::Broker("timeout".to_string()).is_retryable());
240 assert!(CelersError::TaskExecution("temporary failure".to_string()).is_retryable());
241 assert!(CelersError::from(std::io::Error::new(
242 std::io::ErrorKind::ConnectionAborted,
243 "connection aborted"
244 ))
245 .is_retryable());
246
247 assert!(!CelersError::Serialization("bad format".to_string()).is_retryable());
249 assert!(!CelersError::Configuration("invalid config".to_string()).is_retryable());
250 assert!(!CelersError::InvalidStateTransition {
251 from: "a".to_string(),
252 to: "b".to_string()
253 }
254 .is_retryable());
255 }
256}