LCOV - code coverage report
Current view: top level - libs/utils/src - error.rs (source / functions) Coverage Total Hit
Test: 465a86b0c1fda0069b3e0f6c1c126e6b635a1f72.info Lines: 100.0 % 69 69
Test Date: 2024-06-25 15:47:26 Functions: 47.1 % 17 8

            Line data    Source code
       1              : /// Create a reporter for an error that outputs similar to [`anyhow::Error`] with Display with alternative setting.
       2              : ///
       3              : /// It can be used with `anyhow::Error` as well.
       4              : ///
       5              : /// Why would one use this instead of converting to `anyhow::Error` on the spot? Because
       6              : /// anyhow::Error would also capture a stacktrace on the spot, which you would later discard after
       7              : /// formatting.
       8              : ///
       9              : /// ## Usage
      10              : ///
      11              : /// ```rust
      12              : /// #[derive(Debug, thiserror::Error)]
      13              : /// enum MyCoolError {
      14              : ///   #[error("should never happen")]
      15              : ///   Bad(#[source] std::io::Error),
      16              : /// }
      17              : ///
      18              : /// # fn failing_call() -> Result<(), MyCoolError> { Err(MyCoolError::Bad(std::io::ErrorKind::PermissionDenied.into())) }
      19              : ///
      20              : /// # fn main() {
      21              : /// use utils::error::report_compact_sources;
      22              : ///
      23              : /// if let Err(e) = failing_call() {
      24              : ///     let e = report_compact_sources(&e);
      25              : ///     assert_eq!(format!("{e}"), "should never happen: permission denied");
      26              : /// }
      27              : /// # }
      28              : /// ```
      29              : ///
      30              : /// ## TODO
      31              : ///
      32              : /// When we are able to describe return position impl trait in traits, this should of course be an
      33              : /// extension trait. Until then avoid boxing with this more ackward interface.
      34            6 : pub fn report_compact_sources<E: std::error::Error>(e: &E) -> impl std::fmt::Display + '_ {
      35            6 :     struct AnyhowDisplayAlternateAlike<'a, E>(&'a E);
      36            6 : 
      37            6 :     impl<E: std::error::Error> std::fmt::Display for AnyhowDisplayAlternateAlike<'_, E> {
      38            6 :         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      39            6 :             write!(f, "{}", self.0)?;
      40            6 : 
      41            6 :             // why is E a generic parameter here? hope that rustc will see through a default
      42            6 :             // Error::source implementation and leave the following out if there cannot be any
      43            6 :             // sources:
      44            6 :             Sources(self.0.source()).try_for_each(|src| write!(f, ": {}", src))
      45            6 :         }
      46            6 :     }
      47            6 : 
      48            6 :     struct Sources<'a>(Option<&'a (dyn std::error::Error + 'static)>);
      49            6 : 
      50            6 :     impl<'a> Iterator for Sources<'a> {
      51            6 :         type Item = &'a (dyn std::error::Error + 'static);
      52            6 : 
      53           10 :         fn next(&mut self) -> Option<Self::Item> {
      54           10 :             let rem = self.0;
      55           10 : 
      56           10 :             let next = self.0.and_then(|x| x.source());
      57           10 :             self.0 = next;
      58           10 :             rem
      59           10 :         }
      60            6 :     }
      61            6 : 
      62            6 :     AnyhowDisplayAlternateAlike(e)
      63            6 : }
      64              : 
      65              : #[cfg(test)]
      66              : mod tests {
      67              :     use super::report_compact_sources;
      68              : 
      69              :     #[test]
      70            2 :     fn report_compact_sources_examples() {
      71            2 :         use std::fmt::Write;
      72            2 : 
      73           12 :         #[derive(Debug, thiserror::Error)]
      74            2 :         enum EvictionError {
      75            2 :             #[error("cannot evict a remote layer")]
      76            2 :             CannotEvictRemoteLayer,
      77            2 :             #[error("stat failed")]
      78            2 :             StatFailed(#[source] std::io::Error),
      79            2 :             #[error("layer was no longer part of LayerMap")]
      80            2 :             LayerNotFound(#[source] anyhow::Error),
      81            2 :         }
      82            2 : 
      83            2 :         let examples = [
      84            2 :             (
      85            2 :                 line!(),
      86            2 :                 EvictionError::CannotEvictRemoteLayer,
      87            2 :                 "cannot evict a remote layer",
      88            2 :             ),
      89            2 :             (
      90            2 :                 line!(),
      91            2 :                 EvictionError::StatFailed(std::io::ErrorKind::PermissionDenied.into()),
      92            2 :                 "stat failed: permission denied",
      93            2 :             ),
      94            2 :             (
      95            2 :                 line!(),
      96            2 :                 EvictionError::LayerNotFound(anyhow::anyhow!("foobar")),
      97            2 :                 "layer was no longer part of LayerMap: foobar",
      98            2 :             ),
      99            2 :         ];
     100            2 : 
     101            2 :         let mut s = String::new();
     102              : 
     103            8 :         for (line, example, expected) in examples {
     104            6 :             s.clear();
     105            6 : 
     106            6 :             write!(s, "{}", report_compact_sources(&example)).expect("string grows");
     107            6 : 
     108            6 :             assert_eq!(s, expected, "example on line {line}");
     109              :         }
     110            2 :     }
     111              : }
        

Generated by: LCOV version 2.1-beta