Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/(?<!\/)\/(?!\/)/\\/g if /at examples/' examples/src/*.rs
shell: bash
- name: Run unit tests
run: cargo x test --no-capture
shell: bash
env:
RUSTFLAGS: --cfg windows_test

required:
name: Required
Expand Down
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ insta = { version = "1.45.1" }
which = { version = "8.0.0" }

[workspace.lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = ['cfg(windows_test)'] }
unknown_lints = "deny"

[workspace.lints.clippy]
Expand Down
2 changes: 1 addition & 1 deletion examples/src/custom-layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
//! This example shows how to traverse the error chain and create custom
//! formatting to match your application's needs.

use std::error::Error;
use std::fmt::Write;

use exn::Error;
use exn::Exn;
use exn::Frame;
use exn::Result;
Expand Down
9 changes: 4 additions & 5 deletions examples/src/downcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@
//! - Recover from specific error types
//! - Extract structured data (HTTP codes, retry hints, etc.)

use std::error::Error;

use derive_more::Display;
use exn::Error;
use exn::Exn;
use exn::Frame;
use exn::Result;
Expand Down Expand Up @@ -119,8 +118,8 @@ mod http {
// Retryable error, attempting retry #3
//
// HTTP error with status code: 503
// Error: fatal error occurred in application, at examples/src/downcast.rs:54:24
// Error: fatal error occurred in application, at examples/src/downcast.rs:53:24
// |
// |-> 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
64 changes: 10 additions & 54 deletions exn/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand All @@ -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}")
}
2 changes: 1 addition & 1 deletion exn/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<E: Error> fmt::Display for Exn<E> {
Expand Down
23 changes: 13 additions & 10 deletions exn/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<E: Error + 'static> {
pub struct Exn<E: Error> {
// trade one more indirection for less stack size
frame: Box<Frame>,
phantom: PhantomData<E>,
}

impl<E: Error + 'static> From<E> for Exn<E> {
impl<E: Error> From<E> for Exn<E> {
#[track_caller]
fn from(error: E) -> Self {
Exn::new(error)
}
}

impl<E: Error + 'static> Exn<E> {
impl<E: Error> Exn<E> {
/// 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);
Expand All @@ -54,9 +57,9 @@ impl<E: Error + 'static> Exn<E> {
}
}

impl Error for SourceError {}
impl std::error::Error for SourceError {}

fn walk(error: &dyn Error, location: &'static Location<'static>) -> Vec<Frame> {
fn walk(error: &dyn std::error::Error, location: &'static Location<'static>) -> Vec<Frame> {
if let Some(source) = error.source() {
let children = vec![Frame {
error: Box::new(SourceError(source.to_string())),
Expand Down Expand Up @@ -87,7 +90,7 @@ impl<E: Error + 'static> Exn<E> {
#[track_caller]
pub fn from_iter<T, I>(children: I, err: E) -> Self
where
T: Error + 'static,
T: Error,
I: IntoIterator,
I::Item: Into<Exn<T>>,
{
Expand Down Expand Up @@ -124,7 +127,7 @@ impl<E: Error + 'static> Exn<E> {
/// A frame in the exception tree.
pub struct Frame {
/// The error that occurred at this frame.
error: Box<dyn Error + 'static>,
error: Box<dyn Error>,
/// The source code location where this exception frame was created.
location: &'static Location<'static>,
/// Child exception frames that provide additional context or source errors.
Expand All @@ -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
}

Expand Down
16 changes: 15 additions & 1 deletion exn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self>
where
Self: Sized,
{
Exn::new(self)
}
}

impl<T> Error for T where T: std::error::Error + Send + Sync + 'static {}

/// Equivalent to `Ok::<_, Exn<E>>(value)`.
///
/// This simplifies creation of an `exn::Result` in places where type inference cannot deduce the
Expand All @@ -105,6 +119,6 @@ pub use self::result::ResultExt;
/// | consider giving this pattern the explicit type `std::result::Result<i32, E>`, where the type parameter `E` is specified
/// ```
#[expect(non_snake_case)]
pub fn Ok<T, E: std::error::Error + 'static>(value: T) -> Result<T, E> {
pub fn Ok<T, E: Error>(value: T) -> Result<T, E> {
Result::Ok(value)
}
3 changes: 1 addition & 2 deletions exn/src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
11 changes: 5 additions & 6 deletions exn/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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`].
///
Expand All @@ -38,15 +37,15 @@ pub trait ResultExt {

impl<T, E> ResultExt for std::result::Result<T, E>
where
E: Error + 'static,
E: Error,
{
type Success = T;
type Error = E;

#[track_caller]
fn or_raise<A, F>(self, err: F) -> Result<Self::Success, A>
where
A: Error + 'static,
A: Error,
F: FnOnce() -> A,
{
match self {
Expand All @@ -66,7 +65,7 @@ where
#[track_caller]
fn or_raise<A, F>(self, err: F) -> Result<Self::Success, A>
where
A: Error + 'static,
A: Error,
F: FnOnce() -> A,
{
match self {
Expand Down
2 changes: 0 additions & 2 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand All @@ -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")
Expand Down
Loading