Skip to content

Commit

Permalink
Integrate optional anyhow support via feature flag
Browse files Browse the repository at this point in the history
- Introduce `anyhow` as an optional dependency in `wherr/Cargo.toml`.
- Implement conditional compilation in `wherr/src/lib.rs` to determine error type based on the presence of `anyhow` feature flag.
- Add `wherr/examples/anyhow.rs` to demonstrate usage with anyhow.

This allows users to leverage the benefits of the anyhow crate for error handling in projects that already utilize it while maintaining backward compatibility for projects that don't.
  • Loading branch information
joelonsql committed Sep 1, 2023
1 parent b40456e commit 5f41e5a
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 17 deletions.
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ Enhance Rust's `?` operator by appending file and line number details to errors,

## Features

- Appends file and line number to errors propagated with `?`.
- Integrates seamlessly with Rust's existing error handling.
- Helps locate and fix errors faster by pinpointing their origin.
- 📁 Appends file and line number to errors propagated with `?`.
- 🧩 Integrates seamlessly with Rust's existing error handling.
- 📍 Helps locate and fix errors faster by pinpointing their origin.
- 🚀 Supports `anyhow` error handling through the `anyhow` feature flag.

## Why `wherr`?

Expand All @@ -29,10 +30,19 @@ Add the following to your `Cargo.toml`:
wherr = "0.1"
```

For `anyhow` support:

```toml
[dependencies]
wherr = { version = "0.1", features = ["anyhow"] }
```

## Quick Start

By simply annotating your functions with `#[wherr]`, errors propagated using `?` will also include the file and line number.

1. **Standard Usage**: Annotate functions with `#[wherr]`.

```rust
use wherr::wherr;

Expand All @@ -58,6 +68,36 @@ This will highlight the difference `wherr` makes. Your error will now indicate e
error at wherr/examples/with_wherr.rs:5
```

2. **With `anyhow`**:

Ensure you've added the `anyhow` feature as shown in the installation section. Then:

```rust
use anyhow::{Context, Result};
use wherr::wherr;

#[wherr]
fn concat_files(path1: &str, path2: &str) -> Result<String> {
let mut content1 = std::fs::read_to_string(path1).with_context(|| format!("Failed to read {}", path1))?;
let content2 = std::fs::read_to_string(path2).with_context(|| format!("Failed to read {}", path2))?;

content1.push_str(&content2);
Ok(content1)
}
```

Run the provided example:

```sh
cargo run --features=anyhow --example anyhow
```

This will highlight the difference `wherr` makes. Your error will now indicate exactly where the issue arises:

```
error at wherr/examples/anyhow.rs:6
```

## Behind the Scenes

