openzeppelin_monitor/repositories/
error.rs1use crate::utils::logging::error::{ErrorContext, TraceableError};
8use std::collections::HashMap;
9use thiserror::Error as ThisError;
10use uuid::Uuid;
11
12#[derive(ThisError, Debug)]
14pub enum RepositoryError {
15 #[error("Validation error: {0}")]
17 ValidationError(ErrorContext),
18
19 #[error("Load error: {0}")]
21 LoadError(ErrorContext),
22
23 #[error("Internal error: {0}")]
25 InternalError(ErrorContext),
26
27 #[error(transparent)]
29 Other(#[from] anyhow::Error),
30}
31
32impl RepositoryError {
33 pub fn validation_error(
35 msg: impl Into<String>,
36 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
37 metadata: Option<HashMap<String, String>>,
38 ) -> Self {
39 Self::ValidationError(ErrorContext::new_with_log(msg, source, metadata))
40 }
41
42 pub fn load_error(
44 msg: impl Into<String>,
45 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
46 metadata: Option<HashMap<String, String>>,
47 ) -> Self {
48 Self::LoadError(ErrorContext::new_with_log(msg, source, metadata))
49 }
50
51 pub fn internal_error(
53 msg: impl Into<String>,
54 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
55 metadata: Option<HashMap<String, String>>,
56 ) -> Self {
57 Self::InternalError(ErrorContext::new_with_log(msg, source, metadata))
58 }
59}
60
61impl TraceableError for RepositoryError {
62 fn trace_id(&self) -> String {
63 match self {
64 Self::ValidationError(ctx) => ctx.trace_id.clone(),
65 Self::LoadError(ctx) => ctx.trace_id.clone(),
66 Self::InternalError(ctx) => ctx.trace_id.clone(),
67 Self::Other(_) => Uuid::new_v4().to_string(),
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use std::io::{Error as IoError, ErrorKind};
76
77 #[test]
78 fn test_validation_error_formatting() {
79 let error = RepositoryError::validation_error("test error", None, None);
80 assert_eq!(error.to_string(), "Validation error: test error");
81
82 let source_error = IoError::new(ErrorKind::NotFound, "test source");
83 let error = RepositoryError::validation_error(
84 "test error",
85 Some(Box::new(source_error)),
86 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
87 );
88 assert_eq!(
89 error.to_string(),
90 "Validation error: test error [key1=value1]"
91 );
92 }
93
94 #[test]
95 fn test_load_error_formatting() {
96 let error = RepositoryError::load_error("test error", None, None);
97 assert_eq!(error.to_string(), "Load error: test error");
98
99 let source_error = IoError::new(ErrorKind::NotFound, "test source");
100 let error = RepositoryError::load_error(
101 "test error",
102 Some(Box::new(source_error)),
103 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
104 );
105 assert_eq!(error.to_string(), "Load error: test error [key1=value1]");
106 }
107
108 #[test]
109 fn test_internal_error_formatting() {
110 let error = RepositoryError::internal_error("test error", None, None);
111 assert_eq!(error.to_string(), "Internal error: test error");
112
113 let source_error = IoError::new(ErrorKind::NotFound, "test source");
114 let error = RepositoryError::internal_error(
115 "test error",
116 Some(Box::new(source_error)),
117 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
118 );
119 assert_eq!(
120 error.to_string(),
121 "Internal error: test error [key1=value1]"
122 );
123 }
124
125 #[test]
126 fn test_from_anyhow_error() {
127 let anyhow_error = anyhow::anyhow!("test anyhow error");
128 let repository_error: RepositoryError = anyhow_error.into();
129 assert!(matches!(repository_error, RepositoryError::Other(_)));
130 assert_eq!(repository_error.to_string(), "test anyhow error");
131 }
132
133 #[test]
134 fn test_error_source_chain() {
135 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "while reading config");
136
137 let outer_error =
138 RepositoryError::load_error("Failed to initialize", Some(Box::new(io_error)), None);
139
140 assert!(outer_error.to_string().contains("Failed to initialize"));
142
143 if let RepositoryError::LoadError(ctx) = &outer_error {
145 assert_eq!(ctx.message, "Failed to initialize");
147
148 assert!(ctx.source.is_some());
150
151 if let Some(src) = &ctx.source {
152 assert_eq!(src.to_string(), "while reading config");
153 }
154 } else {
155 panic!("Expected LoadError variant");
156 }
157 }
158
159 #[test]
160 fn test_trace_id_propagation() {
161 let error_context = ErrorContext::new("Inner error", None, None);
163 let original_trace_id = error_context.trace_id.clone();
164
165 let repository_error = RepositoryError::LoadError(error_context);
167
168 assert_eq!(repository_error.trace_id(), original_trace_id);
170
171 let source_error = IoError::new(ErrorKind::Other, "Source error");
173 let error_context = ErrorContext::new("Middle error", Some(Box::new(source_error)), None);
174 let original_trace_id = error_context.trace_id.clone();
175
176 let repository_error = RepositoryError::LoadError(error_context);
177 assert_eq!(repository_error.trace_id(), original_trace_id);
178
179 let anyhow_error = anyhow::anyhow!("Test anyhow error");
181 let repository_error: RepositoryError = anyhow_error.into();
182
183 assert!(!repository_error.trace_id().is_empty());
185 }
186}