openzeppelin_monitor/services/blockchain/
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 BlockChainError {
14 #[error("Connection error: {0}")]
16 ConnectionError(ErrorContext),
17
18 #[error("Request error: {0}")]
20 RequestError(ErrorContext),
21
22 #[error("Block not found: {0}")]
24 BlockNotFound(ErrorContext),
25
26 #[error("Transaction error: {0}")]
28 TransactionError(ErrorContext),
29
30 #[error("Internal error: {0}")]
32 InternalError(ErrorContext),
33
34 #[error("Client pool error: {0}")]
36 ClientPoolError(ErrorContext),
37
38 #[error(transparent)]
40 Other(#[from] anyhow::Error),
41}
42
43impl BlockChainError {
44 pub fn connection_error(
46 msg: impl Into<String>,
47 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
48 metadata: Option<HashMap<String, String>>,
49 ) -> Self {
50 Self::ConnectionError(ErrorContext::new_with_log(msg, source, metadata))
51 }
52
53 pub fn request_error(
55 msg: impl Into<String>,
56 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
57 metadata: Option<HashMap<String, String>>,
58 ) -> Self {
59 Self::RequestError(ErrorContext::new_with_log(msg, source, metadata))
60 }
61
62 pub fn block_not_found(
64 msg: impl Into<String>,
65 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
66 metadata: Option<HashMap<String, String>>,
67 ) -> Self {
68 Self::BlockNotFound(ErrorContext::new_with_log(msg, source, metadata))
69 }
70
71 pub fn transaction_error(
73 msg: impl Into<String>,
74 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
75 metadata: Option<HashMap<String, String>>,
76 ) -> Self {
77 Self::TransactionError(ErrorContext::new_with_log(msg, source, metadata))
78 }
79
80 pub fn internal_error(
82 msg: impl Into<String>,
83 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
84 metadata: Option<HashMap<String, String>>,
85 ) -> Self {
86 Self::InternalError(ErrorContext::new_with_log(msg, source, metadata))
87 }
88
89 pub fn client_pool_error(
91 msg: impl Into<String>,
92 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
93 metadata: Option<HashMap<String, String>>,
94 ) -> Self {
95 Self::ClientPoolError(ErrorContext::new_with_log(msg, source, metadata))
96 }
97}
98
99impl TraceableError for BlockChainError {
100 fn trace_id(&self) -> String {
101 match self {
102 Self::ConnectionError(ctx) => ctx.trace_id.clone(),
103 Self::RequestError(ctx) => ctx.trace_id.clone(),
104 Self::BlockNotFound(ctx) => ctx.trace_id.clone(),
105 Self::TransactionError(ctx) => ctx.trace_id.clone(),
106 Self::InternalError(ctx) => ctx.trace_id.clone(),
107 Self::ClientPoolError(ctx) => ctx.trace_id.clone(),
108 Self::Other(_) => Uuid::new_v4().to_string(),
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use std::io::{Error as IoError, ErrorKind};
117
118 #[test]
119 fn test_connection_error_formatting() {
120 let error = BlockChainError::connection_error("test error", None, None);
121 assert_eq!(error.to_string(), "Connection error: test error");
122
123 let source_error = IoError::new(ErrorKind::NotFound, "test source");
124 let error = BlockChainError::connection_error(
125 "test error",
126 Some(Box::new(source_error)),
127 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
128 );
129 assert_eq!(
130 error.to_string(),
131 "Connection error: test error [key1=value1]"
132 );
133 }
134
135 #[test]
136 fn test_request_error_formatting() {
137 let error = BlockChainError::request_error("test error", None, None);
138 assert_eq!(error.to_string(), "Request error: test error");
139
140 let source_error = IoError::new(ErrorKind::NotFound, "test source");
141 let error = BlockChainError::request_error(
142 "test error",
143 Some(Box::new(source_error)),
144 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
145 );
146 assert_eq!(error.to_string(), "Request error: test error [key1=value1]");
147 }
148
149 #[test]
150 fn test_block_not_found_formatting() {
151 let error = BlockChainError::block_not_found("1".to_string(), None, None);
152 assert_eq!(error.to_string(), "Block not found: 1");
153
154 let source_error = IoError::new(ErrorKind::NotFound, "test source");
155 let error = BlockChainError::block_not_found(
156 "1".to_string(),
157 Some(Box::new(source_error)),
158 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
159 );
160 assert_eq!(error.to_string(), "Block not found: 1 [key1=value1]");
161 }
162
163 #[test]
164 fn test_transaction_error_formatting() {
165 let error = BlockChainError::transaction_error("test error", None, None);
166 assert_eq!(error.to_string(), "Transaction error: test error");
167
168 let source_error = IoError::new(ErrorKind::NotFound, "test source");
169 let error = BlockChainError::transaction_error(
170 "test error",
171 Some(Box::new(source_error)),
172 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
173 );
174 assert_eq!(
175 error.to_string(),
176 "Transaction error: test error [key1=value1]"
177 );
178 }
179
180 #[test]
181 fn test_internal_error_formatting() {
182 let error = BlockChainError::internal_error("test error", None, None);
183 assert_eq!(error.to_string(), "Internal error: test error");
184
185 let source_error = IoError::new(ErrorKind::NotFound, "test source");
186 let error = BlockChainError::internal_error(
187 "test error",
188 Some(Box::new(source_error)),
189 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
190 );
191 assert_eq!(
192 error.to_string(),
193 "Internal error: test error [key1=value1]"
194 );
195 }
196
197 #[test]
198 fn test_client_pool_error_formatting() {
199 let error = BlockChainError::client_pool_error("test error", None, None);
200 assert_eq!(error.to_string(), "Client pool error: test error");
201
202 let source_error = IoError::new(ErrorKind::NotFound, "test source");
203 let error = BlockChainError::client_pool_error(
204 "test error",
205 Some(Box::new(source_error)),
206 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
207 );
208 assert_eq!(
209 error.to_string(),
210 "Client pool error: test error [key1=value1]"
211 );
212 }
213
214 #[test]
215 fn test_from_anyhow_error() {
216 let anyhow_error = anyhow::anyhow!("test anyhow error");
217 let block_chain_error: BlockChainError = anyhow_error.into();
218 assert!(matches!(block_chain_error, BlockChainError::Other(_)));
219 assert_eq!(block_chain_error.to_string(), "test anyhow error");
220 }
221
222 #[test]
223 fn test_error_source_chain() {
224 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "while reading config");
225
226 let outer_error =
227 BlockChainError::request_error("Failed to initialize", Some(Box::new(io_error)), None);
228
229 assert!(outer_error.to_string().contains("Failed to initialize"));
231
232 if let BlockChainError::RequestError(ctx) = &outer_error {
234 assert_eq!(ctx.message, "Failed to initialize");
236
237 assert!(ctx.source.is_some());
239
240 if let Some(src) = &ctx.source {
241 assert_eq!(src.to_string(), "while reading config");
242 }
243 } else {
244 panic!("Expected RequestError variant");
245 }
246 }
247
248 #[test]
249 fn test_trace_id_propagation() {
250 let error_context = ErrorContext::new("Inner error", None, None);
252 let original_trace_id = error_context.trace_id.clone();
253
254 let block_chain_error = BlockChainError::RequestError(error_context);
256
257 assert_eq!(block_chain_error.trace_id(), original_trace_id);
259
260 let source_error = IoError::new(ErrorKind::Other, "Source error");
262 let error_context = ErrorContext::new("Middle error", Some(Box::new(source_error)), None);
263 let original_trace_id = error_context.trace_id.clone();
264
265 let block_chain_error = BlockChainError::RequestError(error_context);
266 assert_eq!(block_chain_error.trace_id(), original_trace_id);
267
268 let anyhow_error = anyhow::anyhow!("Test anyhow error");
270 let block_chain_error: BlockChainError = anyhow_error.into();
271
272 assert!(!block_chain_error.trace_id().is_empty());
274 }
275}