openzeppelin_monitor/models/security/
error.rs1use crate::utils::logging::error::{ErrorContext, TraceableError};
8use std::collections::HashMap;
9use thiserror::Error as ThisError;
10use uuid::Uuid;
11
12pub type SecurityResult<T> = Result<T, Box<SecurityError>>;
14
15#[derive(ThisError, Debug)]
22pub enum SecurityError {
23 #[error("Validation error: {0}")]
25 ValidationError(ErrorContext),
26
27 #[error("Parse error: {0}")]
29 ParseError(ErrorContext),
30
31 #[error("Network error: {0}")]
33 NetworkError(ErrorContext),
34
35 #[error(transparent)]
37 Other(#[from] anyhow::Error),
38}
39
40impl SecurityError {
41 pub fn validation_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::ValidationError(ErrorContext::new_with_log(msg, source, metadata))
48 }
49
50 pub fn parse_error(
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::ParseError(ErrorContext::new_with_log(msg, source, metadata))
57 }
58
59 pub fn network_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::NetworkError(ErrorContext::new_with_log(msg, source, metadata))
66 }
67}
68
69impl TraceableError for SecurityError {
70 fn trace_id(&self) -> String {
71 match self {
72 Self::ValidationError(ctx) => ctx.trace_id.clone(),
73 Self::ParseError(ctx) => ctx.trace_id.clone(),
74 Self::NetworkError(ctx) => ctx.trace_id.clone(),
75 Self::Other(_) => Uuid::new_v4().to_string(),
76 }
77 }
78}
79
80impl From<std::io::Error> for SecurityError {
81 fn from(err: std::io::Error) -> Self {
82 Self::parse_error(err.to_string(), None, None)
83 }
84}
85
86impl From<serde_json::Error> for SecurityError {
87 fn from(err: serde_json::Error) -> Self {
88 Self::parse_error(err.to_string(), None, None)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use std::io::{Error as IoError, ErrorKind};
96
97 #[test]
98 fn test_validation_error_formatting() {
99 let error = SecurityError::validation_error("test error", None, None);
100 assert_eq!(error.to_string(), "Validation error: test error");
101
102 let source_error = IoError::new(ErrorKind::NotFound, "test source");
103 let error = SecurityError::validation_error(
104 "test error",
105 Some(Box::new(source_error)),
106 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
107 );
108 assert_eq!(
109 error.to_string(),
110 "Validation error: test error [key1=value1]"
111 );
112 }
113
114 #[test]
115 fn test_parse_error_formatting() {
116 let error = SecurityError::parse_error("test error", None, None);
117 assert_eq!(error.to_string(), "Parse error: test error");
118
119 let source_error = IoError::new(ErrorKind::NotFound, "test source");
120 let error = SecurityError::parse_error(
121 "test error",
122 Some(Box::new(source_error)),
123 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
124 );
125 assert_eq!(error.to_string(), "Parse error: test error [key1=value1]");
126 }
127
128 #[test]
129 fn test_network_error_formatting() {
130 let error = SecurityError::network_error("test error", None, None);
131 assert_eq!(error.to_string(), "Network error: test error");
132
133 let source_error = IoError::new(ErrorKind::NotFound, "test source");
134 let error = SecurityError::network_error(
135 "test error",
136 Some(Box::new(source_error)),
137 Some(HashMap::from([("key1".to_string(), "value1".to_string())])),
138 );
139 assert_eq!(error.to_string(), "Network error: test error [key1=value1]");
140 }
141
142 #[test]
143 fn test_from_anyhow_error() {
144 let anyhow_error = anyhow::anyhow!("test anyhow error");
145 let security_error: SecurityError = anyhow_error.into();
146 assert!(matches!(security_error, SecurityError::Other(_)));
147 assert_eq!(security_error.to_string(), "test anyhow error");
148 }
149
150 #[test]
151 fn test_error_source_chain() {
152 let io_error = std::io::Error::new(std::io::ErrorKind::Other, "while reading config");
153
154 let outer_error =
155 SecurityError::parse_error("Failed to initialize", Some(Box::new(io_error)), None);
156
157 assert!(outer_error.to_string().contains("Failed to initialize"));
159
160 if let SecurityError::ParseError(ctx) = &outer_error {
162 assert_eq!(ctx.message, "Failed to initialize");
164
165 assert!(ctx.source.is_some());
167
168 if let Some(src) = &ctx.source {
169 assert_eq!(src.to_string(), "while reading config");
170 }
171 } else {
172 panic!("Expected ParseError variant");
173 }
174 }
175
176 #[test]
177 fn test_io_error_conversion() {
178 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
179 let security_error: SecurityError = io_error.into();
180 assert!(matches!(security_error, SecurityError::ParseError(_)));
181 }
182
183 #[test]
184 fn test_serde_error_conversion() {
185 let json = "invalid json";
186 let serde_error = serde_json::from_str::<serde_json::Value>(json).unwrap_err();
187 let security_error: SecurityError = serde_error.into();
188 assert!(matches!(security_error, SecurityError::ParseError(_)));
189 }
190
191 #[test]
192 fn test_trace_id_propagation() {
193 let error_context = ErrorContext::new("Inner error", None, None);
195 let original_trace_id = error_context.trace_id.clone();
196
197 let security_error = SecurityError::ParseError(error_context);
199
200 assert_eq!(security_error.trace_id(), original_trace_id);
202
203 let source_error = IoError::new(ErrorKind::Other, "Source error");
205 let error_context = ErrorContext::new("Middle error", Some(Box::new(source_error)), None);
206 let original_trace_id = error_context.trace_id.clone();
207
208 let security_error = SecurityError::ParseError(error_context);
209 assert_eq!(security_error.trace_id(), original_trace_id);
210
211 let anyhow_error = anyhow::anyhow!("Test anyhow error");
213 let security_error: SecurityError = anyhow_error.into();
214
215 assert!(!security_error.trace_id().is_empty());
217 }
218}