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 #[error("Database error: {0}")]
51 Database(String),
52
53 #[error("Serialization error: {0}")]
55 Serialization(#[from] serde_json::Error),
56
57 #[error("YAML parsing error: {0}")]
59 YamlParsing(#[from] serde_yaml::Error),
60
61 #[error("I/O error: {0}")]
63 Io(#[from] std::io::Error),
64
65 #[error("File watcher error: {0}")]
67 FileWatcher(String),
68
69 #[error("Invalid task type: {0}")]
71 InvalidTaskType(String),
72
73 #[error("Missing required parameter: {0}")]
75 MissingParameter(String),
76
77 #[error("Invalid parameter value: {param}, value: {value}")]
79 InvalidParameter {
80 param: String,
82 value: String,
84 },
85
86 #[error("Resource limit exceeded: {resource}, limit: {limit}")]
88 ResourceLimitExceeded {
89 resource: String,
91 limit: String,
93 },
94
95 #[error("Worker pool error: {0}")]
97 WorkerPool(String),
98
99 #[error("Scheduler error: {0}")]
101 Scheduler(String),
102
103 #[error("Invalid cron expression: {0}")]
105 InvalidCronExpression(String),
106
107 #[error("File not found: {0}")]
109 FileNotFound(PathBuf),
110
111 #[error("Invalid URL: {0}")]
113 InvalidUrl(#[from] url::ParseError),
114
115 #[error("HTTP error: {0}")]
117 Http(String),
118
119 #[error("WebSocket error: {0}")]
121 WebSocket(String),
122
123 #[error("Lock poisoned")]
125 LockPoisoned,
126
127 #[error("Shutdown in progress")]
129 ShutdownInProgress,
130
131 #[error("Workflow already running: {0}")]
133 AlreadyRunning(String),
134
135 #[error("Workflow not running: {0}")]
137 NotRunning(String),
138
139 #[error("Invalid state transition: from {from} to {to}")]
141 InvalidStateTransition {
142 from: String,
144 to: String,
146 },
147
148 #[error("{0}")]
150 Generic(String),
151}
152
153impl WorkflowError {
154 pub fn generic(msg: impl Into<String>) -> Self {
156 Self::Generic(msg.into())
157 }
158
159 #[must_use]
161 pub fn is_recoverable(&self) -> bool {
162 matches!(
163 self,
164 Self::TaskTimeout(_)
165 | Self::ResourceLimitExceeded { .. }
166 | Self::WorkerPool(_)
167 | Self::Http(_)
168 )
169 }
170
171 #[must_use]
173 pub fn should_retry(&self) -> bool {
174 matches!(
175 self,
176 Self::TaskExecutionFailed { .. }
177 | Self::TaskTimeout(_)
178 | Self::Http(_)
179 | Self::WorkerPool(_)
180 )
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_error_display() {
190 let err = WorkflowError::WorkflowNotFound("test-workflow".to_string());
191 assert_eq!(err.to_string(), "Workflow not found: test-workflow");
192 }
193
194 #[test]
195 fn test_task_execution_failed() {
196 let err = WorkflowError::TaskExecutionFailed {
197 task_id: "task-1".to_string(),
198 reason: "connection timeout".to_string(),
199 };
200 assert!(err.to_string().contains("task-1"));
201 assert!(err.to_string().contains("connection timeout"));
202 }
203
204 #[test]
205 fn test_is_recoverable() {
206 assert!(WorkflowError::TaskTimeout("task-1".to_string()).is_recoverable());
207 assert!(!WorkflowError::CycleDetected.is_recoverable());
208 }
209
210 #[test]
211 fn test_should_retry() {
212 assert!(WorkflowError::TaskTimeout("task-1".to_string()).should_retry());
213 assert!(!WorkflowError::CycleDetected.should_retry());
214 }
215
216 #[test]
217 fn test_generic_error() {
218 let err = WorkflowError::generic("custom error");
219 assert_eq!(err.to_string(), "custom error");
220 }
221
222 #[test]
223 fn test_invalid_state_transition() {
224 let err = WorkflowError::InvalidStateTransition {
225 from: "running".to_string(),
226 to: "pending".to_string(),
227 };
228 assert!(err.to_string().contains("running"));
229 assert!(err.to_string().contains("pending"));
230 }
231}