1#![doc = include_str!("../README.md")]
2
3use std::error::Error as StdError;
4use std::fmt;
5
6use derive_more::{Deref, DerefMut};
7
8pub type ErrorCollection = Errors;
12
13#[derive(Default, Deref, DerefMut)]
103pub struct Errors(pub Vec<anyhow::Error>);
104
105impl Errors {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn push(&mut self, err: impl Into<anyhow::Error>) {
116 self.0.push(err.into());
117 }
118
119 pub fn append(&mut self, err: impl Into<Self>) {
123 self.0.append(&mut err.into().0);
124 }
125
126 pub fn collect<T, E>(&mut self, result: Result<T, E>) -> Option<T>
128 where
129 E: Into<anyhow::Error>,
130 {
131 match result {
132 Ok(value) => Some(value),
133 Err(err) => {
134 self.append(err.into());
136 None
137 }
138 }
139 }
140
141 pub fn into_vec(self) -> Vec<anyhow::Error> {
143 self.0
144 }
145
146 pub fn as_result(mut self) -> anyhow::Result<()> {
148 match self.len() {
149 0 => Ok(()),
150 1 => Err(self.pop().unwrap()),
151 _ => Err(self.into()),
152 }
153 }
154}
155
156const PADDING: usize = 3;
157
158impl fmt::Debug for Errors {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 if f.alternate() {
161 write!(f, "Errors ")?;
162 let mut list = f.debug_list();
163 for error in self.iter() {
164 list.entry(error);
165 }
166 list.finish()
167 } else {
168 debug_collection(f, self, 0)
169 }
170 }
171}
172
173fn debug_collection(f: &mut fmt::Formatter<'_>, errors: &Errors, indent: usize) -> fmt::Result {
175 if errors.is_empty() {
176 writeln!(f, "none")
177 } else if errors.len() == 1 {
178 debug_error(f, &errors[0], indent)
179 } else {
180 writeln!(f, "{} errors:", errors.len())?;
181 for (idx, error) in errors.iter().enumerate() {
182 write!(f, "{}{}. ", spaces(indent + PADDING), idx + 1)?;
183 match error.downcast_ref::<Errors>() {
184 None => debug_error(f, error, indent + PADDING)?,
185 Some(errors) => debug_collection(f, errors, indent + PADDING)?,
186 }
187 }
188 Ok(())
189 }
190}
191
192fn debug_error(f: &mut fmt::Formatter<'_>, error: &anyhow::Error, indent: usize) -> fmt::Result {
194 let padding = spaces(indent + PADDING);
195 let error_string = format!("{error:?}");
196 for (idx, line) in error_string.split('\n').enumerate() {
197 let padding = if idx == 0 { "" } else { padding };
198 writeln!(f, "{padding}{line}")?;
199 }
200 Ok(())
201}
202
203impl fmt::Display for Errors {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "Errors: ")?;
206
207 if self.is_empty() {
208 return writeln!(f, "none");
209 }
210
211 let mut first = true;
212 display_collection(f, self, &mut first)?;
213 writeln!(f)
214 }
215}
216
217fn display_collection(
219 f: &mut fmt::Formatter<'_>,
220 errors: &Errors,
221 first: &mut bool,
222) -> fmt::Result {
223 for error in errors.iter() {
224 match error.downcast_ref::<Errors>() {
225 Some(errors) => display_collection(f, errors, first)?,
226 None => display_error(f, error, first)?,
227 }
228 }
229 Ok(())
230}
231
232fn display_error(
234 f: &mut fmt::Formatter<'_>,
235 error: &anyhow::Error,
236 first: &mut bool,
237) -> fmt::Result {
238 if *first {
239 *first = false;
240 } else {
241 write!(f, ", ")?;
242 }
243
244 if f.alternate() {
245 write!(f, "{error:#}")
246 } else {
247 write!(f, "{error}")
248 }
249}
250
251fn spaces(indent: usize) -> &'static str {
253 &" "[..indent.min(32)]
254}
255
256impl StdError for Errors {}
257
258impl From<&str> for Errors {
259 fn from(value: &str) -> Self {
260 Self(vec![anyhow::anyhow!("{value}")])
261 }
262}
263
264impl From<String> for Errors {
265 fn from(value: String) -> Self {
266 Self(vec![anyhow::anyhow!(value)])
267 }
268}
269
270impl<T> From<Option<T>> for Errors
271where
272 T: Into<anyhow::Error>,
273{
274 fn from(result: Option<T>) -> Self {
275 match result {
276 Some(err) => err.into().into(),
277 None => Self::default(),
278 }
279 }
280}
281
282impl<T, E> From<Result<T, E>> for Errors
283where
284 E: Into<anyhow::Error>,
285{
286 fn from(result: Result<T, E>) -> Self {
287 match result {
288 Ok(_) => Self::default(),
289 Err(err) => err.into().into(),
290 }
291 }
292}
293
294impl From<Vec<anyhow::Error>> for Errors {
295 fn from(errors: Vec<anyhow::Error>) -> Self {
296 Self(errors)
297 }
298}
299
300impl From<anyhow::Error> for Errors {
301 fn from(error: anyhow::Error) -> Self {
302 match error.downcast::<Self>() {
303 Ok(errors) => errors,
304 Err(error) => Self(vec![error]),
305 }
306 }
307}
308
309impl<T> From<Errors> for anyhow::Result<T>
310where
311 T: Default,
312{
313 fn from(mut value: Errors) -> Self {
314 match value.len() {
315 0 => Ok(T::default()),
316 1 => Err(value.pop().unwrap()),
317 _ => Err(value.into()),
318 }
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use std::io;
325
326 use anyhow::{Context, anyhow};
327
328 use super::*;
329
330 #[test]
331 fn push() {
332 let mut nested = Errors::new();
333 nested.push(anyhow!("Generic error 1"));
334 nested.push(anyhow!("Generic error 2"));
335 nested.push(anyhow!("Generic error 3"));
336
337 let mut errors = Errors::new();
338 errors.push(nested);
339 errors.push(anyhow!("Generic error 4"));
340 errors.push(io::Error::from_raw_os_error(22));
341
342 assert_eq!(errors.len(), 3);
343 }
344
345 #[test]
346 fn append() {
347 let mut nested = Errors::new();
348 nested.append(vec![anyhow!("Generic error 1"), anyhow!("Generic error 2")]);
349
350 let mut errors = Errors::new();
351 errors.append(nested);
352 errors.append(anyhow!("Generic error 3"));
353
354 assert_eq!(errors.len(), 3);
355 }
356
357 #[test]
358 fn collect() {
359 let mut errors = Errors::new();
360
361 let result: Result<(), anyhow::Error> = Ok(());
362 assert_eq!(errors.collect(result), Some(()));
363
364 let result: Result<(), anyhow::Error> = Err(anyhow!("Generic error 1"));
365 assert_eq!(errors.collect(result), None);
366
367 assert_eq!(errors.len(), 1);
368 }
369
370 #[test]
371 fn collect_nested() {
372 let mut nested = Errors::new();
373 nested.push(anyhow!("Generic error 1"));
374 nested.push(anyhow!("Generic error 2"));
375 nested.push(anyhow!("Generic error 3"));
376
377 let mut errors = Errors::new();
378
379 let result: Result<(), Errors> = Err(nested);
380 assert_eq!(errors.collect(result), None);
381
382 assert_eq!(errors.len(), 3);
383 }
384
385 fn deeply_nested() -> Errors {
386 let mut child = Errors::new();
387 child.push(anyhow!("Generic error 2"));
388 child.push(anyhow!("Generic error 3\nnew line"));
389 child.push(Errors(vec![anyhow!("Generic error 4")]));
390
391 let mut parent = Errors::new();
392 parent.push(child);
393 parent.push(io::Error::from_raw_os_error(1));
394
395 let mut errors = Errors::new();
396 errors.push(
397 anyhow::Result::<()>::Err(anyhow!("Original error"))
398 .context("Generic error 1")
399 .unwrap_err(),
400 );
401 errors.push(parent);
402 errors
403 }
404
405 #[test]
406 fn display() {
407 let errors = deeply_nested();
408 assert_eq!(
409 format!("{errors}"),
410 "Errors: Generic error 1, Generic error 2, Generic error 3\n\
411 new line, Generic error 4, Operation not permitted (os error 1)\n"
412 );
413 }
414
415 #[test]
416 fn display_alternate() {
417 let errors = deeply_nested();
418 assert_eq!(
419 format!("{errors:#}"),
420 "Errors: Generic error 1: Original error, Generic error 2, Generic error 3\n\
421 new line, Generic error 4, Operation not permitted (os error 1)\n"
422 );
423 }
424
425 #[test]
426 fn debug() {
427 let errors = deeply_nested();
428 assert_eq!(
429 format!("{errors:?}"),
430 "2 errors:
431 1. Generic error 1
432 \n \
433 Caused by:
434 Original error
435 2. 2 errors:
436 1. 3 errors:
437 1. Generic error 2
438 2. Generic error 3
439 new line
440 3. Generic error 4
441 2. Operation not permitted (os error 1)\n"
442 .replace("\n ", "\n")
443 );
444 }
445
446 #[test]
447 fn debug_alternate() {
448 let errors = deeply_nested();
449 println!("{errors:#?}");
450 assert_eq!(
451 format!("{errors:#?}"),
452 "Errors [
453 Error {
454 context: \"Generic error 1\",
455 source: \"Original error\",
456 },
457 Errors [
458 Errors [
459 \"Generic error 2\",
460 \"Generic error 3\\nnew line\",
461 Errors [
462 \"Generic error 4\",
463 ],
464 ],
465 Os {
466 code: 1,
467 kind: PermissionDenied,
468 message: \"Operation not permitted\",
469 },
470 ],
471 ]"
472 .replace("\n ", "\n")
473 );
474 }
475}