1use crate::error::ForgeError;
2use std::error::Error;
3use std::fmt;
4
5#[derive(Debug, Default)]
7pub struct ErrorCollector<E> {
8 errors: Vec<E>,
10}
11
12impl<E> ErrorCollector<E> {
13 pub fn new() -> Self {
15 Self { errors: Vec::new() }
16 }
17
18 pub fn push(&mut self, error: E) {
20 self.errors.push(error);
21 }
22
23 pub fn with(mut self, error: E) -> Self {
25 self.push(error);
26 self
27 }
28
29 pub fn is_empty(&self) -> bool {
31 self.errors.is_empty()
32 }
33
34 pub fn len(&self) -> usize {
36 self.errors.len()
37 }
38
39 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 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 pub fn into_errors(self) -> Vec<E> {
59 self.errors
60 }
61
62 pub fn errors(&self) -> &Vec<E> {
64 &self.errors
65 }
66
67 pub fn errors_mut(&mut self) -> &mut Vec<E> {
69 &mut self.errors
70 }
71
72 pub fn extend(&mut self, other: ErrorCollector<E>) {
74 self.errors.extend(other.errors);
75 }
76
77 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
114pub trait CollectError<T, E> {
116 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
132impl<E: ForgeError> ErrorCollector<E> {
134 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!(
145 "{} errors collected ({} fatal, {} retryable):\n",
146 self.errors.len(),
147 fatal_count,
148 retryable_count
149 ));
150
151 for (i, err) in self.errors.iter().enumerate() {
152 result.push_str(&format!(
153 " {}. [{}] {}\n",
154 i + 1,
155 err.kind(),
156 err.dev_message()
157 ));
158 }
159
160 result
161 }
162
163 pub fn has_fatal(&self) -> bool {
165 self.errors.iter().any(|e| e.is_fatal())
166 }
167
168 pub fn all_retryable(&self) -> bool {
170 !self.errors.is_empty() && self.errors.iter().all(|e| e.is_retryable())
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::AppError;
178
179 #[test]
180 fn test_error_collector() {
181 let mut collector = ErrorCollector::new();
182
183 assert!(collector.is_empty());
184
185 collector.push(AppError::config("Config error"));
186 collector.push(AppError::filesystem("File not found", None));
187
188 assert_eq!(collector.len(), 2);
189
190 let result: Result<(), _> = collector.into_result(());
191 assert!(result.is_err());
192 }
193
194 #[test]
195 fn test_collect_error() {
196 let mut collector = ErrorCollector::new();
197
198 let result1: Result<i32, AppError> = Ok(42);
199 let result2: Result<i32, AppError> = Err(AppError::network("Connection failed", None));
200
201 let val1 = result1.collect_err(&mut collector);
202 let val2 = result2.collect_err(&mut collector);
203
204 assert_eq!(val1, Some(42));
205 assert_eq!(val2, None);
206 assert_eq!(collector.len(), 1);
207 }
208
209 #[test]
210 fn test_forge_error_collector() {
211 let mut collector = ErrorCollector::new();
212
213 collector.push(AppError::config("Config error").with_fatal(true));
214 collector.push(AppError::network("Connection failed", None).with_retryable(true));
215
216 assert!(collector.has_fatal());
217 assert!(!collector.all_retryable());
218
219 let summary = collector.summary();
220 assert!(summary.contains("2 errors collected (1 fatal, 1 retryable)"));
221 assert!(summary.contains("[Config]"));
222 assert!(summary.contains("[Network]"));
223 }
224}