Skip to content
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
2 changes: 2 additions & 0 deletions packages/libs/error-stack/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ All notable changes to `error-stack` will be documented in this file.

- Add serializing support using [`serde`](https://serde.rs) ([#1290](https://github.com/hashintel/hash/pull/1290))
- Support `Debug` hooks on `no-std` platforms via the `hooks` feature ([#1556](https://github.com/hashintel/hash/pull/1556))
- Support converting `Report` into [`Error`](https://doc.rust-lang.org/core/error/trait.Error.html) via `Report::as_error` and `Report::into_error` ([#1749](https://github.com/hashintel/hash/pull/1749))
- Support converting `Report` into `Box<dyn Error>` via the `From` trait ([#1749](https://github.com/hashintel/hash/pull/1749))

## [0.2.4](https://github.com/hashintel/hash/tree/error-stack%400.2.4/packages/libs/error-stack) - 2022-11-04

Expand Down
43 changes: 43 additions & 0 deletions packages/libs/error-stack/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use core::fmt;
#[cfg(nightly)]
use core::{
any::{Demand, Provider},
error::Error,
};
#[cfg(not(nightly))]
use std::error::Error;

use crate::Report;

#[repr(transparent)]
pub(crate) struct ReportError<C>(Report<C>);

impl<C> ReportError<C> {
pub(crate) const fn new(report: Report<C>) -> Self {
Self(report)
}

pub(crate) const fn from_ref(report: &Report<C>) -> &Self {
// SAFETY: `ReportError` is a `repr(transparent)` wrapper around `Report`.
unsafe { &*(report as *const Report<C>).cast() }
}
}

impl<C> fmt::Debug for ReportError<C> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, fmt)
}
}

impl<C> fmt::Display for ReportError<C> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, fmt)
}
}

impl<C> Error for ReportError<C> {
#[cfg(nightly)]
fn provide<'a>(&'a self, demand: &mut Demand<'a>) {
self.0.frames().for_each(|frame| frame.provide(demand));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, any reason for choosing for_each over for frame in self.frames()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No specific reason. I used it here as it's only one line vs three lines.

}
}
3 changes: 2 additions & 1 deletion packages/libs/error-stack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,6 @@
)]

extern crate alloc;
extern crate core;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love how CLion always automatically adds this line...


pub mod future;
pub mod iter;
Expand All @@ -476,6 +475,8 @@ mod report;
mod result;

mod context;
#[cfg(any(nightly, feature = "std"))]
mod error;
#[cfg(any(feature = "std", feature = "hooks"))]
pub mod fmt;
#[cfg(not(any(feature = "std", feature = "hooks")))]
Expand Down
52 changes: 52 additions & 0 deletions packages/libs/error-stack/src/report.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use alloc::{boxed::Box, vec, vec::Vec};
#[cfg(nightly)]
use core::error::Error;
use core::{fmt, marker::PhantomData, mem, panic::Location};
#[cfg(all(rust_1_65, feature = "std"))]
use std::backtrace::{Backtrace, BacktraceStatus};
#[cfg(all(not(nightly), feature = "std"))]
use std::error::Error;
#[cfg(feature = "std")]
use std::process::ExitCode;

Expand Down Expand Up @@ -637,6 +641,54 @@ impl<C> Report<C> {
);
})
}

/// Converts this `Report` to an [`Error`].
#[cfg(any(nightly, feature = "std"))]
#[must_use]
pub fn into_error(self) -> impl Error + Send + Sync + 'static
where
C: 'static,
{
crate::error::ReportError::new(self)
}

/// Returns this `Report` as an [`Error`].
#[cfg(any(nightly, feature = "std"))]
#[must_use]
pub fn as_error(&self) -> &(impl Error + Send + Sync + 'static)
where
C: 'static,
{
crate::error::ReportError::from_ref(self)
}
}

#[cfg(any(nightly, feature = "std"))]
impl<C: 'static> From<Report<C>> for Box<dyn Error> {
fn from(report: Report<C>) -> Self {
Box::new(report.into_error())
}
}

#[cfg(any(nightly, feature = "std"))]
impl<C: 'static> From<Report<C>> for Box<dyn Error + Send> {
fn from(report: Report<C>) -> Self {
Box::new(report.into_error())
}
}

