openzeppelin_monitor/services/blockwatcher/
error.rs

1//! Block watcher error types and handling.
2//!
3//! Provides a comprehensive error handling system for block watching operations,
4//! including scheduling, network connectivity, and storage operations.
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 that can occur during block watching operations
12#[derive(ThisError, Debug)]
13pub enum BlockWatcherError {
14	/// Errors related to network connectivity issues
15	#[error("Scheduler error: {0}")]
16	SchedulerError(ErrorContext),
17
18	/// Errors related to malformed requests or invalid responses
19	#[error("Network error: {0}")]
20	NetworkError(ErrorContext),
21
22	/// When a requested block cannot be found on the blockchain
23	#[error("Processing error: {0}")]
24	ProcessingError(ErrorContext),
25
26	/// Errors related to transaction processing
27	#[error("Storage error: {0}")]
28	StorageError(ErrorContext),
29
30	/// Internal errors within the blockchain client
31	#[error("Block tracker error: {0}")]
32	BlockTrackerError(ErrorContext),
33
34	/// Other errors that don't fit into the categories above
35	#[error(transparent)]
36	Other(#[from] anyhow::Error),
37}
38
39impl BlockWatcherError {
40	// Scheduler error
41	pub fn scheduler_error(
42		msg: impl Into<String>,
43		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
44		metadata: Option<HashMap<String, String>>,
45	) -> Self {
46		Self::SchedulerError(ErrorContext::new_with_log(msg, source, metadata))
47	}
48
49	// Network error
50	pub fn network_error(
51		msg: impl Into<String>,
52		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
53		metadata: Option<HashMap<String, String>>,
54	) -> Self {
55		Self::NetworkError(ErrorContext::new_with_log(msg, source, metadata))
56	}
57
58	// Processing error
59	pub fn processing_error(
60		msg: impl Into<String>,
61		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
62		metadata: Option<HashMap<String, String>>,
63	) -> Self {
64		Self::ProcessingError(ErrorContext::new_with_log(msg, source, metadata))
65	}
66
67	// Storage error
68	pub fn storage_error(
69		msg: impl Into<String>,
70		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
71		metadata: Option<HashMap<String, String>>,
72	) -> Self {
73		Self::StorageError(ErrorContext::new_with_log(msg, source, metadata))
74	}
75
76	// Block tracker error
77	pub fn block_tracker_error(
78		msg: impl Into<String>,
79		source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
80		metadata: Option<HashMap<String, String>>,
81	) -> Self {
82		Self::BlockTrackerError(ErrorContext::new_with_log(msg, source, metadata))
83	}
84}
85
86impl TraceableError for BlockWatcherError {
87	fn trace_id(&self) -> String {
88		match self {
89			Self::SchedulerError(ctx) => ctx.trace_id.clone(),
90			Self::NetworkError(ctx) => ctx.trace_id.clone(),
91			Self::ProcessingError(ctx) => ctx.trace_id.clone(),
92			Self::StorageError(ctx) => ctx.trace_id.clone(),
93			Self::BlockTrackerError(ctx) => ctx.trace_id.clone(),
94			Self::Other(_) => Uuid::new_v4().to_string(),
95		}
96	}
97}
98
99#[cfg(test)]
100mod tests {
101	use super::*;
102	use std::io::{Error as IoError, ErrorKind};
103
104	#[test]
105	fn test_scheduler_error_formatting() {
106		let error = BlockWatcherError::scheduler_error("test error", None, None);
107		assert_eq!(error.to_string(), "Scheduler error: test error");
108
109		let source_error = IoError::new(ErrorKind::NotFound, "test source");
110		let error = BlockWatcherError::scheduler_error(
111			"test error",
112			Some(Box::new(source_error)),
113			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
114		);
115		assert_eq!(
116			error.to_string(),
117			"Scheduler error: test error [key1=value1]"
118		);
119	}
120
121	#[test]
122	fn test_network_error_formatting() {
123		let error = BlockWatcherError::network_error("test error", None, None);
124		assert_eq!(error.to_string(), "Network error: test error");
125
126		let source_error = IoError::new(ErrorKind::NotFound, "test source");
127		let error = BlockWatcherError::network_error(
128			"test error",
129			Some(Box::new(source_error)),
130			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
131		);
132		assert_eq!(error.to_string(), "Network error: test error [key1=value1]");
133	}
134
135	#[test]
136	fn test_processing_error_formatting() {
137		let error = BlockWatcherError::processing_error("test error", None, None);
138		assert_eq!(error.to_string(), "Processing error: test error");
139
140		let source_error = IoError::new(ErrorKind::NotFound, "test source");
141		let error = BlockWatcherError::processing_error(
142			"test error",
143			Some(Box::new(source_error)),
144			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
145		);
146		assert_eq!(
147			error.to_string(),
148			"Processing error: test error [key1=value1]"
149		);
150	}
151
152	#[test]
153	fn test_storage_error_formatting() {
154		let error = BlockWatcherError::storage_error("test error", None, None);
155		assert_eq!(error.to_string(), "Storage error: test error");
156
157		let source_error = IoError::new(ErrorKind::NotFound, "test source");
158		let error = BlockWatcherError::storage_error(
159			"test error",
160			Some(Box::new(source_error)),
161			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
162		);
163		assert_eq!(error.to_string(), "Storage error: test error [key1=value1]");
164	}
165
166	#[test]
167	fn test_block_tracker_error_formatting() {
168		let error = BlockWatcherError::block_tracker_error("test error", None, None);
169		assert_eq!(error.to_string(), "Block tracker error: test error");
170
171		let source_error = IoError::new(ErrorKind::NotFound, "test source");
172		let error = BlockWatcherError::block_tracker_error(
173			"test error",
174			Some(Box::new(source_error)),
175			Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
176		);
177		assert_eq!(
178			error.to_string(),
179			"Block tracker error: test error [key1=value1]"
180		);
181	}
182
183	#[test]
184	fn test_from_anyhow_error() {
185		let anyhow_error = anyhow::anyhow!("test anyhow error");
186		let block_watcher_error: BlockWatcherError = anyhow_error.into();
187		assert!(matches!(block_watcher_error, BlockWatcherError::Other(_)));
188		assert_eq!(block_watcher_error.to_string(), "test anyhow error");
189	}
190
191	#[test]
192	fn test_error_source_chain() {
193		let io_error = std::io::Error::new(std::io::ErrorKind::Other, "while reading config");
194
195		let outer_error = BlockWatcherError::scheduler_error(
196			"Failed to initialize",
197			Some(Box::new(io_error)),
198			None,
199		);
200
201		// Just test the string representation instead of the source chain
202		assert!(outer_error.to_string().contains("Failed to initialize"));
203
204		// For BlockWatcherError::SchedulerError, we know the implementation details
205		if let BlockWatcherError::SchedulerError(ctx) = &outer_error {
206			// Check that the context has the right message
207			assert_eq!(ctx.message, "Failed to initialize");
208
209			// Check that the context has the source error
210			assert!(ctx.source.is_some());
211
212			if let Some(src) = &ctx.source {
213				assert_eq!(src.to_string(), "while reading config");
214			}
215		} else {
216			panic!("Expected SchedulerError variant");
217		}
218	}
219
220	#[test]
221	fn test_trace_id_propagation() {
222		// Create an error context with a known trace ID
223		let error_context = ErrorContext::new("Inner error", None, None);
224		let original_trace_id = error_context.trace_id.clone();
225
226		// Wrap it in a BlockWatcherError
227		let block_watcher_error = BlockWatcherError::SchedulerError(error_context);
228
229		// Verify the trace ID is preserved
230		assert_eq!(block_watcher_error.trace_id(), original_trace_id);
231
232		// Test trace ID propagation through error chain
233		let source_error = IoError::new(ErrorKind::Other, "Source error");
234		let error_context = ErrorContext::new("Middle error", Some(Box::new(source_error)), None);
235		let original_trace_id = error_context.trace_id.clone();
236
237		let block_watcher_error = BlockWatcherError::SchedulerError(error_context);
238		assert_eq!(block_watcher_error.trace_id(), original_trace_id);
239
240		// Test Other variant
241		let anyhow_error = anyhow::anyhow!("Test anyhow error");
242		let block_watcher_error: BlockWatcherError = anyhow_error.into();
243
244		// Other variant should generate a new UUID
245		assert!(!block_watcher_error.trace_id().is_empty());
246	}
247}