Skip to content
Closed
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
10 changes: 7 additions & 3 deletions packages/libs/error-stack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ categories = ["rust-patterns", "no-std"]

[dependencies]
tracing-error = { version = "0.2.0", optional = true, default_features = false }
once_cell = { version = "1.10.0", optional = true, default_features = false }
once_cell = { version = "1.10.0", optional = true, default_features = false, features = ['std'] }
pin-project = { version = "1.0.10", optional = true, default_features = false }
futures-core = { version = "0.3.21", optional = true, default-features = false }
smallvec = { version = "1.9.0", optional = true, default_features = false, features = ['union'] }
anyhow = { version = "1.0.58", default-features = false, optional = true }
eyre = { version = "0.6.8", default-features = false, optional = true }
serde = { version = "1.0.139", default-features = false, optional = true, features = ['alloc'] }
erased-serde = { version = "0.3.21", default-features = false, optional = true, features = ['alloc'] }

[dev-dependencies]
serde = { version = "1.0.137", features = ["derive"] }
Expand All @@ -34,12 +36,14 @@ once_cell = "1.13.0"
rustc_version = "0.2.3"

[features]
default = ["std", "small"]
std = ["anyhow?/std", "once_cell?/std"]
default = ["std", "small", "serde"]
std = ["anyhow?/std"]
hooks = ["dep:once_cell", "std"]
spantrace = ["dep:tracing-error"]
futures = ["dep:pin-project", "dep:futures-core"]
small = ["dep:smallvec"]
serde = ["dep:serde", "dep:erased-serde"]
experimental = []

[package.metadata.docs.rs]
all-features = true
Expand Down
39 changes: 39 additions & 0 deletions packages/libs/error-stack/src/frame/frame_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,42 @@ impl Frame {
}
}
}

#[cfg(feature = "serde")]
#[repr(C)]
struct SerializableAttachmentFrame<A> {
attachment: A,
location: &'static Location<'static>,
sources: Box<[Frame]>,
}

#[cfg(feature = "serde")]
// SAFETY: `type_id` returns `A` and `A` is the first field in `#[repr(C)]`
unsafe impl<A: 'static + serde::Serialize + Send + Sync> FrameImpl
for SerializableAttachmentFrame<A>
{
fn kind(&self) -> FrameKind<'_> {
FrameKind::Attachment(AttachmentKind::Serializable(&self.attachment))
}

fn type_id(&self) -> TypeId {
TypeId::of::<A>()
}

fn location(&self) -> &'static Location<'static> {
self.location
}

fn sources(&self) -> &[Frame] {
&self.sources
}

fn sources_mut(&mut self) -> &mut [Frame] {
&mut self.sources
}

#[cfg(nightly)]
fn provide<'a>(&'a self, demand: &mut Demand<'a>) {
demand.provide_ref(&self.attachment);
}
}
5 changes: 5 additions & 0 deletions packages/libs/error-stack/src/frame/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ pub enum AttachmentKind<'f> {
///
/// [`attach_printable()`]: crate::Report::attach_printable
Printable(&'f dyn Printable),
/// A serializable attachment created through [`attach_serializable()`].
///
/// [`attach_serializable()`]: crate::Report::attach_serializable
#[cfg(feature = "serde")]
Serializable(&'f dyn erased_serde::Serialize),
}

// TODO: Replace `Printable` by trait bounds when trait objects for multiple traits are supported.
Expand Down
15 changes: 15 additions & 0 deletions packages/libs/error-stack/src/frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,21 @@ impl Frame {
unsafe { &mut *(self.frame.as_mut() as *mut dyn FrameImpl).cast::<T>() }
})
}

/// Return a tuple of `(frame, parents)`, where parents are the frames where a "split" occurred,
/// ~> multiple sources exist
pub(crate) fn collect(&self) -> (Vec<&Frame>, &[Frame]) {
let mut stack = vec![self];

let mut ptr = self.sources();

while let [parent] = ptr {
stack.push(parent);
ptr = parent.sources();
}

(stack, ptr)
}
}

#[cfg(nightly)]
Expand Down
9 changes: 9 additions & 0 deletions packages/libs/error-stack/src/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ use std::{error::Error, fmt};

use once_cell::sync::OnceCell;

#[cfg(feature = "serde")]
use crate::ser::ErasedHooks;
use crate::{Report, Result};

type FormatterHook = Box<dyn Fn(&Report<()>, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync>;

static DEBUG_HOOK: OnceCell<FormatterHook> = OnceCell::new();
static DISPLAY_HOOK: OnceCell<FormatterHook> = OnceCell::new();
#[cfg(feature = "serde")]
static SERIALIZE_HOOK: OnceCell<ErasedHooks> = OnceCell::new();

/// A hook can only be set once.
///
Expand Down Expand Up @@ -127,6 +131,11 @@ impl Report<()> {
{
DISPLAY_HOOK.get()
}

#[cfg(all(feature = "serde", feature = "hooks"))]
pub(crate) fn serialize_hook() -> Option<&'static ErasedHooks> {
SERIALIZE_HOOK.get()
}
}