#[cfg(any(nightly, feature = "std"))]
impl<C: 'static> From<Report<C>> for Box<dyn Error + Sync> {
fn from(report: Report<C>) -> Self {
Box::new(report.into_error())
}
}

#[cfg(any(nightly, feature = "std"))]
impl<C: 'static> From<Report<C>> for Box<dyn Error + Send + Sync> {
fn from(report: Report<C>) -> Self {
Box::new(report.into_error())
}
}

#[cfg(feature = "std")]
Expand Down
21 changes: 21 additions & 0 deletions packages/libs/error-stack/tests/test_compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ use error_stack::IntoReportCompat;
#[cfg(feature = "std")]
use error_stack::Report;

#[test]
#[cfg(any(nightly, feature = "std"))]
fn error() {
let report = create_report().change_context(ContextA(10));
let error_ref = report.as_error();
assert_eq!(error_ref.to_string(), "context A");
#[cfg(nightly)]
assert_eq!(
*core::any::request_ref::<u32>(error_ref).expect("requested value not found"),
10
);

let error = report.into_error();
assert_eq!(error.to_string(), "context A");
#[cfg(nightly)]
assert_eq!(
*core::any::request_ref::<u32>(&error).expect("requested value not found"),
10
);
}

#[test]
#[cfg(all(feature = "std", feature = "anyhow"))]
fn anyhow() {
Expand Down
26 changes: 23 additions & 3 deletions packages/libs/error-stack/tests/test_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,42 @@

use std::io;

use error_stack::IntoReport;
use error_stack::{IntoReport, ResultExt};

fn io_error() -> Result<(), io::Error> {
Err(io::Error::from(io::ErrorKind::Other))
}

#[test]
fn report() {
let report = io_error().into_report().expect_err("Not an error");
let report = io_error().into_report().expect_err("not an error");
assert!(report.contains::<io::Error>());
assert_eq!(report.current_context().kind(), io::ErrorKind::Other);
}

#[test]
fn into_report() {
let report = io_error().into_report().expect_err("Not an error");
let report = io_error().into_report().expect_err("not an error");
assert!(report.contains::<io::Error>());
assert_eq!(report.current_context().kind(), io::ErrorKind::Other);
}

fn returning_boxed_error() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
io_error().into_report().attach(10_u32)?;
Ok(())
}

#[test]
fn boxed_error() {
let report = returning_boxed_error().expect_err("not an error");
assert_eq!(
report.to_string(),
io_error().expect_err("not an error").to_string()
);

#[cfg(nightly)]
assert_eq!(
*core::any::request_ref::<u32>(report.as_ref()).expect("requested value not found"),
10
);
}
23 changes: 23 additions & 0 deletions packages/libs/error-stack/tests/ui/into_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use core::fmt;

use error_stack::{Context, IntoReport, Report};

#[derive(Debug)]
pub struct RootError;

impl fmt::Display for RootError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str("root error")
}
}

impl Context for RootError {}

fn main() {
let result = Err::<(), _>(RootError);
let _: Result<(), Report<RootError>> = result.into_report();

let result = Err::<(), _>(Report::new(RootError));
// Not allowed
let _ = result.into_report();
}
19 changes: 19 additions & 0 deletions packages/libs/error-stack/tests/ui/into_report.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error[E0599]: the method `into_report` exists for enum `Result<(), error_stack::Report<RootError>>`, but its trait bounds were not satisfied
|
::: src/report.rs
|
| pub struct Report<C> {
| -------------------- doesn't satisfy `_: From<error_stack::Report<RootError>>`
|
::: $RUST/core/src/result.rs
|
| pub enum Result<T, E> {
| --------------------- doesn't satisfy `_: IntoReport`
--> tests/ui/into_report.rs:22:20
|
22 | let _ = result.into_report();
| ^^^^^^^^^^^
|
= note: the following trait bounds were not satisfied:
`error_stack::Report<error_stack::Report<RootError>>: From<error_stack::Report<RootError>>`
which is required by `Result<(), error_stack::Report<RootError>>: IntoReport`