openzeppelin_monitor/services/trigger/
error.rs

1//! Trigger error types and handling.
2//!
3//! Provides error types for trigger-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 errors that can occur during trigger operations
12#[derive(ThisError, Debug)]
13pub enum TriggerError {
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 configuration errors
23	#[error("Configuration error: {0}")]
24	ConfigurationError(ErrorContext),
25
26	/// Other errors that don't fit into the categories above
27	#[error(transparent)]
28	Other(#[from] anyhow::Error),
29}
30
31impl TriggerError {
32	// Not found error
33	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	// Execution error
42	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	// Execution error without logging
51	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	// Configuration error
60	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		// Just test the string representation instead of the source chain
155		assert!(outer_error.to_string().contains("Failed to initialize"));
156
157		// For TriggerError::ConfigurationError, we know the implementation details
158		if let TriggerError::ConfigurationError(ctx) = &outer_error {
159			// Check that the context has the right message
160			assert_eq!(ctx.message, "Failed to initialize");
161
162			// Check that the context has the source error
163			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		// Create an error context with a known trace ID
176		let error_context = ErrorContext::new("Inner error", None, None);
177		let original_trace_id = error_context.trace_id.clone();
178
179		// Wrap it in a TriggerError
180		let trigger_error = TriggerError::ConfigurationError(error_context);
181
182		// Verify the trace ID is preserved
183		assert_eq!(trigger_error.trace_id(), original_trace_id);
184
185		// Test trace ID propagation through error chain
186		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		// Test Other variant
194		let anyhow_error = anyhow::anyhow!("Test anyhow error");
195		let trigger_error: TriggerError = anyhow_error.into();
196
197		// Other variant should generate a new UUID
198		assert!(!trigger_error.trace_id().is_empty());
199	}
200}