hotshot_utils/
anytrace.rs

1use std::{cmp::max, fmt::Display};
2
3/// Macros
4mod macros;
5#[allow(unused_imports)]
6pub use macros::*;
7
8/// Default log level for the crate
9pub const DEFAULT_LOG_LEVEL: Level = Level::Debug;
10
11/// Trait for logging errors
12pub trait Log {
13    /// Log an error via `tracing` utilities, printing it.
14    fn log(&self);
15}
16
17impl Log for Error {
18    fn log(&self) {
19        let mut error_level = self.level;
20        if error_level == Level::Unspecified {
21            error_level = DEFAULT_LOG_LEVEL;
22        }
23
24        match error_level {
25            Level::Trace => {
26                tracing::trace!("{}", self.message);
27            },
28            Level::Debug => {
29                tracing::debug!("{}", self.message);
30            },
31            Level::Info => {
32                tracing::info!("{}", self.message);
33            },
34            Level::Warn => {
35                tracing::warn!("{}", self.message);
36            },
37            Level::Error => {
38                tracing::error!("{}", self.message);
39            },
40            // impossible
41            Level::Unspecified => {},
42        }
43    }
44}
45
46impl<T> Log for Result<T> {
47    fn log(&self) {
48        let error = match self {
49            Ok(_) => {
50                return;
51            },
52            Err(e) => e,
53        };
54
55        let mut error_level = error.level;
56        if error_level == Level::Unspecified {
57            error_level = DEFAULT_LOG_LEVEL;
58        }
59
60        match error_level {
61            Level::Trace => {
62                tracing::trace!("{}", error.message);
63            },
64            Level::Debug => {
65                tracing::debug!("{}", error.message);
66            },
67            Level::Info => {
68                tracing::info!("{}", error.message);
69            },
70            Level::Warn => {
71                tracing::warn!("{}", error.message);
72            },
73            Level::Error => {
74                tracing::error!("{}", error.message);
75            },
76            // impossible
77            Level::Unspecified => {},
78        }
79    }
80}
81
82#[derive(Debug, Clone)]
83#[must_use]
84/// main error type
85pub struct Error {
86    /// level
87    pub level: Level,
88    /// message
89    pub message: String,
90}
91
92impl std::error::Error for Error {}
93
94/// Trait for a `std::result::Result` that can be wrapped into a `Result`
95pub trait Wrap<T> {
96    /// Wrap the value into a `Result`
97    ///
98    /// # Errors
99    /// Propagates errors from `self`
100    fn wrap(self) -> Result<T>;
101}
102
103impl<T, E> Wrap<T> for std::result::Result<T, E>
104where
105    E: Display,
106{
107    fn wrap(self) -> Result<T> {
108        match self {
109            Ok(t) => Ok(t),
110            Err(e) => Err(Error {
111                level: Level::Unspecified,
112                message: format!("{e}"),
113            }),
114        }
115    }
116}
117
118impl Display for Error {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        write!(f, "{}", self.message)
121    }
122}
123
124/// Alias for the main `Result` type used by the crate.
125pub type Result<T> = std::result::Result<T, Error>;
126
127#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
128/// Possible log levels
129pub enum Level {
130    /// Unspecified log level
131    Unspecified,
132    /// TRACE
133    Trace,
134    /// DEBUG
135    Debug,
136    /// INFO
137    Info,
138    /// WARN
139    Warn,
140    /// ERROR
141    Error,
142}
143
144/// Prepend an error to its cause
145fn concatenate(error: &String, cause: &String) -> String {
146    format!("{error}\ncaused by: {cause}")
147}
148
149/// Trait for converting error types to a `Result<T>`.
150pub trait Context<T, E> {
151    /// Attach context to the given error.
152    ///
153    /// # Errors
154    /// Propagates errors from `self`
155    fn context(self, error: E) -> Result<T>;
156}
157
158impl<T> Context<T, Error> for Result<T> {
159    fn context(self, error: Error) -> Result<T> {
160        match self {
161            Ok(t) => Ok(t),
162            Err(cause) => Err(Error {
163                level: max(error.level, cause.level),
164                message: concatenate(&error.message, &format!("{cause}")),
165            }),
166        }
167    }
168}
169
170impl<T, F> Context<T, F> for Result<T>
171where
172    F: Fn(Error) -> Error,
173{
174    fn context(self, error: F) -> Result<T> {
175        match self {
176            Ok(t) => Ok(t),
177            Err(cause) => Err(Error {
178                level: max(error(cause.clone()).level, cause.level),
179                message: concatenate(&error(cause.clone()).message, &format!("{cause}")),
180            }),
181        }
182    }
183}
184
185impl<T> Context<T, Error> for Option<T> {
186    fn context(self, error: Error) -> Result<T> {
187        match self {
188            Some(t) => Ok(t),
189            None => Err(error),
190        }
191    }
192}
193
194impl<'a, T> Context<&'a mut T, Error> for &'a mut Option<T> {
195    fn context(self, error: Error) -> Result<&'a mut T> {
196        match self {
197            Some(t) => Ok(t),
198            None => Err(error),
199        }
200    }
201}
202
203#[cfg(test)]
204mod test {
205    use super::*;
206
207    #[test]
208    fn ordering() {
209        assert!(Level::Trace < Level::Debug);
210        assert!(Level::Debug < Level::Info);
211        assert!(Level::Info < Level::Warn);
212        assert!(Level::Warn < Level::Error);
213        assert!(max(Level::Trace, Level::Error) == Level::Error);
214    }
215
216    #[test]
217    fn formatting() {
218        let num = 1234;
219
220        // Trace
221        let log = trace!("num: {num}");
222        assert_eq!(log.level, Level::Trace);
223        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
224
225        let log = trace!("num: {}", num);
226        assert_eq!(log.level, Level::Trace);
227        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
228
229        // Debug
230        let log = debug!("num: {num}");
231        assert_eq!(log.level, Level::Debug);
232        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
233
234        let log = debug!("num: {}", num);
235        assert_eq!(log.level, Level::Debug);
236        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
237
238        // Info
239        let log = info!("num: {num}");
240        assert_eq!(log.level, Level::Info);
241        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
242
243        let log = info!("num: {}", num);
244        assert_eq!(log.level, Level::Info);
245        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
246
247        // Warn
248        let log = warn!("num: {num}");
249        assert_eq!(log.level, Level::Warn);
250        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
251
252        let log = warn!("num: {}", num);
253        assert_eq!(log.level, Level::Warn);
254        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
255
256        // Error
257        let log = error!("num: {num}");
258        assert_eq!(log.level, Level::Error);
259        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
260
261        let log = error!("num: {}", num);
262        assert_eq!(log.level, Level::Error);
263        assert!(log.message.ends_with(": num: 1234"), "log: {log:?}");
264    }
265}