The `#[wherr]` notation is a [proc_macro_attribute](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros), which allows for code transformations at compile time.
Expand Down
2 changes: 1 addition & 1 deletion wherr-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wherr-macro"
version = "0.1.6"
version = "0.1.7"
edition = "2021"
authors = ["Joel Jakobsson <[email protected]>"]
categories = ["rust-patterns"]
Expand Down
15 changes: 14 additions & 1 deletion wherr/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "wherr"
version = "0.1.6"
version = "0.1.7"
edition = "2021"
authors = ["Joel Jakobsson <[email protected]>"]
categories = ["rust-patterns"]
Expand All @@ -9,5 +9,18 @@ keywords = ["error", "error-handling"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/joelonsql/wherr"

[features]
anyhow = ["dep:anyhow"]

[dependencies]
wherr-macro = "0.1"
anyhow = { version = "1.0", optional = true }

[[example]]
name = "anyhow"
required-features = ["anyhow"]

[[test]]
name = "anyhow_error_tests"
path = "tests/anyhow_error_tests.rs"
required-features = ["anyhow"]
16 changes: 16 additions & 0 deletions wherr/examples/anyhow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use anyhow::{Context, Result};
use wherr::wherr;

#[wherr]
fn concat_files(path1: &str, path2: &str) -> Result<String> {
let mut content1 = std::fs::read_to_string(path1).with_context(|| format!("Failed to read {}", path1))?;
let content2 = std::fs::read_to_string(path2).with_context(|| format!("Failed to read {}", path2))?;

content1.push_str(&content2);
Ok(content1)
}

fn main() {
let content = concat_files("file1.txt", "file2.txt").expect("Failed to concatenate the files");
println!("Concatenated content:\n{content}");
}
2 changes: 1 addition & 1 deletion wherr/examples/with_wherr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ fn concat_files(path1: &str, path2: &str) -> Result<String, Box<dyn std::error::

fn main() {
let content = concat_files("file1.txt", "file2.txt").expect("Failed to concatenate the files");
println!("Concatenated content:\n{}", content);
println!("Concatenated content:\n{content}");
}
2 changes: 1 addition & 1 deletion wherr/examples/without_wherr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ fn concat_files(path1: &str, path2: &str) -> Result<String, std::io::Error> {

fn main() {
let content = concat_files("file1.txt", "file2.txt").expect("Failed to concatenate the files");
println!("Concatenated content:\n{}", content);
println!("Concatenated content:\n{content}");
}
24 changes: 16 additions & 8 deletions wherr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ use std::fmt;
// Re-export the procedural macro from the `wherr_macro` crate.
pub use wherr_macro::wherr;

#[cfg(feature = "anyhow")]
use anyhow::Error as AnyhowError;

#[cfg(feature = "anyhow")]
type Wherror = AnyhowError;
#[cfg(not(feature = "anyhow"))]
type Wherror = Box<dyn std::error::Error>;

/// 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 inner: Wherror,
pub file: &'static str,
pub line: u32,
}
Expand All @@ -26,7 +34,7 @@ impl Wherr {
/// * `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 {
pub fn new(err: Wherror, file: &'static str, line: u32) -> Self {
Wherr {
inner: err,
file,
Expand Down Expand Up @@ -71,18 +79,18 @@ pub fn wherrapper<T, E>(
result: Result<T, E>,
file: &'static str,
line: u32,
) -> Result<T, Box<dyn std::error::Error>>
) -> Result<T, Wherror>
where
E: Into<Box<dyn std::error::Error>>,
E: Into<Wherror>,
{
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)
let error: Wherror = err.into();
if error.is::<Wherr>() {
Err(error)
} else {
Err(Box::new(Wherr::new(boxed_err, file, line)))
Err(Wherr::new(error, file, line).into())
}
}
}
Expand Down
76 changes: 76 additions & 0 deletions wherr/tests/anyhow_error_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use wherr::{wherr, wherrapper, Wherr};
use anyhow::{Error, Result};

#[test]
fn test_wherr_new() {
let error_message = "Test error";
let err = anyhow::Error::new(std::io::Error::new(std::io::ErrorKind::Other, error_message));
let wherr = Wherr::new(err, "test.rs", 42);

assert_eq!(wherr.file, "test.rs");
assert_eq!(wherr.line, 42);
assert_eq!(wherr.inner.to_string(), error_message);
}

#[test]
fn test_wherr_display() {
let error_message = "Test error";
let err = anyhow::Error::new(std::io::Error::new(std::io::ErrorKind::Other, error_message));
let wherr = Wherr::new(err, "test.rs", 42);

assert_eq!(
format!("{}", wherr),
format!("{}\nerror at test.rs:42", error_message)
);
}

#[test]
fn test_wherrapper() {
let error_message = "Test error";
let err = std::io::Error::new(std::io::ErrorKind::Other, error_message);
let result: Result<(), _> = Err(err.into()); // Convert the error to anyhow::Error

match wherrapper::<(), anyhow::Error>(result, "test.rs", 42) {
Ok(_) => panic!("Expected an error"),
Err(err) => {
let wherr = err.downcast::<Wherr>().expect("Expected a Wherr error");
assert_eq!(wherr.file, "test.rs");
assert_eq!(wherr.line, 42);
assert_eq!(wherr.inner.to_string(), error_message);
}
}
}

#[wherr]
fn f3() -> Result<()> {
i64::from_str_radix("not a decimal number", 10).map_err(Error::new)?;

Ok(())
}

#[wherr]
fn f2() -> Result<()> {
f3()?;

Ok(())
}

#[wherr]
fn f1() -> Result<()> {
f2()?;

Ok(())
}

#[test]
fn test_wherr_macro() {
match f1() {
Ok(_) => panic!("Expected an error"),
Err(err) => {
let wherr = err.downcast::<Wherr>().expect("Expected a Wherr error");
assert_eq!(wherr.file, "wherr/tests/anyhow_error_tests.rs");
assert_eq!(wherr.line, 46);
assert_eq!(wherr.inner.to_string(), "invalid digit found in string");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
extern crate wherr;
#![cfg(not(feature = "anyhow"))]

use wherr::{wherr, wherrapper, Wherr};

Expand Down Expand Up @@ -69,7 +69,7 @@ fn test_wherr_macro() {
Ok(_) => panic!("Expected an error"),
Err(err) => {
let wherr = err.downcast::<Wherr>().expect("Expected a Wherr error");
assert_eq!(wherr.file, "wherr/tests/integration_tests.rs");
assert_eq!(wherr.file, "wherr/tests/box_error_tests.rs");
assert_eq!(wherr.line, 47);
assert_eq!(wherr.inner.to_string(), "invalid digit found in string");
}
Expand Down

0 comments on commit 5f41e5a

Please sign in to comment.