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