miyabi_workflow/
error.rs

1//! Error types for workflow execution
2
3use miyabi_types::error::{ErrorCode, UnifiedError};
4use std::any::Any;
5use thiserror::Error;
6
7/// Result type for workflow operations
8pub type Result<T> = std::result::Result<T, WorkflowError>;
9
10/// Workflow execution errors
11#[derive(Error, Debug)]
12pub enum WorkflowError {
13    /// Step execution failed
14    #[error("Step '{step_id}' failed: {source}")]
15    StepFailed {
16        step_id: String,
17        source: Box<dyn std::error::Error + Send + Sync>,
18    },
19
20    /// State persistence error
21    #[error("State persistence error: {0}")]
22    PersistenceError(#[from] sled::Error),
23
24    /// Serialization error
25    #[error("Serialization error: {0}")]
26    SerializationError(#[from] serde_json::Error),
27
28    /// IO error
29    #[error("IO error: {0}")]
30    IoError(#[from] std::io::Error),
31
32    /// Workflow not found
33    #[error("Workflow '{0}' not found")]
34    WorkflowNotFound(String),
35
36    /// Step not found
37    #[error("Step '{0}' not found in workflow")]
38    StepNotFound(String),
39
40    /// Empty workflow (no steps defined)
41    #[error("Workflow must have at least one step")]
42    EmptyWorkflow,
43
44    /// Circular dependency detected
45    #[error("Circular dependency detected in workflow")]
46    CircularDependency,
47
48    /// Invalid workflow configuration
49    #[error("Invalid workflow configuration: {0}")]
50    InvalidConfiguration(String),
51
52    /// Branch condition error
53    #[error("Branch condition evaluation failed: {0}")]
54    BranchConditionError(String),
55
56    /// Parallel execution error
57    #[error("Parallel execution failed: {0}")]
58    ParallelExecutionError(String),
59
60    /// Generic error
61    #[error("Workflow error: {0}")]
62    Other(String),
63}
64
65impl WorkflowError {
66    /// Create a step failed error
67    pub fn step_failed(
68        step_id: impl Into<String>,
69        error: impl std::error::Error + Send + Sync + 'static,
70    ) -> Self {
71        Self::StepFailed {
72            step_id: step_id.into(),
73            source: Box::new(error),
74        }
75    }
76}
77
78// ============================================================================
79// UnifiedError Implementation
80// ============================================================================
81
82impl UnifiedError for WorkflowError {
83    fn code(&self) -> ErrorCode {
84        match self {
85            Self::StepFailed { .. } => ErrorCode::STEP_ERROR,
86            Self::PersistenceError(_) => ErrorCode::STORAGE_ERROR,
87            Self::SerializationError(_) => ErrorCode::PARSE_ERROR,
88            Self::IoError(e) => match e.kind() {
89                std::io::ErrorKind::NotFound => ErrorCode::FILE_NOT_FOUND,
90                std::io::ErrorKind::PermissionDenied => ErrorCode::PERMISSION_DENIED,
91                _ => ErrorCode::IO_ERROR,
92            },
93            Self::WorkflowNotFound(_) => ErrorCode::FILE_NOT_FOUND,
94            Self::StepNotFound(_) => ErrorCode::STEP_ERROR,
95            Self::EmptyWorkflow => ErrorCode::VALIDATION_ERROR,
96            Self::CircularDependency => ErrorCode::CIRCULAR_DEPENDENCY_ERROR,
97            Self::InvalidConfiguration(_) => ErrorCode::INVALID_CONFIG,
98            Self::BranchConditionError(_) => ErrorCode::WORKFLOW_ERROR,
99            Self::ParallelExecutionError(_) => ErrorCode::WORKFLOW_ERROR,
100            Self::Other(_) => ErrorCode::INTERNAL_ERROR,
101        }
102    }
103
104    fn user_message(&self) -> String {
105        match self {
106            Self::StepFailed { step_id, .. } => format!(
107                "Workflow step '{}' failed to execute. Please check the step configuration and review the logs for detailed error information.",
108                step_id
109            ),
110            Self::CircularDependency => {
111                "The workflow contains circular dependencies between steps. Please review the dependency graph and remove any cycles.".to_string()
112            }
113            Self::EmptyWorkflow => {
114                "Cannot execute an empty workflow. Please add at least one step to the workflow definition.".to_string()
115            }
116            Self::WorkflowNotFound(name) => format!(
117                "Workflow '{}' was not found. Please verify the workflow name and ensure it has been registered.",
118                name
119            ),
120            Self::StepNotFound(step_id) => format!(
121                "Step '{}' was not found in the workflow. Please check the step ID in your workflow definition.",
122                step_id
123            ),
124            Self::ParallelExecutionError(msg) => format!(
125                "Parallel execution failed: {}. Some steps in the parallel group encountered errors.",
126                msg
127            ),
128            // Reuse existing thiserror messages for other variants
129            _ => self.to_string(),
130        }
131    }
132
133    fn context(&self) -> Option<&dyn Any> {
134        match self {
135            Self::StepFailed { step_id, .. } => Some(step_id as &dyn Any),
136            Self::WorkflowNotFound(name) => Some(name as &dyn Any),
137            Self::StepNotFound(step_id) => Some(step_id as &dyn Any),
138            _ => None,
139        }
140    }
141}
142
143#[cfg(test)]
144mod unified_error_tests {
145    use super::*;
146
147    #[test]
148    fn test_workflow_error_codes() {
149        let error = WorkflowError::CircularDependency;
150        assert_eq!(error.code(), ErrorCode::CIRCULAR_DEPENDENCY_ERROR);
151
152        let error = WorkflowError::EmptyWorkflow;
153        assert_eq!(error.code(), ErrorCode::VALIDATION_ERROR);
154
155        let error = WorkflowError::WorkflowNotFound("test".to_string());
156        assert_eq!(error.code(), ErrorCode::FILE_NOT_FOUND);
157    }
158
159    #[test]
160    fn test_user_messages() {
161        let error = WorkflowError::StepFailed {
162            step_id: "validate_input".to_string(),
163            source: Box::new(std::io::Error::other("test")),
164        };
165        let msg = error.user_message();
166        assert!(msg.contains("validate_input"));
167        assert!(msg.contains("check the step configuration"));
168
169        let error = WorkflowError::CircularDependency;
170        let msg = error.user_message();
171        assert!(msg.contains("circular dependencies"));
172    }
173
174    #[test]
175    fn test_context_extraction() {
176        let error = WorkflowError::StepFailed {
177            step_id: "process_data".to_string(),
178            source: Box::new(std::io::Error::other("test")),
179        };
180        assert!(error.context().is_some());
181
182        let error = WorkflowError::EmptyWorkflow;
183        assert!(error.context().is_none());
184    }
185}