openzeppelin_monitor/repositories/
error.rs

1//! Error types for repository operations.
2//!
3//! This module defines the error types that can occur during repository operations,
4//! including validation errors, loading errors, and internal errors. It provides
5//! a consistent error handling interface across all repository implementations.
6
7use crate::utils::logging::error::{ErrorContext, TraceableError};
8use std::collections::HashMap;
9use thiserror::Error as ThisError;
10use uuid::Uuid;
11
12/// Represents errors that can occur during repository operations
13#[derive(ThisError, Debug)]
14pub enum RepositoryError {
15	/// Errors related to validation errors
16	#[error("Validation error: {0}")]
17	ValidationError(ErrorContext),
18
19	/// Errors related to load errors
20	#[error("Load error: {0}")]
21	LoadError(ErrorContext),
22
23	/// Errors related to internal errors
24	#[error("Internal error: {0}")]
25	InternalError(ErrorContext),
26
27	/// Other errors that don't fit into the categories above
28	#[error(transparent)]
29	Other(#[from] anyhow::Error),
30}
31
32impl RepositoryError {
33	// Validation error
34	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	// Load error
43	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	// Internal error
52	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		// Just test the string representation instead of the source chain
141		assert!(outer_error.to_string().contains("Failed to initialize"));
142
143		// For RepositoryError::LoadError, we know the implementation details
144		if let RepositoryError::LoadError(ctx) = &outer_error {
145			// Check that the context has the right message
146			assert_eq!(ctx.message, "Failed to initialize");
147
148			// Check that the context has the source error
149			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		// Create an error context with a known trace ID
162		let error_context = ErrorContext::new("Inner error", None, None);
163		let original_trace_id = error_context.trace_id.clone();
164
165		// Wrap it in a RepositoryError
166		let repository_error = RepositoryError::LoadError(error_context);
167
168		// Verify the trace ID is preserved
169		assert_eq!(repository_error.trace_id(), original_trace_id);
170
171		// Test trace ID propagation through error chain
172		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		// Test Other variant
180		let anyhow_error = anyhow::anyhow!("Test anyhow error");
181		let repository_error: RepositoryError = anyhow_error.into();
182
183		// Other variant should generate a new UUID
184		assert!(!repository_error.trace_id().is_empty());
185	}
186}