diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e469e7e..a45219e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,16 +64,15 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust-version }} - - name: Run unit tests - if: ${{ !startsWith(matrix.os, 'windows') }} - run: cargo x test --no-capture - shell: bash - - name: Run unit tests (Windows) + - name: Adjust snapshots on Windows if: ${{ startsWith(matrix.os, 'windows') }} + run: | + perl -i -pe 's/\//\\/g if /at exn/' exn/tests/snapshots/*.snap + perl -i -pe 's/(? failed to run app, at examples/src/downcast.rs:82:35 +// |-> failed to run app, at examples/src/downcast.rs:81:35 // | -// |-> HTTP 503: service unavailable, at examples/src/downcast.rs:95:9 +// |-> HTTP 503: service unavailable, at examples/src/downcast.rs:94:9 diff --git a/exn/src/debug.rs b/exn/src/debug.rs index 04c556e..339e9cc 100644 --- a/exn/src/debug.rs +++ b/exn/src/debug.rs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error; use std::fmt; use std::fmt::Formatter; +use crate::Error; use crate::Exn; use crate::Frame; @@ -33,7 +33,15 @@ impl fmt::Debug for Frame { fn write_exn(f: &mut Formatter<'_>, frame: &Frame, level: usize, prefix: &str) -> fmt::Result { write!(f, "{}", frame.error())?; - write_location(f, frame)?; + + let location = frame.location(); + write!( + f, + ", at {}:{}:{}", + location.file(), + location.line(), + location.column() + )?; let children = frame.children(); let children_len = children.len(); @@ -54,55 +62,3 @@ fn write_exn(f: &mut Formatter<'_>, frame: &Frame, level: usize, prefix: &str) - Ok(()) } - -#[cfg(not(windows_test))] -fn write_location(f: &mut Formatter<'_>, exn: &Frame) -> fmt::Result { - let location = exn.location(); - write!( - f, - ", at {}:{}:{}", - location.file(), - location.line(), - location.column() - ) -} - -#[cfg(windows_test)] -fn write_location(f: &mut Formatter<'_>, exn: &Frame) -> fmt::Result { - let location = exn.location(); - use std::os::windows::ffi::OsStrExt; - use std::path::Component; - use std::path::MAIN_SEPARATOR; - use std::path::Path; - - let file = location.file(); - let path = Path::new(file); - - let mut resolved = String::new(); - - for c in path.components() { - match c { - Component::RootDir => {} - Component::CurDir => resolved.push('.'), - Component::ParentDir => resolved.push_str(".."), - Component::Prefix(prefix) => { - resolved.push_str(&prefix.as_os_str().to_string_lossy()); - continue; - } - Component::Normal(s) => resolved.push_str(&s.to_string_lossy()), - } - resolved.push('/'); - } - - if path.as_os_str().encode_wide().last() != Some(MAIN_SEPARATOR as u16) - && resolved != "/" - && resolved.ends_with('/') - { - resolved.pop(); // Pop last '/' - } - - let line = location.line(); - let column = location.column(); - - write!(f, ", at {resolved}:{line}:{column}") -} diff --git a/exn/src/display.rs b/exn/src/display.rs index 80c8875..66c9b14 100644 --- a/exn/src/display.rs +++ b/exn/src/display.rs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error; use std::fmt; +use crate::Error; use crate::Exn; impl fmt::Display for Exn { diff --git a/exn/src/impls.rs b/exn/src/impls.rs index 43ed0cd..fd8399d 100644 --- a/exn/src/impls.rs +++ b/exn/src/impls.rs @@ -12,32 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error; use std::fmt; use std::marker::PhantomData; use std::panic::Location; +use crate::Error; + /// An exception type that can hold an error tree and additional context. -pub struct Exn { +pub struct Exn { // trade one more indirection for less stack size frame: Box, phantom: PhantomData, } -impl From for Exn { +impl From for Exn { #[track_caller] fn from(error: E) -> Self { Exn::new(error) } } -impl Exn { +impl Exn { /// Create a new exception with the given error. /// /// This will automatically walk the [source chain of the error] and add them as children /// frames. /// - /// [source chain of the error]: Error::source + /// See also [`Error::raise`] for a fluent way to convert an error into an `Exn` instance. + /// + /// [source chain of the error]: std::error::Error::source #[track_caller] pub fn new(error: E) -> Self { struct SourceError(String); @@ -54,9 +57,9 @@ impl Exn { } } - impl Error for SourceError {} + impl std::error::Error for SourceError {} - fn walk(error: &dyn Error, location: &'static Location<'static>) -> Vec { + fn walk(error: &dyn std::error::Error, location: &'static Location<'static>) -> Vec { if let Some(source) = error.source() { let children = vec![Frame { error: Box::new(SourceError(source.to_string())), @@ -87,7 +90,7 @@ impl Exn { #[track_caller] pub fn from_iter(children: I, err: E) -> Self where - T: Error + 'static, + T: Error, I: IntoIterator, I::Item: Into>, { @@ -124,7 +127,7 @@ impl Exn { /// A frame in the exception tree. pub struct Frame { /// The error that occurred at this frame. - error: Box, + error: Box, /// The source code location where this exception frame was created. location: &'static Location<'static>, /// Child exception frames that provide additional context or source errors. @@ -133,7 +136,7 @@ pub struct Frame { impl Frame { /// Return the error that occurred at this frame. - pub fn error(&self) -> &(dyn Error + 'static) { + pub fn error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) { &*self.error } diff --git a/exn/src/lib.rs b/exn/src/lib.rs index 46ecec8..a772899 100644 --- a/exn/src/lib.rs +++ b/exn/src/lib.rs @@ -88,6 +88,20 @@ pub use self::option::OptionExt; pub use self::result::Result; pub use self::result::ResultExt; +/// A trait bound of the supported error type of [`Exn`]. +pub trait Error: std::error::Error + Send + Sync + 'static { + /// Raise this error as a new exception. + #[track_caller] + fn raise(self) -> Exn + where + Self: Sized, + { + Exn::new(self) + } +} + +impl Error for T where T: std::error::Error + Send + Sync + 'static {} + /// Equivalent to `Ok::<_, Exn>(value)`. /// /// This simplifies creation of an `exn::Result` in places where type inference cannot deduce the @@ -105,6 +119,6 @@ pub use self::result::ResultExt; /// | consider giving this pattern the explicit type `std::result::Result`, where the type parameter `E` is specified /// ``` #[expect(non_snake_case)] -pub fn Ok(value: T) -> Result { +pub fn Ok(value: T) -> Result { Result::Ok(value) } diff --git a/exn/src/option.rs b/exn/src/option.rs index e9f589c..209fd7e 100644 --- a/exn/src/option.rs +++ b/exn/src/option.rs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error; - +use crate::Error; use crate::Exn; use crate::Result; diff --git a/exn/src/result.rs b/exn/src/result.rs index 8b0f5e6..8d58548 100644 --- a/exn/src/result.rs +++ b/exn/src/result.rs @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error; - +use crate::Error; use crate::Exn; /// A reasonable return type to use throughout an application. @@ -25,7 +24,7 @@ pub trait ResultExt { type Success; /// The `Err` type that would be wrapped in an [`Exn`]. - type Error: Error + 'static; + type Error: Error; /// Raise a new exception on the [`Exn`] inside the [`Result`]. /// @@ -38,7 +37,7 @@ pub trait ResultExt { impl ResultExt for std::result::Result where - E: Error + 'static, + E: Error, { type Success = T; type Error = E; @@ -46,7 +45,7 @@ where #[track_caller] fn or_raise(self, err: F) -> Result where - A: Error + 'static, + A: Error, F: FnOnce() -> A, { match self { @@ -66,7 +65,7 @@ where #[track_caller] fn or_raise(self, err: F) -> Result where - A: Error + 'static, + A: Error, F: FnOnce() -> A, { match self { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1a3110a..3113287 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -66,7 +66,6 @@ struct CommandTest { impl CommandTest { fn run(self) { run_command(make_test_cmd(self.no_capture, true, &[])); - #[cfg(not(windows_test))] run_example_tests(); } } @@ -88,7 +87,6 @@ impl CommandLint { } } -#[cfg(not(windows_test))] fn run_example_tests() { let examples_dir = PathBuf::from(env!("CARGO_WORKSPACE_DIR")) .join("examples")