mirror of
https://github.com/haraldh/chainerror.git
synced 2025-04-19 14:22:52 +02:00
Compare commits
12 commits
Author | SHA1 | Date | |
---|---|---|---|
|
28eb28e47d | ||
|
4c42d37598 | ||
|
82a4164780 | ||
|
75b7fdf363 | ||
|
d164662537 | ||
|
5390007cbe | ||
|
354f7b92ed | ||
|
9aa0183d65 | ||
|
46bf63fd32 | ||
|
5cb96eeee3 | ||
|
0cee763264 | ||
|
74f1dd4314 |
27
.github/workflows/coverage.yml
vendored
27
.github/workflows/coverage.yml
vendored
|
@ -13,31 +13,26 @@ on:
|
||||||
types:
|
types:
|
||||||
- created
|
- created
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
coverage:
|
||||||
name: coverage
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
components: llvm-tools-preview
|
components: llvm-tools-preview
|
||||||
|
|
||||||
- name: Install cargo-llvm-cov
|
- name: Install cargo-llvm-cov
|
||||||
run: >
|
uses: taiki-e/install-action@cargo-llvm-cov
|
||||||
curl -LsSf 'https://github.com/taiki-e/cargo-llvm-cov/releases/download/v0.5.23/cargo-llvm-cov-x86_64-unknown-linux-musl.tar.gz'
|
- name: Generate code coverage
|
||||||
| tar xzf -
|
run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json
|
||||||
&& mv cargo-llvm-cov $HOME/.cargo/bin
|
|
||||||
|
|
||||||
- name: Run cargo-llvm-cov
|
|
||||||
run: cargo llvm-cov --doctests --all --all-features --lcov --output-path lcov.info
|
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
directory: ./
|
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||||
fail_ci_if_error: false
|
files: codecov.json
|
||||||
files: ./lcov.info
|
fail_ci_if_error: true
|
||||||
verbose: true
|
|
||||||
|
|
323
src/lib.rs
323
src/lib.rs
|
@ -338,11 +338,8 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> {
|
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> {
|
||||||
if self.is_chain::<T>() {
|
if self.is_chain::<T>() {
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
// Use transmute when we've verified the types match
|
||||||
unsafe {
|
unsafe { Some(std::mem::transmute::<&Error<U>, &Error<T>>(self)) }
|
||||||
#[allow(trivial_casts)]
|
|
||||||
Some(*(self as *const dyn StdError as *const &Error<T>))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -351,11 +348,8 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> {
|
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> {
|
||||||
if self.is_chain::<T>() {
|
if self.is_chain::<T>() {
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
// Use transmute when we've verified the types match
|
||||||
unsafe {
|
unsafe { Some(std::mem::transmute::<&mut Error<U>, &mut Error<T>>(self)) }
|
||||||
#[allow(trivial_casts)]
|
|
||||||
Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -363,11 +357,8 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> {
|
fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> {
|
||||||
if self.is_chain::<T>() {
|
if self.is_chain::<T>() {
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
// Use transmute when we've verified the types match
|
||||||
unsafe {
|
unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) }
|
||||||
#[allow(trivial_casts)]
|
|
||||||
Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -376,11 +367,8 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> {
|
fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> {
|
||||||
if self.is_chain::<T>() {
|
if self.is_chain::<T>() {
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
// Use transmute when we've verified the types match
|
||||||
unsafe {
|
unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) }
|
||||||
#[allow(trivial_casts)]
|
|
||||||
Some(&mut (*(self as *mut dyn StdError as *mut &mut Error<T>)).kind)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -607,6 +595,7 @@ macro_rules! str_context {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct $e(pub String);
|
pub struct $e(pub String);
|
||||||
impl $e {
|
impl $e {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn new<S: Into<String>>(s: S) -> Self {
|
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||||
$e(s.into())
|
$e(s.into())
|
||||||
}
|
}
|
||||||
|
@ -688,6 +677,12 @@ macro_rules! str_context {
|
||||||
/// do_some_io(filename).map_context(|e| ErrorKind::from(e))?;
|
/// do_some_io(filename).map_context(|e| ErrorKind::from(e))?;
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() {
|
||||||
|
/// # if let Err(e) = func1() {
|
||||||
|
/// # eprintln!("Error:\n{:?}", e);
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! err_kind {
|
macro_rules! err_kind {
|
||||||
|
@ -740,3 +735,291 @@ macro_rules! err_kind {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Context as _;
|
||||||
|
use super::*;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_chain_with_multiple_causes() {
|
||||||
|
// Create a chain of errors
|
||||||
|
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
|
||||||
|
|
||||||
|
str_context!(Level3Error);
|
||||||
|
str_context!(Level2Error);
|
||||||
|
str_context!(Level1Error);
|
||||||
|
|
||||||
|
let err = Result::<(), _>::Err(io_error.into())
|
||||||
|
.context(Level3Error("level 3".into()))
|
||||||
|
.context(Level2Error("level 2".into()))
|
||||||
|
.context(Level1Error("level 1".into()))
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
// Test the error chain
|
||||||
|
assert!(err.is_chain::<Level1Error>());
|
||||||
|
assert!(err.find_chain_cause::<Level2Error>().is_some());
|
||||||
|
assert!(err.find_chain_cause::<Level3Error>().is_some());
|
||||||
|
assert!(err.find_chain_cause::<io::Error>().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_root_cause() {
|
||||||
|
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
|
||||||
|
|
||||||
|
str_context!(WrapperError);
|
||||||
|
let err = Result::<(), _>::Err(io_error.into())
|
||||||
|
.context(WrapperError("wrapper".into()))
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
let root = err.root_cause().unwrap();
|
||||||
|
assert!(root.is_chain::<io::Error>());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_display_and_debug() {
|
||||||
|
str_context!(CustomError);
|
||||||
|
let err = Error::new(
|
||||||
|
CustomError("test error".into()),
|
||||||
|
None,
|
||||||
|
Some("src/lib.rs:100".into()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Display formatting
|
||||||
|
assert_eq!(format!("{}", err), "test error");
|
||||||
|
|
||||||
|
// Test alternate Display formatting
|
||||||
|
assert_eq!(format!("{:#}", err), "test error");
|
||||||
|
|
||||||
|
// Test Debug formatting
|
||||||
|
let debug_output = format!("{:?}", err);
|
||||||
|
assert!(debug_output.contains("test error"));
|
||||||
|
assert!(debug_output.contains("src/lib.rs:100"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_annotation() {
|
||||||
|
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
|
||||||
|
let err = Result::<(), _>::Err(io_error.into())
|
||||||
|
.annotate()
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(err.source().is_some());
|
||||||
|
err.source()
|
||||||
|
.unwrap()
|
||||||
|
.downcast_inner_ref::<io::Error>()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_context() {
|
||||||
|
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
|
||||||
|
|
||||||
|
str_context!(MappedError);
|
||||||
|
let err = Result::<(), _>::Err(io_error.into())
|
||||||
|
.map_context(|e| MappedError(format!("Mapped: {}", e)))
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert!(err.is_chain::<MappedError>());
|
||||||
|
assert!(err.find_chain_cause::<io::Error>().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_downcasting() {
|
||||||
|
str_context!(OriginalError);
|
||||||
|
let original = Error::new(OriginalError("test".into()), None, None);
|
||||||
|
|
||||||
|
let error: Box<dyn StdError + Send + Sync> = Box::new(original);
|
||||||
|
|
||||||
|
// Test downcast_chain_ref
|
||||||
|
assert!(error.is_chain::<OriginalError>());
|
||||||
|
assert!(error.downcast_chain_ref::<OriginalError>().is_some());
|
||||||
|
|
||||||
|
// Test downcast_inner_ref
|
||||||
|
let inner = error.downcast_inner_ref::<OriginalError>();
|
||||||
|
assert!(inner.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum TestErrorKind {
|
||||||
|
Basic(String),
|
||||||
|
Complex { message: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TestErrorKind {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
TestErrorKind::Basic(msg) => write!(f, "Basic error: {}", msg),
|
||||||
|
TestErrorKind::Complex { message } => write!(f, "Complex error: {}", message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_err_kind_macro() {
|
||||||
|
err_kind!(TestError, TestErrorKind);
|
||||||
|
|
||||||
|
let err = TestError::from(TestErrorKind::Basic("test".into()));
|
||||||
|
assert!(matches!(err.kind(), TestErrorKind::Basic(_)));
|
||||||
|
// The annotated error should display "(passed error)" even in a chain
|
||||||
|
assert_eq!(format!("{}", err), "Basic error: test");
|
||||||
|
assert_eq!(format!("{:?}", err), "Basic(\"test\")");
|
||||||
|
|
||||||
|
let complex_err = TestError::from(TestErrorKind::Complex {
|
||||||
|
message: "test".into(),
|
||||||
|
});
|
||||||
|
assert!(matches!(complex_err.kind(), TestErrorKind::Complex { .. }));
|
||||||
|
// The annotated error should display "(passed error)" even in a chain
|
||||||
|
assert_eq!(format!("{}", complex_err), "Complex error: test");
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:?}", complex_err),
|
||||||
|
"Complex { message: \"test\" }"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_annotated_error_display_and_debug() {
|
||||||
|
let annotated = AnnotatedError(());
|
||||||
|
|
||||||
|
// Test Display formatting
|
||||||
|
assert_eq!(format!("{}", annotated), "(passed error)");
|
||||||
|
|
||||||
|
// Test Debug formatting
|
||||||
|
assert_eq!(format!("{:?}", annotated), "(passed error)");
|
||||||
|
|
||||||
|
// Test with error chain
|
||||||
|
let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
|
||||||
|
let err = Result::<(), _>::Err(io_error.into())
|
||||||
|
.annotate()
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
// The annotated error should display "(passed error)" even in a chain
|
||||||
|
assert_eq!(format!("{}", err), "(passed error)");
|
||||||
|
assert!(format!("{:?}", err).contains("(passed error)"));
|
||||||
|
|
||||||
|
// Verify the error chain is preserved
|
||||||
|
assert!(err.source().is_some());
|
||||||
|
assert!(err.source().unwrap().is_chain::<io::Error>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper error types for testing
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TestError(String);
|
||||||
|
|
||||||
|
impl std::fmt::Display for TestError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for TestError {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downcast_chain_operations() {
|
||||||
|
// Create a test error chain
|
||||||
|
let original_error = Error::new(
|
||||||
|
TestError("test message".to_string()),
|
||||||
|
None,
|
||||||
|
Some("test location".to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test is_chain
|
||||||
|
assert!(original_error.is_chain::<TestError>());
|
||||||
|
assert!(!original_error.is_chain::<io::Error>());
|
||||||
|
|
||||||
|
// Test downcast_chain_ref
|
||||||
|
let downcast_ref = original_error.downcast_chain_ref::<TestError>();
|
||||||
|
assert!(downcast_ref.is_some());
|
||||||
|
let downcast_kind = downcast_ref.unwrap().kind();
|
||||||
|
assert_eq!(format!("{}", downcast_kind), "test message");
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:?}", downcast_kind),
|
||||||
|
"TestError(\"test message\")"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test invalid downcast_chain_ref
|
||||||
|
let invalid_downcast = original_error.downcast_chain_ref::<io::Error>();
|
||||||
|
assert!(invalid_downcast.is_none());
|
||||||
|
|
||||||
|
// Test downcast_chain_mut
|
||||||
|
let mut mutable_error = original_error;
|
||||||
|
let downcast_mut = mutable_error.downcast_chain_mut::<TestError>();
|
||||||
|
assert!(downcast_mut.is_some());
|
||||||
|
assert_eq!(downcast_mut.unwrap().kind().0, "test message");
|
||||||
|
|
||||||
|
// Test invalid downcast_chain_mut
|
||||||
|
let invalid_downcast_mut = mutable_error.downcast_chain_mut::<io::Error>();
|
||||||
|
assert!(invalid_downcast_mut.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_downcast_inner_operations() {
|
||||||
|
// Create a test error
|
||||||
|
let mut error = Error::new(
|
||||||
|
TestError("inner test".to_string()),
|
||||||
|
None,
|
||||||
|
Some("test location".to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test downcast_inner_ref
|
||||||
|
let inner_ref = error.downcast_inner_ref::<TestError>();
|
||||||
|
assert!(inner_ref.is_some());
|
||||||
|
assert_eq!(inner_ref.unwrap().0, "inner test");
|
||||||
|
// Test invalid downcast_inner_ref
|
||||||
|
let invalid_inner = error.downcast_inner_ref::<io::Error>();
|
||||||
|
assert!(invalid_inner.is_none());
|
||||||
|
|
||||||
|
// Test downcast_inner_mut
|
||||||
|
let inner_mut = error.downcast_inner_mut::<TestError>();
|
||||||
|
assert!(inner_mut.is_some());
|
||||||
|
assert_eq!(inner_mut.unwrap().0, "inner test");
|
||||||
|
|
||||||
|
// Test invalid downcast_inner_mut
|
||||||
|
let invalid_inner_mut = error.downcast_inner_mut::<io::Error>();
|
||||||
|
assert!(invalid_inner_mut.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_down_for_dyn_error() {
|
||||||
|
// Create a boxed error
|
||||||
|
let error: Box<dyn std::error::Error + 'static> = Box::new(Error::new(
|
||||||
|
TestError("dyn test".to_string()),
|
||||||
|
None,
|
||||||
|
Some("test location".to_string()),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Test is_chain through trait object
|
||||||
|
assert!(error.is_chain::<TestError>());
|
||||||
|
assert!(!error.is_chain::<io::Error>());
|
||||||
|
|
||||||
|
// Test downcast_chain_ref through trait object
|
||||||
|
let chain_ref = error.downcast_chain_ref::<TestError>();
|
||||||
|
assert!(chain_ref.is_some());
|
||||||
|
assert_eq!(chain_ref.unwrap().kind().0, "dyn test");
|
||||||
|
|
||||||
|
// Test downcast_inner_ref through trait object
|
||||||
|
let inner_ref = error.downcast_inner_ref::<TestError>();
|
||||||
|
assert!(inner_ref.is_some());
|
||||||
|
assert_eq!(inner_ref.unwrap().0, "dyn test");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_down_with_sync_send() {
|
||||||
|
// Create a boxed error with Send + Sync
|
||||||
|
let error: Box<dyn std::error::Error + Send + Sync> = Box::new(Error::new(
|
||||||
|
TestError("sync test".to_string()),
|
||||||
|
None,
|
||||||
|
Some("test location".to_string()),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Test operations on Send + Sync error
|
||||||
|
assert!(error.is_chain::<TestError>());
|
||||||
|
assert!(error.downcast_chain_ref::<TestError>().is_some());
|
||||||
|
assert!(error.downcast_inner_ref::<TestError>().is_some());
|
||||||
|
|
||||||
|
// Test invalid downcasts
|
||||||
|
assert!(!error.is_chain::<io::Error>());
|
||||||
|
assert!(error.downcast_chain_ref::<io::Error>().is_none());
|
||||||
|
assert!(error.downcast_inner_ref::<io::Error>().is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue