diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e464b9..1c9d68c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,4 +32,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all-targets --all-features + args: --all-targets --all-features -- -D warnings diff --git a/src/error.rs b/src/error.rs index ff2399e..5c167af 100644 --- a/src/error.rs +++ b/src/error.rs @@ -68,7 +68,7 @@ //! No such file or directory (os error 2) //! ``` -use std::{fmt, io, process, sync}; +use std::{fmt, process}; /// A trait to add context to an error result. /// @@ -265,22 +265,42 @@ impl fmt::Display for Error { } } -impl From for Error { - fn from(error: io::Error) -> Self { - Self { - context: None, - message: Some(error.to_string()), - status: error.raw_os_error().unwrap_or(1), +impl From for Error { + fn from(error: T) -> Self { + let mut context = None; + let mut current = &error as &dyn std::error::Error; + let message; + let mut status = 1; + + // Allow for error source traversal. + loop { + // If not at the lowest level, capture the error as context. + if let Some(next) = current.source() { + context + .get_or_insert_with(Vec::new) + .push(current.to_string()); + + current = next; + + // If at the lowest level, capture the message. + } else { + message = Some(current.to_string()); + + // If std::io::Error, capture the OS error code as the status. + if let Some(other) = current.downcast_ref::() { + if let Some(code) = other.raw_os_error() { + status = code; + } + } + + break; + } } - } -} -impl From> for Error { - fn from(error: sync::PoisonError) -> Self { Self { - context: None, - message: Some(error.to_string()), - status: 1, + context, + message, + status, } } } @@ -594,6 +614,51 @@ mod test { ); } + #[test] + fn from_error() { + fn generate_error() -> Result<()> { + fn source_error() -> Result<()> { + let _ = std::fs::File::open("/should/not/exist")?; + + Ok(()) + } + + source_error().context(|| "The lower level message.")?; + + Ok(()) + } + + let error = generate_error() + .context(|| "The higher level message.") + .unwrap_err(); + + assert_eq!( + error.context, + Some(vec![ + "The lower level message.".to_string(), + "The higher level message.".to_string() + ]) + ); + + #[cfg(not(windows))] + { + assert_eq!( + error.message, + Some("No such file or directory (os error 2)".to_string()) + ); + assert_eq!(error.status, 2); + } + + #[cfg(windows)] + { + assert_eq!( + error.message, + Some("The system cannot find the path specified. (os error 3)".to_string()) + ); + assert_eq!(error.status, 3); + } + } + #[test] fn result_context() { let err: Result<()> = Err(Error::default()).context(|| "The context message."); diff --git a/src/io.rs b/src/io.rs index 363a9a1..5dc5ff1 100644 --- a/src/io.rs +++ b/src/io.rs @@ -529,7 +529,7 @@ mod test { assert_eq!(vec, b"test"); } else { - assert!(false, "Unexpected StreamKind."); + panic!("Unexpected StreamKind."); } }