mirror of
https://github.com/haraldh/chainerror.git
synced 2025-04-19 14:22:52 +02:00
Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
27
.github/workflows/coverage.yml
vendored
27
.github/workflows/coverage.yml
vendored
|
@ -13,26 +13,31 @@ on:
|
|||
types:
|
||||
- created
|
||||
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
test:
|
||||
name: coverage
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
toolchain: nightly
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- name: Generate code coverage
|
||||
run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json
|
||||
run: >
|
||||
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'
|
||||
| tar xzf -
|
||||
&& 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
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
directory: ./
|
||||
fail_ci_if_error: false
|
||||
files: ./lcov.info
|
||||
verbose: true
|
||||
|
|
323
src/lib.rs
323
src/lib.rs
|
@ -338,8 +338,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
|||
#[inline]
|
||||
fn downcast_chain_ref<T: 'static + Display + Debug>(&self) -> Option<&Error<T>> {
|
||||
if self.is_chain::<T>() {
|
||||
// Use transmute when we've verified the types match
|
||||
unsafe { Some(std::mem::transmute::<&Error<U>, &Error<T>>(self)) }
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
unsafe {
|
||||
#[allow(trivial_casts)]
|
||||
Some(*(self as *const dyn StdError as *const &Error<T>))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -348,8 +351,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
|||
#[inline]
|
||||
fn downcast_chain_mut<T: 'static + Display + Debug>(&mut self) -> Option<&mut Error<T>> {
|
||||
if self.is_chain::<T>() {
|
||||
// Use transmute when we've verified the types match
|
||||
unsafe { Some(std::mem::transmute::<&mut Error<U>, &mut Error<T>>(self)) }
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
unsafe {
|
||||
#[allow(trivial_casts)]
|
||||
Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -357,8 +363,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
|||
#[inline]
|
||||
fn downcast_inner_ref<T: 'static + StdError>(&self) -> Option<&T> {
|
||||
if self.is_chain::<T>() {
|
||||
// Use transmute when we've verified the types match
|
||||
unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) }
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
unsafe {
|
||||
#[allow(trivial_casts)]
|
||||
Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -367,8 +376,11 @@ impl<U: 'static + Display + Debug> ErrorDown for Error<U> {
|
|||
#[inline]
|
||||
fn downcast_inner_mut<T: 'static + StdError>(&mut self) -> Option<&mut T> {
|
||||
if self.is_chain::<T>() {
|
||||
// Use transmute when we've verified the types match
|
||||
unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) }
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
unsafe {
|
||||
#[allow(trivial_casts)]
|
||||
Some(&mut (*(self as *mut dyn StdError as *mut &mut Error<T>)).kind)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -595,7 +607,6 @@ macro_rules! str_context {
|
|||
#[derive(Clone)]
|
||||
pub struct $e(pub String);
|
||||
impl $e {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||
$e(s.into())
|
||||
}
|
||||
|
@ -677,12 +688,6 @@ macro_rules! str_context {
|
|||
/// do_some_io(filename).map_context(|e| ErrorKind::from(e))?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// # fn main() {
|
||||
/// # if let Err(e) = func1() {
|
||||
/// # eprintln!("Error:\n{:?}", e);
|
||||
/// # }
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! err_kind {
|
||||
|
@ -735,291 +740,3 @@ 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