@@ -346,6 +346,13 @@ pub(crate) enum SetupScriptError {
346346#[ derive( Clone , Debug ) ]
347347pub struct ErrorList < T > ( pub Vec < T > ) ;
348348
349+ impl < T > ErrorList < T > {
350+ /// Returns true if the error list is empty.
351+ pub fn is_empty ( & self ) -> bool {
352+ self . 0 . is_empty ( )
353+ }
354+ }
355+
349356impl < T : std:: error:: Error > fmt:: Display for ErrorList < T > {
350357 fn fmt ( & self , mut f : & mut fmt:: Formatter ) -> fmt:: Result {
351358 // If a single error occurred, pretend that this is just that.
@@ -356,15 +363,8 @@ impl<T: std::error::Error> fmt::Display for ErrorList<T> {
356363 // Otherwise, list all errors.
357364 writeln ! ( f, "{} errors occurred:" , self . 0 . len( ) ) ?;
358365 for error in & self . 0 {
359- writeln ! ( f, "- {}" , error) ?;
360- // Also display the chain of causes here, since we can't return a single error in the
361- // causes section below.
362- let mut indent = IndentWriter :: new ( " " , f) ;
363- let mut cause = error. source ( ) ;
364- while let Some ( cause_error) = cause {
365- writeln ! ( indent, "Caused by: {}" , cause_error) ?;
366- cause = cause_error. source ( ) ;
367- }
366+ let mut indent = IndentWriter :: new_skip_initial ( " " , f) ;
367+ writeln ! ( indent, "* {}" , DisplayErrorChain ( error) ) ?;
368368 f = indent. into_inner ( ) ;
369369 }
370370 Ok ( ( ) )
@@ -383,6 +383,44 @@ impl<T: std::error::Error> std::error::Error for ErrorList<T> {
383383 }
384384}
385385
386+ /// A wrapper type to display a chain of errors with internal indentation.
387+ ///
388+ /// This is similar to the display-error-chain crate, but uses IndentWriter
389+ /// internally to ensure that subsequent lines are also nested.
390+ pub ( crate ) struct DisplayErrorChain < E > ( E ) ;
391+
392+ impl < E : std:: error:: Error > DisplayErrorChain < E > {
393+ pub ( crate ) fn new ( error : E ) -> Self {
394+ Self ( error)
395+ }
396+ }
397+
398+ impl < E > fmt:: Display for DisplayErrorChain < E >
399+ where
400+ E : std:: error:: Error ,
401+ {
402+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
403+ write ! ( f, "{}" , self . 0 ) ?;
404+
405+ let Some ( mut cause) = self . 0 . source ( ) else {
406+ return Ok ( ( ) ) ;
407+ } ;
408+
409+ write ! ( f, "\n caused by:" ) ?;
410+
411+ let mut indent = IndentWriter :: new_skip_initial ( " " , f) ;
412+ loop {
413+ write ! ( indent, "\n - {}" , cause) ?;
414+
415+ let Some ( next_cause) = cause. source ( ) else {
416+ break Ok ( ( ) ) ;
417+ } ;
418+
419+ cause = next_cause;
420+ }
421+ }
422+ }
423+
386424/// An error was returned during the process of managing a child process.
387425#[ derive( Clone , Debug , Error ) ]
388426#[ non_exhaustive]
@@ -1757,3 +1795,100 @@ mod self_update_errors {
17571795
17581796#[ cfg( feature = "self-update" ) ]
17591797pub use self_update_errors:: * ;
1798+
1799+ #[ cfg( test) ]
1800+ mod tests {
1801+ use super :: * ;
1802+
1803+ #[ test]
1804+ fn display_error_chain ( ) {
1805+ let err1 = StringError :: new ( "err1" , None ) ;
1806+
1807+ insta:: assert_snapshot!( format!( "{}" , DisplayErrorChain :: new( & err1) ) , @"err1" ) ;
1808+
1809+ let err2 = StringError :: new ( "err2" , Some ( err1) ) ;
1810+ let err3 = StringError :: new ( "err3\n err3 line 2" , Some ( err2) ) ;
1811+
1812+ insta:: assert_snapshot!( format!( "{}" , DisplayErrorChain :: new( & err3) ) , @r"
1813+ err3
1814+ err3 line 2
1815+ caused by:
1816+ - err2
1817+ - err1
1818+ " ) ;
1819+ }
1820+
1821+ #[ test]
1822+ fn display_error_list ( ) {
1823+ let err1 = StringError :: new ( "err1" , None ) ;
1824+
1825+ let error_list = ErrorList ( vec ! [ err1. clone( ) ] ) ;
1826+ insta:: assert_snapshot!( format!( "{}" , error_list) , @"err1" ) ;
1827+ insta:: assert_snapshot!( format!( "{}" , DisplayErrorChain :: new( & error_list) ) , @"err1" ) ;
1828+
1829+ let err2 = StringError :: new ( "err2" , Some ( err1) ) ;
1830+ let err3 = StringError :: new ( "err3" , Some ( err2) ) ;
1831+
1832+ let error_list = ErrorList ( vec ! [ err3. clone( ) ] ) ;
1833+ insta:: assert_snapshot!( format!( "{}" , error_list) , @"err3" ) ;
1834+ insta:: assert_snapshot!( format!( "{}" , DisplayErrorChain :: new( & error_list) ) , @r"
1835+ err3
1836+ caused by:
1837+ - err2
1838+ - err1
1839+ " ) ;
1840+
1841+ let err4 = StringError :: new ( "err4" , None ) ;
1842+ let err5 = StringError :: new ( "err5" , Some ( err4) ) ;
1843+ let err6 = StringError :: new ( "err6\n err6 line 2" , Some ( err5) ) ;
1844+
1845+ let error_list = ErrorList ( vec ! [ err3, err6] ) ;
1846+
1847+ insta:: assert_snapshot!( format!( "{}" , error_list) , @r"
1848+ 2 errors occurred:
1849+ * err3
1850+ caused by:
1851+ - err2
1852+ - err1
1853+ * err6
1854+ err6 line 2
1855+ caused by:
1856+ - err5
1857+ - err4
1858+ " ) ;
1859+ insta:: assert_snapshot!( format!( "{}" , DisplayErrorChain :: new( & error_list) ) , @r"
1860+ 2 errors occurred:
1861+ * err3
1862+ caused by:
1863+ - err2
1864+ - err1
1865+ * err6
1866+ err6 line 2
1867+ caused by:
1868+ - err5
1869+ - err4
1870+ " ) ;
1871+ }
1872+
1873+ #[ derive( Clone , Debug , Error ) ]
1874+ struct StringError {
1875+ message : String ,
1876+ #[ source]
1877+ source : Option < Box < StringError > > ,
1878+ }
1879+
1880+ impl StringError {
1881+ fn new ( message : impl Into < String > , source : Option < StringError > ) -> Self {
1882+ Self {
1883+ message : message. into ( ) ,
1884+ source : source. map ( Box :: new) ,
1885+ }
1886+ }
1887+ }
1888+
1889+ impl fmt:: Display for StringError {
1890+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
1891+ write ! ( f, "{}" , self . message)
1892+ }
1893+ }
1894+ }
0 commit comments