1use thiserror::Error;
4
5fn cycle_display(steps: &[String]) -> String {
6 steps.join(" -> ")
7}
8
9#[derive(Debug, Error)]
11pub enum WorkflowError {
12 #[error("Cycle detected: {}", cycle_display(.steps))]
13 CycleDetected { steps: Vec<String> },
14
15 #[error("Step not found: {step_id}")]
16 StepNotFound { step_id: String },
17
18 #[error("Duplicate step: {step_id}")]
19 DuplicateStep { step_id: String },
20
21 #[error("Step '{step_id}' failed: {message}")]
22 StepFailed { step_id: String, message: String },
23
24 #[error("Step '{step_id}' skipped: dependency '{dependency_id}' failed")]
25 DependencyFailed {
26 step_id: String,
27 dependency_id: String,
28 },
29
30 #[error("Step '{step_id}' depends on unknown step '{dependency_id}'")]
31 MissingDependency {
32 step_id: String,
33 dependency_id: String,
34 },
35
36 #[error("Workflow has no steps")]
37 EmptyWorkflow,
38
39 #[error("Checkpoint error: {message}")]
40 Checkpoint { message: String },
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
50 fn cycle_detected_displays_steps() {
51 let err = WorkflowError::CycleDetected {
52 steps: vec!["a".into(), "b".into(), "a".into()],
53 };
54 assert_eq!(err.to_string(), "Cycle detected: a -> b -> a");
55 }
56
57 #[test]
58 fn step_not_found_displays_id() {
59 let err = WorkflowError::StepNotFound {
60 step_id: "missing".into(),
61 };
62 assert_eq!(err.to_string(), "Step not found: missing");
63 }
64
65 #[test]
66 fn duplicate_step_displays_id() {
67 let err = WorkflowError::DuplicateStep {
68 step_id: "dup".into(),
69 };
70 assert_eq!(err.to_string(), "Duplicate step: dup");
71 }
72
73 #[test]
74 fn step_failed_displays_id_and_message() {
75 let err = WorkflowError::StepFailed {
76 step_id: "build".into(),
77 message: "compile error".into(),
78 };
79 assert_eq!(err.to_string(), "Step 'build' failed: compile error");
80 }
81
82 #[test]
83 fn dependency_failed_displays_ids() {
84 let err = WorkflowError::DependencyFailed {
85 step_id: "deploy".into(),
86 dependency_id: "build".into(),
87 };
88 assert_eq!(
89 err.to_string(),
90 "Step 'deploy' skipped: dependency 'build' failed"
91 );
92 }
93
94 #[test]
95 fn missing_dependency_displays_ids() {
96 let err = WorkflowError::MissingDependency {
97 step_id: "b".into(),
98 dependency_id: "unknown".into(),
99 };
100 assert_eq!(
101 err.to_string(),
102 "Step 'b' depends on unknown step 'unknown'"
103 );
104 }
105
106 #[test]
107 fn empty_workflow_displays_message() {
108 let err = WorkflowError::EmptyWorkflow;
109 assert_eq!(err.to_string(), "Workflow has no steps");
110 }
111
112 #[test]
113 fn checkpoint_error_displays_message() {
114 let err = WorkflowError::Checkpoint {
115 message: "write failed".into(),
116 };
117 assert_eq!(err.to_string(), "Checkpoint error: write failed");
118 }
119}