From 391dcc8052f956a43a6118e8d228c8502a84c5b0 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 18 Dec 2022 15:04:51 +0100 Subject: [PATCH 1/5] feat: move `HookContext` into `hook` instead of `fmt` --- packages/libs/error-stack/src/fmt/hook.rs | 533 +---------------- packages/libs/error-stack/src/fmt/mod.rs | 4 +- packages/libs/error-stack/src/hook/context.rs | 537 ++++++++++++++++++ .../error-stack/src/{hook.rs => hook/mod.rs} | 2 + 4 files changed, 560 insertions(+), 516 deletions(-) create mode 100644 packages/libs/error-stack/src/hook/context.rs rename packages/libs/error-stack/src/{hook.rs => hook/mod.rs} (99%) diff --git a/packages/libs/error-stack/src/fmt/hook.rs b/packages/libs/error-stack/src/fmt/hook.rs index 31b98007b97..18b688e1236 100644 --- a/packages/libs/error-stack/src/fmt/hook.rs +++ b/packages/libs/error-stack/src/fmt/hook.rs @@ -7,10 +7,9 @@ // implementation: `pub(crate)` and `pub`. #![cfg_attr(not(feature = "std"), allow(unreachable_pub))] -use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec}; +use alloc::{boxed::Box, string::String, vec::Vec}; use core::{ any::{Any, TypeId}, - marker::PhantomData, mem, }; @@ -18,52 +17,28 @@ pub(crate) use default::install_builtin_hooks; use crate::fmt::Frame; -type Storage = BTreeMap>>; - -/// Private struct which is used to hold the information about the current count for every type. -/// This is used so that others cannot interfere with the counter and ensure that there's no -/// unexpected behavior. -struct Counter(isize); - -impl Counter { - const fn new(value: isize) -> Self { - Self(value) - } - - const fn as_inner(&self) -> isize { - self.0 - } - - fn increment(&mut self) { - self.0 += 1; - } - - fn decrement(&mut self) { - self.0 -= 1; - } -} - -#[derive(Debug)] -pub(crate) struct HookContextInner { - storage: Storage, - +pub(crate) struct Extra { alternate: bool, body: Vec, appendix: Vec, } -impl HookContextInner { - fn storage(&self) -> &Storage { - &self.storage +impl Extra { + pub(crate) fn new(alternate: bool) -> Self { + Self { + alternate, + body: Vec::new(), + appendix: Vec::new(), + } } - fn storage_mut(&mut self) -> &mut Storage { - &mut self.storage + pub fn body(&self) -> &[String] { + &self.body } - const fn alternate(&self) -> bool { - self.alternate + pub fn appendix(&self) -> &[String] { + &self.appendix } fn take_body(&mut self) -> Vec { @@ -71,214 +46,11 @@ impl HookContextInner { } } -impl HookContextInner { - fn new(alternate: bool) -> Self { - Self { - storage: Storage::default(), - body: Vec::new(), - appendix: Vec::new(), - alternate, - } - } -} - -/// Carrier for contextual information used across hook invocations. -/// -/// `HookContext` has two fundamental use-cases: -/// 1) Adding body entries and appendix entries -/// 2) Storage -/// -/// ## Adding body entries and appendix entries -/// -/// A [`Debug`] backtrace consists of two different sections, a rendered tree of objects (the -/// **body**) and additional text/information that is too large to fit into the tree (the -/// **appendix**). -/// -/// Entries for the body can be attached to the rendered tree of objects via -/// [`HookContext::push_body`]. An appendix entry can be attached via -/// [`HookContext::push_appendix`]. -/// -/// [`Debug`]: core::fmt::Debug -/// -/// ### Example -/// -/// ```rust -/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version -/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] -/// use std::io::{Error, ErrorKind}; -/// -/// use error_stack::Report; -/// -/// struct Warning(&'static str); -/// struct HttpResponseStatusCode(u64); -/// struct Suggestion(&'static str); -/// struct Secret(&'static str); -/// -/// Report::install_debug_hook::(|HttpResponseStatusCode(value), context| { -/// // Create a new appendix, which is going to be displayed when someone requests the alternate -/// // version (`:#?`) of the report. -/// if context.alternate() { -/// context.push_appendix(format!("error {value}: {} error", if *value < 500 {"client"} else {"server"})) -/// } -/// -/// // This will push a new entry onto the body with the specified value -/// context.push_body(format!("error code: {value}")); -/// }); -/// -/// Report::install_debug_hook::(|Suggestion(value), context| { -/// let idx = context.increment_counter(); -/// -/// // Create a new appendix, which is going to be displayed when someone requests the alternate -/// // version (`:#?`) of the report. -/// if context.alternate() { -/// context.push_body(format!("suggestion {idx}:\n {value}")); -/// } -/// -/// // This will push a new entry onto the body with the specified value -/// context.push_body(format!("suggestion ({idx})")); -/// }); -/// -/// Report::install_debug_hook::(|Warning(value), context| { -/// // You can add multiples entries to the body (and appendix) in the same hook. -/// context.push_body("abnormal program execution detected"); -/// context.push_body(format!("warning: {value}")); -/// }); -/// -/// // By not adding anything you are able to hide an attachment -/// // (it will still be counted towards opaque attachments) -/// Report::install_debug_hook::(|_, _| {}); -/// -/// let report = Report::new(Error::from(ErrorKind::InvalidInput)) -/// .attach(HttpResponseStatusCode(404)) -/// .attach(Suggestion("do you have a connection to the internet?")) -/// .attach(HttpResponseStatusCode(405)) -/// .attach(Warning("unable to determine environment")) -/// .attach(Secret("pssst, don't tell anyone else c;")) -/// .attach(Suggestion("execute the program from the fish shell")) -/// .attach(HttpResponseStatusCode(501)) -/// .attach(Suggestion("try better next time!")); -/// -/// # owo_colors::set_override(true); -/// # fn render(value: String) -> String { -/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); -/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); -/// # -/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); -/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); -/// # -/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() -/// # } -/// # -/// # #[cfg(rust_1_65)] -/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap")].assert_eq(&render(format!("{report:?}"))); -/// # -/// println!("{report:?}"); -/// -/// # #[cfg(rust_1_65)] -/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap")].assert_eq(&render(format!("{report:#?}"))); -/// # -/// println!("{report:#?}"); -/// ``` -/// -/// The output of `println!("{report:?}")`: -/// -///
-#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap"))]
-/// 
-/// -/// The output of `println!("{report:#?}")`: -/// -///
-#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap"))]
-/// 
-/// -/// ## Storage -/// -/// `HookContext` can be used to store and retrieve values that are going to be used on multiple -/// hook invocations in a single [`Debug`] call. -/// -/// Every hook can request their corresponding `HookContext`. -/// This is especially useful for incrementing/decrementing values, but can also be used to store -/// any arbitrary value for the duration of the [`Debug`] invocation. -/// -/// All data stored in `HookContext` is completely separated from all other hooks and can store -/// any arbitrary data of any type, and even data of multiple types at the same time. -/// -/// ### Example -/// -/// ```rust -/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version -/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] -/// use std::io::ErrorKind; -/// -/// use error_stack::Report; -/// -/// struct Computation(u64); -/// -/// Report::install_debug_hook::(|Computation(value), context| { -/// // Get a value of type `u64`, if we didn't insert one yet, default to 0 -/// let mut acc = context.get::().copied().unwrap_or(0); -/// acc += *value; -/// -/// // Get a value of type `f64`, if we didn't insert one yet, default to 1.0 -/// let mut div = context.get::().copied().unwrap_or(1.0); -/// div /= *value as f32; -/// -/// // Insert the calculated `u64` and `f32` back into storage, so that we can use them -/// // in the invocations following this one (for the same `Debug` call) -/// context.insert(acc); -/// context.insert(div); -/// -/// context.push_body(format!( -/// "computation for {value} (acc = {acc}, div = {div})" -/// )); -/// }); -/// -/// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) -/// .attach(Computation(2)) -/// .attach(Computation(3)); -/// -/// # owo_colors::set_override(true); -/// # fn render(value: String) -> String { -/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); -/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); -/// # -/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); -/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); -/// # -/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() -/// # } -/// # -/// # #[cfg(rust_1_65)] -/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap")].assert_eq(&render(format!("{report:?}"))); -/// # -/// println!("{report:?}"); -/// ``` -/// -///
-#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap"))]
-/// 
-/// -/// [`Debug`]: core::fmt::Debug -// TODO: ideally we would want to make `HookContextInner` private, as it is an implementation -// detail, but "attribute privacy" as outlined in https://github.com/rust-lang/rust/pull/61969 -// is currently not implemented for repr(transparent). -#[cfg_attr(not(doc), repr(transparent))] -pub struct HookContext { - inner: HookContextInner, - _marker: PhantomData, -} +pub type HookContext = crate::hook::context::HookContext; impl HookContext { - pub(crate) fn new(alternate: bool) -> Self { - Self { - inner: HookContextInner::new(alternate), - _marker: PhantomData, - } - } - pub(crate) fn appendix(&self) -> &[String] { - &self.inner.appendix + self.inner().extra().appendix() } /// The contents of the appendix are going to be displayed after the body in the order they have @@ -340,7 +112,7 @@ impl HookContext { #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_emit.snap"))] /// pub fn push_appendix(&mut self, content: impl Into) { - self.inner.appendix.push(content.into()); + self.inner_mut().extra_mut().appendix.push(content.into()); } /// Add a new entry to the body. @@ -387,75 +159,7 @@ impl HookContext { #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__diagnostics_add.snap"))] /// pub fn push_body(&mut self, content: impl Into) { - self.inner.body.push(content.into()); - } - - /// Cast the [`HookContext`] to a new type `U`. - /// - /// The storage of [`HookContext`] is partitioned, meaning that if `T` and `U` are different - /// types the values stored in [`HookContext`] will be separated from values in - /// [`HookContext`]. - /// - /// In most situations this functions isn't needed, as it transparently casts between different - /// partitions of the storage. Only hooks that share storage with hooks of different types - /// should need to use this function. - /// - /// ### Example - /// - /// ```rust - /// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version - /// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] - /// use std::io::ErrorKind; - /// - /// use error_stack::Report; - /// - /// struct Warning(&'static str); - /// struct Error(&'static str); - /// - /// Report::install_debug_hook::(|Error(frame), context| { - /// let idx = context.increment_counter() + 1; - /// - /// context.push_body(format!("[{idx}] [ERROR] {frame}")); - /// }); - /// Report::install_debug_hook::(|Warning(frame), context| { - /// // We want to share the same counter with `Error`, so that we're able to have - /// // a global counter to keep track of all errors and warnings in order, this means - /// // we need to access the storage of `Error` using `cast()`. - /// let context = context.cast::(); - /// let idx = context.increment_counter() + 1; - /// context.push_body(format!("[{idx}] [WARN] {frame}")) - /// }); - /// - /// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) - /// .attach(Error("unable to reach remote host")) - /// .attach(Warning("disk nearly full")) - /// .attach(Error("cannot resolve example.com: unknown host")); - /// - /// # owo_colors::set_override(true); - /// # fn render(value: String) -> String { - /// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); - /// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); - /// # - /// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); - /// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); - /// # - /// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() - /// # } - /// # - /// # #[cfg(rust_1_65)] - /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_cast.snap")].assert_eq(&render(format!("{report:?}"))); - /// # - /// println!("{report:?}"); - /// ``` - /// - ///
-    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_cast.snap"))]
-    /// 
- #[must_use] - pub fn cast(&mut self) -> &mut HookContext { - // SAFETY: `HookContext` is marked as repr(transparent) and the generic is only used inside - // of the `PhantomData` - unsafe { &mut *(self as *mut Self).cast::>() } + self.inner_mut().extra_mut().body.push(content.into()); } /// Returns if the currently requested format should render the alternate representation. @@ -463,210 +167,11 @@ impl HookContext { /// This corresponds to the output of [`std::fmt::Formatter::alternate`]. #[must_use] pub const fn alternate(&self) -> bool { - self.inner.alternate() - } - - fn storage(&self) -> &Storage { - self.inner.storage() - } - - fn storage_mut(&mut self) -> &mut Storage { - self.inner.storage_mut() + self.inner().extra().alternate() } pub(crate) fn take_body(&mut self) -> Vec { - self.inner.take_body() - } -} - -impl HookContext { - /// Return a reference to a value of type `U`, if a value of that type exists. - /// - /// Values returned are isolated and "bound" to `T`, this means that [`HookContext`] - /// and [`HookContext`] do not share the same values. Values are only retained during the - /// invocation of [`Debug`]. - /// - /// [`Debug`]: core::fmt::Debug - #[must_use] - pub fn get(&self) -> Option<&U> { - self.storage() - .get(&TypeId::of::())? - .get(&TypeId::of::())? - .downcast_ref() - } - - /// Return a mutable reference to a value of type `U`, if a value of that type exists. - /// - /// Values returned are isolated and "bound" to `T`, this means that [`HookContext`] - /// and [`HookContext`] do not share the same values. Values are only retained during the - /// invocation of [`Debug`]. - /// - /// [`Debug`]: core::fmt::Debug - pub fn get_mut(&mut self) -> Option<&mut U> { - self.storage_mut() - .get_mut(&TypeId::of::())? - .get_mut(&TypeId::of::())? - .downcast_mut() - } - - /// Insert a new value of type `U` into the storage of [`HookContext`]. - /// - /// The returned value will the previously stored value of the same type `U` scoped over type - /// `T`, if it existed, did no such value exist it will return [`None`]. - pub fn insert(&mut self, value: U) -> Option { - self.storage_mut() - .entry(TypeId::of::()) - .or_default() - .insert(TypeId::of::(), Box::new(value))? - .downcast() - .map(|boxed| *boxed) - .ok() - } - - /// Remove the value of type `U` from the storage of [`HookContext`] if it existed. - /// - /// The returned value will be the previously stored value of the same type `U` if it existed in - /// the scope of `T`, did no such value exist, it will return [`None`]. - pub fn remove(&mut self) -> Option { - self.storage_mut() - .get_mut(&TypeId::of::())? - .remove(&TypeId::of::())? - .downcast() - .map(|boxed| *boxed) - .ok() - } - - /// One of the most common interactions with [`HookContext`] is a counter to reference previous - /// frames in an entry to the appendix that was added using [`HookContext::push_appendix`]. - /// - /// This is a utility method, which uses the other primitive methods provided to automatically - /// increment a counter, if the counter wasn't initialized this method will return `0`. - /// - /// ```rust - /// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version - /// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] - /// use std::io::ErrorKind; - /// - /// use error_stack::Report; - /// - /// struct Suggestion(&'static str); - /// - /// Report::install_debug_hook::(|Suggestion(value), context| { - /// let idx = context.increment_counter(); - /// context.push_body(format!("suggestion {idx}: {value}")); - /// }); - /// - /// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) - /// .attach(Suggestion("use a file you can read next time!")) - /// .attach(Suggestion("don't press any random keys!")); - /// - /// # owo_colors::set_override(true); - /// # fn render(value: String) -> String { - /// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); - /// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); - /// # - /// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); - /// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); - /// # - /// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() - /// # } - /// # - /// # #[cfg(rust_1_65)] - /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_increment.snap")].assert_eq(&render(format!("{report:?}"))); - /// # - /// println!("{report:?}"); - /// ``` - /// - ///
-    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_increment.snap"))]
-    /// 
- /// - /// [`Debug`]: core::fmt::Debug - pub fn increment_counter(&mut self) -> isize { - let counter = self.get_mut::(); - - // reason: This would fail as we cannot move out of `self` because it is borrowed - #[allow(clippy::option_if_let_else)] - match counter { - None => { - // if the counter hasn't been set yet, default to `0` - self.insert(Counter::new(0)); - - 0 - } - Some(ctr) => { - ctr.increment(); - - ctr.as_inner() - } - } - } - - /// One of the most common interactions with [`HookContext`] is a counter to reference previous - /// frames in an entry to the appendix that was added using [`HookContext::push_appendix`]. - /// - /// This is a utility method, which uses the other primitive method provided to automatically - /// decrement a counter, if the counter wasn't initialized this method will return `-1` to stay - /// consistent with [`HookContext::increment_counter`]. - /// - /// ```rust - /// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version - /// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] - /// use std::io::ErrorKind; - /// - /// use error_stack::Report; - /// - /// struct Suggestion(&'static str); - /// - /// Report::install_debug_hook::(|Suggestion(value), context| { - /// let idx = context.decrement_counter(); - /// context.push_body(format!("suggestion {idx}: {value}")); - /// }); - /// - /// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) - /// .attach(Suggestion("use a file you can read next time!")) - /// .attach(Suggestion("don't press any random keys!")); - /// - /// # owo_colors::set_override(true); - /// # fn render(value: String) -> String { - /// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); - /// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); - /// # - /// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); - /// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); - /// # - /// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() - /// # } - /// # - /// # #[cfg(rust_1_65)] - /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_decrement.snap")].assert_eq(&render(format!("{report:?}"))); - /// # - /// println!("{report:?}"); - /// ``` - /// - ///
-    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_decrement.snap"))]
-    /// 
- pub fn decrement_counter(&mut self) -> isize { - let counter = self.get_mut::(); - - // reason: This would fail as we cannot move out of `self` because it is borrowed - #[allow(clippy::option_if_let_else)] - match counter { - None => { - // given that increment starts with `0` (which is therefore the implicit default - // value) decrementing the default value results in `-1`, - // which is why we output that value. - self.insert(Counter::new(-1)); - - -1 - } - Some(ctr) => { - ctr.decrement(); - - ctr.as_inner() - } - } + self.inner_mut().extra_mut().take_body() } } diff --git a/packages/libs/error-stack/src/fmt/mod.rs b/packages/libs/error-stack/src/fmt/mod.rs index 9413dd4eac0..c0a5332e27a 100644 --- a/packages/libs/error-stack/src/fmt/mod.rs +++ b/packages/libs/error-stack/src/fmt/mod.rs @@ -166,7 +166,7 @@ pub(crate) use hook::{install_builtin_hooks, Hooks}; #[cfg(feature = "pretty-print")] use owo_colors::{OwoColorize, Stream, Style as OwOStyle}; -use crate::{AttachmentKind, Context, Frame, FrameKind, Report}; +use crate::{fmt::hook::Extra, AttachmentKind, Context, Frame, FrameKind, Report}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum Symbol { @@ -947,7 +947,7 @@ fn debug_frame( impl Debug for Report { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { #[cfg(any(feature = "std", feature = "hooks"))] - let mut context = HookContext::new(fmt.alternate()); + let mut context = HookContext::new(Extra::new(fmt.alternate())).cast(); #[cfg_attr(not(any(feature = "std", feature = "hooks")), allow(unused_mut))] let mut lines = self diff --git a/packages/libs/error-stack/src/hook/context.rs b/packages/libs/error-stack/src/hook/context.rs new file mode 100644 index 00000000000..bcca93d6130 --- /dev/null +++ b/packages/libs/error-stack/src/hook/context.rs @@ -0,0 +1,537 @@ +use alloc::{boxed::Box, collections::BTreeMap}; +use core::{ + any::{Any, TypeId}, + marker::PhantomData, +}; + +type Storage = BTreeMap>>; + +/// Private struct which is used to hold the information about the current count for every type. +/// This is used so that others cannot interfere with the counter and ensure that there's no +/// unexpected behavior. +struct Counter(isize); + +impl Counter { + const fn new(value: isize) -> Self { + Self(value) + } + + const fn as_inner(&self) -> isize { + self.0 + } + + fn increment(&mut self) { + self.0 += 1; + } + + fn decrement(&mut self) { + self.0 -= 1; + } +} + +struct Inner { + storage: Storage, + extra: T, +} + +impl Inner { + pub fn new(extra: T) -> Self { + Self { + storage: Storage::new(), + extra, + } + } +} + +impl Inner { + pub(crate) fn storage(&self) -> &Storage { + &self.storage + } + + pub(crate) fn storage_mut(&mut self) -> &mut Storage { + &mut self.storage + } + + pub(crate) fn extra(&self) -> &T { + &self.extra + } + + pub(crate) fn extra_mut(&mut self) -> &mut T { + &mut self.extra + } +} + +/// Carrier for contextual information used across hook invocations. +/// +/// `HookContext` has two fundamental use-cases: +/// 1) Adding body entries and appendix entries +/// 2) Storage +/// +/// ## Adding body entries and appendix entries +/// +/// A [`Debug`] backtrace consists of two different sections, a rendered tree of objects (the +/// **body**) and additional text/information that is too large to fit into the tree (the +/// **appendix**). +/// +/// Entries for the body can be attached to the rendered tree of objects via +/// [`HookContext::push_body`]. An appendix entry can be attached via +/// [`HookContext::push_appendix`]. +/// +/// [`Debug`]: core::fmt::Debug +/// +/// ### Example +/// +/// ```rust +/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version +/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] +/// use std::io::{Error, ErrorKind}; +/// +/// use error_stack::Report; +/// +/// struct Warning(&'static str); +/// struct HttpResponseStatusCode(u64); +/// struct Suggestion(&'static str); +/// struct Secret(&'static str); +/// +/// Report::install_debug_hook::(|HttpResponseStatusCode(value), context| { +/// // Create a new appendix, which is going to be displayed when someone requests the alternate +/// // version (`:#?`) of the report. +/// if context.alternate() { +/// context.push_appendix(format!("error {value}: {} error", if *value < 500 {"client"} else {"server"})) +/// } +/// +/// // This will push a new entry onto the body with the specified value +/// context.push_body(format!("error code: {value}")); +/// }); +/// +/// Report::install_debug_hook::(|Suggestion(value), context| { +/// let idx = context.increment_counter(); +/// +/// // Create a new appendix, which is going to be displayed when someone requests the alternate +/// // version (`:#?`) of the report. +/// if context.alternate() { +/// context.push_body(format!("suggestion {idx}:\n {value}")); +/// } +/// +/// // This will push a new entry onto the body with the specified value +/// context.push_body(format!("suggestion ({idx})")); +/// }); +/// +/// Report::install_debug_hook::(|Warning(value), context| { +/// // You can add multiples entries to the body (and appendix) in the same hook. +/// context.push_body("abnormal program execution detected"); +/// context.push_body(format!("warning: {value}")); +/// }); +/// +/// // By not adding anything you are able to hide an attachment +/// // (it will still be counted towards opaque attachments) +/// Report::install_debug_hook::(|_, _| {}); +/// +/// let report = Report::new(Error::from(ErrorKind::InvalidInput)) +/// .attach(HttpResponseStatusCode(404)) +/// .attach(Suggestion("do you have a connection to the internet?")) +/// .attach(HttpResponseStatusCode(405)) +/// .attach(Warning("unable to determine environment")) +/// .attach(Secret("pssst, don't tell anyone else c;")) +/// .attach(Suggestion("execute the program from the fish shell")) +/// .attach(HttpResponseStatusCode(501)) +/// .attach(Suggestion("try better next time!")); +/// +/// # owo_colors::set_override(true); +/// # fn render(value: String) -> String { +/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); +/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); +/// # +/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); +/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); +/// # +/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() +/// # } +/// # +/// # #[cfg(rust_1_65)] +/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap")].assert_eq(&render(format!("{report:?}"))); +/// # +/// println!("{report:?}"); +/// +/// # #[cfg(rust_1_65)] +/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap")].assert_eq(&render(format!("{report:#?}"))); +/// # +/// println!("{report:#?}"); +/// ``` +/// +/// The output of `println!("{report:?}")`: +/// +///
+#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap"))]
+/// 
+/// +/// The output of `println!("{report:#?}")`: +/// +///
+#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap"))]
+/// 
+/// +/// ## Storage +/// +/// `HookContext` can be used to store and retrieve values that are going to be used on multiple +/// hook invocations in a single [`Debug`] call. +/// +/// Every hook can request their corresponding `HookContext`. +/// This is especially useful for incrementing/decrementing values, but can also be used to store +/// any arbitrary value for the duration of the [`Debug`] invocation. +/// +/// All data stored in `HookContext` is completely separated from all other hooks and can store +/// any arbitrary data of any type, and even data of multiple types at the same time. +/// +/// ### Example +/// +/// ```rust +/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version +/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] +/// use std::io::ErrorKind; +/// +/// use error_stack::Report; +/// +/// struct Computation(u64); +/// +/// Report::install_debug_hook::(|Computation(value), context| { +/// // Get a value of type `u64`, if we didn't insert one yet, default to 0 +/// let mut acc = context.get::().copied().unwrap_or(0); +/// acc += *value; +/// +/// // Get a value of type `f64`, if we didn't insert one yet, default to 1.0 +/// let mut div = context.get::().copied().unwrap_or(1.0); +/// div /= *value as f32; +/// +/// // Insert the calculated `u64` and `f32` back into storage, so that we can use them +/// // in the invocations following this one (for the same `Debug` call) +/// context.insert(acc); +/// context.insert(div); +/// +/// context.push_body(format!( +/// "computation for {value} (acc = {acc}, div = {div})" +/// )); +/// }); +/// +/// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) +/// .attach(Computation(2)) +/// .attach(Computation(3)); +/// +/// # owo_colors::set_override(true); +/// # fn render(value: String) -> String { +/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); +/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); +/// # +/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); +/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); +/// # +/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() +/// # } +/// # +/// # #[cfg(rust_1_65)] +/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap")].assert_eq(&render(format!("{report:?}"))); +/// # +/// println!("{report:?}"); +/// ``` +/// +///
+#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap"))]
+/// 
+/// +/// [`Debug`]: core::fmt::Debug +// TODO: ideally we would want to make `HookContextInner` private, as it is an implementation +// detail, but "attribute privacy" as outlined in https://github.com/rust-lang/rust/pull/61969 +// is currently not implemented for repr(transparent). +#[cfg_attr(not(doc), repr(transparent))] +pub struct HookContext { + inner: Inner, + _marker: PhantomData, +} + +impl HookContext { + pub(crate) fn new(extra: T) -> Self { + Self { + inner: Inner::new(extra), + _marker: PhantomData, + } + } +} + +impl HookContext { + pub(crate) fn inner(&self) -> &Inner { + &self.inner + } + + pub(crate) fn inner_mut(&mut self) -> &mut Inner { + &mut self.inner + } + + fn storage(&self) -> &Storage { + self.inner().storage() + } + + fn storage_mut(&mut self) -> &mut Storage { + self.inner_mut().storage_mut() + } +} + +impl HookContext { + /// Cast the [`HookContext`] to a new type `U`. + /// + /// The storage of [`HookContext`] is partitioned, meaning that if `T` and `U` are different + /// types the values stored in [`HookContext`] will be separated from values in + /// [`HookContext`]. + /// + /// In most situations this functions isn't needed, as it transparently casts between different + /// partitions of the storage. Only hooks that share storage with hooks of different types + /// should need to use this function. + /// + /// ### Example + /// + /// ```rust + /// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version + /// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] + /// use std::io::ErrorKind; + /// + /// use error_stack::Report; + /// + /// struct Warning(&'static str); + /// struct Error(&'static str); + /// + /// Report::install_debug_hook::(|Error(frame), context| { + /// let idx = context.increment_counter() + 1; + /// + /// context.push_body(format!("[{idx}] [ERROR] {frame}")); + /// }); + /// Report::install_debug_hook::(|Warning(frame), context| { + /// // We want to share the same counter with `Error`, so that we're able to have + /// // a global counter to keep track of all errors and warnings in order, this means + /// // we need to access the storage of `Error` using `cast()`. + /// let context = context.cast::(); + /// let idx = context.increment_counter() + 1; + /// context.push_body(format!("[{idx}] [WARN] {frame}")) + /// }); + /// + /// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) + /// .attach(Error("unable to reach remote host")) + /// .attach(Warning("disk nearly full")) + /// .attach(Error("cannot resolve example.com: unknown host")); + /// + /// # owo_colors::set_override(true); + /// # fn render(value: String) -> String { + /// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); + /// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); + /// # + /// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); + /// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); + /// # + /// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() + /// # } + /// # + /// # #[cfg(rust_1_65)] + /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_cast.snap")].assert_eq(&render(format!("{report:?}"))); + /// # + /// println!("{report:?}"); + /// ``` + /// + ///
+    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_cast.snap"))]
+    /// 
+ #[must_use] + pub fn cast(&mut self) -> &mut HookContext { + // SAFETY: `HookContext` is marked as repr(transparent) and the changed generic is only used + // inside of the `PhantomData` + unsafe { &mut *(self as *mut Self).cast::>() } + } +} + +impl HookContext { + /// Return a reference to a value of type `U`, if a value of that type exists. + /// + /// Values returned are isolated and "bound" to `T`, this means that [`HookContext`] + /// and [`HookContext`] do not share the same values. Values are only retained during the + /// invocation of [`Debug`]. + /// + /// [`Debug`]: core::fmt::Debug + #[must_use] + pub fn get(&self) -> Option<&U> { + self.storage() + .get(&TypeId::of::())? + .get(&TypeId::of::())? + .downcast_ref() + } + + /// Return a mutable reference to a value of type `U`, if a value of that type exists. + /// + /// Values returned are isolated and "bound" to `T`, this means that [`HookContext`] + /// and [`HookContext`] do not share the same values. Values are only retained during the + /// invocation of [`Debug`]. + /// + /// [`Debug`]: core::fmt::Debug + pub fn get_mut(&mut self) -> Option<&mut U> { + self.storage_mut() + .get_mut(&TypeId::of::())? + .get_mut(&TypeId::of::())? + .downcast_mut() + } + + /// Insert a new value of type `U` into the storage of [`HookContext`]. + /// + /// The returned value will the previously stored value of the same type `U` scoped over type + /// `T`, if it existed, did no such value exist it will return [`None`]. + pub fn insert(&mut self, value: U) -> Option { + self.storage_mut() + .entry(TypeId::of::()) + .or_default() + .insert(TypeId::of::(), Box::new(value))? + .downcast() + .map(|boxed| *boxed) + .ok() + } + + /// Remove the value of type `U` from the storage of [`HookContext`] if it existed. + /// + /// The returned value will be the previously stored value of the same type `U` if it existed in + /// the scope of `T`, did no such value exist, it will return [`None`]. + pub fn remove(&mut self) -> Option { + self.storage_mut() + .get_mut(&TypeId::of::())? + .remove(&TypeId::of::())? + .downcast() + .map(|boxed| *boxed) + .ok() + } + + /// One of the most common interactions with [`HookContext`] is a counter to reference previous + /// frames in an entry to the appendix that was added using [`HookContext::push_appendix`]. + /// + /// This is a utility method, which uses the other primitive methods provided to automatically + /// increment a counter, if the counter wasn't initialized this method will return `0`. + /// + /// ```rust + /// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version + /// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] + /// use std::io::ErrorKind; + /// + /// use error_stack::Report; + /// + /// struct Suggestion(&'static str); + /// + /// Report::install_debug_hook::(|Suggestion(value), context| { + /// let idx = context.increment_counter(); + /// context.push_body(format!("suggestion {idx}: {value}")); + /// }); + /// + /// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) + /// .attach(Suggestion("use a file you can read next time!")) + /// .attach(Suggestion("don't press any random keys!")); + /// + /// # owo_colors::set_override(true); + /// # fn render(value: String) -> String { + /// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); + /// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); + /// # + /// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); + /// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); + /// # + /// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() + /// # } + /// # + /// # #[cfg(rust_1_65)] + /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_increment.snap")].assert_eq(&render(format!("{report:?}"))); + /// # + /// println!("{report:?}"); + /// ``` + /// + ///
+    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_increment.snap"))]
+    /// 
+ /// + /// [`Debug`]: core::fmt::Debug + pub fn increment_counter(&mut self) -> isize { + let counter = self.get_mut::(); + + // reason: This would fail as we cannot move out of `self` because it is borrowed + #[allow(clippy::option_if_let_else)] + match counter { + None => { + // if the counter hasn't been set yet, default to `0` + self.insert(Counter::new(0)); + + 0 + } + Some(ctr) => { + ctr.increment(); + + ctr.as_inner() + } + } + } + + /// One of the most common interactions with [`HookContext`] is a counter to reference previous + /// frames in an entry to the appendix that was added using [`HookContext::push_appendix`]. + /// + /// This is a utility method, which uses the other primitive method provided to automatically + /// decrement a counter, if the counter wasn't initialized this method will return `-1` to stay + /// consistent with [`HookContext::increment_counter`]. + /// + /// ```rust + /// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version + /// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] + /// use std::io::ErrorKind; + /// + /// use error_stack::Report; + /// + /// struct Suggestion(&'static str); + /// + /// Report::install_debug_hook::(|Suggestion(value), context| { + /// let idx = context.decrement_counter(); + /// context.push_body(format!("suggestion {idx}: {value}")); + /// }); + /// + /// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) + /// .attach(Suggestion("use a file you can read next time!")) + /// .attach(Suggestion("don't press any random keys!")); + /// + /// # owo_colors::set_override(true); + /// # fn render(value: String) -> String { + /// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); + /// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); + /// # + /// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); + /// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); + /// # + /// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() + /// # } + /// # + /// # #[cfg(rust_1_65)] + /// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_decrement.snap")].assert_eq(&render(format!("{report:?}"))); + /// # + /// println!("{report:?}"); + /// ``` + /// + ///
+    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_decrement.snap"))]
+    /// 
+ pub fn decrement_counter(&mut self) -> isize { + let counter = self.get_mut::(); + + // reason: This would fail as we cannot move out of `self` because it is borrowed + #[allow(clippy::option_if_let_else)] + match counter { + None => { + // given that increment starts with `0` (which is therefore the implicit default + // value) decrementing the default value results in `-1`, + // which is why we output that value. + self.insert(Counter::new(-1)); + + -1 + } + Some(ctr) => { + ctr.decrement(); + + ctr.as_inner() + } + } + } +} diff --git a/packages/libs/error-stack/src/hook.rs b/packages/libs/error-stack/src/hook/mod.rs similarity index 99% rename from packages/libs/error-stack/src/hook.rs rename to packages/libs/error-stack/src/hook/mod.rs index c5af82d1eef..5962a45d6ff 100644 --- a/packages/libs/error-stack/src/hook.rs +++ b/packages/libs/error-stack/src/hook/mod.rs @@ -1,3 +1,5 @@ +pub(crate) mod context; + use alloc::vec::Vec; use crate::{ From 42b079a7f766e086261280fa1d52e6ef1f3fe120 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 18 Dec 2022 15:25:31 +0100 Subject: [PATCH 2/5] fix: docs + type naming --- packages/libs/error-stack/src/fmt/hook.rs | 189 ++++++++++++++- packages/libs/error-stack/src/fmt/mod.rs | 8 +- packages/libs/error-stack/src/hook/context.rs | 225 ++---------------- 3 files changed, 210 insertions(+), 212 deletions(-) diff --git a/packages/libs/error-stack/src/fmt/hook.rs b/packages/libs/error-stack/src/fmt/hook.rs index 18b688e1236..2e45c1ba0e4 100644 --- a/packages/libs/error-stack/src/fmt/hook.rs +++ b/packages/libs/error-stack/src/fmt/hook.rs @@ -8,16 +8,13 @@ #![cfg_attr(not(feature = "std"), allow(unreachable_pub))] use alloc::{boxed::Box, string::String, vec::Vec}; -use core::{ - any::{Any, TypeId}, - mem, -}; +use core::{any::TypeId, mem}; pub(crate) use default::install_builtin_hooks; use crate::fmt::Frame; -pub(crate) struct Extra { +pub struct Extra { alternate: bool, body: Vec, @@ -25,7 +22,7 @@ pub(crate) struct Extra { } impl Extra { - pub(crate) fn new(alternate: bool) -> Self { + pub(crate) const fn new(alternate: bool) -> Self { Self { alternate, body: Vec::new(), @@ -46,6 +43,184 @@ impl Extra { } } +/// Carrier for contextual information used across hook invocations. +/// +/// `HookContext` has two fundamental use-cases: +/// 1) Adding body entries and appendix entries +/// 2) Storage +/// +/// ## Adding body entries and appendix entries +/// +/// A [`Debug`] backtrace consists of two different sections, a rendered tree of objects (the +/// **body**) and additional text/information that is too large to fit into the tree (the +/// **appendix**). +/// +/// Entries for the body can be attached to the rendered tree of objects via +/// [`HookContext::push_body`]. An appendix entry can be attached via +/// [`HookContext::push_appendix`]. +/// +/// [`Debug`]: core::fmt::Debug +/// +/// ### Example +/// +/// ```rust +/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version +/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] +/// use std::io::{Error, ErrorKind}; +/// +/// use error_stack::Report; +/// +/// struct Warning(&'static str); +/// struct HttpResponseStatusCode(u64); +/// struct Suggestion(&'static str); +/// struct Secret(&'static str); +/// +/// Report::install_debug_hook::(|HttpResponseStatusCode(value), context| { +/// // Create a new appendix, which is going to be displayed when someone requests the alternate +/// // version (`:#?`) of the report. +/// if context.alternate() { +/// context.push_appendix(format!("error {value}: {} error", if *value < 500 {"client"} else {"server"})) +/// } +/// +/// // This will push a new entry onto the body with the specified value +/// context.push_body(format!("error code: {value}")); +/// }); +/// +/// Report::install_debug_hook::(|Suggestion(value), context| { +/// let idx = context.increment_counter(); +/// +/// // Create a new appendix, which is going to be displayed when someone requests the alternate +/// // version (`:#?`) of the report. +/// if context.alternate() { +/// context.push_body(format!("suggestion {idx}:\n {value}")); +/// } +/// +/// // This will push a new entry onto the body with the specified value +/// context.push_body(format!("suggestion ({idx})")); +/// }); +/// +/// Report::install_debug_hook::(|Warning(value), context| { +/// // You can add multiples entries to the body (and appendix) in the same hook. +/// context.push_body("abnormal program execution detected"); +/// context.push_body(format!("warning: {value}")); +/// }); +/// +/// // By not adding anything you are able to hide an attachment +/// // (it will still be counted towards opaque attachments) +/// Report::install_debug_hook::(|_, _| {}); +/// +/// let report = Report::new(Error::from(ErrorKind::InvalidInput)) +/// .attach(HttpResponseStatusCode(404)) +/// .attach(Suggestion("do you have a connection to the internet?")) +/// .attach(HttpResponseStatusCode(405)) +/// .attach(Warning("unable to determine environment")) +/// .attach(Secret("pssst, don't tell anyone else c;")) +/// .attach(Suggestion("execute the program from the fish shell")) +/// .attach(HttpResponseStatusCode(501)) +/// .attach(Suggestion("try better next time!")); +/// +/// # owo_colors::set_override(true); +/// # fn render(value: String) -> String { +/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); +/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); +/// # +/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); +/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); +/// # +/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() +/// # } +/// # +/// # #[cfg(rust_1_65)] +/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap")].assert_eq(&render(format!("{report:?}"))); +/// # +/// println!("{report:?}"); +/// +/// # #[cfg(rust_1_65)] +/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap")].assert_eq(&render(format!("{report:#?}"))); +/// # +/// println!("{report:#?}"); +/// ``` +/// +/// The output of `println!("{report:?}")`: +/// +///
+#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap"))]
+/// 
+/// +/// The output of `println!("{report:#?}")`: +/// +///
+#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap"))]
+/// 
+/// +/// ## Storage +/// +/// `HookContext` can be used to store and retrieve values that are going to be used on multiple +/// hook invocations in a single [`Debug`] call. +/// +/// Every hook can request their corresponding `HookContext`. +/// This is especially useful for incrementing/decrementing values, but can also be used to store +/// any arbitrary value for the duration of the [`Debug`] invocation. +/// +/// All data stored in `HookContext` is completely separated from all other hooks and can store +/// any arbitrary data of any type, and even data of multiple types at the same time. +/// +/// ### Example +/// +/// ```rust +/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version +/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] +/// use std::io::ErrorKind; +/// +/// use error_stack::Report; +/// +/// struct Computation(u64); +/// +/// Report::install_debug_hook::(|Computation(value), context| { +/// // Get a value of type `u64`, if we didn't insert one yet, default to 0 +/// let mut acc = context.get::().copied().unwrap_or(0); +/// acc += *value; +/// +/// // Get a value of type `f64`, if we didn't insert one yet, default to 1.0 +/// let mut div = context.get::().copied().unwrap_or(1.0); +/// div /= *value as f32; +/// +/// // Insert the calculated `u64` and `f32` back into storage, so that we can use them +/// // in the invocations following this one (for the same `Debug` call) +/// context.insert(acc); +/// context.insert(div); +/// +/// context.push_body(format!( +/// "computation for {value} (acc = {acc}, div = {div})" +/// )); +/// }); +/// +/// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) +/// .attach(Computation(2)) +/// .attach(Computation(3)); +/// +/// # owo_colors::set_override(true); +/// # fn render(value: String) -> String { +/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); +/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); +/// # +/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); +/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); +/// # +/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() +/// # } +/// # +/// # #[cfg(rust_1_65)] +/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap")].assert_eq(&render(format!("{report:?}"))); +/// # +/// println!("{report:?}"); +/// ``` +/// +///
+#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap"))]
+/// 
+/// +/// [`Debug`]: core::fmt::Debug pub type HookContext = crate::hook::context::HookContext; impl HookContext { @@ -167,7 +342,7 @@ impl HookContext { /// This corresponds to the output of [`std::fmt::Formatter::alternate`]. #[must_use] pub const fn alternate(&self) -> bool { - self.inner().extra().alternate() + self.inner().extra().alternate } pub(crate) fn take_body(&mut self) -> Vec { diff --git a/packages/libs/error-stack/src/fmt/mod.rs b/packages/libs/error-stack/src/fmt/mod.rs index c0a5332e27a..b14e089e276 100644 --- a/packages/libs/error-stack/src/fmt/mod.rs +++ b/packages/libs/error-stack/src/fmt/mod.rs @@ -162,11 +162,11 @@ use core::{ #[cfg(any(feature = "std", feature = "hooks"))] pub use hook::HookContext; #[cfg(any(feature = "std", feature = "hooks"))] -pub(crate) use hook::{install_builtin_hooks, Hooks}; +pub(crate) use hook::{install_builtin_hooks, Extra, Hooks}; #[cfg(feature = "pretty-print")] use owo_colors::{OwoColorize, Stream, Style as OwOStyle}; -use crate::{fmt::hook::Extra, AttachmentKind, Context, Frame, FrameKind, Report}; +use crate::{AttachmentKind, Context, Frame, FrameKind, Report}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum Symbol { @@ -947,7 +947,7 @@ fn debug_frame( impl Debug for Report { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { #[cfg(any(feature = "std", feature = "hooks"))] - let mut context = HookContext::new(Extra::new(fmt.alternate())).cast(); + let mut context = HookContext::new(Extra::new(fmt.alternate())); #[cfg_attr(not(any(feature = "std", feature = "hooks")), allow(unused_mut))] let mut lines = self @@ -958,7 +958,7 @@ impl Debug for Report { frame, &[], #[cfg(any(feature = "std", feature = "hooks"))] - &mut context, + context.cast(), ) }) .enumerate() diff --git a/packages/libs/error-stack/src/hook/context.rs b/packages/libs/error-stack/src/hook/context.rs index bcca93d6130..f631ab0f681 100644 --- a/packages/libs/error-stack/src/hook/context.rs +++ b/packages/libs/error-stack/src/hook/context.rs @@ -29,13 +29,13 @@ impl Counter { } } -struct Inner { +pub(crate) struct Inner { storage: Storage, extra: T, } impl Inner { - pub fn new(extra: T) -> Self { + pub(crate) fn new(extra: T) -> Self { Self { storage: Storage::new(), extra, @@ -44,7 +44,7 @@ impl Inner { } impl Inner { - pub(crate) fn storage(&self) -> &Storage { + pub(crate) const fn storage(&self) -> &Storage { &self.storage } @@ -52,7 +52,7 @@ impl Inner { &mut self.storage } - pub(crate) fn extra(&self) -> &T { + pub(crate) const fn extra(&self) -> &T { &self.extra } @@ -61,184 +61,7 @@ impl Inner { } } -/// Carrier for contextual information used across hook invocations. -/// -/// `HookContext` has two fundamental use-cases: -/// 1) Adding body entries and appendix entries -/// 2) Storage -/// -/// ## Adding body entries and appendix entries -/// -/// A [`Debug`] backtrace consists of two different sections, a rendered tree of objects (the -/// **body**) and additional text/information that is too large to fit into the tree (the -/// **appendix**). -/// -/// Entries for the body can be attached to the rendered tree of objects via -/// [`HookContext::push_body`]. An appendix entry can be attached via -/// [`HookContext::push_appendix`]. -/// -/// [`Debug`]: core::fmt::Debug -/// -/// ### Example -/// -/// ```rust -/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version -/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] -/// use std::io::{Error, ErrorKind}; -/// -/// use error_stack::Report; -/// -/// struct Warning(&'static str); -/// struct HttpResponseStatusCode(u64); -/// struct Suggestion(&'static str); -/// struct Secret(&'static str); -/// -/// Report::install_debug_hook::(|HttpResponseStatusCode(value), context| { -/// // Create a new appendix, which is going to be displayed when someone requests the alternate -/// // version (`:#?`) of the report. -/// if context.alternate() { -/// context.push_appendix(format!("error {value}: {} error", if *value < 500 {"client"} else {"server"})) -/// } -/// -/// // This will push a new entry onto the body with the specified value -/// context.push_body(format!("error code: {value}")); -/// }); -/// -/// Report::install_debug_hook::(|Suggestion(value), context| { -/// let idx = context.increment_counter(); -/// -/// // Create a new appendix, which is going to be displayed when someone requests the alternate -/// // version (`:#?`) of the report. -/// if context.alternate() { -/// context.push_body(format!("suggestion {idx}:\n {value}")); -/// } -/// -/// // This will push a new entry onto the body with the specified value -/// context.push_body(format!("suggestion ({idx})")); -/// }); -/// -/// Report::install_debug_hook::(|Warning(value), context| { -/// // You can add multiples entries to the body (and appendix) in the same hook. -/// context.push_body("abnormal program execution detected"); -/// context.push_body(format!("warning: {value}")); -/// }); -/// -/// // By not adding anything you are able to hide an attachment -/// // (it will still be counted towards opaque attachments) -/// Report::install_debug_hook::(|_, _| {}); -/// -/// let report = Report::new(Error::from(ErrorKind::InvalidInput)) -/// .attach(HttpResponseStatusCode(404)) -/// .attach(Suggestion("do you have a connection to the internet?")) -/// .attach(HttpResponseStatusCode(405)) -/// .attach(Warning("unable to determine environment")) -/// .attach(Secret("pssst, don't tell anyone else c;")) -/// .attach(Suggestion("execute the program from the fish shell")) -/// .attach(HttpResponseStatusCode(501)) -/// .attach(Suggestion("try better next time!")); -/// -/// # owo_colors::set_override(true); -/// # fn render(value: String) -> String { -/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); -/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); -/// # -/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); -/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); -/// # -/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() -/// # } -/// # -/// # #[cfg(rust_1_65)] -/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap")].assert_eq(&render(format!("{report:?}"))); -/// # -/// println!("{report:?}"); -/// -/// # #[cfg(rust_1_65)] -/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap")].assert_eq(&render(format!("{report:#?}"))); -/// # -/// println!("{report:#?}"); -/// ``` -/// -/// The output of `println!("{report:?}")`: -/// -///
-#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit.snap"))]
-/// 
-/// -/// The output of `println!("{report:#?}")`: -/// -///
-#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__emit_alt.snap"))]
-/// 
-/// -/// ## Storage -/// -/// `HookContext` can be used to store and retrieve values that are going to be used on multiple -/// hook invocations in a single [`Debug`] call. -/// -/// Every hook can request their corresponding `HookContext`. -/// This is especially useful for incrementing/decrementing values, but can also be used to store -/// any arbitrary value for the duration of the [`Debug`] invocation. -/// -/// All data stored in `HookContext` is completely separated from all other hooks and can store -/// any arbitrary data of any type, and even data of multiple types at the same time. -/// -/// ### Example -/// -/// ```rust -/// # // we only test with Rust 1.65, which means that `render()` is unused on earlier version -/// # #![cfg_attr(not(rust_1_65), allow(dead_code, unused_variables, unused_imports))] -/// use std::io::ErrorKind; -/// -/// use error_stack::Report; -/// -/// struct Computation(u64); -/// -/// Report::install_debug_hook::(|Computation(value), context| { -/// // Get a value of type `u64`, if we didn't insert one yet, default to 0 -/// let mut acc = context.get::().copied().unwrap_or(0); -/// acc += *value; -/// -/// // Get a value of type `f64`, if we didn't insert one yet, default to 1.0 -/// let mut div = context.get::().copied().unwrap_or(1.0); -/// div /= *value as f32; -/// -/// // Insert the calculated `u64` and `f32` back into storage, so that we can use them -/// // in the invocations following this one (for the same `Debug` call) -/// context.insert(acc); -/// context.insert(div); -/// -/// context.push_body(format!( -/// "computation for {value} (acc = {acc}, div = {div})" -/// )); -/// }); -/// -/// let report = Report::new(std::io::Error::from(ErrorKind::InvalidInput)) -/// .attach(Computation(2)) -/// .attach(Computation(3)); -/// -/// # owo_colors::set_override(true); -/// # fn render(value: String) -> String { -/// # let backtrace = regex::Regex::new(r"backtrace no\. (\d+)\n(?: .*\n)* .*").unwrap(); -/// # let backtrace_info = regex::Regex::new(r"backtrace( with (\d+) frames)? \((\d+)\)").unwrap(); -/// # -/// # let value = backtrace.replace_all(&value, "backtrace no. $1\n [redacted]"); -/// # let value = backtrace_info.replace_all(value.as_ref(), "backtrace ($3)"); -/// # -/// # ansi_to_html::convert_escaped(value.as_ref()).unwrap() -/// # } -/// # -/// # #[cfg(rust_1_65)] -/// # expect_test::expect_file![concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap")].assert_eq(&render(format!("{report:?}"))); -/// # -/// println!("{report:?}"); -/// ``` -/// -///
-#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_storage.snap"))]
-/// 
-/// -/// [`Debug`]: core::fmt::Debug +/// Internal version that is generic over the `T`, which is extra information supplied. // TODO: ideally we would want to make `HookContextInner` private, as it is an implementation // detail, but "attribute privacy" as outlined in https://github.com/rust-lang/rust/pull/61969 // is currently not implemented for repr(transparent). @@ -258,7 +81,7 @@ impl HookContext { } impl HookContext { - pub(crate) fn inner(&self) -> &Inner { + pub(crate) const fn inner(&self) -> &Inner { &self.inner } @@ -279,8 +102,8 @@ impl HookContext { /// Cast the [`HookContext`] to a new type `U`. /// /// The storage of [`HookContext`] is partitioned, meaning that if `T` and `U` are different - /// types the values stored in [`HookContext`] will be separated from values in - /// [`HookContext`]. + /// types the values stored in [`HookContext<_, T>`] will be separated from values in + /// [`HookContext<_, U>`]. /// /// In most situations this functions isn't needed, as it transparently casts between different /// partitions of the storage. Only hooks that share storage with hooks of different types @@ -348,30 +171,30 @@ impl HookContext { impl HookContext { /// Return a reference to a value of type `U`, if a value of that type exists. /// - /// Values returned are isolated and "bound" to `T`, this means that [`HookContext`] - /// and [`HookContext`] do not share the same values. Values are only retained during the - /// invocation of [`Debug`]. + /// Values returned are isolated and "bound" to `T`, this means that [`HookContext<_, Warning>`] + /// and [`HookContext<_, Error>`] do not share the same values. Values are only valid during the + /// invocation of the corresponding call (e.g. [`Debug`]). /// /// [`Debug`]: core::fmt::Debug #[must_use] - pub fn get(&self) -> Option<&U> { + pub fn get(&self) -> Option<&V> { self.storage() - .get(&TypeId::of::())? .get(&TypeId::of::())? + .get(&TypeId::of::())? .downcast_ref() } /// Return a mutable reference to a value of type `U`, if a value of that type exists. /// - /// Values returned are isolated and "bound" to `T`, this means that [`HookContext`] - /// and [`HookContext`] do not share the same values. Values are only retained during the - /// invocation of [`Debug`]. + /// Values returned are isolated and "bound" to `T`, this means that [`HookContext<_, Warning>`] + /// and [`HookContext<_, Error>`] do not share the same values. Values are only valid during the + /// invocation of the corresponding call (e.g. [`Debug`]). /// /// [`Debug`]: core::fmt::Debug - pub fn get_mut(&mut self) -> Option<&mut U> { + pub fn get_mut(&mut self) -> Option<&mut V> { self.storage_mut() - .get_mut(&TypeId::of::())? .get_mut(&TypeId::of::())? + .get_mut(&TypeId::of::())? .downcast_mut() } @@ -379,11 +202,11 @@ impl HookContext { /// /// The returned value will the previously stored value of the same type `U` scoped over type /// `T`, if it existed, did no such value exist it will return [`None`]. - pub fn insert(&mut self, value: U) -> Option { + pub fn insert(&mut self, value: V) -> Option { self.storage_mut() - .entry(TypeId::of::()) + .entry(TypeId::of::()) .or_default() - .insert(TypeId::of::(), Box::new(value))? + .insert(TypeId::of::(), Box::new(value))? .downcast() .map(|boxed| *boxed) .ok() @@ -393,10 +216,10 @@ impl HookContext { /// /// The returned value will be the previously stored value of the same type `U` if it existed in /// the scope of `T`, did no such value exist, it will return [`None`]. - pub fn remove(&mut self) -> Option { + pub fn remove(&mut self) -> Option { self.storage_mut() - .get_mut(&TypeId::of::())? - .remove(&TypeId::of::())? + .get_mut(&TypeId::of::())? + .remove(&TypeId::of::())? .downcast() .map(|boxed| *boxed) .ok() From 118099c6f94856099b05cc835ff6821c20a4d968 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 18 Dec 2022 15:42:55 +0100 Subject: [PATCH 3/5] docs: fix wording for `HookContext`, make intent of generics clear --- packages/libs/error-stack/src/hook/context.rs | 48 +++++++++++-------- packages/libs/error-stack/src/hook/mod.rs | 4 +- packages/libs/error-stack/src/lib.rs | 3 ++ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/packages/libs/error-stack/src/hook/context.rs b/packages/libs/error-stack/src/hook/context.rs index f631ab0f681..e1463d1e279 100644 --- a/packages/libs/error-stack/src/hook/context.rs +++ b/packages/libs/error-stack/src/hook/context.rs @@ -61,14 +61,20 @@ impl Inner { } } -/// Internal version that is generic over the `T`, which is extra information supplied. +/// Internal [`HookContext`] +/// +/// This is an implementation detail and cannot be used directly. This is only exposed for +/// documentation purposes and cannot be directly imported. +/// +/// Instead use [`error_stack::fmt::HookContext`] for [`Debug`] hooks. +// TODO: add link to serde hooks once implemented // TODO: ideally we would want to make `HookContextInner` private, as it is an implementation // detail, but "attribute privacy" as outlined in https://github.com/rust-lang/rust/pull/61969 // is currently not implemented for repr(transparent). #[cfg_attr(not(doc), repr(transparent))] -pub struct HookContext { - inner: Inner, - _marker: PhantomData, +pub struct HookContext { + inner: Inner, + _marker: PhantomData, } impl HookContext { @@ -80,12 +86,12 @@ impl HookContext { } } -impl HookContext { - pub(crate) const fn inner(&self) -> &Inner { +impl HookContext { + pub(crate) const fn inner(&self) -> &Inner { &self.inner } - pub(crate) fn inner_mut(&mut self) -> &mut Inner { + pub(crate) fn inner_mut(&mut self) -> &mut Inner { &mut self.inner } @@ -98,7 +104,7 @@ impl HookContext { } } -impl HookContext { +impl HookContext { /// Cast the [`HookContext`] to a new type `U`. /// /// The storage of [`HookContext`] is partitioned, meaning that if `T` and `U` are different @@ -161,14 +167,14 @@ impl HookContext { #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/snapshots/doc/fmt__hookcontext_cast.snap"))] /// #[must_use] - pub fn cast(&mut self) -> &mut HookContext { + pub fn cast(&mut self) -> &mut HookContext { // SAFETY: `HookContext` is marked as repr(transparent) and the changed generic is only used // inside of the `PhantomData` - unsafe { &mut *(self as *mut Self).cast::>() } + unsafe { &mut *(self as *mut Self).cast::>() } } } -impl HookContext { +impl HookContext { /// Return a reference to a value of type `U`, if a value of that type exists. /// /// Values returned are isolated and "bound" to `T`, this means that [`HookContext<_, Warning>`] @@ -177,10 +183,10 @@ impl HookContext { /// /// [`Debug`]: core::fmt::Debug #[must_use] - pub fn get(&self) -> Option<&V> { + pub fn get(&self) -> Option<&U> { self.storage() + .get(&TypeId::of::())? .get(&TypeId::of::())? - .get(&TypeId::of::())? .downcast_ref() } @@ -191,10 +197,10 @@ impl HookContext { /// invocation of the corresponding call (e.g. [`Debug`]). /// /// [`Debug`]: core::fmt::Debug - pub fn get_mut(&mut self) -> Option<&mut V> { + pub fn get_mut(&mut self) -> Option<&mut U> { self.storage_mut() + .get_mut(&TypeId::of::())? .get_mut(&TypeId::of::())? - .get_mut(&TypeId::of::())? .downcast_mut() } @@ -202,11 +208,11 @@ impl HookContext { /// /// The returned value will the previously stored value of the same type `U` scoped over type /// `T`, if it existed, did no such value exist it will return [`None`]. - pub fn insert(&mut self, value: V) -> Option { + pub fn insert(&mut self, value: U) -> Option { self.storage_mut() - .entry(TypeId::of::()) + .entry(TypeId::of::()) .or_default() - .insert(TypeId::of::(), Box::new(value))? + .insert(TypeId::of::(), Box::new(value))? .downcast() .map(|boxed| *boxed) .ok() @@ -216,10 +222,10 @@ impl HookContext { /// /// The returned value will be the previously stored value of the same type `U` if it existed in /// the scope of `T`, did no such value exist, it will return [`None`]. - pub fn remove(&mut self) -> Option { + pub fn remove(&mut self) -> Option { self.storage_mut() - .get_mut(&TypeId::of::())? - .remove(&TypeId::of::())? + .get_mut(&TypeId::of::())? + .remove(&TypeId::of::())? .downcast() .map(|boxed| *boxed) .ok() diff --git a/packages/libs/error-stack/src/hook/mod.rs b/packages/libs/error-stack/src/hook/mod.rs index 5962a45d6ff..26d5dc287b2 100644 --- a/packages/libs/error-stack/src/hook/mod.rs +++ b/packages/libs/error-stack/src/hook/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod context; use alloc::vec::Vec; use crate::{ - fmt::{install_builtin_hooks, HookContext, Hooks}, + fmt::{install_builtin_hooks, Hooks}, Report, }; @@ -151,7 +151,7 @@ impl Report<()> { /// [`Error::provide`]: std::error::Error::provide #[cfg(any(feature = "std", feature = "hooks"))] pub fn install_debug_hook( - hook: impl Fn(&T, &mut HookContext) + Send + Sync + 'static, + hook: impl Fn(&T, &mut crate::fmt::HookContext) + Send + Sync + 'static, ) { install_builtin_hooks(); diff --git a/packages/libs/error-stack/src/lib.rs b/packages/libs/error-stack/src/lib.rs index 6ea49f6ed4b..2f30ae51251 100644 --- a/packages/libs/error-stack/src/lib.rs +++ b/packages/libs/error-stack/src/lib.rs @@ -485,6 +485,9 @@ mod hook; #[cfg(feature = "serde")] mod serde; +#[cfg(all(doc, any(feature = "std", feature = "hooks")))] +pub use hook::context::HookContext; + pub use self::{ compat::IntoReportCompat, context::Context, From c0197779766f5820e33f050c5f89f511320eba28 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sun, 18 Dec 2022 15:46:32 +0100 Subject: [PATCH 4/5] docs: rename `Extra` to make intend clearer --- packages/libs/error-stack/src/fmt/hook.rs | 6 +++--- packages/libs/error-stack/src/fmt/mod.rs | 4 ++-- packages/libs/error-stack/src/hook/context.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/libs/error-stack/src/fmt/hook.rs b/packages/libs/error-stack/src/fmt/hook.rs index 2e45c1ba0e4..f634de23b64 100644 --- a/packages/libs/error-stack/src/fmt/hook.rs +++ b/packages/libs/error-stack/src/fmt/hook.rs @@ -14,14 +14,14 @@ pub(crate) use default::install_builtin_hooks; use crate::fmt::Frame; -pub struct Extra { +pub struct Format { alternate: bool, body: Vec, appendix: Vec, } -impl Extra { +impl Format { pub(crate) const fn new(alternate: bool) -> Self { Self { alternate, @@ -221,7 +221,7 @@ impl Extra { /// /// /// [`Debug`]: core::fmt::Debug -pub type HookContext = crate::hook::context::HookContext; +pub type HookContext = crate::hook::context::HookContext; impl HookContext { pub(crate) fn appendix(&self) -> &[String] { diff --git a/packages/libs/error-stack/src/fmt/mod.rs b/packages/libs/error-stack/src/fmt/mod.rs index b14e089e276..40b669c7470 100644 --- a/packages/libs/error-stack/src/fmt/mod.rs +++ b/packages/libs/error-stack/src/fmt/mod.rs @@ -162,7 +162,7 @@ use core::{ #[cfg(any(feature = "std", feature = "hooks"))] pub use hook::HookContext; #[cfg(any(feature = "std", feature = "hooks"))] -pub(crate) use hook::{install_builtin_hooks, Extra, Hooks}; +pub(crate) use hook::{install_builtin_hooks, Format, Hooks}; #[cfg(feature = "pretty-print")] use owo_colors::{OwoColorize, Stream, Style as OwOStyle}; @@ -947,7 +947,7 @@ fn debug_frame( impl Debug for Report { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { #[cfg(any(feature = "std", feature = "hooks"))] - let mut context = HookContext::new(Extra::new(fmt.alternate())); + let mut context = HookContext::new(Format::new(fmt.alternate())); #[cfg_attr(not(any(feature = "std", feature = "hooks")), allow(unused_mut))] let mut lines = self diff --git a/packages/libs/error-stack/src/hook/context.rs b/packages/libs/error-stack/src/hook/context.rs index e1463d1e279..573b1bf983e 100644 --- a/packages/libs/error-stack/src/hook/context.rs +++ b/packages/libs/error-stack/src/hook/context.rs @@ -66,7 +66,7 @@ impl Inner { /// This is an implementation detail and cannot be used directly. This is only exposed for /// documentation purposes and cannot be directly imported. /// -/// Instead use [`error_stack::fmt::HookContext`] for [`Debug`] hooks. +/// Instead use [`crate::fmt::HookContext`] for [`Debug`] hooks. // TODO: add link to serde hooks once implemented // TODO: ideally we would want to make `HookContextInner` private, as it is an implementation // detail, but "attribute privacy" as outlined in https://github.com/rust-lang/rust/pull/61969 From 37798ff21433c139194d0ecf4a4003fd2d998460 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Tue, 20 Dec 2022 11:03:54 +0100 Subject: [PATCH 5/5] chore: update snapshots --- .../error-stack/tests/snapshots/doc/fmt__hookcontext_cast.snap | 2 +- .../tests/snapshots/doc/fmt__hookcontext_decrement.snap | 2 +- .../tests/snapshots/doc/fmt__hookcontext_increment.snap | 2 +- .../libs/error-stack/tests/snapshots/doc/hook__debug_hook.snap | 2 +- .../tests/snapshots/doc/hook__debug_hook_provide.snap | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_cast.snap b/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_cast.snap index 1613a009c97..150ca3df7f2 100644 --- a/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_cast.snap +++ b/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_cast.snap @@ -1,5 +1,5 @@ invalid input parameter - src/fmt/hook.rs:27:14 + src/hook/context.rs:27:14 backtrace (1) [1] [ERROR] unable to reach remote host [2] [WARN] disk nearly full diff --git a/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_decrement.snap b/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_decrement.snap index 9f2ea03f46b..7e689f694cc 100644 --- a/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_decrement.snap +++ b/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_decrement.snap @@ -1,5 +1,5 @@ invalid input parameter - src/fmt/hook.rs:17:14 + src/hook/context.rs:17:14 backtrace (1) suggestion -1: use a file you can read next time! suggestion -2: don't press any random keys! diff --git a/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_increment.snap b/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_increment.snap index ea4e04c1e81..9ffbd8ea5d7 100644 --- a/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_increment.snap +++ b/packages/libs/error-stack/tests/snapshots/doc/fmt__hookcontext_increment.snap @@ -1,5 +1,5 @@ invalid input parameter - src/fmt/hook.rs:17:14 + src/hook/context.rs:17:14 backtrace (1) suggestion 0: use a file you can read next time! suggestion 1: don't press any random keys! diff --git a/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook.snap b/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook.snap index edab7145c61..febf1d18dea 100644 --- a/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook.snap +++ b/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook.snap @@ -1,5 +1,5 @@ invalid input parameter - src/hook.rs:19:5 + src/hook/mod.rs:19:5 backtrace (1) suggestion: oh no, try again diff --git a/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook_provide.snap b/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook_provide.snap index 2b3a5303378..da573607251 100644 --- a/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook_provide.snap +++ b/packages/libs/error-stack/tests/snapshots/doc/hook__debug_hook_provide.snap @@ -1,7 +1,7 @@ invalid user input suggestion: try better next time! error code: 420 - src/hook.rs:49:14 + src/hook/mod.rs:49:14 backtrace (1) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━