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