openzeppelin_monitor/models/security/
error.rs

1//! Security error types and error handling utilities.
2//!
3//! This module defines error types for handling security-related operations.
4//! The `SecurityError` type provides a structured way to handle and propagate security-related errors
5//! with rich context and tracing capabilities.
6
7use crate::utils::logging::error::{ErrorContext, TraceableError};
8use std::collections::HashMap;
9use thiserror::Error as ThisError;
10use uuid::Uuid;
11
12/// Result type alias for security operations
13pub type SecurityResult<T> = Result<T, Box<SecurityError>>;
14
15/// Represents errors that can occur during security operations.
16///
17/// This error type is used throughout the security module to provide consistent error handling
18/// with rich context and tracing capabilities. Each variant includes an `ErrorContext` that
19/// contains detailed information about the error, including a trace ID for distributed tracing.
20
21#[derive(ThisError, Debug)]
22pub enum SecurityError {
23	/// Errors related to validation failures.
24	#[error("Validation error: {0}")]
25	ValidationError(ErrorContext),
26
27	/// Errors related to parsing failures.
28	#[error("Parse error: {0}")]
29	ParseError(ErrorContext),
30
31	/// Errors related to network failures.
32	#[error("Network error: {0}")]
33	NetworkError(ErrorContext),
34
35	/// Other errors that don't fit into the categories above.
36	#[error(transparent)]
37	Other(#[from] anyhow::Error),
38}
39
40impl SecurityError {
41	// Validation error
42	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	// Parse error
51	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	// Network error
60	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		// Just test the string representation instead of the source chain
158		assert!(outer_error.to_string().contains("Failed to initialize"));
159
160		// For SecurityError::ParseError, we know the implementation details
161		if let SecurityError::ParseError(ctx) = &outer_error {
162			// Check that the context has the right message
163			assert_eq!(ctx.message, "Failed to initialize");
164
165			// Check that the context has the source error
166			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		// Create an error context with a known trace ID
194		let error_context = ErrorContext::new("Inner error", None, None);
195		let original_trace_id = error_context.trace_id.clone();
196
197		// Wrap it in a SecurityError
198		let security_error = SecurityError::ParseError(error_context);
199
200		// Verify the trace ID is preserved
201		assert_eq!(security_error.trace_id(), original_trace_id);
202
203		// Test trace ID propagation through error chain
204		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		// Test Other variant
212		let anyhow_error = anyhow::anyhow!("Test anyhow error");
213		let security_error: SecurityError = anyhow_error.into();
214
215		// Other variant should generate a new UUID
216		assert!(!security_error.trace_id().is_empty());
217	}
218}