openzeppelin_monitor/services/trigger/script/
error.rs

1//! Script error types and handling.
2//!
3//! Provides error types for script-related operations,
4//! including execution failures and configuration issues.
5
6use crate::utils::logging::error::{ErrorContext, TraceableError};
7use std::collections::HashMap;
8use thiserror::Error as ThisError;
9use uuid::Uuid;
10
11/// Represents possible errors during script operations
12#[derive(ThisError, Debug)]
13pub enum ScriptError {
14	/// Errors related to not found errors
15	#[error("Not found error: {0}")]
16	NotFound(ErrorContext),
17
18	/// Errors related to execution failures
19	#[error("Execution error: {0}")]
20	ExecutionError(ErrorContext),
21
22	/// Errors related to parsing errors
23	#[error("Parse error: {0}")]
24	ParseError(ErrorContext),
25
26	/// Errors related to system errors
27	#[error("System error: {0}")]
28	SystemError(ErrorContext),
29
30	/// Other errors that don't fit into the categories above
31	#[error(transparent)]
32	Other(#[from] anyhow::Error),
33}
34
35impl ScriptError {
36	// Not found error
37	pub fn not_found(
38		msg: impl Into<String>,
39		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
40		metadata: Option<HashMap<String, String>>,
41	) -> Self {
42		Self::NotFound(ErrorContext::new(msg, source, metadata))
43	}
44
45	// Execution error
46	pub fn execution_error(
47		msg: impl Into<String>,
48		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
49		metadata: Option<HashMap<String, String>>,
50	) -> Self {
51		Self::ExecutionError(ErrorContext::new(msg, source, metadata))
52	}
53
54	// Parse error
55	pub fn parse_error(
56		msg: impl Into<String>,
57		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
58		metadata: Option<HashMap<String, String>>,
59	) -> Self {
60		Self::ParseError(ErrorContext::new(msg, source, metadata))
61	}
62
63	// System error
64	pub fn system_error(
65		msg: impl Into<String>,
66		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
67		metadata: Option<HashMap<String, String>>,
68	) -> Self {
69		Self::SystemError(ErrorContext::new(msg, source, metadata))
70	}
71}
72
73impl TraceableError for ScriptError {
74	fn trace_id(&self) -> String {
75		match self {
76			Self::NotFound(ctx) => ctx.trace_id.clone(),
77			Self::ExecutionError(ctx) => ctx.trace_id.clone(),
78			Self::ParseError(ctx) => ctx.trace_id.clone(),
79			Self::SystemError(ctx) => ctx.trace_id.clone(),
80			Self::Other(_) => Uuid::new_v4().to_string(),
81		}
82	}
83}
84
85#[cfg(test)]
86mod tests {
87	use super::*;
88	use std::io::{Error as IoError, ErrorKind};
89
90	#[test]
91	fn test_not_found_error_formatting() {
92		let error = ScriptError::not_found("test error", None, None);
93		assert_eq!(error.to_string(), "Not found error: test error");
94
95		let source_error = IoError::new(ErrorKind::NotFound, "test source");
96		let error = ScriptError::not_found(
97			"test error",
98			Some(Box::new(source_error)),
99			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
100		);
101		assert_eq!(
102			error.to_string(),
103			"Not found error: test error [key1=value1]"
104		);
105	}
106
107	#[test]
108	fn test_execution_error_formatting() {
109		let error = ScriptError::execution_error("test error", None, None);
110		assert_eq!(error.to_string(), "Execution error: test error");
111
112		let source_error = IoError::new(ErrorKind::NotFound, "test source");
113		let error = ScriptError::execution_error(
114			"test error",
115			Some(Box::new(source_error)),
116			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
117		);
118		assert_eq!(
119			error.to_string(),
120			"Execution error: test error [key1=value1]"
121		);
122	}
123
124	#[test]
125	fn test_parse_error_formatting() {
126		let error = ScriptError::parse_error("test error", None, None);
127		assert_eq!(error.to_string(), "Parse error: test error");
128
129		let source_error = IoError::new(ErrorKind::NotFound, "test source");
130		let error = ScriptError::parse_error(
131			"test error",
132			Some(Box::new(source_error)),
133			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
134		);
135		assert_eq!(error.to_string(), "Parse error: test error [key1=value1]");
136	}
137
138	#[test]
139	fn test_system_error_formatting() {
140		let error = ScriptError::system_error("test error", None, None);
141		assert_eq!(error.to_string(), "System error: test error");
142
143		let source_error = IoError::new(ErrorKind::NotFound, "test source");
144		let error = ScriptError::system_error(
145			"test error",
146			Some(Box::new(source_error)),
147			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
148		);
149		assert_eq!(error.to_string(), "System error: test error [key1=value1]");
150	}
151
152	#[test]
153	fn test_from_anyhow_error() {
154		let anyhow_error = anyhow::anyhow!("test anyhow error");
155		let script_error: ScriptError = anyhow_error.into();
156		assert!(matches!(script_error, ScriptError::Other(_)));
157		assert_eq!(script_error.to_string(), "test anyhow error");
158	}
159
160	#[test]
161	fn test_error_source_chain() {
162		let io_error = std::io::Error::new(std::io::ErrorKind::Other, "while reading config");
163
164		let outer_error =
165			ScriptError::system_error("Failed to initialize", Some(Box::new(io_error)), None);
166
167		// Just test the string representation instead of the source chain
168		assert!(outer_error.to_string().contains("Failed to initialize"));
169
170		// For ScriptError::SystemError, we know the implementation details
171		if let ScriptError::SystemError(ctx) = &outer_error {
172			// Check that the context has the right message
173			assert_eq!(ctx.message, "Failed to initialize");
174
175			// Check that the context has the source error
176			assert!(ctx.source.is_some());
177
178			if let Some(src) = &ctx.source {
179				assert_eq!(src.to_string(), "while reading config");
180			}
181		} else {
182			panic!("Expected SystemError variant");
183		}
184	}
185
186	#[test]
187	fn test_trace_id_propagation() {
188		// Create an error context with a known trace ID
189		let error_context = ErrorContext::new("Inner error", None, None);
190		let original_trace_id = error_context.trace_id.clone();
191
192		// Wrap it in a ScriptError
193		let script_error = ScriptError::ExecutionError(error_context);
194
195		// Verify the trace ID is preserved
196		assert_eq!(script_error.trace_id(), original_trace_id);
197
198		// Test trace ID propagation through error chain
199		let source_error = IoError::new(ErrorKind::Other, "Source error");
200		let error_context = ErrorContext::new("Middle error", Some(Box::new(source_error)), None);
201		let original_trace_id = error_context.trace_id.clone();
202
203		let script_error = ScriptError::SystemError(error_context);
204		assert_eq!(script_error.trace_id(), original_trace_id);
205
206		// Test Other variant
207		let anyhow_error = anyhow::anyhow!("Test anyhow error");
208		let script_error: ScriptError = anyhow_error.into();
209
210		// Other variant should generate a new UUID
211		assert!(!script_error.trace_id().is_empty());
212	}
213}