-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bb23d13
Showing
10 changed files
with
445 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/target | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[workspace] | ||
members = [ | ||
"wherr", | ||
"wherr-macro", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright 2023 Joel Jakobsson | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
# `wherr` Crate Documentation | ||
|
||
The `wherr` crate provides utilities to embed where errors originate from by enhancing them with additional file and line number information. | ||
|
||
## Table of Contents | ||
|
||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Using the `wherr` procedural macro](#using-the-wherr-procedural-macro) | ||
- [API Reference](#api-reference) | ||
- [`Wherr`](#wherr) | ||
- [`wherrapper`](#wherrapper) | ||
- [`wherr` procedural macro](#wherr-procedural-macro) | ||
- [Contributing](#contributing) | ||
- [License](#license) | ||
|
||
## Installation | ||
|
||
Add the `wherr` crate to your `Cargo.toml`: | ||
|
||
```toml | ||
[dependencies] | ||
wherr = "0.1" | ||
``` | ||
|
||
## Usage | ||
|
||
To understand the benefits of the `wherr` crate, let's first observe the problem it aims to solve: | ||
|
||
### Without `#[wherr]`: | ||
|
||
```rust | ||
fn add_two(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> { | ||
let radix = 10; | ||
let i1 = i64::from_str_radix(s1, radix)?; | ||
let i2 = i64::from_str_radix(s2, radix)?; | ||
Ok(i1 + i2) | ||
} | ||
|
||
fn main() { | ||
let sum1 = add_two("10", "20").unwrap(); | ||
println!("sum1 = {}", sum1); | ||
|
||
let sum2 = add_two("123", "not a number").unwrap(); | ||
println!("sum2 = {}", sum2); | ||
} | ||
``` | ||
|
||
Running this code would produce: | ||
|
||
```sh | ||
sum1 = 30 | ||
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }', wherr/examples/macro.rs:12:47 | ||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace | ||
``` | ||
|
||
The error message tells us where the `.unwrap()` call happened, but it doesn't pinpoint where the actual error occurred. | ||
|
||
### Using the `wherr` procedural macro | ||
|
||
By adding `#[wherr]`, the location of the error becomes visible in the error message: | ||
|
||
```rust | ||
use wherr::wherr; | ||
|
||
#[wherr] | ||
fn add_two(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> { | ||
let radix = 10; | ||
let i1 = i64::from_str_radix(s1, radix)?; | ||
let i2 = i64::from_str_radix(s2, radix)?; | ||
Ok(i1 + i2) | ||
} | ||
|
||
fn main() { | ||
let sum1 = add_two("10", "20").unwrap(); | ||
println!("sum1 = {}", sum1); | ||
|
||
let sum2 = add_two("123", "not a number").unwrap(); | ||
println!("sum2 = {}", sum2); | ||
} | ||
``` | ||
|
||
The resulting error is: | ||
|
||
```sh | ||
sum1 = 30 | ||
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error at wherr/examples/macro.rs:7. Original error: ParseIntError { kind: InvalidDigit }', wherr/examples/macro.rs:15:47 | ||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace | ||
``` | ||
|
||
Now we see that the error originates from **wherr/examples/macro.rs:7**. | ||
|
||
The `file` and `line` info can also be extracted from the `Wherr` struct, | ||
that wraps the original `Err`. | ||
|
||
```rust | ||
match add_two("123", "not a number") { | ||
Ok(sum) => { | ||
println!("sum2 = {}", sum); | ||
} | ||
Err(e) => { | ||
if let Some(wherr) = e.downcast_ref::<wherr::Wherr>() { | ||
println!( | ||
"Error at file: '{}', line: {}. Original error: {}", | ||
wherr.file, wherr.line, wherr.inner | ||
); | ||
} else { | ||
println!("Unexpected error: {}", e); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
```sh | ||
Error at file: 'wherr/examples/macro.rs', line: 7. Original error: invalid digit found in string | ||
``` | ||
|
||
## API Reference | ||
|
||
### `Wherr` | ||
|
||
Represents an error that includes file and line number information. | ||
|
||
```rust | ||
pub struct Wherr { | ||
inner: Box<dyn std::error::Error>, | ||
file: &'static str, | ||
line: u32, | ||
} | ||
``` | ||
|
||
Methods: | ||
|
||
- `new(err: Box<dyn std::error::Error>, file: &'static str, line: u32) -> Self`: Creates a new `Wherr` error that wraps another error, providing additional context. | ||
|
||
### `wherrapper` | ||
|
||
This internal utility function is used by the procedural macro to wrap errors with file and line information. | ||
|
||
```rust | ||
pub fn wherrapper<T, E>( | ||
result: Result<T, E>, | ||
file: &'static str, | ||
line: u32, | ||
) -> Result<T, Box<dyn std::error::Error>> | ||
``` | ||
|
||
### `wherr` procedural macro | ||
|
||
A procedural macro that auto-wraps errors (using the `?` operator) inside a function with file and line number details. | ||
|
||
```rust | ||
#[wherr] | ||
fn some_function() -> Result<(), Box<dyn std::error::Error>> { /* ... */ } | ||
``` | ||
|
||
## Contributing | ||
|
||
If you're interested in contributing to `wherr`, please follow standard Rust community guidelines and submit a PR on our repository. | ||
|
||
## License | ||
|
||
Please refer to the `LICENSE` file in the root directory of the crate for license details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "wherr-macro" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
syn = { version = "2.0", features = ["full", "visit-mut", "parsing", "printing"] } | ||
quote = "1.0" | ||
proc-macro2 = { version = "1.0", features = ["span-locations"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
//! `wherr-macro` crate provides a procedural macro to enhance Rust errors with file and line number information. | ||
//! | ||
//! When using the provided `wherr` macro attribute on a function, any error returned by the `?` operator within that function | ||
//! is automatically wrapped to include the file and line number where the error occurred. | ||
use proc_macro::TokenStream; | ||
use quote::{quote, quote_spanned}; | ||
use syn::spanned::Spanned; | ||
use syn::{parse_macro_input, visit_mut::VisitMut, Expr}; | ||
|
||
/// Procedural macro attribute that processes a function to automatically wrap errors using the `?` operator | ||
/// with file and line number details. | ||
#[proc_macro_attribute] | ||
pub fn wherr(_attrs: TokenStream, input: TokenStream) -> TokenStream { | ||
let mut function = parse_macro_input!(input as syn::ItemFn); | ||
|
||
let mut visitor = WherrVisitor; | ||
visitor.visit_item_fn_mut(&mut function); | ||
|
||
TokenStream::from(quote! { #function }) | ||
} | ||
|
||
/// Visitor used by the `wherr` procedural macro to traverse and mutate the Abstract Syntax Tree (AST) of the function. | ||
/// | ||
/// This visitor specifically looks for expressions using the `?` operator and wraps them with additional | ||
/// file and line information. | ||
struct WherrVisitor; | ||
|
||
impl VisitMut for WherrVisitor { | ||
/// Visit expressions in the AST. | ||
/// | ||
/// Specifically, it targets the use of the `?` operator to wrap the error with file and line number details. | ||
fn visit_expr_mut(&mut self, expr: &mut Expr) { | ||
// Check if the expression is a `?` usage. If it is, wrap it with our `wherrapper` function. | ||
if let Expr::Try(expr_try) = expr { | ||
let span = expr_try.question_token.span(); | ||
let inner_expr = &expr_try.expr; | ||
let new_expr = syn::parse2(quote_spanned! { span=> | ||
wherr::wherrapper(#inner_expr, file!(), line!())? | ||
}) | ||
.expect("Failed to create wherr wrapped expression"); | ||
|
||
*expr = new_expr; | ||
} else { | ||
// Only continue visiting child expressions if it's not a Try expression | ||
syn::visit_mut::visit_expr_mut(self, expr); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "wherr" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
wherr-macro = { path = "../wherr-macro" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
use wherr::wherr; | ||
|
||
#[wherr] | ||
fn add_two(s1: &str, s2: &str) -> Result<i64, Box<dyn std::error::Error>> { | ||
let radix = 10; | ||
let i1 = i64::from_str_radix(s1, radix)?; | ||
let i2 = i64::from_str_radix(s2, radix)?; | ||
Ok(i1 + i2) | ||
} | ||
|
||
fn main() { | ||
let sum1 = add_two("10", "20").unwrap(); | ||
println!("sum1 = {}", sum1); | ||
|
||
|
||
match add_two("123", "not a number") { | ||
Ok(sum) => { | ||
println!("sum2 = {}", sum); | ||
} | ||
Err(e) => { | ||
if let Some(wherr) = e.downcast_ref::<wherr::Wherr>() { | ||
println!( | ||
"Error at file: '{}', line: {}. Original error: {}", | ||
wherr.file, wherr.line, wherr.inner | ||
); | ||
} else { | ||
println!("Unexpected error: {}", e); | ||
} | ||
} | ||
} | ||
|
||
let sum3 = add_two("also not a number", "456").unwrap(); | ||
println!("sum3 = {}", sum3); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//! `wherr` crate provides a way to enhance Rust errors with file and line number information. | ||
//! | ||
//! The main struct `Wherr` represents an error containing additional metadata about where it originated from. | ||
//! | ||
//! The `wherr` attribute macro, defined in the `wherr_macro` crate, is re-exported here for ease of use. | ||
use std::fmt; | ||
|
||
// Re-export the procedural macro from the `wherr_macro` crate. | ||
pub use wherr_macro::wherr; | ||
|
||
/// Represents an error that includes file and line number metadata. | ||
/// | ||
/// This error struct wraps around any error and provides a consistent interface to access the original error | ||
/// and the file and line where it originated from. | ||
pub struct Wherr { | ||
pub inner: Box<dyn std::error::Error>, | ||
pub file: &'static str, | ||
pub line: u32, | ||
} | ||
|
||
impl Wherr { | ||
/// Create a new `Wherr` error from the given error, file, and line. | ||
/// | ||
/// # Parameters | ||
/// * `err`: The original error to wrap. | ||
/// * `file`: The file where the error occurred. | ||
/// * `line`: The line number where the error occurred. | ||
pub fn new(err: Box<dyn std::error::Error>, file: &'static str, line: u32) -> Self { | ||
Wherr { | ||
inner: err, | ||
file, | ||
line, | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Display for Wherr { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
write!( | ||
f, | ||
"Error at {}:{}. Original error: {}", | ||
self.file, self.line, self.inner | ||
) | ||
} | ||
} | ||
|
||
impl fmt::Debug for Wherr { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
write!( | ||
f, | ||
"Error at {}:{}. Original error: {:?}", | ||
self.file, self.line, self.inner | ||
) | ||
} | ||
} | ||
|
||
impl std::error::Error for Wherr {} | ||
|
||
/// Utility function to wrap the given error into a `Wherr` struct, adding file and line number details. | ||
/// | ||
/// # Parameters | ||
/// * `result`: The result containing the error to wrap. | ||
/// * `file`: The file where the error occurred. | ||
/// * `line`: The line number where the error occurred. | ||
/// | ||
/// # Returns | ||
/// If the original error is already of type `Wherr`, it is returned as is. | ||
/// Otherwise, the original error is wrapped inside a `Wherr` and returned. | ||
pub fn wherrapper<T, E>( | ||
result: Result<T, E>, | ||
file: &'static str, | ||
line: u32, | ||
) -> Result<T, Box<dyn std::error::Error>> | ||
where | ||
E: Into<Box<dyn std::error::Error>>, | ||
{ | ||
match result { | ||
Ok(val) => Ok(val), | ||
Err(err) => { | ||
let boxed_err: Box<dyn std::error::Error> = err.into(); | ||
if boxed_err.is::<Wherr>() { | ||
Err(boxed_err) | ||
} else { | ||
Err(Box::new(Wherr::new(boxed_err, file, line))) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.