1use miyabi_types::error::{ErrorCode, UnifiedError};
4use std::any::Any;
5use thiserror::Error;
6
7pub type Result<T> = std::result::Result<T, WorkflowError>;
9
10#[derive(Error, Debug)]
12pub enum WorkflowError {
13 #[error("Step '{step_id}' failed: {source}")]
15 StepFailed {
16 step_id: String,
17 source: Box<dyn std::error::Error + Send + Sync>,
18 },
19
20 #[error("State persistence error: {0}")]
22 PersistenceError(#[from] sled::Error),
23
24 #[error("Serialization error: {0}")]
26 SerializationError(#[from] serde_json::Error),
27
28 #[error("IO error: {0}")]
30 IoError(#[from] std::io::Error),
31
32 #[error("Workflow '{0}' not found")]
34 WorkflowNotFound(String),
35
36 #[error("Step '{0}' not found in workflow")]
38 StepNotFound(String),
39
40 #[error("Workflow must have at least one step")]
42 EmptyWorkflow,
43
44 #[error("Circular dependency detected in workflow")]
46 CircularDependency,
47
48 #[error("Invalid workflow configuration: {0}")]
50 InvalidConfiguration(String),
51
52 #[error("Branch condition evaluation failed: {0}")]
54 BranchConditionError(String),
55
56 #[error("Parallel execution failed: {0}")]
58 ParallelExecutionError(String),
59
60 #[error("Workflow error: {0}")]
62 Other(String),
63}
64
65impl WorkflowError {
66 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
78impl 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 _ => 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}