Skip to content

Commit

Permalink
Extend Option with ok_or_eyre (#129)
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 authored Dec 6, 2023
1 parent da84e8c commit 4e1f323
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 11 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions eyre/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- next-header -->

## [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
Expand Down
72 changes: 66 additions & 6 deletions eyre/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1120,6 +1125,61 @@ 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
///
/// ```
/// # #[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<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_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");
}

0 comments on commit 4e1f323

Please sign in to comment.