Skip to content

Commit

Permalink
Extend Option with ok_or_eyre
Browse files Browse the repository at this point in the history
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
  • Loading branch information
LeoniePhiline committed Dec 6, 2023
1 parent da84e8c commit 91e36de
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 1 deletion.
54 changes: 53 additions & 1 deletion eyre/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1120,6 +1121,57 @@ pub trait WrapErr<T, E>: 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<T>: context::private::Sealed {
/// Transform the [`Option<T>`] into a [`Result<T, E>`],
/// 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<M>(self, message: M) -> crate::Result<T>
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
Expand Down
14 changes: 14 additions & 0 deletions eyre/src/option.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::OptionExt;
use core::fmt::{Debug, Display};

impl<T> OptionExt<T> for Option<T> {
fn ok_or_eyre<M>(self, message: M) -> crate::Result<T>
where
M: Debug + Display + Send + Sync + 'static,
{
match self {
Some(ok) => Ok(ok),
None => Err(crate::Report::msg(message)),
}
}
}
15 changes: 15 additions & 0 deletions eyre/tests/test_option.rs
Original file line number Diff line number Diff line change
@@ -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");
}

0 comments on commit 91e36de

Please sign in to comment.