openzeppelin_monitor/services/trigger/script/
error.rs1use crate::utils::logging::error::{ErrorContext, TraceableError};
7use std::collections::HashMap;
8use thiserror::Error as ThisError;
9use uuid::Uuid;
10
11#[derive(ThisError, Debug)]
13pub enum ScriptError {
14 #[error("Not found error: {0}")]
16 NotFound(ErrorContext),
17
18 #[error("Execution error: {0}")]
20 ExecutionError(ErrorContext),
21
22 #[error("Parse error: {0}")]
24 ParseError(ErrorContext),
25
26 #[error("System error: {0}")]
28 SystemError(ErrorContext),
29
30 #[error(transparent)]
32 Other(#[from] anyhow::Error),
33}
34
35impl ScriptError {
36 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 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 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 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 assert!(outer_error.to_string().contains("Failed to initialize"));
169
170 if let ScriptError::SystemError(ctx) = &outer_error {
172 assert_eq!(ctx.message, "Failed to initialize");
174
175 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 let error_context = ErrorContext::new("Inner error", None, None);
190 let original_trace_id = error_context.trace_id.clone();
191
192 let script_error = ScriptError::ExecutionError(error_context);
194
195 assert_eq!(script_error.trace_id(), original_trace_id);
197
198 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 let anyhow_error = anyhow::anyhow!("Test anyhow error");
208 let script_error: ScriptError = anyhow_error.into();
209
210 assert!(!script_error.trace_id().is_empty());
212 }
213}