LCOV - differential code coverage report
Current view: top level - libs/utils/src - error.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 100.0 % 70 70 70
Current Date: 2023-10-19 02:04:12 Functions: 56.2 % 16 9 7 9
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  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 CBC           3 : pub fn report_compact_sources<E: std::error::Error>(e: &E) -> impl std::fmt::Display + '_ {
      35               3 :     struct AnyhowDisplayAlternateAlike<'a, E>(&'a E);
      36               3 : 
      37               3 :     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               3 : 
      41               3 :             // why is E a generic parameter here? hope that rustc will see through a default
      42               3 :             // Error::source implementation and leave the following out if there cannot be any
      43               3 :             // sources:
      44               3 :             Sources(self.0.source()).try_for_each(|src| write!(f, ": {}", src))
      45               3 :         }
      46               3 :     }
      47               3 : 
      48               3 :     struct Sources<'a>(Option<&'a (dyn std::error::Error + 'static)>);
      49               3 : 
      50               3 :     impl<'a> Iterator for Sources<'a> {
      51               3 :         type Item = &'a (dyn std::error::Error + 'static);
      52               3 : 
      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               3 :     }
      61               3 : 
      62               3 :     AnyhowDisplayAlternateAlike(e)
      63               3 : }
      64                 : 
      65                 : #[cfg(test)]
      66                 : mod tests {
      67                 :     use super::report_compact_sources;
      68                 : 
      69               1 :     #[test]
      70               1 :     fn report_compact_sources_examples() {
      71               1 :         use std::fmt::Write;
      72               1 : 
      73               6 :         #[derive(Debug, thiserror::Error)]
      74               1 :         enum EvictionError {
      75               1 :             #[error("cannot evict a remote layer")]
      76               1 :             CannotEvictRemoteLayer,
      77               1 :             #[error("stat failed")]
      78               1 :             StatFailed(#[source] std::io::Error),
      79               1 :             #[error("layer was no longer part of LayerMap")]
      80               1 :             LayerNotFound(#[source] anyhow::Error),
      81               1 :         }
      82               1 : 
      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