Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add backtrace #3

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions wherr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository = "https://github.com/joelonsql/wherr"

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

[dependencies]
wherr-macro = "0.1"
Expand All @@ -24,3 +25,8 @@ required-features = ["anyhow"]
name = "anyhow_error_tests"
path = "tests/anyhow_error_tests.rs"
required-features = ["anyhow"]

[[test]]
name = "backtrace_tests"
path = "tests/backtrace_tests.rs"
required-features = ["backtrace"]
8 changes: 7 additions & 1 deletion wherr/examples/with_wherr.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#[cfg(not(feature = "backtrace"))]
type MyError = Box<dyn std::error::Error>;

#[cfg(feature = "backtrace")]
type MyError = wherr::GenericWherrError;

use wherr::wherr;

#[wherr]
fn concat_files(path1: &str, path2: &str) -> Result<String, Box<dyn std::error::Error>> {
fn concat_files(path1: &str, path2: &str) -> Result<String, MyError> {
let mut content1 = std::fs::read_to_string(path1)?;
let content2 = std::fs::read_to_string(path2)?;

Expand Down
122 changes: 122 additions & 0 deletions wherr/src/backtrace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::{fmt, error::Error};

pub use wherr_macro::wherr;
pub type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;
pub type GenericWherrError = Box<dyn WherrError + Send + Sync + 'static>;

pub trait WherrError: Error {
fn into_inner(&self) -> Option<&GenericError>;
fn take_inner(&mut self) -> Option<GenericError>;
fn locations(&self) -> &[Location];
fn push_location(&mut self, file: &'static str, line: u32);
fn stack(&self) -> String {
let mut result = String::with_capacity(128);
for loc in self.locations() {
result.push_str("at ");
result.push_str(loc.file);
result.push(':');
result.push_str(loc.line.to_string().as_str());
result.push('\n');
}
result
}
}

impl WherrError for WherrWithBacktrace {
fn into_inner(&self) -> Option<&GenericError> {
self.inner.as_ref()
}

fn take_inner(&mut self) -> Option<GenericError> {
self.inner.take()
}

fn locations(&self) -> &[Location] {
self.locations.as_slice()
}

fn push_location(&mut self, file: &'static str, line: u32) {
self.locations.push(Location { file, line });
}
}

pub struct WherrWithBacktrace {
pub inner: Option<GenericError>,

pub locations: Vec<Location>,
}

#[derive(Debug)]
pub struct Location {
pub file: &'static str,
pub line: u32,
}

impl WherrWithBacktrace {
pub fn new(err: GenericError) -> Self {
WherrWithBacktrace {
inner: Some(err),
locations: Vec::default(),
}
}
}

impl Default for WherrWithBacktrace {
fn default() -> Self {
WherrWithBacktrace {
inner: None,
locations: Vec::default(),
}
}
}

impl fmt::Display for WherrWithBacktrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for source in &self.inner {
write!(f, "{}", source)?;
}

for loc in self.locations.iter() {
write!(f, "\nat {}:{}", loc.file, loc.line)?;
}

Ok(())
}
}

impl fmt::Debug for WherrWithBacktrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.inner)?;
for loc in self.locations.iter() {
write!(f, "\nat {}:{}", loc.file, loc.line)?;
}

Ok(())
}
}

impl std::error::Error for WherrWithBacktrace { }

pub fn wherrapper<T, E>(
result: Result<T, E>,
file: &'static str,
line: u32,
) -> Result<T, GenericWherrError>
where
E: Into<GenericWherrError> + 'static,
{
match result {
Ok(val) => Ok(val),
Err(err) => {
let mut wherr_error: GenericWherrError = err.into();
wherr_error.push_location(file, line);
Err(wherr_error)
}
}
}

impl<E: Error + Send + Sync + 'static> From<E> for GenericWherrError {
fn from(error: E) -> Self {
Box::new(WherrWithBacktrace::new(Box::new(error)))
}
}
7 changes: 7 additions & 0 deletions wherr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
//!
//! The `wherr` attribute macro, defined in the `wherr_macro` crate, is re-exported here for ease of use.

#[cfg(feature = "backtrace")]
mod backtrace;

#[cfg(feature = "backtrace")]
pub use self::backtrace::*;

use std::fmt;

// Re-export the procedural macro from the `wherr_macro` crate.
Expand Down Expand Up @@ -75,6 +81,7 @@ impl std::error::Error for Wherr {}
/// # 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.
#[cfg(not(feature = "backtrace"))]
pub fn wherrapper<T, E>(
result: Result<T, E>,
file: &'static str,
Expand Down
2 changes: 1 addition & 1 deletion wherr/tests/anyhow_error_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,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/anyhow_error_tests.rs");
assert_eq!(wherr.file.replace('\\', "/"), "wherr/tests/anyhow_error_tests.rs");
assert_eq!(wherr.line, 46);
assert_eq!(wherr.inner.to_string(), "invalid digit found in string");
}
Expand Down
31 changes: 31 additions & 0 deletions wherr/tests/backtrace_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#![cfg(feature = "backtrace")]

use wherr::wherr;
use wherr::GenericWherrError;

const ERROR_MESSAGE: &str = "Test error";

#[test]
fn test_backtrace() {
// Act
let result = f1();

// Assert
assert!(result.is_err());
let wherr = result.unwrap_err();
assert_eq!(wherr.locations().len(), 2);
let error_message = wherr.to_string();
assert_eq!(error_message.replace("\\", "/"), format!("{ERROR_MESSAGE}\nat wherr/tests/backtrace_tests.rs:29\nat wherr/tests/backtrace_tests.rs:23"));
}

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

#[wherr]
fn f2() -> Result<(), GenericWherrError> {
Err(std::io::Error::new(std::io::ErrorKind::Other, ERROR_MESSAGE))?;
Ok(())
}
4 changes: 2 additions & 2 deletions wherr/tests/box_error_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![cfg(not(feature = "anyhow"))]
#![cfg(not(any(feature = "anyhow", feature = "backtrace")))]

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/box_error_tests.rs");
assert_eq!(wherr.file.replace("\\", "/"), "wherr/tests/box_error_tests.rs");
assert_eq!(wherr.line, 47);
assert_eq!(wherr.inner.to_string(), "invalid digit found in string");
}
Expand Down