impl<T> Report<T> {
Expand Down
2 changes: 2 additions & 0 deletions packages/libs/error-stack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,8 @@ mod context;
mod ext;
#[cfg(feature = "hooks")]
mod hook;
#[cfg(feature = "serde")]
mod ser;

#[doc(inline)]
pub use self::ext::*;
Expand Down
20 changes: 16 additions & 4 deletions packages/libs/error-stack/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ impl<C> Report<C> {
let provider = temporary_provider(&context);

#[cfg(all(nightly, feature = "std"))]
let backtrace = if core::any::request_ref::<Backtrace, _>(&provider)
.filter(|backtrace| backtrace.status() == BacktraceStatus::Captured)
let backtrace = if core::any::request_ref(&provider)
.filter(|backtrace: &&Backtrace| backtrace.status() == BacktraceStatus::Captured)
.is_some()
{
None
Expand All @@ -243,8 +243,8 @@ impl<C> Report<C> {
};

#[cfg(all(nightly, feature = "spantrace"))]
let span_trace = if core::any::request_ref::<SpanTrace, _>(&provider)
.filter(|span_trace| span_trace.status() == SpanTraceStatus::CAPTURED)
let span_trace = if core::any::request_ref(&provider)
.filter(|span_trace: &&SpanTrace| span_trace.status() == SpanTraceStatus::CAPTURED)
.is_some()
{
None
Expand Down Expand Up @@ -393,6 +393,8 @@ impl<C> Report<C> {
))
}

// TODO: attach_serializable

/// Adds additional (printable) information to the [`Frame`] stack.
///
/// This behaves like [`attach()`] but the display implementation will be called when
Expand Down Expand Up @@ -569,6 +571,16 @@ impl<C> Report<C> {
pub fn downcast_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
self.frames_mut().find_map(Frame::downcast_mut::<T>)
}

/// Return a tuple of `(frame, parents)`, where parents are the frames where a "split" occurred,
/// ~> multiple sources exist
pub(crate) fn collect(&self) -> (Vec<&Frame>, &[Frame]) {
match self.current_frames() {
[] => (vec![], &[]),
[frame] => frame.collect(),
frames => (frames.iter().collect(), &[]),
}
}
}

impl<T: Context> Report<T> {
Expand Down
142 changes: 142 additions & 0 deletions packages/libs/error-stack/src/ser/hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use std::marker::PhantomData;

use erased_serde::{Error, Serialize, Serializer};
use serde::Serialize as _;

use crate::{frame::Frame, ser::hook};

macro_rules! all_the_tuples {
($name:ident) => {
$name!(T1);
$name!(T1, T2);
$name!(T1, T2, T3);
$name!(T1, T2, T3, T4);
$name!(T1, T2, T3, T4, T5);
$name!(T1, T2, T3, T4, T5, T6);
$name!(T1, T2, T3, T4, T5, T6, T7);
$name!(T1, T2, T3, T4, T5, T6, T7, T8);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
$name!(
T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15
);
$name!(
T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
);
};
}

type UInt0 = ();
type UInt1 = ((), UInt0);
type UInt2 = ((), UInt1);

trait UInt {}

impl UInt for () {}
impl<T: UInt> UInt for ((), T) {}

pub trait Hook<T, U> {
fn call(&self, frame: &T) -> Option<Box<dyn Serialize>>;
}

impl<F, T, U> Hook<T, UInt1> for F
where
F: Fn(&T) -> U,
T: Send + Sync + 'static,
U: serde::Serialize,
{
fn call(&self, frame: &T) -> Option<Box<dyn Serialize>> {
Some(Box::new((self)(frame)))
}
}

struct Phantom<T> {
_marker: PhantomData<T>,
}

macro_rules! impl_hook_tuple {
() => {};

( $($ty:ident),* $(,)? ) => {
#[allow(non_snake_case)]
#[automatically_derived]
impl<$($ty,)*> Hook<Frame, ($($ty,)*)> for Phantom<($($ty,)*)>
where
$($ty: serde::Serialize + Send + Sync + 'static),*
{
fn call(&self, frame: &Frame) -> Option<Box<dyn Serialize>> {
$(
if let Some($ty) = frame.downcast_ref::<$ty>() {
return Some(Box::new($ty))
}
)*

None
}
}
}
}

all_the_tuples!(impl_hook_tuple);

struct Stack<L, T, R> {
left: L,
right: R,
_marker: PhantomData<T>,
}

impl<L, T, U, R> Hook<Frame, UInt0> for Stack<L, (T, U), R>
where
L: Hook<T, U>,
T: Sync + Send + 'static,
R: Hook<Frame, UInt0>,
{
fn call(&self, frame: &Frame) -> Option<Box<dyn Serialize>> {
frame
.downcast_ref()
.and_then(|value| self.left.call(value))
.or_else(|| self.right.call(frame))
}
}

struct Combine<L, R> {
left: L,
right: R,
}

impl<L, R> Hook<Frame, UInt0> for Combine<L, R>
where
L: Hook<Frame, UInt0>,
R: Hook<Frame, UInt0>,
{
fn call(&self, frame: &Frame) -> Option<Box<dyn Serialize>> {
self.left.call(frame).or_else(|| self.right.call(frame))
}
}

impl Hook<Frame, UInt0> for Box<dyn Hook<Frame, UInt0>> {
fn call(&self, frame: &Frame) -> Option<Box<dyn Serialize>> {
let hook: &dyn Hook<Frame, UInt0> = self;
hook.call(frame)
}
}

impl Hook<Frame, UInt0> for () {
fn call(&self, _: &Frame) -> Option<Box<dyn Serialize>> {
None
}
}

pub struct Hooks<T: Hook<Frame, UInt0>>(T);

impl<T: Hook<Frame, UInt0>> Hooks<T> {
pub(crate) fn call(&self, frame: &Frame) -> Option<Box<dyn Serialize>> {
self.0.call(frame)
}
}

pub(crate) type ErasedHooks = Hooks<Box<dyn Hook<Frame, UInt0>>>;
Loading