openzeppelin_monitor/models/config/
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 ConfigError {
14 #[error("Validation error: {0}")]
16 ValidationError(ErrorContext),
17
18 #[error("Parse error: {0}")]
20 ParseError(ErrorContext),
21
22 #[error("File error: {0}")]
24 FileError(ErrorContext),
25
26 #[error(transparent)]
28 Other(#[from] anyhow::Error),
29}
30
31impl ConfigError {
32 pub fn validation_error(
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::ValidationError(ErrorContext::new(msg, source, metadata))
41 }
42
43 pub fn parse_error(
45 msg: impl Into<String>,
46 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
47 metadata: Option<HashMap<String, String>>,
48 ) -> Self {
49 Self::ParseError(ErrorContext::new(msg, source, metadata))
52 }
53
54 pub fn file_error(
56 msg: impl Into<String>,
57 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
58 metadata: Option<HashMap<String, String>>,
59 ) -> Self {
60 Self::FileError(ErrorContext::new(msg, source, metadata))
63 }
64}
65
66impl TraceableError for ConfigError {
67 fn trace_id(&self) -> String {
68 match self {
69 Self::ValidationError(ctx) => ctx.trace_id.clone(),
70 Self::ParseError(ctx) => ctx.trace_id.clone(),
71 Self::FileError(ctx) => ctx.trace_id.clone(),
72 Self::Other(_) => Uuid::new_v4().to_string(),
73 }
74 }
75}
76
77impl From<std::io::Error> for ConfigError {
78 fn from(err: std::io::Error) -> Self {
79 Self::file_error(err.to_string(), None, None)
80 }
81}
82
83impl From<serde_json::Error> for ConfigError {
84 fn from(err: serde_json::Error) -> Self {
85 Self::parse_error(err.to_string(), None, None)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use std::io::{Error as IoError, ErrorKind};
93
94 #[test]
95 fn test_validation_error_formatting() {
96 let error = ConfigError::validation_error("test error", None, None);
97 assert_eq!(error.to_string(), "Validation error: test error");
98
99 let source_error = IoError::new(ErrorKind::NotFound, "test source");
100 let error = ConfigError::validation_error(
101 "test error",
102 Some(Box::new(source_error)),
103 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
104 );
105 assert_eq!(
106 error.to_string(),
107 "Validation error: test error [key1=value1]"
108 );
109 }
110
111 #[test]
112 fn test_parse_error_formatting() {
113 let error = ConfigError::parse_error("test error", None, None);
114 assert_eq!(error.to_string(), "Parse error: test error");
115
116 let source_error = IoError::new(ErrorKind::NotFound, "test source");
117 let error = ConfigError::parse_error(
118 "test error",
119 Some(Box::new(source_error)),
120 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
121 );
122 assert_eq!(error.to_string(), "Parse error: test error [key1=value1]");
123 }
124
125 #[test]
126 fn test_file_error_formatting() {
127 let error = ConfigError::file_error("test error", None, None);
128 assert_eq!(error.to_string(), "File error: test error");
129
130 let source_error = IoError::new(ErrorKind::NotFound, "test source");
131 let error = ConfigError::file_error(
132 "test error",
133 Some(Box::new(source_error)),
134 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
135 );
136
137 assert_eq!(error.to_string(), "File error: test error [key1=value1]");
138 }
139
140 #[test]
141 fn test_from_anyhow_error() {
142 let anyhow_error = anyhow::anyhow!("test anyhow error");
143 let config_error: ConfigError = anyhow_error.into();
144 assert!(matches!(config_error, ConfigError::Other(_)));
145 assert_eq!(config_error.to_string(), "test anyhow error");
146 }
147
148 #[test]
149 fn test_error_source_chain() {
150 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "while reading config");
151
152 let outer_error =
153 ConfigError::file_error("Failed to initialize", Some(Box::new(io_error)), None);
154
155 assert!(outer_error.to_string().contains("Failed to initialize"));
157
158 if let ConfigError::FileError(ctx) = &outer_error {
160 assert_eq!(ctx.message, "Failed to initialize");
162
163 assert!(ctx.source.is_some());
165
166 if let Some(src) = &ctx.source {
167 assert_eq!(src.to_string(), "while reading config");
168 }
169 } else {
170 panic!("Expected FileError variant");
171 }
172 }
173
174 #[test]
175 fn test_io_error_conversion() {
176 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
177 let config_error: ConfigError = io_error.into();
178 assert!(matches!(config_error, ConfigError::FileError(_)));
179 }
180
181 #[test]
182 fn test_serde_error_conversion() {
183 let json = "invalid json";
184 let serde_error = serde_json::from_str::<serde_json::Value>(json).unwrap_err();
185 let config_error: ConfigError = serde_error.into();
186 assert!(matches!(config_error, ConfigError::ParseError(_)));
187 }
188
189 #[test]
190 fn test_trace_id_propagation() {
191 let error_context = ErrorContext::new("Inner error", None, None);
193 let original_trace_id = error_context.trace_id.clone();
194
195 let config_error = ConfigError::FileError(error_context);
197
198 assert_eq!(config_error.trace_id(), original_trace_id);
200
201 let source_error = IoError::new(ErrorKind::Other, "Source error");
203 let error_context = ErrorContext::new("Middle error", Some(Box::new(source_error)), None);
204 let original_trace_id = error_context.trace_id.clone();
205
206 let config_error = ConfigError::FileError(error_context);
207 assert_eq!(config_error.trace_id(), original_trace_id);
208
209 let anyhow_error = anyhow::anyhow!("Test anyhow error");
211 let config_error: ConfigError = anyhow_error.into();
212
213 assert!(!config_error.trace_id().is_empty());
215 }
216}