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
2 changes: 2 additions & 0 deletions exn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]

pub mod report;

mod debug;
mod display;
mod ext;
Expand Down
32 changes: 32 additions & 0 deletions exn/src/report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2025 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Alternative [`Exn`] representation providers
//!
//! Types in this module wrap [`Exn`] to provide alternative implementations of its [`Debug`] and
//! [`Display`] traits.
//!
//! [`Exn`]: crate::Exn
//! [`Debug`]: std::fmt::Debug
//! [`Display`]: std::fmt::Display
mod compact;
mod native;

#[doc(inline)]
pub use compact::Compact;
#[doc(inline)]
pub use native::Native;

trait Report: std::fmt::Debug + std::fmt::Display + Send + Sync + 'static {}
169 changes: 169 additions & 0 deletions exn/src/report/compact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2025 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::error::Error;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;

use super::Report;
use crate::Exn;
use crate::Frame;

/// Compact [`Debug`] representation provider for [`Exn`]
///
/// This report type does not modify [`Exn`]'s [`Display`] representation.
///
/// # Example
///
/// ```
/// use std::io::Error;
///
/// use exn::ErrorExt;
/// use exn::Exn;
///
/// let first = Error::other("first").raise();
/// let second = Error::other("second").raise();
/// let third = Error::other("third").raise();
/// let fourth = Exn::from_iter([first, second], Error::other("fourth"));
/// let fifth = Exn::from_iter([fourth, third], Error::other("fifth"));
///
/// println!("{:?}", exn::report::Compact::from(fifth));
/// ```
///
/// This prints something similar to the following:
///
/// ```text
/// fifth, at exn/src/report/compact.rs:42:17
/// ├─ fourth, at exn/src/report/compact.rs:41:18
/// │ ├─ first, at exn/src/report/compact.rs:38:39
/// │ └─ second, at exn/src/report/compact.rs:39:41
/// └─ third, at exn/src/report/compact.rs:40:39
/// ```
///
/// [`Compact`] is convertible from any [`Exn`], so it is suitable for application-level error
/// reporting:
///
/// ```no_run
/// use std::fmt;
/// use std::io;
///
/// use exn::ErrorExt;
///
/// fn foo() -> exn::Result<(), fmt::Error> {
/// Err(fmt::Error.raise())
/// }
///
/// fn bar() -> exn::Result<(), io::Error> {
/// Err(io::Error::other("bar").raise())
/// }
///
/// fn main() -> Result<(), exn::report::Compact> {
/// foo()?;
/// bar()?;
/// Ok(())
/// }
/// ```
pub struct Compact(Box<dyn Report>);

impl Debug for Compact {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&*self.0, f)
}
}

impl Display for Compact {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&*self.0, f)
}
}

impl Error for Compact {}

impl<T> From<Exn<T>> for Compact
where
T: Error + Send + Sync + 'static,
{
fn from(exn: Exn<T>) -> Self {
Self(Box::new(CompactExn(exn)))
}
}

struct CompactExn<T>(Exn<T>)
where
T: Error + Send + Sync + 'static;

impl<T> Debug for CompactExn<T>
where
T: Error + Send + Sync + 'static,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
CompactFrame(self.0.frame()).debug(f)
}
}

impl<T> Display for CompactExn<T>
where
T: Error + Send + Sync + 'static,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}

impl<T> Report for CompactExn<T> where T: Error + Send + Sync + 'static {}

struct CompactFrame<'a>(&'a Frame);

impl CompactFrame<'_> {
fn debug(&self, f: &mut Formatter) -> fmt::Result {
self.debug_recursive(f, true, "")
}

fn debug_recursive(&self, f: &mut Formatter, root: bool, prefix: &str) -> fmt::Result {
let frame = self.0;

{
let location = frame.location();
write!(
f,
"{}, at {}:{}:{}",
frame.error(),
location.file(),
location.line(),
location.column()
)?;
}

let children = frame.children();
let child_count = children.len();
for (i, child) in children.iter().enumerate() {
let child_child_count = child.children().len();
let child = Self(child);
if root && child_count == 1 && child_child_count == 1 {
write!(f, "\n{prefix}├─ ")?;
child.debug_recursive(f, root, prefix)?;
} else if i + 1 < child_count {
write!(f, "\n{prefix}├─ ")?;
child.debug_recursive(f, false, &format!("{prefix}│ "))?;
} else {
write!(f, "\n{prefix}└─ ")?;
child.debug_recursive(f, false, &format!("{prefix} "))?;
}
}

Ok(())
}
}
98 changes: 98 additions & 0 deletions exn/src/report/native.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2025 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::error::Error;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;

