Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend Option with ok_or_eyre #129

Merged
merged 1 commit into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
}
Loading