Skip to main content

pistonite_cu/
errhand.rs

1pub use anyhow::{Context, Error, Ok, Result, anyhow as fmterr, bail};
2
3/// # Error Handling
4/// *Does not require any feature flag. Please make sure to sponsor [David Tolnay](https://github.com/dtolnay) if you depend heavily on his work
5/// on the Rust ecosystem.*
6///
7/// Most of the error handling stuff is re-exported from [`anyhow`](https://docs.rs/anyhow),
8/// which is a crate that makes tracing and formatting error messages SUPER easy.
9/// This is the easiest way to quickly write debuggable program without structured error.
10/// Structured error types would be more useful if you are making a library though.
11///
12/// The traits required for error handling are included in the prelude import
13/// ```rust
14/// # use pistonite_cu as cu;
15/// use cu::pre::*;
16/// ```
17///
18/// Here are the most commonly used `anyhow` re-exports
19/// - `anyhow::Result` is `cu::Result`
20/// - `anyhow::bail!`  is `cu::bail!`
21/// - `anyhow::Ok`     is `cu::Ok`
22///
23/// Here are custom utilities from `cu` that integrates with `anyhow`
24/// - `cu::check!` wraps `.with_context()`. This is the general-purpose way to add context
25///   to errors that can happen at runtime even when the program is correct, such as
26///   invalid user input or system errors (e.g. file does not exist)
27///    ```rust
28///    # use pistonite_cu as cu;
29///    use cu::pre::*;
30///
31///    fn some_fallable_func() -> cu::Result<String> {
32///        Ok("foo".to_string())
33///    }
34///    fn main() -> cu::Result<()> {
35///        // this input is just to show the formatting
36///        let input: i32 = 42;
37///
38///        let foo = cu::check!(some_fallable_func(), "failed: {input}")?;
39///        // with anyhow, this would be:
40///        // let foo = some_fallable_func().with_context(|| format!("failed: {input}"))?;
41///        // -- much longer!
42///        assert_eq!(foo, "foo");
43///        Ok(())
44///    }
45///    ```
46/// - [`cu::rethrow!`](macro@crate::rethrow) is similar to `bail!`, but works with an `Error` instance at hand
47/// - [`cu::unimplemented!`](macro@crate::unimplemented)
48///   and [`cu::unreachable!`](macro@crate::unreachable)
49///   that are similar to the std macros, but instead of `panic!`, they will `bail!`
50/// - [`cu::ensure!`](macro@crate::ensure) is an assert-like check for invariants that
51///   should almost never fail if the program is correct (i.e. a debugging aid, like `assert!`,
52///   but returns an error instead of panicking). Unlike `anyhow::ensure`,
53///   it evaluates to a `Result<()>` instead of generates a return.
54///   It also does not automatically generate debug information.
55///   Do not use it to validate user input or check for system errors - use `check!` or `bail!` for those.
56/// - [`cu::some!`] checks an `Option` and returns `Ok(None)` if the option is `None`.
57///
58/// Here are other `anyhow` re-exports that are less commonly used
59/// - `anyhow::anyhow` is `cu::fmterr`
60///
61/// Finally, if you do need to panic, [`cu::panicand`](macro@crate::panicand)
62/// allows you to also log the same message so you can debug it easier.
63///
64/// # Context (To `check` or not to `check`)
65/// It is tricky to determine if you should wrap the result
66/// with a `check!`, or just propagate it with a `?`.
67/// Ultimately, there is no correct answer (you may say it is contextual).
68/// Another way of phrasing the same question is if the context
69/// should be added by the caller or the callee.
70///
71/// The principle I personally follow is the caller should only `check!`
72/// if there are additional information in the caller's context
73/// that the callee does not already know. The information that both
74/// the callee and caller have access to is:
75/// - The function parameters
76/// - The name of the call
77/// - The behavior of the function
78///
79/// These make up the API of the function.
80///
81/// For example, I will write the following code
82#[cfg_attr(not(feature = "fs"), doc = "```rust,ignore")]
83#[cfg_attr(feature = "fs", doc = "```rust,no_run")]
84/// # use pistonite_cu as cu;
85/// use cu::pre::*;
86///
87/// fn process_paths(paths: &[&str]) -> cu::Result<()> {
88///     cu::info!("processing paths...");
89///     for (i, path) in paths.iter().enumerate() {
90///         // here there might be some candidates for context:
91///         // - "failed to save important value to '{path}'"
92///         //   - the callee knows the current context is saving
93///         //     "important value" to "path" (from function name and parameter),
94///         //     therefore this message does not add additional context
95///         // - "process paths failed on '{path}'"
96///         //   - this could work in some cases, but here I know
97///         //     cu would already log the path if it fails
98///         // - here I choose to log {i} which might help me finding the erroreous
99///         //   path from some kind of data set.
100///         cu::check!(save_important_value_to(path), "failed to process {i}th path")?;
101///     }
102///     Ok(())
103/// }
104///
105/// fn save_important_value_to(path: &str) -> cu::Result<()> {
106///     // here, the only information I have that cu::fs::write
107///     // does not have, is "important value" is "some random thing".
108///     // this is not an important context to log to the error,
109///     // so I choose to simply ?
110///     cu::fs::write(path, "some random thing")?;
111///     Ok(())
112/// }
113/// ```
114///
115/// With that said, now we can introduce [`cu::context`](macro@crate::context),
116/// which wraps a function and append a formatted context to it.
117/// This is a double-edge sword. You could end up with unnecessarily bloated
118/// error stack if you put too much context. However,
119/// it can be extremely useful in a complex function with many possible error paths
120/// to add context for what the overall failure is.
121///
122/// If you find yourself writing the same `check!` to every invocation of some function.
123/// Considering using it. However, I would not use this on any public API of your code.
124#[macro_export]
125macro_rules! check {
126    ($result:expr, $($args:tt)*) => {{
127        { $result }.with_context(|| format!($($args)*))
128    }};
129}
130
131/// Rethrow an `Err`, optionally with additional context
132///
133/// This is useful if the error path requires additional handling
134///
135/// Prelude import is required to bring in the Context trait.
136///
137/// ```rust
138/// # use pistonite_cu as cu;
139/// use cu::pre::*;
140///
141/// fn some_fallable_func() -> cu::Result<String> {
142///     Ok("foo".to_string())
143/// }
144///
145/// fn main() -> cu::Result<()> {
146///     // this input is just to show the formatting
147///     let input: i32 = 42;
148///
149///     let foo = match some_fallable_func() {
150///         Ok(x) => x,
151///         Err(e) => {
152///             // supposed some additional handling is needed,
153///             // like setting some error state...
154///
155///             cu::rethrow!(e, "failed: {input}");
156///         }
157///     };
158///
159///     assert_eq!(foo, "foo");
160///
161///     Ok(())
162/// }
163/// ```
164#[macro_export]
165macro_rules! rethrow {
166    ($result:expr) => {
167        return Err($result);
168    };
169    ($result:expr, $($args:tt)*) => {{
170        return Err($result).context(format!($($args)*));
171    }};
172}
173
174/// Like `unimplemented!` in std library, but log a message
175/// and return an error instead of panicking
176#[macro_export]
177macro_rules! unimplemented {
178    () => {{
179        $crate::trace!("unexpected: not implemented reached");
180        return Err($crate::Error::msg("not implemented"));
181    }};
182    ($($args:tt)*) => {{
183        let msg = format!("{}", format_args!($($args)*));
184        $crate::trace!("unexpected: not implemented reached: {msg}");
185        $crate::bail!("not implemented: {msg}")
186    }}
187}
188
189/// Like `unreachable!` in std library, but log a message
190/// and return an error instead of panicking reached.
191/// This might be less performant in release builds
192#[macro_export]
193macro_rules! unreachable {
194    () => {{
195        $crate::trace!("unexpected: entered unreachable code");
196        return Err($crate::Error::msg("unreachable"));
197    }};
198    ($($args:tt)*) => {{
199        let msg = format!("{}", format_args!($(args)*));
200        $crate::trace!("unexpected: entered unreachable code: {msg}");
201        $crate::bail!("unreachable: {msg}")
202    }}
203}
204
205/// Check if an expression is `true`, as an assertion
206///
207/// This is meant to be assert-like: the condition is an invariant that should
208/// almost never fail if everything is correct, so a failure indicates a bug
209/// (i.e. it's a debugging aid, like `assert!`, but returns an error instead of panicking).
210/// For errors that are expected to happen at runtime, such as invalid user input
211/// or system errors (e.g. file does not exist), use [`cu::check!`](macro@crate::check)
212/// or `cu::bail!` with a descriptive message instead.
213///
214/// Unlike `anyhow::ensure`, if the condition fail, this will generate an `Error`
215/// instead of returning an error directly, so you need to add a `?`.
216/// It also always include the expression stringified in the debug info.
217/// However, it does not automatically parse the input and generate debug
218/// info message based on that (unlike `anyhow`)
219#[macro_export]
220macro_rules! ensure {
221    ($result:expr) => {{
222        if !bool::from($result) {
223            Err($crate::fmterr!("condition failed: `{}`", stringify!($result)))
224        } else {
225            Ok(())
226        }
227    }};
228    ($result:expr, $($args:tt)*) => {{
229        if !bool::from($result) {
230            Err($crate::fmterr!("condition failed: `{}`: {}", stringify!($result), format_args!($($args)*)))
231        } else {
232            Ok(())
233        }
234    }};
235}
236
237/// Check if an expression is `Some`
238///
239/// This is a convienence macro to achieve a similar effect
240/// of `?` on an option, in a function that returns `Result<Option<T>>`
241///
242/// Effectively expands to
243/// ```text
244/// match <expr> {
245///     Some(x) => x,
246///     None => return Ok(None),
247/// }
248/// ```
249#[macro_export]
250macro_rules! some {
251    ($result:expr) => {
252        match $result {
253            Some(x) => x,
254            None => return Ok(None),
255        }
256    };
257}
258
259/// Invoke a print macro, then panic with the same message
260///
261/// # Example
262/// ```rust,no_run
263/// # use pistonite_cu as cu;
264/// cu::panicand!(error!("found {} errors", 3));
265/// ```
266#[macro_export]
267macro_rules! panicand {
268    ($mac:ident !( $($fmt_args:tt)* )) => {{
269        let s = format!($($fmt_args)*);
270        $crate::$mac!("{s}");
271        panic!("{s}");
272    }}
273}