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}