wrap_match/lib.rs
1/*!
2# The Problem
3
4If you ever want to log when an error occurs and what caused it, you may find yourself using a `match` statement for every possible error instead of using the `?` operator.
5
6This results in extremely verbose code. It's a pain to write and maintain.
7
8# Introducing wrap-match!
9
10wrap-match is an attribute macro that wraps your function in a `match` statement. Additionally, **it attaches rich error information to all statements using the `?` operator (aka try expressions).**
11This allows you to know exactly what line and expression caused the error.
12
13wrap-match supports both `log` and `tracing`. It defaults to `log`, but it will use `tracing` if the `tracing` feature is enabled. See [`tracing` support](#tracing-support) for more info.
14
15> **Note**
16>
17> wrap-match uses the `log` or `tracing` crate to log success and error messages. It does not expose the `log` or `tracing` crate for expanded functions to use; you must depend on them yourself.
18>
19> Additionally, **no messages will appear unless you use a logging implementation or `tracing` subscriber.** For `log`, I recommend `env_logger`, but you can find a full list
20> [here](https://docs.rs/log/#available-logging-implementations). For `tracing`, I recommend `tracing-subscriber`, but you can find a full list [here](https://docs.rs//tracing/#related-crates).
21
22## Example
23
24First, add this to your `Cargo.toml`:
25
26```toml
27[dependencies]
28# For log users:
29wrap-match = "1"
30log = "*"
31# You'll also want a logging implementation, for example `env_logger`
32# More info here: https://docs.rs/log/#available-logging-implementations
33
34# For tracing users:
35wrap-match = { version = "1", features = ["tracing"] }
36tracing = "0.1"
37# You'll also want a `tracing` subscriber, for example `tracing-subscriber`
38# More info here: https://docs.rs//tracing/#related-crates
39```
40
41Now you can use the `wrap_match` attribute macro:
42
43```
44# #[derive(Debug)] enum CustomError { Error }
45#[wrap_match::wrap_match]
46fn my_function() -> Result<(), CustomError> {
47 Err(CustomError::Error)?; // notice the ?; when the macro is expanded, it will be modified to include line number and expression
48 // If you need to return an error, just do `Err(CustomError::Error.into())`
49 Ok(())
50}
51```
52
53This would expand to something like this (comments are not included normally, and some of the output is simplified; use `cargo-expand` if you are curious what the macro really expands to):
54
55```ignore
56fn my_function() -> Result<(), CustomError> {
57 // This is where the original function is
58 fn _wrap_match_inner_my_function() -> Result<(), WrapMatchError<CustomError>> {
59 Err(CustomError::Error)
60 .map_err(|e| WrapMatchError {
61 // Here, line number and expression are added to the error
62 line_and_expr: Some((3, "Err(CustomError::Error)")),
63 inner: e.into(), // This is so you can have `Box<dyn Error>` as your error type
64 })?;
65 // If you need to return an error, just do `Err(CustomError::Error.into())`
66 Ok(())
67 }
68
69 match _wrap_match_inner_my_function() {
70 Ok(r) => {
71 ::log::info!("Successfully ran my_function"); // when the tracing feature is enabled, it will use tracing macros instead
72 Ok(r)
73 }
74 Err(e) => {
75 if let Some((_line, _expr)) = e.line_and_expr {
76 ::log::error!("An error occurred when running my_function (when running `{_expr}` on line {_line}): {:?}", e.inner); // when the tracing feature is enabled, it will use tracing macros instead
77 } else {
78 ::log::error!("An error occurred when running my_function: {:?}", e.inner); // when the tracing feature is enabled, it will use tracing macros instead
79 }
80 Err(e.inner)
81 }
82 }
83}
84```
85
86If we run this code, it would log this:
87
88```log
89[ERROR] An error occurred when running my_function (when running `Err(CustomError::Error)` on line 3): Error
90```
91
92As you can see, wrap-match makes error logging extremely easy while still logging information like what caused the error.
93
94## `tracing` support
95
96Added in wrap-match 1.0.5, wrap-match supports `tracing` if the `tracing` feature is enabled. wrap-match **does not** do anything with spans. Additionally, you will not be able to manually create
97spans in functions you use wrap-match on. This is because the span will be dropped before wrap-match logs anything.
98
99To put both the function and wrap-match logs in a span, you have to use the `tracing::instrument` attribute macro. The ordering of the attribute macros is important; **it must go after wrap-match**.
100
101Example:
102
103```
104#[wrap_match::wrap_match(success_message = "still in span!")]
105#[tracing::instrument] // IMPORTANT: after wrap-match!
106fn my_function() -> Result<(), ()> {
107 tracing::info!("hello from tracing!");
108 Ok(())
109}
110```
111
112## Customization
113
114wrap-match allows the user to customize success and error messages, as well as choosing whether or not to log anything on success.
115
116### `success_message`
117
118The message that's logged on success.
119
120Available format parameters:
121
122- `function`: The original function name. **Note**: You can only use `{function}`; other formats such as `{function:?}` are not supported.
123- [Function arguments](#using-function-arguments-in-messages)
124
125Default value: `Successfully ran {function}`
126
127Example:
128
129```
130# #[derive(Debug)] enum CustomError { Error }
131#[wrap_match::wrap_match(success_message = "{function} ran successfully!! 🎉🎉")]
132fn my_function() -> Result<(), CustomError> {
133 Ok(())
134}
135```
136
137This would log:
138
139```log
140[INFO] my_function ran successfully!! 🎉🎉
141```
142
143### `error_message`
144
145The message that's logged on error, when line and expression info **is** available. Currently, this is only for try expressions (expressions with a `?` after them).
146
147Available format parameters:
148
149- `function`: The original function name. **Note**: You can only use `{function}`; other formats such as `{function:?}` are not supported.
150- `line`: The line the error occurred on.
151- `expr`: The expression that caused the error.
152- `error`: The error.
153- [Function arguments](#using-function-arguments-in-messages)
154
155Default value: `` An error occurred when running {function} (caused by `{expr}` on line {line}): {error:?} ``
156
157Example:
158
159```
160# #[derive(Debug)] enum CustomError { Error }
161#[wrap_match::wrap_match(error_message = "oh no, {function} failed! `{expr}` on line {line} caused the error: {error:?}")]
162fn my_function() -> Result<(), CustomError> {
163 Err(CustomError::Error)?;
164 Ok(())
165}
166```
167
168This would log:
169
170```log
171[ERROR] oh no, my_function failed! `Err(CustomError::Error)` on line 3 caused the error: Error
172```
173
174### `error_message_without_info`
175
176The message that's logged on error, when line and expression info **is not** available. This is usually triggered if you return an error yourself and use `.into()`.
177
178Available format parameters:
179
180- `function`: The original function name. **Note**: You can only use `{function}`; other formats such as `{function:?}` are not supported.
181- `error`: The error.
182- [Function arguments](#using-function-arguments-in-messages)
183
184Default value: `An error occurred when running {function}: {error:?}`
185
186Example:
187
188```
189# #[derive(Debug)] enum CustomError { Error }
190#[wrap_match::wrap_match(error_message_without_info = "oh no, {function} failed with this error: {error:?}")]
191fn my_function() -> Result<(), CustomError> {
192 Err(CustomError::Error.into())
193}
194```
195
196This would log:
197
198```log
199[ERROR] oh no, my_function failed with this error: Error
200```
201
202### `log_success`
203
204If `false`, nothing will be logged on success.
205
206Default value: `true`
207
208Example:
209
210```
211# #[derive(Debug)] enum CustomError { Error }
212#[wrap_match::wrap_match(log_success = false)]
213fn my_function() -> Result<(), CustomError> {
214 Ok(())
215}
216```
217
218This would log nothing.
219
220### `disregard_result`
221
222If `true`, the resulting function will return `()` and throw away whatever the `Result` is. Useful for `main` functions.
223
224Default value: `false`
225
226Example:
227
228```
229# #[derive(Debug)] enum CustomError { Error }
230#[wrap_match::wrap_match(disregard_result = true)]
231fn main() -> Result<(), CustomError> {
232 Ok(())
233}
234```
235
236The `main` function would be turned into this:
237
238```ignore
239fn main() {
240 fn _wrap_match_inner_main() -> Result<(), WrapMatchError<CustomError>> {
241 Ok(())
242 }
243
244 match _wrap_match_inner_main() {
245 // the Result would be logged like normal, but it is not returned
246 }
247}
248```
249
250## Using function arguments in messages
251
252As of wrap-match 1.0.5, you can use function arguments in messages, as long as they weren't moved/dropped. You should only use this for references and items that implement `Copy`.
253
254Example:
255
256```rust
257#[wrap_match::wrap_match(success_message = "Success! input = {input}")]
258fn my_function(input: i64) -> Result<i64, ()> {
259 Ok(input)
260}
261```
262
263## Limitations
264
265wrap-match currently has the following limitations:
266
2671. ~~wrap-match cannot be used on functions in implementations that take a `self` parameter. If you need support for this, please create a GitHub issue with your use case.~~ This is now supported!
268 However, it does require wrap-match to move the inner function out of the generated one, so it will add a new method to the implementation. This method is marked as deprecated, made private, and
269 is not shown in documentation. Hopefully this won't cause any issues.
270
2711. wrap-match only supports `Result`s. If you need support for `Option`s, please create a GitHub issue with your use case.
272
2731. ~~`error_message` and `error_message_without_info` only support formatting `error` using the `Debug` or `Display` formatters. This is because of how we determine what formatting specifiers are
274 used. If you need support for other formatting specifiers, please create a GitHub issue with your use case.~~ All format parameters (except `function`) now support all basic formats that `format!`
275 supports (however, features such as precision, sign, fill, alignment and width will most likely never be supported)..
276
2771. wrap-match cannot be used on `const` functions. This is because the `log` crate cannot be used in `const` contexts.
278
279If wrap-match doesn't work for something not on this list, please create a GitHub issue!
280*/
281
282#[doc(inline)]
283pub use wrap_match_impl::wrap_match;
284
285// Not public API.
286#[doc(hidden)]
287pub mod __private {
288 #[doc(hidden)]
289 pub struct WrapMatchError<'lt, E> {
290 pub line_and_expr: Option<(u32, &'lt str)>,
291 pub inner: E,
292 }
293
294 impl<E> From<E> for WrapMatchError<'_, E> {
295 fn from(inner: E) -> Self {
296 Self {
297 line_and_expr: None,
298 inner,
299 }
300 }
301 }
302}