mirror of
https://github.com/haraldh/chainerror.git
synced 2025-04-19 22:32:51 +02:00
Compare commits
No commits in common. "28eb28e47d0a08e880bc6c0041a462562648f5f9" and "24382fbaed2d2acd97076f1f1a46c854da6789e1" have entirely different histories.
28eb28e47d
...
24382fbaed
27
.github/workflows/coverage.yml
vendored
27
.github/workflows/coverage.yml
vendored
|
@ -13,26 +13,31 @@ on:
|
||||||
types:
|
types:
|
||||||
- created
|
- created
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
coverage:
|
test:
|
||||||
|
name: coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v1
|
||||||
- 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
|
||||||
uses: taiki-e/install-action@cargo-llvm-cov
|
run: >
|
||||||
- name: Generate code coverage
|
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'
|
||||||
run: cargo +nightly llvm-cov --all-features --workspace --codecov --doctests --output-path codecov.json
|
| 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
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
directory: ./
|
||||||
files: codecov.json
|
fail_ci_if_error: false
|
||||||
fail_ci_if_error: true
|
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]
|
#[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>() {
|
||||||
// Use transmute when we've verified the types match
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
unsafe { Some(std::mem::transmute::<&Error<U>, &Error<T>>(self)) }
|
unsafe {
|
||||||
|
#[allow(trivial_casts)]
|
||||||
|
Some(*(self as *const dyn StdError as *const &Error<T>))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -348,8 +351,11 @@ 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>() {
|
||||||
// Use transmute when we've verified the types match
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
unsafe { Some(std::mem::transmute::<&mut Error<U>, &mut Error<T>>(self)) }
|
unsafe {
|
||||||
|
#[allow(trivial_casts)]
|
||||||
|
Some(&mut *(self as *mut dyn StdError as *mut &mut Error<T>))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -357,8 +363,11 @@ 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>() {
|
||||||
// Use transmute when we've verified the types match
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
unsafe { Some(std::mem::transmute::<&U, &T>(&self.kind)) }
|
unsafe {
|
||||||
|
#[allow(trivial_casts)]
|
||||||
|
Some(&(*(self as *const dyn StdError as *const &Error<T>)).kind)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -367,8 +376,11 @@ 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>() {
|
||||||
// Use transmute when we've verified the types match
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
unsafe { Some(std::mem::transmute::<&mut U, &mut T>(&mut self.kind)) }
|
unsafe {
|
||||||
|
#[allow(trivial_casts)]
|
||||||
|
Some(&mut (*(self as *mut dyn StdError as *mut &mut Error<T>)).kind)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -595,7 +607,6 @@ 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())
|
||||||
}
|
}
|
||||||
|
@ -677,12 +688,6 @@ 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 {
|
||||||
|
@ -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