From 39d973ecd7582001593fb72b7a2cef18ddb4f2f8 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Tue, 12 Jul 2022 20:18:19 +0200 Subject: [PATCH 1/9] init: serde --- packages/libs/error-stack/Cargo.toml | 4 +++- packages/libs/error-stack/src/lib.rs | 2 ++ packages/libs/error-stack/src/serde.rs | 0 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 packages/libs/error-stack/src/serde.rs diff --git a/packages/libs/error-stack/Cargo.toml b/packages/libs/error-stack/Cargo.toml index dd7696292f3..0382326c24e 100644 --- a/packages/libs/error-stack/Cargo.toml +++ b/packages/libs/error-stack/Cargo.toml @@ -20,6 +20,7 @@ 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'] } [dev-dependencies] serde = { version = "1.0.137", features = ["derive"] } @@ -35,11 +36,12 @@ rustc_version = "0.2.3" [features] default = ["std", "small"] -std = ["anyhow?/std", "once_cell?/std"] +std = ["anyhow?/std", "once_cell?/std", "serde?/std"] hooks = ["dep:once_cell", "std"] spantrace = ["dep:tracing-error"] futures = ["dep:pin-project", "dep:futures-core"] small = ["dep:smallvec"] +serde = ["dep:serde"] [package.metadata.docs.rs] all-features = true diff --git a/packages/libs/error-stack/src/lib.rs b/packages/libs/error-stack/src/lib.rs index 59c8981ed3f..fb38d574b28 100644 --- a/packages/libs/error-stack/src/lib.rs +++ b/packages/libs/error-stack/src/lib.rs @@ -443,6 +443,8 @@ mod context; mod ext; #[cfg(feature = "hooks")] mod hook; +#[cfg(feature = "serde")] +mod serde; #[doc(inline)] pub use self::ext::*; diff --git a/packages/libs/error-stack/src/serde.rs b/packages/libs/error-stack/src/serde.rs new file mode 100644 index 00000000000..e69de29bb2d From d45f5967521314bf5e56efd41cb3f1e7a1587d5b Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Tue, 19 Jul 2022 17:33:45 +0200 Subject: [PATCH 2/9] feat: prototype implementation of hook --- packages/libs/error-stack/Cargo.toml | 3 +- packages/libs/error-stack/src/lib.rs | 2 +- packages/libs/error-stack/src/ser.rs | 138 +++++++++++++++++++++++++ packages/libs/error-stack/src/serde.rs | 0 4 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 packages/libs/error-stack/src/ser.rs delete mode 100644 packages/libs/error-stack/src/serde.rs diff --git a/packages/libs/error-stack/Cargo.toml b/packages/libs/error-stack/Cargo.toml index 0382326c24e..0318f2bace5 100644 --- a/packages/libs/error-stack/Cargo.toml +++ b/packages/libs/error-stack/Cargo.toml @@ -21,6 +21,7 @@ smallvec = { version = "1.9.0", optional = true, default_features = false, featu 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"] } @@ -41,7 +42,7 @@ hooks = ["dep:once_cell", "std"] spantrace = ["dep:tracing-error"] futures = ["dep:pin-project", "dep:futures-core"] small = ["dep:smallvec"] -serde = ["dep:serde"] +serde = ["dep:serde", "dep:erased-serde"] [package.metadata.docs.rs] all-features = true diff --git a/packages/libs/error-stack/src/lib.rs b/packages/libs/error-stack/src/lib.rs index fb38d574b28..47e2f5ea099 100644 --- a/packages/libs/error-stack/src/lib.rs +++ b/packages/libs/error-stack/src/lib.rs @@ -444,7 +444,7 @@ mod ext; #[cfg(feature = "hooks")] mod hook; #[cfg(feature = "serde")] -mod serde; +mod ser; #[doc(inline)] pub use self::ext::*; diff --git a/packages/libs/error-stack/src/ser.rs b/packages/libs/error-stack/src/ser.rs new file mode 100644 index 00000000000..185de31639e --- /dev/null +++ b/packages/libs/error-stack/src/ser.rs @@ -0,0 +1,138 @@ +use std::{ + any::{Any, TypeId}, + collections::HashMap, + marker::PhantomData, + mem, + sync::Arc, +}; + +use erased_serde::Serializer; + +use crate::Frame; + +pub trait Hook { + fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option>; +} + +impl Hook for F +where + F: Fn(&T, &mut dyn Serializer) -> erased_serde::Result<()>, + T: Send + Sync + 'static, +{ + fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option> { + Some((self)(frame, serializer)) + } +} + +pub struct Stack { + left: L, + right: R, + _marker: PhantomData, +} + +impl Hook for Stack +where + L: Hook, + T: Send + Sync + 'static, + R: Hook, +{ + fn call( + &self, + frame: &Frame, + serializer: &mut dyn Serializer, + ) -> Option> { + if let Some(frame) = frame.downcast_ref::() { + self.left.call(frame, serializer) + } else { + self.right.call(frame, serializer) + } + } +} + +impl Hook for () { + fn call(&self, _: &T, _: &mut dyn Serializer) -> Option> { + None + } +} + +impl Hook for Box> { + fn call( + &self, + frame: &Frame, + serializer: &mut dyn Serializer, + ) -> Option> { + let hook = self.as_ref(); + + hook.call(frame, serializer) + } +} + +struct Hooks(Box>); + +impl Hooks { + pub fn new() -> Self { + Hooks(Box::new(())) + } + + pub fn insert + 'static, T: Send + Sync + 'static>(&mut self, hook: F) { + let inner = Stack { + left: hook, + right: mem::replace(&mut self.0, Box::new(())), + _marker: PhantomData::default(), + }; + + self.0 = Box::new(inner); + } + + pub fn call( + &self, + frame: &Frame, + serializer: &mut dyn Serializer, + ) -> Option> { + self.0.call(frame, serializer) + } +} + +#[cfg(test)] +mod tests { + use core::fmt::{Display, Formatter}; + + use erased_serde::Serializer; + use futures::TryFutureExt; + + use crate::{report, ser::Hooks, Context}; + + fn serialize_a(a: &u32, s: &mut dyn Serializer) -> erased_serde::Result<()> { + s.erased_serialize_u32(*a).map(|_| ()) + } + + #[derive(Debug)] + struct IoError; + + impl Display for IoError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str("Io Error") + } + } + + impl Context for IoError {} + + #[test] + fn no() { + let frames = report!(IoError).attach(2u32); + + let frame = &frames.current_frames()[0]; + + let mut hooks = Hooks::new(); + hooks.insert(serialize_a); + + let mut buf = vec![]; + + let mut s = serde_json::Serializer::new(&mut buf); + let mut s = ::erase(&mut s); + + let result = hooks.call(frame, &mut s); + assert!(result.is_some()); + println!("{}", std::str::from_utf8(&buf).unwrap()) + } +} diff --git a/packages/libs/error-stack/src/serde.rs b/packages/libs/error-stack/src/serde.rs deleted file mode 100644 index e69de29bb2d..00000000000 From b8021ca738170c82df46d473cee91b613d7a179e Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Thu, 28 Jul 2022 08:58:21 +0200 Subject: [PATCH 3/9] feat: new hook interface --- packages/libs/error-stack/src/report.rs | 8 +- packages/libs/error-stack/src/ser/hook.rs | 75 ++++++++++++ packages/libs/error-stack/src/ser/mod.rs | 140 ++++++++++++++++++++++ 3 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 packages/libs/error-stack/src/ser/hook.rs create mode 100644 packages/libs/error-stack/src/ser/mod.rs diff --git a/packages/libs/error-stack/src/report.rs b/packages/libs/error-stack/src/report.rs index 49aaf7399f7..a26562db912 100644 --- a/packages/libs/error-stack/src/report.rs +++ b/packages/libs/error-stack/src/report.rs @@ -233,8 +233,8 @@ impl Report { let provider = temporary_provider(&context); #[cfg(all(nightly, feature = "std"))] - let backtrace = if core::any::request_ref::(&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 @@ -243,8 +243,8 @@ impl Report { }; #[cfg(all(nightly, feature = "spantrace"))] - let span_trace = if core::any::request_ref::(&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 diff --git a/packages/libs/error-stack/src/ser/hook.rs b/packages/libs/error-stack/src/ser/hook.rs new file mode 100644 index 00000000000..ffdc084b1bd --- /dev/null +++ b/packages/libs/error-stack/src/ser/hook.rs @@ -0,0 +1,75 @@ +use erased_serde::{Serialize, Serializer}; +use serde::Serialize; + +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 UInt for ((), T) {} + +pub trait Hook { + fn call(&self, frame: &T, s: &mut dyn Serializer) -> Option>; +} + +// TODO: tuple types (how to do second argument?) +impl Hook for F +where + F: Fn(&T, &mut dyn Serializer) -> erased_serde::Result<()>, + T: Send + Sync + 'static, +{ +} + +impl Hook for F +where + F: Fn(&T) -> U, + T: Send + Sync + 'static, + U: serde::Serialize, +{ + fn call(&self, frame: &T, s: &mut dyn Serializer) -> Option> { + let res: dyn Serialize = (self)(frame); + Some(res.erased_serialize(s).map(|_| ())) + } +} + +macro_rules! impl_hook_tuple { + () => {}; + + ( $($ty:ident),* $(,)? ) => { + impl<$($ty,)*> Hook for ($($ty,)*) + where + $($ty: serde::Serialize),* + { + + } + } +} + +all_the_tuples!(impl_hook_tuple); diff --git a/packages/libs/error-stack/src/ser/mod.rs b/packages/libs/error-stack/src/ser/mod.rs new file mode 100644 index 00000000000..75221446856 --- /dev/null +++ b/packages/libs/error-stack/src/ser/mod.rs @@ -0,0 +1,140 @@ +mod hook; + +use std::{ + any::{Any, TypeId}, + collections::HashMap, + marker::PhantomData, + mem, + sync::Arc, +}; + +use erased_serde::Serializer; + +use crate::Frame; + +pub trait Hook { + fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option>; +} + +impl Hook for F +where + F: Fn(&T, &mut dyn Serializer) -> erased_serde::Result<()>, + T: Send + Sync + 'static, +{ + fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option> { + Some((self)(frame, serializer)) + } +} + +pub struct Stack { + left: L, + right: R, + _marker: PhantomData, +} + +impl Hook for Stack +where + L: Hook, + T: Send + Sync + 'static, + R: Hook, +{ + fn call( + &self, + frame: &Frame, + serializer: &mut dyn Serializer, + ) -> Option> { + if let Some(frame) = frame.downcast_ref::() { + self.left.call(frame, serializer) + } else { + self.right.call(frame, serializer) + } + } +} + +impl Hook for () { + fn call(&self, _: &T, _: &mut dyn Serializer) -> Option> { + None + } +} + +impl Hook for Box> { + fn call( + &self, + frame: &Frame, + serializer: &mut dyn Serializer, + ) -> Option> { + let hook = self.as_ref(); + + hook.call(frame, serializer) + } +} + +struct Hooks(Box>); + +impl Hooks { + pub fn new() -> Self { + Hooks(Box::new(())) + } + + pub fn insert + 'static, T: Send + Sync + 'static>(&mut self, hook: F) { + let inner = Stack { + left: hook, + right: mem::replace(&mut self.0, Box::new(())), + _marker: PhantomData::default(), + }; + + self.0 = Box::new(inner); + } + + pub fn call( + &self, + frame: &Frame, + serializer: &mut dyn Serializer, + ) -> Option> { + self.0.call(frame, serializer) + } +} + +#[cfg(test)] +mod tests { + use core::fmt::{Display, Formatter}; + + use erased_serde::Serializer; + use futures::TryFutureExt; + + use crate::{report, ser::Hooks, Context}; + + fn serialize_a(a: &u32, s: &mut dyn Serializer) -> erased_serde::Result<()> { + s.erased_serialize_u32(*a).map(|_| ()) + } + + #[derive(Debug)] + struct IoError; + + impl Display for IoError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.write_str("Io Error") + } + } + + impl Context for IoError {} + + #[test] + fn no() { + let frames = report!(IoError).attach(2u32); + + let frame = &frames.current_frames()[0]; + + let mut hooks = Hooks::new(); + hooks.insert(serialize_a); + + let mut buf = vec![]; + + let mut s = serde_json::Serializer::new(&mut buf); + let mut s = ::erase(&mut s); + + let result = hooks.call(frame, &mut s); + assert!(result.is_some()); + println!("{}", std::str::from_utf8(&buf).unwrap()) + } +} From 92eb51489e59b8626d21b08f9602fbd6ee025623 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Fri, 29 Jul 2022 15:47:51 +0200 Subject: [PATCH 4/9] feat: add additional frame impl --- packages/libs/error-stack/Cargo.toml | 2 +- .../libs/error-stack/src/frame/frame_impl.rs | 39 +++++ packages/libs/error-stack/src/frame/kind.rs | 2 + packages/libs/error-stack/src/ser.rs | 138 ----------------- packages/libs/error-stack/src/ser/hook.rs | 34 ++++- packages/libs/error-stack/src/ser/mod.rs | 139 ------------------ 6 files changed, 72 insertions(+), 282 deletions(-) delete mode 100644 packages/libs/error-stack/src/ser.rs diff --git a/packages/libs/error-stack/Cargo.toml b/packages/libs/error-stack/Cargo.toml index 0318f2bace5..21bad36dbc9 100644 --- a/packages/libs/error-stack/Cargo.toml +++ b/packages/libs/error-stack/Cargo.toml @@ -36,7 +36,7 @@ once_cell = "1.13.0" rustc_version = "0.2.3" [features] -default = ["std", "small"] +default = ["std", "small", "serde"] std = ["anyhow?/std", "once_cell?/std", "serde?/std"] hooks = ["dep:once_cell", "std"] spantrace = ["dep:tracing-error"] diff --git a/packages/libs/error-stack/src/frame/frame_impl.rs b/packages/libs/error-stack/src/frame/frame_impl.rs index 96ae54f4e8c..e1caac89de2 100644 --- a/packages/libs/error-stack/src/frame/frame_impl.rs +++ b/packages/libs/error-stack/src/frame/frame_impl.rs @@ -214,3 +214,42 @@ impl Frame { } } } + +#[cfg(feature = "serde")] +#[repr(C)] +struct SerializableAttachmentFrame { + 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 FrameImpl + for SerializableAttachmentFrame +{ + fn kind(&self) -> FrameKind<'_> { + FrameKind::Attachment(AttachmentKind::Serializable(&self.attachment)) + } + + fn type_id(&self) -> TypeId { + TypeId::of::() + } + + 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); + } +} diff --git a/packages/libs/error-stack/src/frame/kind.rs b/packages/libs/error-stack/src/frame/kind.rs index 8073921b17a..2c82b52168c 100644 --- a/packages/libs/error-stack/src/frame/kind.rs +++ b/packages/libs/error-stack/src/frame/kind.rs @@ -29,6 +29,8 @@ pub enum AttachmentKind<'f> { /// /// [`attach_printable()`]: crate::Report::attach_printable Printable(&'f dyn Printable), + #[cfg(feature = "serde")] + Serializable(&'f dyn erased_serde::Serialize), } // TODO: Replace `Printable` by trait bounds when trait objects for multiple traits are supported. diff --git a/packages/libs/error-stack/src/ser.rs b/packages/libs/error-stack/src/ser.rs deleted file mode 100644 index 185de31639e..00000000000 --- a/packages/libs/error-stack/src/ser.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::{ - any::{Any, TypeId}, - collections::HashMap, - marker::PhantomData, - mem, - sync::Arc, -}; - -use erased_serde::Serializer; - -use crate::Frame; - -pub trait Hook { - fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option>; -} - -impl Hook for F -where - F: Fn(&T, &mut dyn Serializer) -> erased_serde::Result<()>, - T: Send + Sync + 'static, -{ - fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option> { - Some((self)(frame, serializer)) - } -} - -pub struct Stack { - left: L, - right: R, - _marker: PhantomData, -} - -impl Hook for Stack -where - L: Hook, - T: Send + Sync + 'static, - R: Hook, -{ - fn call( - &self, - frame: &Frame, - serializer: &mut dyn Serializer, - ) -> Option> { - if let Some(frame) = frame.downcast_ref::() { - self.left.call(frame, serializer) - } else { - self.right.call(frame, serializer) - } - } -} - -impl Hook for () { - fn call(&self, _: &T, _: &mut dyn Serializer) -> Option> { - None - } -} - -impl Hook for Box> { - fn call( - &self, - frame: &Frame, - serializer: &mut dyn Serializer, - ) -> Option> { - let hook = self.as_ref(); - - hook.call(frame, serializer) - } -} - -struct Hooks(Box>); - -impl Hooks { - pub fn new() -> Self { - Hooks(Box::new(())) - } - - pub fn insert + 'static, T: Send + Sync + 'static>(&mut self, hook: F) { - let inner = Stack { - left: hook, - right: mem::replace(&mut self.0, Box::new(())), - _marker: PhantomData::default(), - }; - - self.0 = Box::new(inner); - } - - pub fn call( - &self, - frame: &Frame, - serializer: &mut dyn Serializer, - ) -> Option> { - self.0.call(frame, serializer) - } -} - -#[cfg(test)] -mod tests { - use core::fmt::{Display, Formatter}; - - use erased_serde::Serializer; - use futures::TryFutureExt; - - use crate::{report, ser::Hooks, Context}; - - fn serialize_a(a: &u32, s: &mut dyn Serializer) -> erased_serde::Result<()> { - s.erased_serialize_u32(*a).map(|_| ()) - } - - #[derive(Debug)] - struct IoError; - - impl Display for IoError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.write_str("Io Error") - } - } - - impl Context for IoError {} - - #[test] - fn no() { - let frames = report!(IoError).attach(2u32); - - let frame = &frames.current_frames()[0]; - - let mut hooks = Hooks::new(); - hooks.insert(serialize_a); - - let mut buf = vec![]; - - let mut s = serde_json::Serializer::new(&mut buf); - let mut s = ::erase(&mut s); - - let result = hooks.call(frame, &mut s); - assert!(result.is_some()); - println!("{}", std::str::from_utf8(&buf).unwrap()) - } -} diff --git a/packages/libs/error-stack/src/ser/hook.rs b/packages/libs/error-stack/src/ser/hook.rs index ffdc084b1bd..802e4fd2b0d 100644 --- a/packages/libs/error-stack/src/ser/hook.rs +++ b/packages/libs/error-stack/src/ser/hook.rs @@ -1,5 +1,8 @@ +use std::marker::PhantomData; + use erased_serde::{Serialize, Serializer}; -use serde::Serialize; + +use crate::frame::Frame; macro_rules! all_the_tuples { ($name:ident) => { @@ -45,6 +48,9 @@ where F: Fn(&T, &mut dyn Serializer) -> erased_serde::Result<()>, T: Send + Sync + 'static, { + fn call(&self, frame: &T, s: &mut dyn Serializer) -> Option> { + Some((self)(frame, s)) + } } impl Hook for F @@ -54,22 +60,42 @@ where U: serde::Serialize, { fn call(&self, frame: &T, s: &mut dyn Serializer) -> Option> { - let res: dyn Serialize = (self)(frame); + let res = (self)(frame); Some(res.erased_serialize(s).map(|_| ())) } } +struct Phantom { + _marker: PhantomData, +} + macro_rules! impl_hook_tuple { () => {}; ( $($ty:ident),* $(,)? ) => { - impl<$($ty,)*> Hook for ($($ty,)*) + #[allow(non_snake_case)] + #[automatically_derived] + impl<$($ty,)*> Hook for Phantom<($($ty,)*)> where - $($ty: serde::Serialize),* + $($ty: serde::Serialize + Send + Sync + 'static),* { + fn call(&self, frame: &Frame, s: &mut dyn Serializer) -> Option> { + $( + if let Some($ty) = frame.downcast_ref::<$ty>() { + return Some($ty.erased_serialize(s).map(|_| ())) + } + )* + None + } } } } all_the_tuples!(impl_hook_tuple); + +struct Stack { + left: L, + right: R, + _marker: PhantomData, +} diff --git a/packages/libs/error-stack/src/ser/mod.rs b/packages/libs/error-stack/src/ser/mod.rs index 75221446856..cb8fa85dc2a 100644 --- a/packages/libs/error-stack/src/ser/mod.rs +++ b/packages/libs/error-stack/src/ser/mod.rs @@ -1,140 +1 @@ mod hook; - -use std::{ - any::{Any, TypeId}, - collections::HashMap, - marker::PhantomData, - mem, - sync::Arc, -}; - -use erased_serde::Serializer; - -use crate::Frame; - -pub trait Hook { - fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option>; -} - -impl Hook for F -where - F: Fn(&T, &mut dyn Serializer) -> erased_serde::Result<()>, - T: Send + Sync + 'static, -{ - fn call(&self, frame: &T, serializer: &mut dyn Serializer) -> Option> { - Some((self)(frame, serializer)) - } -} - -pub struct Stack { - left: L, - right: R, - _marker: PhantomData, -} - -impl Hook for Stack -where - L: Hook, - T: Send + Sync + 'static, - R: Hook, -{ - fn call( - &self, - frame: &Frame, - serializer: &mut dyn Serializer, - ) -> Option> { - if let Some(frame) = frame.downcast_ref::() { - self.left.call(frame, serializer) - } else { - self.right.call(frame, serializer) - } - } -} - -impl Hook for () { - fn call(&self, _: &T, _: &mut dyn Serializer) -> Option> { - None - } -} - -impl Hook for Box> { - fn call( - &self, - frame: &Frame, - serializer: &mut dyn Serializer, - ) -> Option> { - let hook = self.as_ref(); - - hook.call(frame, serializer) - } -} - -struct Hooks(Box>); - -impl Hooks { - pub fn new() -> Self { - Hooks(Box::new(())) - } - - pub fn insert + 'static, T: Send + Sync + 'static>(&mut self, hook: F) { - let inner = Stack { - left: hook, - right: mem::replace(&mut self.0, Box::new(())), - _marker: PhantomData::default(), - }; - - self.0 = Box::new(inner); - } - - pub fn call( - &self, - frame: &Frame, - serializer: &mut dyn Serializer, - ) -> Option> { - self.0.call(frame, serializer) - } -} - -#[cfg(test)] -mod tests { - use core::fmt::{Display, Formatter}; - - use erased_serde::Serializer; - use futures::TryFutureExt; - - use crate::{report, ser::Hooks, Context}; - - fn serialize_a(a: &u32, s: &mut dyn Serializer) -> erased_serde::Result<()> { - s.erased_serialize_u32(*a).map(|_| ()) - } - - #[derive(Debug)] - struct IoError; - - impl Display for IoError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.write_str("Io Error") - } - } - - impl Context for IoError {} - - #[test] - fn no() { - let frames = report!(IoError).attach(2u32); - - let frame = &frames.current_frames()[0]; - - let mut hooks = Hooks::new(); - hooks.insert(serialize_a); - - let mut buf = vec![]; - - let mut s = serde_json::Serializer::new(&mut buf); - let mut s = ::erase(&mut s); - - let result = hooks.call(frame, &mut s); - assert!(result.is_some()); - println!("{}", std::str::from_utf8(&buf).unwrap()) - } -} From 7a4299d2c8406c07888530dd7961ed0d93f66d7a Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Fri, 29 Jul 2022 15:50:03 +0200 Subject: [PATCH 5/9] feat: docs about serializable attachment --- packages/libs/error-stack/src/frame/kind.rs | 3 +++ packages/libs/error-stack/src/report.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/packages/libs/error-stack/src/frame/kind.rs b/packages/libs/error-stack/src/frame/kind.rs index 2c82b52168c..aa2cc15b595 100644 --- a/packages/libs/error-stack/src/frame/kind.rs +++ b/packages/libs/error-stack/src/frame/kind.rs @@ -29,6 +29,9 @@ 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), } diff --git a/packages/libs/error-stack/src/report.rs b/packages/libs/error-stack/src/report.rs index a26562db912..d839a569be3 100644 --- a/packages/libs/error-stack/src/report.rs +++ b/packages/libs/error-stack/src/report.rs @@ -393,6 +393,8 @@ impl Report { )) } + // TODO: attach_serializable + /// Adds additional (printable) information to the [`Frame`] stack. /// /// This behaves like [`attach()`] but the display implementation will be called when From 11762e829462dd29d9bdb6db9303c1e39b076ec7 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Fri, 29 Jul 2022 15:56:16 +0200 Subject: [PATCH 6/9] feat: docs for ser --- packages/libs/error-stack/src/ser/hook.rs | 31 ++++++++++++++ packages/libs/error-stack/src/ser/mod.rs | 51 +++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/packages/libs/error-stack/src/ser/hook.rs b/packages/libs/error-stack/src/ser/hook.rs index 802e4fd2b0d..ac29c9655b7 100644 --- a/packages/libs/error-stack/src/ser/hook.rs +++ b/packages/libs/error-stack/src/ser/hook.rs @@ -99,3 +99,34 @@ struct Stack { right: R, _marker: PhantomData, } + +impl Hook for Stack +where + L: Hook, + T: Sync + Send + 'static, + R: Hook, +{ + fn call(&self, frame: &Frame, s: &mut dyn Serializer) -> Option> { + frame + .downcast_ref() + .and_then(|value| self.left.call(value, s)) + .or_else(|| self.right.call(frame, s)) + } +} + +struct Combine { + left: L, + right: R, +} + +impl Hook for Combine +where + L: Hook, + R: Hook, +{ + fn call(&self, frame: &Frame, s: &mut dyn Serializer) -> Option> { + self.left + .call(frame, s) + .or_else(|| self.right.call(frame, s)) + } +} diff --git a/packages/libs/error-stack/src/ser/mod.rs b/packages/libs/error-stack/src/ser/mod.rs index cb8fa85dc2a..3f55b4ba140 100644 --- a/packages/libs/error-stack/src/ser/mod.rs +++ b/packages/libs/error-stack/src/ser/mod.rs @@ -1 +1,52 @@ +//! Serialization logic for report +//! +//! This implements the following serialization logic (rendered in json) +//! +//! ```json +//! { +//! "frames": [ +//! { +//! "type": "attachment", +//! "letter": "A" +//! }, +//! { +//! "type": "attachment", +//! "letter": "B" +//! }, +//! { +//! "type": "context", +//! "letter": "C" +//! } +//! ], +//! "sources": [ +//! { +//! "frames": [ +//! { +//! "type": "attachment", +//! "letter": "E" +//! }, +//! { +//! "type": "attachment", +//! "letter": "G" +//! }, +//! { +//! "type": "context", +//! "letter": "H" +//! } +//! ], +//! "sources": [] +//! }, +//! { +//! "frames": [ +//! { +//! "type": "context", +//! "letter": "F" +//! } +//! ], +//! "sources": [] +//! } +//! ], +//! } +//! ``` + mod hook; From 8f9f2ea62f1e421711871f1a7c98e9343bdfc907 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Fri, 29 Jul 2022 18:24:38 +0200 Subject: [PATCH 7/9] feat: serialize first implementation --- packages/libs/error-stack/Cargo.toml | 1 + packages/libs/error-stack/src/frame/mod.rs | 15 +++ packages/libs/error-stack/src/hook.rs | 9 ++ packages/libs/error-stack/src/report.rs | 10 ++ packages/libs/error-stack/src/ser/hook.rs | 88 +++++++++---- packages/libs/error-stack/src/ser/mod.rs | 127 +++++++++++++++++++ packages/libs/error-stack/src/ser/nightly.rs | 14 ++ 7 files changed, 238 insertions(+), 26 deletions(-) create mode 100644 packages/libs/error-stack/src/ser/nightly.rs diff --git a/packages/libs/error-stack/Cargo.toml b/packages/libs/error-stack/Cargo.toml index 21bad36dbc9..e1322223e73 100644 --- a/packages/libs/error-stack/Cargo.toml +++ b/packages/libs/error-stack/Cargo.toml @@ -43,6 +43,7 @@ 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 diff --git a/packages/libs/error-stack/src/frame/mod.rs b/packages/libs/error-stack/src/frame/mod.rs index 591c9bd50cd..811625fb5ee 100644 --- a/packages/libs/error-stack/src/frame/mod.rs +++ b/packages/libs/error-stack/src/frame/mod.rs @@ -118,6 +118,21 @@ impl Frame { unsafe { &mut *(self.frame.as_mut() as *mut dyn FrameImpl).cast::() } }) } + + /// 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)] diff --git a/packages/libs/error-stack/src/hook.rs b/packages/libs/error-stack/src/hook.rs index cd58b6431f0..b2b41ccab78 100644 --- a/packages/libs/error-stack/src/hook.rs +++ b/packages/libs/error-stack/src/hook.rs @@ -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, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync>; static DEBUG_HOOK: OnceCell = OnceCell::new(); static DISPLAY_HOOK: OnceCell = OnceCell::new(); +#[cfg(feature = "serde")] +static SERIALIZE_HOOK: OnceCell = OnceCell::new(); /// A hook can only be set once. /// @@ -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 Report { diff --git a/packages/libs/error-stack/src/report.rs b/packages/libs/error-stack/src/report.rs index d839a569be3..9636f009924 100644 --- a/packages/libs/error-stack/src/report.rs +++ b/packages/libs/error-stack/src/report.rs @@ -571,6 +571,16 @@ impl Report { pub fn downcast_mut(&mut self) -> Option<&mut T> { self.frames_mut().find_map(Frame::downcast_mut::) } + + /// 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 Report { diff --git a/packages/libs/error-stack/src/ser/hook.rs b/packages/libs/error-stack/src/ser/hook.rs index ac29c9655b7..092f83045a7 100644 --- a/packages/libs/error-stack/src/ser/hook.rs +++ b/packages/libs/error-stack/src/ser/hook.rs @@ -1,8 +1,9 @@ use std::marker::PhantomData; -use erased_serde::{Serialize, Serializer}; +use erased_serde::{Error, Serialize, Serializer}; +use serde::Serialize as _; -use crate::frame::Frame; +use crate::{frame::Frame, ser::hook}; macro_rules! all_the_tuples { ($name:ident) => { @@ -39,18 +40,7 @@ impl UInt for () {} impl UInt for ((), T) {} pub trait Hook { - fn call(&self, frame: &T, s: &mut dyn Serializer) -> Option>; -} - -// TODO: tuple types (how to do second argument?) -impl Hook for F -where - F: Fn(&T, &mut dyn Serializer) -> erased_serde::Result<()>, - T: Send + Sync + 'static, -{ - fn call(&self, frame: &T, s: &mut dyn Serializer) -> Option> { - Some((self)(frame, s)) - } + fn call(&self, frame: &T) -> Option>; } impl Hook for F @@ -59,9 +49,8 @@ where T: Send + Sync + 'static, U: serde::Serialize, { - fn call(&self, frame: &T, s: &mut dyn Serializer) -> Option> { - let res = (self)(frame); - Some(res.erased_serialize(s).map(|_| ())) + fn call(&self, frame: &T) -> Option> { + Some(Box::new((self)(frame))) } } @@ -79,10 +68,10 @@ macro_rules! impl_hook_tuple { where $($ty: serde::Serialize + Send + Sync + 'static),* { - fn call(&self, frame: &Frame, s: &mut dyn Serializer) -> Option> { + fn call(&self, frame: &Frame) -> Option> { $( if let Some($ty) = frame.downcast_ref::<$ty>() { - return Some($ty.erased_serialize(s).map(|_| ())) + return Some(Box::new($ty)) } )* @@ -106,11 +95,11 @@ where T: Sync + Send + 'static, R: Hook, { - fn call(&self, frame: &Frame, s: &mut dyn Serializer) -> Option> { + fn call(&self, frame: &Frame) -> Option> { frame .downcast_ref() - .and_then(|value| self.left.call(value, s)) - .or_else(|| self.right.call(frame, s)) + .and_then(|value| self.left.call(value)) + .or_else(|| self.right.call(frame)) } } @@ -124,9 +113,56 @@ where L: Hook, R: Hook, { - fn call(&self, frame: &Frame, s: &mut dyn Serializer) -> Option> { - self.left - .call(frame, s) - .or_else(|| self.right.call(frame, s)) + fn call(&self, frame: &Frame) -> Option> { + self.left.call(frame).or_else(|| self.right.call(frame)) + } +} + +impl Hook for Box> { + fn call(&self, frame: &Frame) -> Option> { + let hook: &dyn Hook = self; + hook.call(frame) + } +} + +impl Hook for () { + fn call(&self, _: &Frame) -> Option> { + None + } +} + +pub struct Hooks>(T); + +impl> Hooks { + fn bind(&self, frame: &Frame) -> Bound { + Bound::new(self, frame) + } + + pub(crate) fn call(&self, frame: &Frame) -> Option> { + self.0.call(frame) + } +} + +pub(crate) type ErasedHooks = Hooks>>; + +struct Bound<'a, T> +where + T: Hook, +{ + hooks: &'a Hooks, + frame: &'a Frame, +} + +impl<'a, T: Hook> Bound<'a, T> { + fn new(hooks: &'a Hooks, frame: &'a Frame) -> Self { + Self { hooks, frame } + } + + fn serialize(&self, frame: &Frame, mut serializer: S) -> Option> + where + S: serde::Serializer, + { + let s = self.hooks.call(frame)?; + Some(s.serialize(serializer)) } } diff --git a/packages/libs/error-stack/src/ser/mod.rs b/packages/libs/error-stack/src/ser/mod.rs index 3f55b4ba140..2e3962135e5 100644 --- a/packages/libs/error-stack/src/ser/mod.rs +++ b/packages/libs/error-stack/src/ser/mod.rs @@ -49,4 +49,131 @@ //! } //! ``` +pub(crate) use hook::ErasedHooks; +use serde::{ + ser::{SerializeMap, SerializeSeq}, + Serialize, Serializer, +}; + +#[cfg(all(nightly, feature = "experimental"))] +use crate::ser::nightly::SerializeDiagnostic; +use crate::{AttachmentKind, Frame, FrameKind, Report}; + mod hook; +mod nightly; + +impl Serialize for Frame { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self.kind() { + FrameKind::Context(context) => { + let mut s = serializer.serialize_map(Some(2))?; + + s.serialize_entry("type", "context")?; + + #[cfg(all(nightly, feature = "experimental"))] + if let Some(diagnostic) = self.request_ref::() { + s.serialize_entry("value", diagnostic)?; + } else { + s.serialize_entry("value", &context.to_string())?; + } + + #[cfg(not(all(nightly, feature = "experimental")))] + { s.serialize_entry("value", &context.to_string()) }?; + + s.end() + } + FrameKind::Attachment(AttachmentKind::Opaque(attachment)) => { + let mut s = serializer.serialize_map(Some(2))?; + + s.serialize_entry("type", "attachment")?; + + let mut fallback = true; + #[cfg(all(nightly, feature = "experimental"))] + if let Some(diagnostic) = self.request_ref::() { + s.serialize_entry("value", diagnostic)?; + fallback = false; + } + + #[cfg(feature = "hooks")] + if let Some(hooks) = Report::serialize_hook() { + if let Some(value) = hooks.call(self) { + s.serialize_entry("value", &value)?; + fallback = false; + } + } + + if fallback { + // explicit `None` if the value isn't provided. + s.serialize_entry("value", &Option::<()>::None)?; + } + + s.end() + } + FrameKind::Attachment(AttachmentKind::Printable(attachment)) => { + let mut s = serializer.serialize_map(Some(2))?; + + s.serialize_entry("type", "attachment")?; + s.serialize_entry("value", &attachment.to_string())?; + + s.end() + } + FrameKind::Attachment(AttachmentKind::Serializable(attachment)) => { + let mut s = serializer.serialize_map(Some(2))?; + + s.serialize_entry("type", "attachment")?; + s.serialize_entry("value", attachment)?; + + s.end() + } + } + } +} + +struct Root<'a> { + stack: Vec<&'a Frame>, + split: &'a [Frame], +} + +impl<'a> From<&'a Frame> for Root<'a> { + fn from(frame: &'a Frame) -> Self { + let (stack, split) = frame.collect(); + + Self { stack, split } + } +} + +impl<'a, C> From<&'a Report> for Root<'a> { + fn from(frame: &'a Report) -> Self { + let (stack, split) = frame.collect(); + + Self { stack, split } + } +} + +impl Serialize for Root<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let Self { stack, split } = self; + + let mut s = serializer.serialize_map(Some(2))?; + + s.serialize_entry("frames", &stack)?; + s.serialize_entry("sources", &split.iter().map(Root::from).collect::>())?; + + s.end() + } +} + +impl Serialize for Report { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Root::from(self).serialize(serializer) + } +} diff --git a/packages/libs/error-stack/src/ser/nightly.rs b/packages/libs/error-stack/src/ser/nightly.rs new file mode 100644 index 00000000000..663d05c6425 --- /dev/null +++ b/packages/libs/error-stack/src/ser/nightly.rs @@ -0,0 +1,14 @@ +use serde::{Serialize, Serializer}; + +pub struct SerializeDiagnostic { + inner: Box, +} + +impl Serialize for SerializeDiagnostic { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} From 3a83ebc795f8618085349034e4cbc89fc915a8b6 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Wed, 3 Aug 2022 20:25:28 +0200 Subject: [PATCH 8/9] chore: remove unused code --- packages/libs/error-stack/src/ser/hook.rs | 26 ----------------------- 1 file changed, 26 deletions(-) diff --git a/packages/libs/error-stack/src/ser/hook.rs b/packages/libs/error-stack/src/ser/hook.rs index 092f83045a7..8bc95c2764b 100644 --- a/packages/libs/error-stack/src/ser/hook.rs +++ b/packages/libs/error-stack/src/ser/hook.rs @@ -134,35 +134,9 @@ impl Hook for () { pub struct Hooks>(T); impl> Hooks { - fn bind(&self, frame: &Frame) -> Bound { - Bound::new(self, frame) - } - pub(crate) fn call(&self, frame: &Frame) -> Option> { self.0.call(frame) } } pub(crate) type ErasedHooks = Hooks>>; - -struct Bound<'a, T> -where - T: Hook, -{ - hooks: &'a Hooks, - frame: &'a Frame, -} - -impl<'a, T: Hook> Bound<'a, T> { - fn new(hooks: &'a Hooks, frame: &'a Frame) -> Self { - Self { hooks, frame } - } - - fn serialize(&self, frame: &Frame, mut serializer: S) -> Option> - where - S: serde::Serializer, - { - let s = self.hooks.call(frame)?; - Some(s.serialize(serializer)) - } -} From 535922c0ab7340a972fbdbd73d0a9b56ceef5752 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Thu, 4 Aug 2022 09:56:26 +0200 Subject: [PATCH 9/9] feat: drive-by dependencies --- packages/libs/error-stack/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/libs/error-stack/Cargo.toml b/packages/libs/error-stack/Cargo.toml index e1322223e73..e5a7c970aa6 100644 --- a/packages/libs/error-stack/Cargo.toml +++ b/packages/libs/error-stack/Cargo.toml @@ -14,7 +14,7 @@ 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'] } @@ -37,7 +37,7 @@ rustc_version = "0.2.3" [features] default = ["std", "small", "serde"] -std = ["anyhow?/std", "once_cell?/std", "serde?/std"] +std = ["anyhow?/std"] hooks = ["dep:once_cell", "std"] spantrace = ["dep:tracing-error"] futures = ["dep:pin-project", "dep:futures-core"]