use super::Report;
use crate::Exn;

/// Native representation provider for [`Exn`]
///
/// This report type provides [`Exn`]'s [`Debug`] and [`Display`] representations unmodified.
///
/// [`Native`] is convertible from any [`Exn`], so it is suitable for application-level error
/// reporting:
///
/// ```no_run
/// use std::fmt;
/// use std::io;
///
/// use exn::ErrorExt;
///
/// fn foo() -> exn::Result<(), fmt::Error> {
/// Err(fmt::Error.raise())
/// }
///
/// fn bar() -> exn::Result<(), io::Error> {
/// Err(io::Error::other("bar").raise())
/// }
///
/// fn main() -> Result<(), exn::report::Native> {
/// foo()?;
/// bar()?;
/// Ok(())
/// }
/// ```
pub struct Native(Box<dyn Report>);

impl Debug for Native {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&*self.0, f)
}
}

impl Display for Native {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&*self.0, f)
}
}

impl Error for Native {}

impl<T> From<Exn<T>> for Native
where
T: Error + Send + Sync + 'static,
{
fn from(exn: Exn<T>) -> Self {
Self(Box::new(NativeExn(exn)))
}
}

struct NativeExn<T>(Exn<T>)
where
T: Error + Send + Sync + 'static;

impl<T> Debug for NativeExn<T>
where
T: Error + Send + Sync + 'static,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}

impl<T> Display for NativeExn<T>
where
T: Error + Send + Sync + 'static,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}

impl<T> Report for NativeExn<T> where T: Error + Send + Sync + 'static {}
61 changes: 61 additions & 0 deletions exn/tests/report_compact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2025 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use exn::ErrorExt;
use exn::Exn;
use exn::report::Compact;

#[derive(Debug)]
struct SimpleError(&'static str);

impl std::fmt::Display for SimpleError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

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

#[test]
fn test_report_compact_straightforward() {
let e1 = SimpleError("E1").raise();
let e2 = e1.raise(SimpleError("E2"));
let e3 = e2.raise(SimpleError("E3"));
let e4 = e3.raise(SimpleError("E4"));
let e5 = e4.raise(SimpleError("E5"));
insta::assert_debug_snapshot!(Compact::from(e5));
}

#[test]
fn test_report_compact_tree() {
let e1 = SimpleError("E1").raise();
let e3 = e1.raise(SimpleError("E3"));

let e9 = SimpleError("E9").raise();
let e10 = e9.raise(SimpleError("E10"));

let e11 = SimpleError("E11").raise();
let e12 = e11.raise(SimpleError("E12"));

let e5 = Exn::from_iter([e3, e10, e12], SimpleError("E5"));

let e2 = SimpleError("E2").raise();
let e4 = e2.raise(SimpleError("E4"));

let e7 = SimpleError("E7").raise();
let e8 = e7.raise(SimpleError("E8"));

let e6 = Exn::from_iter([e5, e4, e8], SimpleError("E6"));
insta::assert_debug_snapshot!(Compact::from(e6));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: exn/tests/report_compact.rs
expression: "Compact::from(e5)"
---
E5, at exn/tests/report_compact.rs:36:17
├─ E4, at exn/tests/report_compact.rs:35:17
├─ E3, at exn/tests/report_compact.rs:34:17
├─ E2, at exn/tests/report_compact.rs:33:17
└─ E1, at exn/tests/report_compact.rs:32:32
16 changes: 16 additions & 0 deletions exn/tests/snapshots/report_compact__report_compact_tree.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: exn/tests/report_compact.rs
expression: "Compact::from(e6)"
---
E6, at exn/tests/report_compact.rs:59:14
├─ E5, at exn/tests/report_compact.rs:51:14
│ ├─ E3, at exn/tests/report_compact.rs:43:17
│ │ └─ E1, at exn/tests/report_compact.rs:42:32
│ ├─ E10, at exn/tests/report_compact.rs:46:18
│ │ └─ E9, at exn/tests/report_compact.rs:45:32
│ └─ E12, at exn/tests/report_compact.rs:49:19
│ └─ E11, at exn/tests/report_compact.rs:48:34
├─ E4, at exn/tests/report_compact.rs:54:17
│ └─ E2, at exn/tests/report_compact.rs:53:32
└─ E8, at exn/tests/report_compact.rs:57:17
└─ E7, at exn/tests/report_compact.rs:56:32
Loading