openzeppelin_monitor/services/trigger/
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 TriggerError {
14 #[error("Not found error: {0}")]
16 NotFound(ErrorContext),
17
18 #[error("Execution error: {0}")]
20 ExecutionError(ErrorContext),
21
22 #[error("Configuration error: {0}")]
24 ConfigurationError(ErrorContext),
25
26 #[error(transparent)]
28 Other(#[from] anyhow::Error),
29}
30
31impl TriggerError {
32 pub fn not_found(
34 msg: impl Into<String>,
35 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
36 metadata: Option<HashMap<String, String>>,
37 ) -> Self {
38 Self::NotFound(ErrorContext::new_with_log(msg, source, metadata))
39 }
40
41 pub fn execution_error(
43 msg: impl Into<String>,
44 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
45 metadata: Option<HashMap<String, String>>,
46 ) -> Self {
47 Self::ExecutionError(ErrorContext::new_with_log(msg, source, metadata))
48 }
49
50 pub fn execution_error_without_log(
52 msg: impl Into<String>,
53 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
54 metadata: Option<HashMap<String, String>>,
55 ) -> Self {
56 Self::ExecutionError(ErrorContext::new(msg, source, metadata))
57 }
58
59 pub fn configuration_error(
61 msg: impl Into<String>,
62 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
63 metadata: Option<HashMap<String, String>>,
64 ) -> Self {
65 Self::ConfigurationError(ErrorContext::new_with_log(msg, source, metadata))
66 }
67}
68
69impl TraceableError for TriggerError {
70 fn trace_id(&self) -> String {
71 match self {
72 Self::NotFound(ctx) => ctx.trace_id.clone(),
73 Self::ExecutionError(ctx) => ctx.trace_id.clone(),
74 Self::ConfigurationError(ctx) => ctx.trace_id.clone(),
75 Self::Other(_) => Uuid::new_v4().to_string(),
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use std::io::{Error as IoError, ErrorKind};
84
85 #[test]
86 fn test_not_found_error_formatting() {
87 let error = TriggerError::not_found("test error", None, None);
88 assert_eq!(error.to_string(), "Not found error: test error");
89
90 let source_error = IoError::new(ErrorKind::NotFound, "test source");
91 let error = TriggerError::not_found(
92 "test error",
93 Some(Box::new(source_error)),
94 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
95 );
96 assert_eq!(
97 error.to_string(),
98 "Not found error: test error [key1=value1]"
99 );
100 }
101
102 #[test]
103 fn test_execution_error_formatting() {
104 let error = TriggerError::execution_error("test error", None, None);
105 assert_eq!(error.to_string(), "Execution error: test error");
106
107 let source_error = IoError::new(ErrorKind::NotFound, "test source");
108 let error = TriggerError::execution_error(
109 "test error",
110 Some(Box::new(source_error)),
111 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
112 );
113 assert_eq!(
114 error.to_string(),
115 "Execution error: test error [key1=value1]"
116 );
117 }
118
119 #[test]
120 fn test_internal_error_formatting() {
121 let error = TriggerError::configuration_error("test error", None, None);
122 assert_eq!(error.to_string(), "Configuration error: test error");
123
124 let source_error = IoError::new(ErrorKind::NotFound, "test source");
125 let error = TriggerError::configuration_error(
126 "test error",
127 Some(Box::new(source_error)),
128 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
129 );
130 assert_eq!(
131 error.to_string(),
132 "Configuration error: test error [key1=value1]"
133 );
134 }
135
136 #[test]
137 fn test_from_anyhow_error() {
138 let anyhow_error = anyhow::anyhow!("test anyhow error");
139 let trigger_error: TriggerError = anyhow_error.into();
140 assert!(matches!(trigger_error, TriggerError::Other(_)));
141 assert_eq!(trigger_error.to_string(), "test anyhow error");
142 }
143
144 #[test]
145 fn test_error_source_chain() {
146 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "while reading config");
147
148 let outer_error = TriggerError::configuration_error(
149 "Failed to initialize",
150 Some(Box::new(io_error)),
151 None,
152 );
153
154 assert!(outer_error.to_string().contains("Failed to initialize"));
156
157 if let TriggerError::ConfigurationError(ctx) = &outer_error {
159 assert_eq!(ctx.message, "Failed to initialize");
161
162 assert!(ctx.source.is_some());
164
165 if let Some(src) = &ctx.source {
166 assert_eq!(src.to_string(), "while reading config");
167 }
168 } else {
169 panic!("Expected ConfigurationError variant");
170 }
171 }
172
173 #[test]
174 fn test_trace_id_propagation() {
175 let error_context = ErrorContext::new("Inner error", None, None);
177 let original_trace_id = error_context.trace_id.clone();
178
179 let trigger_error = TriggerError::ConfigurationError(error_context);
181
182 assert_eq!(trigger_error.trace_id(), original_trace_id);
184
185 let source_error = IoError::new(ErrorKind::Other, "Source error");
187 let error_context = ErrorContext::new("Middle error", Some(Box::new(source_error)), None);
188 let original_trace_id = error_context.trace_id.clone();
189
190 let trigger_error = TriggerError::ConfigurationError(error_context);
191 assert_eq!(trigger_error.trace_id(), original_trace_id);
192
193 let anyhow_error = anyhow::anyhow!("Test anyhow error");
195 let trigger_error: TriggerError = anyhow_error.into();
196
197 assert!(!trigger_error.trace_id().is_empty());
199 }
200}