1use thiserror::Error;
2
3use crate::control_flow::ControlFlow;
4
5#[derive(Error, Debug)]
6pub enum StepError {
7 #[error("Step failed: {0}")]
8 Fail(String),
9
10 #[error("Control flow: {0:?}")]
11 ControlFlow(ControlFlow),
12
13 #[error("Timeout after {0:?}")]
14 Timeout(std::time::Duration),
15
16 #[error("Template error: {0}")]
17 Template(String),
18
19 #[error("Sandbox error: {message} (image: {image})")]
20 Sandbox {
21 message: String,
22 image: String,
23 },
24
25 #[error("Config error in '{field}': {message}")]
26 Config {
27 field: String,
28 message: String,
29 },
30
31 #[error("{0}")]
32 Other(#[from] anyhow::Error),
33}
34
35impl StepError {
36 pub fn sandbox(message: impl Into<String>, image: impl Into<String>) -> Self {
38 StepError::Sandbox {
39 message: message.into(),
40 image: image.into(),
41 }
42 }
43
44 pub fn config(field: impl Into<String>, message: impl Into<String>) -> Self {
46 StepError::Config {
47 field: field.into(),
48 message: message.into(),
49 }
50 }
51
52 pub fn is_timeout(&self) -> bool {
54 matches!(self, StepError::Timeout(_))
55 }
56
57 pub fn is_control_flow(&self) -> bool {
59 matches!(self, StepError::ControlFlow(_))
60 }
61
62 pub fn category(&self) -> &'static str {
64 match self {
65 StepError::Fail(_) => "step_failure",
66 StepError::ControlFlow(_) => "control_flow",
67 StepError::Timeout(_) => "timeout",
68 StepError::Template(_) => "template",
69 StepError::Sandbox { .. } => "sandbox",
70 StepError::Config { .. } => "config",
71 StepError::Other(_) => "internal",
72 }
73 }
74}
75
76impl From<ControlFlow> for StepError {
77 fn from(cf: ControlFlow) -> Self {
78 StepError::ControlFlow(cf)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_error_categories() {
88 let err = StepError::Fail("test".into());
89 assert_eq!(err.category(), "step_failure");
90 assert!(!err.is_timeout());
91 assert!(!err.is_control_flow());
92
93 let err = StepError::Timeout(std::time::Duration::from_secs(30));
94 assert_eq!(err.category(), "timeout");
95 assert!(err.is_timeout());
96
97 let err = StepError::sandbox("container crashed", "node:20");
98 assert_eq!(err.category(), "sandbox");
99 assert_eq!(
100 err.to_string(),
101 "Sandbox error: container crashed (image: node:20)"
102 );
103
104 let err = StepError::config("sandbox.image", "image not found");
105 assert_eq!(err.category(), "config");
106 assert_eq!(
107 err.to_string(),
108 "Config error in 'sandbox.image': image not found"
109 );
110 }
111
112 #[test]
113 fn test_error_display() {
114 let err = StepError::Fail("connection refused".into());
115 assert_eq!(err.to_string(), "Step failed: connection refused");
116
117 let err = StepError::Template("bad syntax in {{name}}".into());
118 assert_eq!(err.to_string(), "Template error: bad syntax in {{name}}");
119 }
120}