1use std::fmt;
2use std::error::Error;
3use crate::error::ForgeError;
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!("{} 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 pub fn has_fatal(&self) -> bool {
159 self.errors.iter().any(|e| e.is_fatal())
160 }
161
162 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}