From 91e36dea2f179b697fb626a2a878c473de0b560b Mon Sep 17 00:00:00 2001 From: LeoniePhiline <22329650+LeoniePhiline@users.noreply.github.com> Date: Wed, 6 Dec 2023 15:36:57 +0100 Subject: [PATCH] Extend `Option` with `ok_or_eyre` Previously, a closure and macro invocation was required to generate a static string error object from an `Option::None`. This change adds an extension trait, providing the `ok_or_eyre` method on the `Option` type. `Option::ok_or_eyre` accepts static error messages and creates `Report` objects lazily in the `None` case. Implements #125 --- eyre/src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++- eyre/src/option.rs | 14 ++++++++++ eyre/tests/test_option.rs | 15 +++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 eyre/src/option.rs create mode 100644 eyre/tests/test_option.rs diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index 3d887ab..ff59760 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -359,12 +359,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 +1121,57 @@ 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 +/// +/// ``` +/// 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: +/// +/// ``` +/// 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..133d6ca --- /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_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"); +}