Skip to content

Commit 49d4d96

Browse files
committed
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 eyre-rs#125
1 parent da84e8c commit 49d4d96

File tree

4 files changed

+97
-6
lines changed

4 files changed

+97
-6
lines changed

eyre/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
<!-- next-header -->
88

99
## [Unreleased] - ReleaseDate
10+
### Added
11+
- Add `OptionExt::ok_or_eyre` for yielding static `Report`s from `None` [by LeoniePhiline](https://github.com/eyre-rs/eyre/pull/125)
1012

1113
## [0.6.9] - 2023-11-17
1214
### Fixed

eyre/src/lib.rs

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,27 +273,31 @@
273273
//! `source`. With `Option` there is no source error to wrap, so `wrap_err` ends up
274274
//! being somewhat meaningless.
275275
//!
276-
//! Instead `eyre` intends for users to use the combinator functions provided by
277-
//! `std` for converting `Option`s to `Result`s. So where you would write this with
276+
//! Instead `eyre` offers [`OptionExt::ok_or_eyre`] to yield _static_ errors from `None`,
277+
//! and intends for users to use the combinator functions provided by
278+
//! `std`, converting `Option`s to `Result`s, for _dynamic_ errors.
279+
//! So where you would write this with
278280
//! anyhow:
279281
//!
280282
//! ```rust
281283
//! use anyhow::Context;
282284
//!
283285
//! let opt: Option<()> = None;
284-
//! let result = opt.context("new error message");
286+
//! let result_static = opt.context("static error message");
287+
//! let result_dynamic = opt.with_context(|| format!("{} error message", "dynamic"));
285288
//! ```
286289
//!
287290
//! With `eyre` we want users to write:
288291
//!
289292
//! ```rust
290-
//! use eyre::{eyre, Result};
293+
//! use eyre::{eyre, OptionExt, Result};
291294
//!
292295
//! # #[cfg(not(feature = "auto-install"))]
293296
//! # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
294297
//! #
295298
//! let opt: Option<()> = None;
296-
//! let result: Result<()> = opt.ok_or_else(|| eyre!("new error message"));
299+
//! let result_static: Result<()> = opt.ok_or_eyre("static error message");
300+
//! let result_dynamic: Result<()> = opt.ok_or_else(|| eyre!("{} error message", "dynamic"));
297301
//! ```
298302
//!
299303
//! **NOTE**: However, to help with porting we do provide a `ContextCompat` trait which
@@ -359,12 +363,13 @@ mod error;
359363
mod fmt;
360364
mod kind;
361365
mod macros;
366+
mod option;
362367
mod ptr;
363368
mod wrapper;
364369

365370
use crate::backtrace::Backtrace;
366371
use crate::error::ErrorImpl;
367-
use core::fmt::Display;
372+
use core::fmt::{Debug, Display};
368373

369374
use std::error::Error as StdError;
370375

@@ -1120,6 +1125,61 @@ pub trait WrapErr<T, E>: context::private::Sealed {
11201125
F: FnOnce() -> D;
11211126
}
11221127

1128+
/// Provides the [`ok_or_eyre`][OptionExt::ok_or_eyre] method for [`Option`].
1129+
///
1130+
/// This trait is sealed and cannot be implemented for types outside of
1131+
/// `eyre`.
1132+
///
1133+
/// # Example
1134+
///
1135+
/// ```
1136+
/// # #[cfg(not(feature = "auto-install"))]
1137+
/// # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
1138+
/// use eyre::OptionExt;
1139+
///
1140+
/// let option: Option<()> = None;
1141+
///
1142+
/// let result = option.ok_or_eyre("static str error");
1143+
///
1144+
/// assert_eq!(result.unwrap_err().to_string(), "static str error");
1145+
/// ```
1146+
///
1147+
/// # `ok_or_eyre` vs `ok_or_else`
1148+
///
1149+
/// If string interpolation is required for the generated [report][Report],
1150+
/// use [`ok_or_else`][Option::ok_or_else] instead,
1151+
/// invoking [`eyre!`] to perform string interpolation:
1152+
///
1153+
/// ```
1154+
/// # #[cfg(not(feature = "auto-install"))]
1155+
/// # eyre::set_hook(Box::new(eyre::DefaultHandler::default_with)).unwrap();
1156+
/// use eyre::eyre;
1157+
///
1158+
/// let option: Option<()> = None;
1159+
///
1160+
/// let result = option.ok_or_else(|| eyre!("{} error", "dynamic"));
1161+
///
1162+
/// assert_eq!(result.unwrap_err().to_string(), "dynamic error");
1163+
/// ```
1164+
///
1165+
/// `ok_or_eyre` incurs no runtime cost, as the error object
1166+
/// is constructed from the provided static argument
1167+
/// only in the `None` case.
1168+
pub trait OptionExt<T>: context::private::Sealed {
1169+
/// Transform the [`Option<T>`] into a [`Result<T, E>`],
1170+
/// mapping [`Some(v)`][Option::Some] to [`Ok(v)`][Result::Ok]
1171+
/// and [`None`] to [`Report`].
1172+
///
1173+
/// `ok_or_eyre` allows for eyre [`Report`] error objects
1174+
/// to be lazily created from static messages in the `None` case.
1175+
///
1176+
/// For dynamic error messages, use [`ok_or_else`][Option::ok_or_else],
1177+
/// invoking [`eyre!`] in the closure to perform string interpolation.
1178+
fn ok_or_eyre<M>(self, message: M) -> crate::Result<T>
1179+
where
1180+
M: Debug + Display + Send + Sync + 'static;
1181+
}
1182+
11231183
/// Provides the `context` method for `Option` when porting from `anyhow`
11241184
///
11251185
/// This trait is sealed and cannot be implemented for types outside of

eyre/src/option.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use crate::OptionExt;
2+
use core::fmt::{Debug, Display};
3+
4+
impl<T> OptionExt<T> for Option<T> {
5+
fn ok_or_eyre<M>(self, message: M) -> crate::Result<T>
6+
where
7+
M: Debug + Display + Send + Sync + 'static,
8+
{
9+
match self {
10+
Some(ok) => Ok(ok),
11+
None => Err(crate::Report::msg(message)),
12+
}
13+
}
14+
}

eyre/tests/test_option.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
mod common;
2+
3+
use self::common::maybe_install_handler;
4+
use eyre::OptionExt;
5+
6+
#[test]
7+
fn test_option_ok_or_eyre() {
8+
maybe_install_handler().unwrap();
9+
10+
let option: Option<()> = None;
11+
12+
let result = option.ok_or_eyre("static str error");
13+
14+
assert_eq!(result.unwrap_err().to_string(), "static str error");
15+
}

0 commit comments

Comments
 (0)