use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, WorkflowError>;
#[derive(Debug, Error)]
pub enum WorkflowError {
#[error("Workflow not found: {0}")]
WorkflowNotFound(String),
#[error("Task not found: {0}")]
TaskNotFound(String),
#[error("Cycle detected in workflow DAG")]
CycleDetected,
#[error("Invalid workflow configuration: {0}")]
InvalidConfiguration(String),
#[error("Task execution failed: {task_id}, reason: {reason}")]
TaskExecutionFailed {
task_id: String,
reason: String,
},
#[error("Task timeout: {0}")]
TaskTimeout(String),
#[error("Task cancelled: {0}")]
TaskCancelled(String),
#[error("Dependency failed: {0}")]
DependencyFailed(String),
#[error("Database error: {0}")]
Database(String),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("YAML parsing error: {0}")]
YamlParsing(#[from] serde_yaml::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("File watcher error: {0}")]
FileWatcher(String),
#[error("Invalid task type: {0}")]
InvalidTaskType(String),
#[error("Missing required parameter: {0}")]
MissingParameter(String),
#[error("Invalid parameter value: {param}, value: {value}")]
InvalidParameter {
param: String,
value: String,
},
#[error("Resource limit exceeded: {resource}, limit: {limit}")]
ResourceLimitExceeded {
resource: String,
limit: String,
},
#[error("Worker pool error: {0}")]
WorkerPool(String),
#[error("Scheduler error: {0}")]
Scheduler(String),
#[error("Invalid cron expression: {0}")]
InvalidCronExpression(String),
#[error("File not found: {0}")]
FileNotFound(PathBuf),
#[error("Invalid URL: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error("HTTP error: {0}")]
Http(String),
#[error("WebSocket error: {0}")]
WebSocket(String),
#[error("Lock poisoned")]
LockPoisoned,
#[error("Shutdown in progress")]
ShutdownInProgress,
#[error("Workflow already running: {0}")]
AlreadyRunning(String),
#[error("Workflow not running: {0}")]
NotRunning(String),
#[error("Invalid state transition: from {from} to {to}")]
InvalidStateTransition {
from: String,
to: String,
},
#[error("{0}")]
Generic(String),
}
impl WorkflowError {
pub fn generic(msg: impl Into<String>) -> Self {
Self::Generic(msg.into())
}
#[must_use]
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::TaskTimeout(_)
| Self::ResourceLimitExceeded { .. }
| Self::WorkerPool(_)
| Self::Http(_)
)
}
#[must_use]
pub fn should_retry(&self) -> bool {
matches!(
self,
Self::TaskExecutionFailed { .. }
| Self::TaskTimeout(_)
| Self::Http(_)
| Self::WorkerPool(_)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = WorkflowError::WorkflowNotFound("test-workflow".to_string());
assert_eq!(err.to_string(), "Workflow not found: test-workflow");
}
#[test]
fn test_task_execution_failed() {
let err = WorkflowError::TaskExecutionFailed {
task_id: "task-1".to_string(),
reason: "connection timeout".to_string(),
};
assert!(err.to_string().contains("task-1"));
assert!(err.to_string().contains("connection timeout"));
}
#[test]
fn test_is_recoverable() {
assert!(WorkflowError::TaskTimeout("task-1".to_string()).is_recoverable());
assert!(!WorkflowError::CycleDetected.is_recoverable());
}
#[test]
fn test_should_retry() {
assert!(WorkflowError::TaskTimeout("task-1".to_string()).should_retry());
assert!(!WorkflowError::CycleDetected.should_retry());
}
#[test]
fn test_generic_error() {
let err = WorkflowError::generic("custom error");
assert_eq!(err.to_string(), "custom error");
}
#[test]
fn test_invalid_state_transition() {
let err = WorkflowError::InvalidStateTransition {
from: "running".to_string(),
to: "pending".to_string(),
};
assert!(err.to_string().contains("running"));
assert!(err.to_string().contains("pending"));
}
}