oximedia_workflow/
error.rs1use std::path::PathBuf;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, WorkflowError>;
8
9#[derive(Debug, Error)]
11pub enum WorkflowError {
12 #[error("Workflow not found: {0}")]
14 WorkflowNotFound(String),
15
16 #[error("Task not found: {0}")]
18 TaskNotFound(String),
19
20 #[error("Cycle detected in workflow DAG")]
22 CycleDetected,
23
24 #[error("Invalid workflow configuration: {0}")]
26 InvalidConfiguration(String),
27
28 #[error("Task execution failed: {task_id}, reason: {reason}")]
30 TaskExecutionFailed {
31 task_id: String,
33 reason: String,
35 },
36
37 #[error("Task timeout: {0}")]
39 TaskTimeout(String),
40
41 #[error("Task cancelled: {0}")]
43 TaskCancelled(String),
44
45 #[error("Dependency failed: {0}")]
47 DependencyFailed(String),
48
49 #[cfg(all(not(target_arch = "wasm32"), feature = "sqlite"))]
51 #[error("Database error: {0}")]
52 Database(#[from] rusqlite::Error),
53
54 #[cfg(any(target_arch = "wasm32", not(feature = "sqlite")))]
56 #[error("Database error: {0}")]
57 Database(String),
58
59 #[error("Serialization error: {0}")]
61 Serialization(#[from] serde_json::Error),
62
63 #[error("YAML parsing error: {0}")]
65 YamlParsing(#[from] serde_yaml::Error),
66
67 #[error("I/O error: {0}")]
69 Io(#[from] std::io::Error),
70
71 #[error("File watcher error: {0}")]
73 FileWatcher(String),
74
75 #[error("Invalid task type: {0}")]
77 InvalidTaskType(String),
78
79 #[error("Missing required parameter: {0}")]
81 MissingParameter(String),
82
83 #[error("Invalid parameter value: {param}, value: {value}")]
85 InvalidParameter {
86 param: String,
88 value: String,
90 },
91
92 #[error("Resource limit exceeded: {resource}, limit: {limit}")]
94 ResourceLimitExceeded {
95 resource: String,
97 limit: String,
99 },
100
101 #[error("Worker pool error: {0}")]
103 WorkerPool(String),
104
105 #[error("Scheduler error: {0}")]
107 Scheduler(String),
108
109 #[error("Invalid cron expression: {0}")]
111 InvalidCronExpression(String),
112
113 #[error("File not found: {0}")]
115 FileNotFound(PathBuf),
116
117 #[error("Invalid URL: {0}")]
119 InvalidUrl(#[from] url::ParseError),
120
121 #[error("HTTP error: {0}")]
123 Http(String),
124
125 #[error("WebSocket error: {0}")]
127 WebSocket(String),
128
129 #[error("Lock poisoned")]
131 LockPoisoned,
132
133 #[error("Shutdown in progress")]
135 ShutdownInProgress,
136
137 #[error("Workflow already running: {0}")]
139 AlreadyRunning(String),
140
141 #[error("Workflow not running: {0}")]
143 NotRunning(String),
144
145 #[error("Invalid state transition: from {from} to {to}")]
147 InvalidStateTransition {
148 from: String,
150 to: String,
152 },
153
154 #[error("{0}")]
156 Generic(String),
157}
158
159impl WorkflowError {
160 pub fn generic(msg: impl Into<String>) -> Self {
162 Self::Generic(msg.into())
163 }
164
165 #[must_use]
167 pub fn is_recoverable(&self) -> bool {
168 matches!(
169 self,
170 Self::TaskTimeout(_)
171 | Self::ResourceLimitExceeded { .. }
172 | Self::WorkerPool(_)
173 | Self::Http(_)
174 )
175 }
176
177 #[must_use]
179 pub fn should_retry(&self) -> bool {
180 matches!(
181 self,
182 Self::TaskExecutionFailed { .. }
183 | Self::TaskTimeout(_)
184 | Self::Http(_)
185 | Self::WorkerPool(_)
186 )
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_error_display() {
196 let err = WorkflowError::WorkflowNotFound("test-workflow".to_string());
197 assert_eq!(err.to_string(), "Workflow not found: test-workflow");
198 }
199
200 #[test]
201 fn test_task_execution_failed() {
202 let err = WorkflowError::TaskExecutionFailed {
203 task_id: "task-1".to_string(),
204 reason: "connection timeout".to_string(),
205 };
206 assert!(err.to_string().contains("task-1"));
207 assert!(err.to_string().contains("connection timeout"));
208 }
209
210 #[test]
211 fn test_is_recoverable() {
212 assert!(WorkflowError::TaskTimeout("task-1".to_string()).is_recoverable());
213 assert!(!WorkflowError::CycleDetected.is_recoverable());
214 }
215
216 #[test]
217 fn test_should_retry() {
218 assert!(WorkflowError::TaskTimeout("task-1".to_string()).should_retry());
219 assert!(!WorkflowError::CycleDetected.should_retry());
220 }
221
222 #[test]
223 fn test_generic_error() {
224 let err = WorkflowError::generic("custom error");
225 assert_eq!(err.to_string(), "custom error");
226 }
227
228 #[test]
229 fn test_invalid_state_transition() {
230 let err = WorkflowError::InvalidStateTransition {
231 from: "running".to_string(),
232 to: "pending".to_string(),
233 };
234 assert!(err.to_string().contains("running"));
235 assert!(err.to_string().contains("pending"));
236 }
237}