Skip to main content

trace_err/
lib.rs

1/*!
2# Trace_Err
3A small extension to the [tracing](https://crates.io/crates/tracing) crate, which provides a single method for `core::result::Result<T, E>` and `core::option::Option<T>`.
4
5Adds `trace_expect` to `Result`, which invoke the `tracing::error!` macro (in case of `Result::Err`) in _addition_ to unwrapping/expecting the `Result`.
6
7Adds `trace_expect` to `Option`, which invoke the `tracing::error!` macro (in case of `Option::None`) in _addition_ to unwrapping/expecting the `Option`.
8
9Shamelessly derived from the [Log_Err](https://crates.io/crates/log_err) crate.
10
11Shorthand for:
12
13```should_panic
14# use tracing::error;
15# fn something() -> Result<(), &'static str> {Err("there was some problem")}
16# let msg = "Some message";
17something().map_err(|e| tracing::error!("{}: {:?}", msg, e)).expect(msg)
18```
19
20Example:
21
22```should_panic
23# use std::fs::File;
24# use trace_err::*;
25let mut file = File::open("foo.txt").trace_expect("Error creating file");
26```
27```text
28# Error will be traced with the error! macro
292024-06-12T09:31:23.933299Z ERROR expect: trace-err/lib.rs:87:39: Error creating file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
30
31# Main program panic'ing with same message
32thread 'main' panicked at trace-err/lib.rs:87:39:
33Error creating file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
34```
35*/
36
37#![no_std]
38
39pub trait TraceErrResult<T, E: core::fmt::Debug> {
40    #[track_caller]
41    fn trace_expect(self, msg: &str) -> T;
42}
43
44impl<T, E: core::fmt::Debug> TraceErrResult<T, E> for core::result::Result<T, E> {
45    /// `expect`s the `Result`, and outputs error message (in exact same style as `expect`) through `error!` as well.
46    fn trace_expect(self, msg: &str) -> T {
47        match self {
48            Ok(v) => v,
49            Err(ref e) => {
50                tracing::error!(target: "expect","{}: {msg}: {e:?}",core::panic::Location::caller());
51                self.expect(msg)
52            }
53        }
54    }
55}
56
57pub trait TraceErrOption<T> {
58    #[track_caller]
59    fn trace_expect(self, msg: &str) -> T;
60}
61
62impl<T> TraceErrOption<T> for core::option::Option<T> {
63    /// `expect`s the `Option`, and outputs error message (in exact same style as `expect`) through `error!` as well.
64    fn trace_expect(self, msg: &str) -> T {
65        match self {
66            Some(n) => n,
67            None => {
68                tracing::error!(target:"expect","{}: {msg}",core::panic::Location::caller());
69                self.expect(msg)
70            }
71        }
72    }
73}
74
75#[cfg(test)]
76mod test {
77    use super::*;
78    use tracing_subscriber;
79
80    fn init_subscriber() {
81        let _ = tracing_subscriber::fmt().with_test_writer().try_init();
82        // try_init() returns an error if already initialized, which we can ignore
83    }
84
85    #[test]
86    #[should_panic(expected = "We caught: \"An Error!\"")]
87    fn test_expect() {
88        init_subscriber();
89        Result::<(), &str>::Err("An Error!").trace_expect("We caught");
90    }
91
92    #[test]
93    #[should_panic(expected = "It's None!")]
94    fn test_unwrap() {
95        init_subscriber();
96        let _ = Option::<()>::None.trace_expect("It's None!");
97    }
98}