LCOV - code coverage report
Current view: top level - libs/utils/src - error.rs (source / functions) Coverage Total Hit
Test: 2aa98e37cd3250b9a68c97ef6050b16fe702ab33.info Lines: 100.0 % 69 69
Test Date: 2024-08-29 11:33:10 Functions: 40.0 % 20 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           18 : pub fn report_compact_sources<E: std::error::Error>(e: &E) -> impl std::fmt::Display + '_ {
      35           18 :     struct AnyhowDisplayAlternateAlike<'a, E>(&'a E);
      36           18 : 
      37           18 :     impl<E: std::error::Error> std::fmt::Display for AnyhowDisplayAlternateAlike<'_, E> {
      38           18 :         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      39           18 :             write!(f, "{}", self.0)?;
      40           18 : 
      41           18 :             // why is E a generic parameter here? hope that rustc will see through a default
      42           18 :             // Error::source implementation and leave the following out if there cannot be any
      43           18 :             // sources:
      44           18 :             Sources(self.0.source()).try_for_each(|src| write!(f, ": {}", src))
      45           18 :         }
      46           18 :     }
      47           18 : 
      48           18 :     struct Sources<'a>(Option<&'a (dyn std::error::Error + 'static)>);
      49           18 : 
      50           18 :     impl<'a> Iterator for Sources<'a> {
      51           18 :         type Item = &'a (dyn std::error::Error + 'static);
      52           18 : 
      53           30 :         fn next(&mut self) -> Option<Self::Item> {
      54           30 :             let rem = self.0;
      55           30 : 
      56           30 :             let next = self.0.and_then(|x| x.source());
      57           30 :             self.0 = next;
      58           30 :             rem
      59           30 :         }
      60           18 :     }
      61           18 : 
      62           18 :     AnyhowDisplayAlternateAlike(e)
      63           18 : }
      64              : 
      65              : #[cfg(test)]
      66              : mod tests {
      67              :     use super::report_compact_sources;
      68              : 
      69              :     #[test]
      70            6 :     fn report_compact_sources_examples() {
      71            6 :         use std::fmt::Write;
      72            6 : 
      73           36 :         #[derive(Debug, thiserror::Error)]
      74            6 :         enum EvictionError {
      75            6 :             #[error("cannot evict a remote layer")]
      76            6 :             CannotEvictRemoteLayer,
      77            6 :             #[error("stat failed")]
      78            6 :             StatFailed(#[source] std::io::Error),
      79            6 :             #[error("layer was no longer part of LayerMap")]
      80            6 :             LayerNotFound(#[source] anyhow::Error),
      81            6 :         }
      82            6 : 
      83            6 :         let examples = [
      84            6 :             (
      85            6 :                 line!(),
      86            6 :                 EvictionError::CannotEvictRemoteLayer,
      87            6 :                 "cannot evict a remote layer",
      88            6 :             ),
      89            6 :             (
      90            6 :                 line!(),
      91            6 :                 EvictionError::StatFailed(std::io::ErrorKind::PermissionDenied.into()),
      92            6 :                 "stat failed: permission denied",
      93            6 :             ),
      94            6 :             (
      95            6 :                 line!(),
      96            6 :                 EvictionError::LayerNotFound(anyhow::anyhow!("foobar")),
      97            6 :                 "layer was no longer part of LayerMap: foobar",
      98            6 :             ),
      99            6 :         ];
     100            6 : 
     101            6 :         let mut s = String::new();
     102              : 
     103           24 :         for (line, example, expected) in examples {
     104           18 :             s.clear();
     105           18 : 
     106           18 :             write!(s, "{}", report_compact_sources(&example)).expect("string grows");
     107           18 : 
     108           18 :             assert_eq!(s, expected, "example on line {line}");
     109              :         }
     110            6 :     }
     111              : }
        

Generated by: LCOV version 2.1-beta