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

Generated by: LCOV version 2.1-beta