diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d6f08..0601820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,3 +13,4 @@ All significant changes to this project will be documented in this file. ### New Features * This crate is now `no_std` compatible, while the `alloc` crate is still required for heap allocations. It is worth noting that `no_std` support is a nice-to-have feature, and can be dropped if it blocks other important features in the future. Before 1.0, once `exn` APIs settle down, the decision on whether to keep `no_std` as a promise will be finalized. +* `Frame` now implements `std::error::Error`, and `Exn` can be converted into `Box`. diff --git a/Cargo.lock b/Cargo.lock index 9c9adba..460a695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "bitflags" version = "2.10.0" @@ -180,6 +186,7 @@ dependencies = [ name = "examples" version = "0.0.0" dependencies = [ + "anyhow", "derive_more", "exn", ] diff --git a/Cargo.toml b/Cargo.toml index 5b4c41e..a70ffc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9285df6..f053462 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -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 } diff --git a/examples/src/into-anyhow.rs b/examples/src/into-anyhow.rs new file mode 100644 index 0000000..ff9e253 --- /dev/null +++ b/examples/src/into-anyhow.rs @@ -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` internally. +//! - At the boundary, convert `Exn` 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(err: exn::Exn) -> 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 { + let raw = "not-a-number"; + + let port = raw + .parse::() + .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 diff --git a/examples/src/into-std-error.rs b/examples/src/into-std-error.rs new file mode 100644 index 0000000..d76f598 --- /dev/null +++ b/examples/src/into-std-error.rs @@ -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>` +//! +//! This example shows a common pattern: +//! - Using `exn::Result` internally. +//! - At the boundary, convert `Exn` into `Box`. + +use std::error::Error; + +use derive_more::Display; +use exn::Result; +use exn::ResultExt; + +fn main() -> std::result::Result<(), Box> { + 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 { + let raw = "not-a-number"; + + let port = raw + .parse::() + .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 diff --git a/exn/src/impls.rs b/exn/src/impls.rs index 389fe74..98f54d3 100644 --- a/exn/src/impls.rs +++ b/exn/src/impls.rs @@ -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 From> for Box { + fn from(exn: Exn) -> Self { + Box::new(exn.frame) + } +} + +impl From> for Box { + fn from(exn: Exn) -> Self { + Box::new(exn.frame) + } +} + +impl From> for Box { + fn from(exn: Exn) -> Self { + Box::new(exn.frame) + } +}