error_forge/
collector.rs

1use std::fmt;
2use std::error::Error;
3use crate::error::ForgeError;
4
5/// A collection of errors that can be accumulated and returned as a single result
6#[derive(Debug, Default)]
7pub struct ErrorCollector<E> {
8    /// The collected errors
9    errors: Vec<E>,
10}
11
12impl<E> ErrorCollector<E> {
13    /// Create a new empty error collector
14    pub fn new() -> Self {
15        Self { errors: Vec::new() }
16    }
17    
18    /// Add an error to the collection
19    pub fn push(&mut self, error: E) {
20        self.errors.push(error);
21    }
22    
23    /// Add an error to the collection and return self for chaining
24    pub fn with(mut self, error: E) -> Self {
25        self.push(error);
26        self
27    }
28    
29    /// Check if the collection is empty
30    pub fn is_empty(&self) -> bool {
31        self.errors.is_empty()
32    }
33    
34    /// Get the number of collected errors
35    pub fn len(&self) -> usize {
36        self.errors.len()
37    }
38    
39    /// Return a result that is Ok if there are no errors, or Err with the collector otherwise
40    pub fn into_result<T>(self, ok_value: T) -> Result<T, Self> {
41        if self.is_empty() {
42            Ok(ok_value)
43        } else {
44            Err(self)
45        }
46    }
47    
48    /// Return a result that is Ok if there are no errors, or Err with the collector otherwise
49    pub fn result<T>(&self, ok_value: T) -> Result<T, &Self> {
50        if self.is_empty() {
51            Ok(ok_value)
52        } else {
53            Err(self)
54        }
55    }
56    
57    /// Consume the collector and return the vector of errors
58    pub fn into_errors(self) -> Vec<E> {
59        self.errors
60    }
61    
62    /// Get a reference to the vector of errors
63    pub fn errors(&self) -> &Vec<E> {
64        &self.errors
65    }
66    
67    /// Get a mutable reference to the vector of errors
68    pub fn errors_mut(&mut self) -> &mut Vec<E> {
69        &mut self.errors
70    }
71    
72    /// Add all errors from another collector
73    pub fn extend(&mut self, other: ErrorCollector<E>) {
74        self.errors.extend(other.errors);
75    }
76    
77    /// Try an operation that may return an error, collecting the error if it occurs
78    pub fn try_collect<F, T>(&mut self, op: F) -> Option<T>
79    where
80        F: FnOnce() -> Result<T, E>,
81    {
82        match op() {
83            Ok(val) => Some(val),
84            Err(e) => {
85                self.push(e);
86                None
87            }
88        }
89    }
90}
91
92impl<E: fmt::Display> fmt::Display for ErrorCollector<E> {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        if self.errors.is_empty() {
95            write!(f, "No errors")
96        } else if self.errors.len() == 1 {
97            write!(f, "1 error: {}", self.errors[0])
98        } else {
99            writeln!(f, "{} errors:", self.errors.len())?;
100            for (i, err) in self.errors.iter().enumerate() {
101                writeln!(f, "  {}. {}", i + 1, err)?;
102            }
103            Ok(())
104        }
105    }
106}
107
108impl<E: Error> Error for ErrorCollector<E> {
109    fn source(&self) -> Option<&(dyn Error + 'static)> {
110        self.errors.first().and_then(|e| e.source())
111    }
112}
113
114/// Extension trait for Result types to collect errors
115pub trait CollectError<T, E> {
116    /// Collect an error into an ErrorCollector if the result is an error
117    fn collect_err(self, collector: &mut ErrorCollector<E>) -> Option<T>;
118}
119
120impl<T, E> CollectError<T, E> for Result<T, E> {
121    fn collect_err(self, collector: &mut ErrorCollector<E>) -> Option<T> {
122        match self {
123            Ok(val) => Some(val),
124            Err(e) => {
125                collector.push(e);
126                None
127            }
128        }
129    }
130}
131
132// Special implementation for ForgeError types to provide rich error collection
133impl<E: ForgeError> ErrorCollector<E> {
134    /// Return a summary of the collected errors using ForgeError traits
135    pub fn summary(&self) -> String {
136        if self.errors.is_empty() {
137            return "No errors".to_string();
138        }
139        
140        let mut result = String::new();
141        let fatal_count = self.errors.iter().filter(|e| e.is_fatal()).count();
142        let retryable_count = self.errors.iter().filter(|e| e.is_retryable()).count();
143        
144        result.push_str(&format!("{} errors collected ({} fatal, {} retryable):\n", 
145            self.errors.len(), fatal_count, retryable_count));
146        
147        for (i, err) in self.errors.iter().enumerate() {
148            result.push_str(&format!("  {}. [{}] {}\n", 
149                i + 1, 
150                err.kind(), 
151                err.dev_message()));
152        }
153        
154        result
155    }
156    
157    /// Check if any of the collected errors is marked as fatal
158    pub fn has_fatal(&self) -> bool {
159        self.errors.iter().any(|e| e.is_fatal())
160    }
161    
162    /// Check if all collected errors are retryable
163    pub fn all_retryable(&self) -> bool {
164        !self.errors.is_empty() && self.errors.iter().all(|e| e.is_retryable())
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use crate::AppError;
172    
173    #[test]
174    fn test_error_collector() {
175        let mut collector = ErrorCollector::new();
176        
177        assert!(collector.is_empty());
178        
179        collector.push(AppError::config("Config error"));
180        collector.push(AppError::filesystem("File not found", None));
181        
182        assert_eq!(collector.len(), 2);
183        
184        let result: Result<(), _> = collector.into_result(());
185        assert!(result.is_err());
186    }
187    
188    #[test]
189    fn test_collect_error() {
190        let mut collector = ErrorCollector::new();
191        
192        let result1: Result<i32, AppError> = Ok(42);
193        let result2: Result<i32, AppError> = Err(AppError::network("Connection failed", None));
194        
195        let val1 = result1.collect_err(&mut collector);
196        let val2 = result2.collect_err(&mut collector);
197        
198        assert_eq!(val1, Some(42));
199        assert_eq!(val2, None);
200        assert_eq!(collector.len(), 1);
201    }
202    
203    #[test]
204    fn test_forge_error_collector() {
205        let mut collector = ErrorCollector::new();
206        
207        collector.push(AppError::config("Config error").with_fatal(true));
208        collector.push(AppError::network("Connection failed", None).with_retryable(true));
209        
210        assert!(collector.has_fatal());
211        assert!(!collector.all_retryable());
212        
213        let summary = collector.summary();
214        assert!(summary.contains("2 errors collected (1 fatal, 1 retryable)"));
215        assert!(summary.contains("[Config]"));
216        assert!(summary.contains("[Network]"));
217    }
218}