Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ repository = "https://github.com/fast/exn"
exn = { path = "exn" }

# Crates.io dependencies
anyhow = { version = "1.0.100" }
clap = { version = "4.5.20", features = ["derive"] }
derive_more = { version = "2.1.0", features = ["full"] }
insta = { version = "1.45.1" }
Expand Down
9 changes: 9 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,19 @@ path = "src/downcast.rs"
name = "make-error"
path = "src/make-error.rs"

[[example]]
name = "into-anyhow"
path = "src/into-anyhow.rs"

[[example]]
name = "into-std-error"
path = "src/into-std-error.rs"

[package.metadata.release]
release = false

[dev-dependencies]
anyhow = { workspace = true }
derive_more = { workspace = true }
exn = { workspace = true }

Expand Down
75 changes: 75 additions & 0 deletions examples/src/into-anyhow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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.

//! # Anyhow Interoperate Example - Returning `anyhow::Result<_>`
//!
//! This example shows a common pattern:
//! - Using `exn::Result<T, E>` internally.
//! - At the boundary, convert `Exn<E>` into `anyhow::Error`.

use std::error::Error;

use derive_more::Display;
use exn::Result;
use exn::ResultExt;

fn main() -> anyhow::Result<()> {
app::run().map_err(convert_error)?;
Ok(())
}

fn convert_error<E: Error + Send + Sync + 'static>(err: exn::Exn<E>) -> anyhow::Error {
anyhow::Error::from_boxed(err.into())
}

mod app {
use super::*;

pub fn run() -> Result<(), AppError> {
let port = config::load_port().or_raise(|| AppError)?;
let _ = port;
Ok(())
}

#[derive(Debug, Display)]
#[display("failed to start app")]
pub struct AppError;
impl Error for AppError {}
}

mod config {
use super::*;

pub fn load_port() -> Result<u16, ConfigError> {
let raw = "not-a-number";

let port = raw
.parse::<u16>()
.or_raise(|| ConfigError(format!("PORT must be a number; got {raw:?}")))?;

Ok(port)
}

#[derive(Debug, Display)]
pub struct ConfigError(String);
impl Error for ConfigError {}
}

// Output when running `cargo run -p examples --example into-anyhow`:
//
// Error: failed to start app
//
// Caused by:
// 0: PORT must be a number; got "not-a-number"
// 1: invalid digit found in string
71 changes: 71 additions & 0 deletions examples/src/into-std-error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// 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.

//! # std::error::Error Interoperate Example - Returning `Result<_, Box<dyn std::error::Error>>`
//!
//! This example shows a common pattern:
//! - Using `exn::Result<T, E>` internally.
//! - At the boundary, convert `Exn<E>` into `Box<dyn std::error::Error>`.

use std::error::Error;

use derive_more::Display;
use exn::Result;
use exn::ResultExt;

fn main() -> std::result::Result<(), Box<dyn Error>> {
app::run()?;
Ok(())
}

mod app {
use super::*;

pub fn run() -> Result<(), AppError> {
let port = config::load_port().or_raise(|| AppError)?;
let _ = port;
Ok(())
}

#[derive(Debug, Display)]
#[display("failed to start app")]
pub struct AppError;
impl Error for AppError {}
}

mod config {
use super::*;

pub fn load_port() -> Result<u16, ConfigError> {
let raw = "not-a-number";

let port = raw
.parse::<u16>()
.or_raise(|| ConfigError(format!("PORT must be a number; got {raw:?}")))?;

Ok(port)
}

#[derive(Debug, Display)]
pub struct ConfigError(String);
impl Error for ConfigError {}
}

// Output when running `cargo run -p examples --example into-std-error`:
//
// Error: failed to start app, at examples/src/into-std-error.rs:36:40
// |
// |-> PORT must be a number; got "not-a-number", at examples/src/into-std-error.rs:55:14
// |
// |-> invalid digit found in string, at examples/src/into-std-error.rs:55:14
26 changes: 26 additions & 0 deletions exn/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,29 @@ impl Frame {
&self.children
}
}

impl Error for Frame {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.children
.first()
.map(|child| child as &(dyn Error + 'static))
}
}

impl<E: Error + Send + Sync + 'static> From<Exn<E>> for Box<dyn Error + 'static> {
fn from(exn: Exn<E>) -> Self {
Box::new(exn.frame)
}
}

impl<E: Error + Send + Sync + 'static> From<Exn<E>> for Box<dyn Error + Send + 'static> {
fn from(exn: Exn<E>) -> Self {
Box::new(exn.frame)
}
}

impl<E: Error + Send + Sync + 'static> From<Exn<E>> for Box<dyn Error + Send + Sync + 'static> {
fn from(exn: Exn<E>) -> Self {
Box::new(exn.frame)
}
}