diff --git a/README.md b/README.md index 6dd66e8..56e80be 100644 --- a/README.md +++ b/README.md @@ -208,24 +208,30 @@ implies that you're creating a new error that saves the old error as its `source`. With `Option` there is no source error to wrap, so `wrap_err` ends up being somewhat meaningless. -Instead `eyre` intends for users to use the combinator functions provided by -`std` for converting `Option`s to `Result`s. So where you would write this with +Instead `eyre` offers [`OptionExt::ok_or_eyre`] to yield _static_ errors from `None`, +and intends for users to use the combinator functions provided by +`std`, converting `Option`s to `Result`s, for _dynamic_ errors. +So where you would write this with anyhow: +[`OptionExt::ok_or_eyre`]: https://docs.rs/eyre/latest/eyre/trait.OptionExt.html#tymethod.ok_or_eyre + ```rust use anyhow::Context; let opt: Option<()> = None; -let result = opt.context("new error message"); +let result_static = opt.context("static error message"); +let result_dynamic = opt.with_context(|| format!("{} error message", "dynamic")); ``` With `eyre` we want users to write: ```rust -use eyre::{eyre, Result}; +use eyre::{eyre, OptionExt, Result}; let opt: Option<()> = None; -let result: Result<()> = opt.ok_or_else(|| eyre!("new error message")); +let result_static: Result<()> = opt.ok_or_eyre("static error message"); +let result_dynamic: Result<()> = opt.ok_or_else(|| eyre!("{} error message", "dynamic")); ``` **NOTE**: However, to help with porting we do provide a `ContextCompat` trait which diff --git a/eyre/CHANGELOG.md b/eyre/CHANGELOG.md index 84f809a..949ad8f 100644 --- a/eyre/CHANGELOG.md +++ b/eyre/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Added +- Add `OptionExt::ok_or_eyre` for yielding static `Report`s from `None` [by LeoniePhiline](https://github.com/eyre-rs/eyre/pull/125) ## [0.6.9] - 2023-11-17 ### Fixed diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index 3d887ab..979a50f 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -273,27 +273,31 @@ //! `source`. With `Option` there is no source error to wrap, so `wrap_err` ends up //! being somewhat meaningless. //! -//! Instead `eyre` intends for users to use the combinator functions provided by -//! `std` for converting `Option`s to `Result`s. So where you would write this with +//! Instead `eyre` offers [`OptionExt::ok_or_eyre`] to yield _static_ errors from `None`, +//! and intends for users to use the combinator functions provided by +//! `std`, converting `Option`s to `Result`s, for _dynamic_ errors. +//! So where you would write this with //! anyhow: //! //! ```rust //! use anyhow::Context; //! //! let opt: Option<()> = None; -//! let result = opt.context("new error message"); +//! let result_static = opt.context("static error message"); +//! let result_dynamic = opt.with_context(|| format!("{} error message", "dynamic")); //! ``` //! //! With `eyre` we want users to write: //! //! ```rust -//! use eyre::{eyre, Result}; +//! use eyre::{eyre, OptionExt, Result}; //! //! # #[cfg(not(feature = "auto-install"))] //! # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap(); //! # //! let opt: Option<()> = None; -//! let result: Result<()> = opt.ok_or_else(|| eyre!("new error message")); +//! let result_static: Result<()> = opt.ok_or_eyre("static error message"); +//! let result_dynamic: Result<()> = opt.ok_or_else(|| eyre!("{} error message", "dynamic")); //! ``` //! //! **NOTE**: However, to help with porting we do provide a `ContextCompat` trait which @@ -359,12 +363,13 @@ mod error; mod fmt; mod kind; mod macros; +mod option; mod ptr; mod wrapper; use crate::backtrace::Backtrace; use crate::error::ErrorImpl; -use core::fmt::Display; +use core::fmt::{Debug, Display}; use std::error::Error as StdError; @@ -1120,6 +1125,61 @@ pub trait WrapErr: context::private::Sealed { F: FnOnce() -> D; } +/// Provides the [`ok_or_eyre`][OptionExt::ok_or_eyre] method for [`Option`]. +/// +/// This trait is sealed and cannot be implemented for types outside of +/// `eyre`. +/// +/// # Example +/// +/// ``` +/// # #[cfg(not(feature = "auto-install"))] +/// # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap(); +/// use eyre::OptionExt; +/// +/// let option: Option<()> = None; +/// +/// let result = option.ok_or_eyre("static str error"); +/// +/// assert_eq!(result.unwrap_err().to_string(), "static str error"); +/// ``` +/// +/// # `ok_or_eyre` vs `ok_or_else` +/// +/// If string interpolation is required for the generated [report][Report], +/// use [`ok_or_else`][Option::ok_or_else] instead, +/// invoking [`eyre!`] to perform string interpolation: +/// +/// ``` +/// # #[cfg(not(feature = "auto-install"))] +/// # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap(); +/// use eyre::eyre; +/// +/// let option: Option<()> = None; +/// +/// let result = option.ok_or_else(|| eyre!("{} error", "dynamic")); +/// +/// assert_eq!(result.unwrap_err().to_string(), "dynamic error"); +/// ``` +/// +/// `ok_or_eyre` incurs no runtime cost, as the error object +/// is constructed from the provided static argument +/// only in the `None` case. +pub trait OptionExt: context::private::Sealed { + /// Transform the [`Option`] into a [`Result`], + /// mapping [`Some(v)`][Option::Some] to [`Ok(v)`][Result::Ok] + /// and [`None`] to [`Report`]. + /// + /// `ok_or_eyre` allows for eyre [`Report`] error objects + /// to be lazily created from static messages in the `None` case. + /// + /// For dynamic error messages, use [`ok_or_else`][Option::ok_or_else], + /// invoking [`eyre!`] in the closure to perform string interpolation. + fn ok_or_eyre(self, message: M) -> crate::Result + where + M: Debug + Display + Send + Sync + 'static; +} + /// Provides the `context` method for `Option` when porting from `anyhow` /// /// This trait is sealed and cannot be implemented for types outside of diff --git a/eyre/src/option.rs b/eyre/src/option.rs new file mode 100644 index 0000000..eaa1e84 --- /dev/null +++ b/eyre/src/option.rs @@ -0,0 +1,14 @@ +use crate::OptionExt; +use core::fmt::{Debug, Display}; + +impl OptionExt for Option { + fn ok_or_eyre(self, message: M) -> crate::Result + where + M: Debug + Display + Send + Sync + 'static, + { + match self { + Some(ok) => Ok(ok), + None => Err(crate::Report::msg(message)), + } + } +} diff --git a/eyre/tests/test_option.rs b/eyre/tests/test_option.rs new file mode 100644 index 0000000..7c9349a --- /dev/null +++ b/eyre/tests/test_option.rs @@ -0,0 +1,15 @@ +mod common; + +use self::common::maybe_install_handler; +use eyre::OptionExt; + +#[test] +fn test_option_ok_or_eyre() { + maybe_install_handler().unwrap(); + + let option: Option<()> = None; + + let result = option.ok_or_eyre("static str error"); + + assert_eq!(result.unwrap_err().to_string(), "static str error"); +}