From 39e605b1c39b4b222a386d269baf28ecaae3c7ae Mon Sep 17 00:00:00 2001 From: Tethys Svensson Date: Sat, 21 Mar 2026 18:57:33 +0100 Subject: [PATCH] Move the preformat functionality to a separate crate --- Cargo.lock | 7 + Cargo.toml | 7 +- README.md | 2 + examples/conditional_formatting.rs | 5 +- examples/context_methods.rs | 19 +- examples/derive_more_interop.rs | 11 +- examples/following_error_sources.rs | 4 +- examples/formatting_hooks.rs | 6 +- examples/thiserror_interop.rs | 11 +- rootcause-preformat/Cargo.toml | 16 + rootcause-preformat/src/lib.rs | 273 ++++++++++++++++++ .../src}/preformatted.rs | 39 ++- src/hooks/attachment_formatter.rs | 173 ++--------- src/hooks/context_formatter.rs | 172 ++--------- src/lib.rs | 6 +- src/report/mut_.rs | 29 +- src/report/owned.rs | 135 +-------- src/report/ref_.rs | 54 +--- src/report_attachment/mut_.rs | 20 +- src/report_attachment/owned.rs | 17 -- src/report_attachment/ref_.rs | 23 +- src/result_ext.rs | 99 ------- 22 files changed, 416 insertions(+), 712 deletions(-) create mode 100644 rootcause-preformat/Cargo.toml create mode 100644 rootcause-preformat/src/lib.rs rename {src => rootcause-preformat/src}/preformatted.rs (90%) diff --git a/Cargo.lock b/Cargo.lock index f091a13..4a3b799 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1030,6 +1030,13 @@ dependencies = [ "triomphe", ] +[[package]] +name = "rootcause-preformat" +version = "0.12.1" +dependencies = [ + "rootcause", +] + [[package]] name = "rootcause-tracing" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 80c1f02..f6c560a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,12 @@ documentation = "https://docs.rs/rootcause" rust-version = "1.89" [workspace] -members = ["rootcause-internals", "rootcause-backtrace", "rootcause-tracing"] +members = [ + "rootcause-internals", + "rootcause-backtrace", + "rootcause-tracing", + "rootcause-preformat", +] [features] default = [] diff --git a/README.md b/README.md index 89ff2b8..df5bbc8 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,7 @@ rootcause is designed to be lightweight and extensible. The core library provide - **[`rootcause-backtrace`](https://docs.rs/rootcause-backtrace)** - Automatic stack trace capture for debugging. Install hooks to attach backtraces to all errors, or use the extension trait to add them selectively. - **[`rootcause-tracing`](https://docs.rs/rootcause-tracing)** - Tracing span capture for error reports. Automatically capture and display the active tracing spans when errors occur, providing operation context especially useful in async code. +- **[`rootcause-preformat`](https://docs.rs/rootcause-preformat)** - Methods for preformatting a reports and attachments. This is mostly useful when you need to go from a `Local` report to a `SendSync` report. ## Next Steps @@ -302,6 +303,7 @@ The rootcause ecosystem consists of multiple crates: - **`rootcause-backtrace`** - Optional backtrace capture support. Provides hooks for automatic stack trace collection. - **`rootcause-tracing`** - Optional tracing span capture. Provides hooks to attach active tracing spans to error reports. +- **`rootcause-preformat`** - Provides extension traits for preformatting a report in order to go from `Local` objects to a `SendSync` ones. The split between `rootcause` and `rootcause-internals` provides a clean API boundary: internals define how data is stored, while the main crate ensures that storage is accessed safely through Rust's type system. This makes it easy to understand the underlying representation while keeping the safe API ergonomic. Extensions integrate via the hook system without requiring changes to core. diff --git a/examples/conditional_formatting.rs b/examples/conditional_formatting.rs index 630dc38..4ffb540 100644 --- a/examples/conditional_formatting.rs +++ b/examples/conditional_formatting.rs @@ -13,7 +13,6 @@ use std::env; use rootcause::{ hooks::{Hooks, attachment_formatter::AttachmentFormatterHook}, - markers::Dynamic, prelude::*, report_attachment::ReportAttachmentRef, }; @@ -46,7 +45,7 @@ struct CredentialsFormatter; impl AttachmentFormatterHook for CredentialsFormatter { fn preferred_formatting_style( &self, - _attachment: ReportAttachmentRef<'_, Dynamic>, + _attachment: ReportAttachmentRef<'_, ApiCredentials>, report_formatting_function: rootcause::handlers::FormattingFunction, ) -> rootcause::handlers::AttachmentFormattingStyle { use rootcause::handlers::{ @@ -101,7 +100,7 @@ struct DebugSnapshotFormatter; impl AttachmentFormatterHook for DebugSnapshotFormatter { fn preferred_formatting_style( &self, - _attachment: ReportAttachmentRef<'_, Dynamic>, + _attachment: ReportAttachmentRef<'_, DebugSnapshot>, report_formatting_function: rootcause::handlers::FormattingFunction, ) -> rootcause::handlers::AttachmentFormattingStyle { use rootcause::handlers::{ diff --git a/examples/context_methods.rs b/examples/context_methods.rs index 5e52ce1..90c9470 100644 --- a/examples/context_methods.rs +++ b/examples/context_methods.rs @@ -5,12 +5,12 @@ //! - `context()`: Wraps report as child under new context //! - `context_to()`: Uses `ReportConversion` trait implementation //! - `context_transform()`: Changes context type in-place -//! - `context_transform_nested()`: Preformats and wraps as child +//! - Cloning the context and using `context()` to preserve multiple locations //! //! The focus is on understanding **what each method does to the report //! structure** and **what information is preserved or lost**. -use rootcause::{ReportConversion, markers, preformatted::PreformattedContext, prelude::*}; +use rootcause::{ReportConversion, markers, prelude::*}; #[derive(Debug)] enum AppError { @@ -30,12 +30,12 @@ impl std::fmt::Display for AppError { impl ReportConversion for AppError where AppError: markers::ObjectMarkerFor, - rootcause::preformatted::PreformattedContext: markers::ObjectMarkerFor, { fn convert_report( report: Report, ) -> Report { - report.context_transform_nested(AppError::Parse) + let current_context = report.current_context().clone(); + report.context(AppError::Parse(current_context)) } } @@ -61,16 +61,17 @@ fn main() { println!("{report2}\n"); assert_eq!(report2.iter_sub_reports().count(), 0); - // context_transform_nested() - Creates new parent node, child preformatted - // (type lost) + // Clones the context to preserve multiple locations, while preserving the original + // context type println!("Using context_transform_nested():"); - let report3: Report = - parse_error("not_a_number").context_transform_nested(AppError::Parse); + let report3 = parse_error("not_a_number"); + let report3_context = report3.current_context().clone(); + let report3 = report3.context(AppError::Parse(report3_context)); println!("{report3}\n"); assert_eq!(report3.iter_sub_reports().count(), 1); assert_eq!( report3.children().get(0).unwrap().current_context_type_id(), - std::any::TypeId::of::() + std::any::TypeId::of::() ); // context_to() - Uses ReportConversion impl (context_transform_nested in this diff --git a/examples/derive_more_interop.rs b/examples/derive_more_interop.rs index ee99e53..5bf1d15 100644 --- a/examples/derive_more_interop.rs +++ b/examples/derive_more_interop.rs @@ -19,7 +19,7 @@ use derive_more::{Display, Error, From}; use rootcause::{ReportConversion, markers, prelude::*}; // Shared error types used across patterns -#[derive(Error, Debug, Display)] +#[derive(Clone, Error, Debug, Display)] #[expect(dead_code, reason = "example code")] enum DatabaseError { #[display("Connection timeout after {seconds}s")] @@ -96,10 +96,13 @@ fn process_early_report(user_id: u32) -> Result> { Ok(data) } -// If we want to capture multiple locations, we can use context_transform_nested -// instead +// If we want to capture multiple locations, we can clone the context. Alternatively we could use +// the extensions in rootcause-preformat. fn process_early_report_multiple_locations(user_id: u32) -> Result> { - let data = query_report(user_id).context_transform_nested(AppError2::Database)?; + let data = query_report(user_id).map_err(|report| { + let current_context = report.current_context().clone(); + report.context(AppError2::Database(current_context)) + })?; Ok(data) } diff --git a/examples/following_error_sources.rs b/examples/following_error_sources.rs index b528c22..9db1862 100644 --- a/examples/following_error_sources.rs +++ b/examples/following_error_sources.rs @@ -16,7 +16,7 @@ use rootcause::{ ReportRef, hooks::{Hooks, context_formatter::ContextFormatterHook}, - markers::{Dynamic, Local, Uncloneable}, + markers::{Local, Uncloneable}, prelude::*, }; use rootcause_internals::handlers::{ContextFormattingStyle, FormattingFunction}; @@ -35,7 +35,7 @@ struct ReqwestErrorFormatter; impl ContextFormatterHook for ReqwestErrorFormatter { fn preferred_context_formatting_style( &self, - _report: ReportRef<'_, Dynamic, Uncloneable, Local>, + _report: ReportRef<'_, reqwest::Error, Uncloneable, Local>, report_formatting_function: FormattingFunction, ) -> ContextFormattingStyle { ContextFormattingStyle { diff --git a/examples/formatting_hooks.rs b/examples/formatting_hooks.rs index efbd6b9..c1e085d 100644 --- a/examples/formatting_hooks.rs +++ b/examples/formatting_hooks.rs @@ -19,7 +19,7 @@ use rootcause::{ Hooks, attachment_formatter::AttachmentFormatterHook, context_formatter::ContextFormatterHook, }, - markers::{Dynamic, Local, Uncloneable}, + markers::{Local, Uncloneable}, prelude::*, report_attachment::ReportAttachmentRef, }; @@ -54,7 +54,7 @@ struct DatabaseQueryFormatter; impl AttachmentFormatterHook for DatabaseQueryFormatter { fn preferred_formatting_style( &self, - _attachment: ReportAttachmentRef<'_, Dynamic>, + _attachment: ReportAttachmentRef<'_, DatabaseQuery>, formatting_function: FormattingFunction, ) -> AttachmentFormattingStyle { match formatting_function { @@ -100,7 +100,7 @@ struct ActionRequiredFormatter; impl AttachmentFormatterHook for ActionRequiredFormatter { fn preferred_formatting_style( &self, - _attachment: ReportAttachmentRef<'_, Dynamic>, + _attachment: ReportAttachmentRef<'_, ActionRequired>, _report_formatting_function: FormattingFunction, ) -> AttachmentFormattingStyle { AttachmentFormattingStyle { diff --git a/examples/thiserror_interop.rs b/examples/thiserror_interop.rs index 57f9a05..fbcba75 100644 --- a/examples/thiserror_interop.rs +++ b/examples/thiserror_interop.rs @@ -19,7 +19,7 @@ use rootcause::{ReportConversion, markers, prelude::*}; use thiserror::Error; // Shared error types used across patterns -#[derive(Error, Debug)] +#[derive(Clone, Error, Debug)] #[expect(dead_code, reason = "example code")] enum DatabaseError { #[error("Connection timeout after {seconds}s")] @@ -96,10 +96,13 @@ fn process_early_report(user_id: u32) -> Result> { Ok(data) } -// If we want to capture multiple locations, we can use context_transform_nested -// instead +// If we want to capture multiple locations, we can clone the context. Alternatively we could use +// the extensions in rootcause-preformat. fn process_early_report_multiple_locations(user_id: u32) -> Result> { - let data = query_report(user_id).context_transform_nested(AppError2::Database)?; + let data = query_report(user_id).map_err(|report| { + let current_context = report.current_context().clone(); + report.context(AppError2::Database(current_context)) + })?; Ok(data) } diff --git a/rootcause-preformat/Cargo.toml b/rootcause-preformat/Cargo.toml new file mode 100644 index 0000000..07eaa22 --- /dev/null +++ b/rootcause-preformat/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rootcause-preformat" +version = "0.12.1" +edition = "2024" +license = "MIT/Apache-2.0" +categories = ["rust-patterns", "development-tools::debugging"] +keywords = ["error", "error-handling", "tracing", "spans", "observability"] +description = "Preformatting support for the rootcause error reporting library" +repository = "https://github.com/rootcause-rs/rootcause" +documentation = "https://docs.rs/rootcause-preformat" +rust-version = "1.89" + +[dependencies] + +# Internal dependencies +rootcause = { path = "../", version = "=0.12.1" } diff --git a/rootcause-preformat/src/lib.rs b/rootcause-preformat/src/lib.rs new file mode 100644 index 0000000..98e8083 --- /dev/null +++ b/rootcause-preformat/src/lib.rs @@ -0,0 +1,273 @@ +#![no_std] + +extern crate alloc; + +use rootcause::{ + Report, ReportMut, ReportRef, handlers, + markers::{self, Mutable, ReportOwnershipMarker, SendSync}, + report_attachment::{ReportAttachment, ReportAttachmentMut, ReportAttachmentRef}, +}; + +mod preformatted; + +pub use preformatted::{PreformattedAttachment, PreformattedContext}; + +pub trait PreformatReportExt { + /// Creates a new report, which has the same structure as the current + /// report, but has all the contexts and attachments preformatted. + /// + /// This can be useful, as the new report is mutable because it was just + /// created, and additionally the new report is [`Send`]+[`Sync`]. + /// + /// # Examples + /// ``` + /// # use rootcause::{prelude::*, ReportMut, preformatted::PreformattedContext}; + /// # #[derive(Default)] + /// # struct NonSendSyncError(core::cell::Cell<()>); + /// # let non_send_sync_error = NonSendSyncError::default(); + /// # let mut report = report!(non_send_sync_error); + /// let report_mut: ReportMut<'_, NonSendSyncError, markers::Local> = report.as_mut(); + /// let preformatted: Report = + /// report_mut.preformat(); + /// assert_eq!(format!("{report}"), format!("{preformatted}")); + /// ``` + #[track_caller] + #[must_use] + fn preformat(&self) -> Report; +} + +pub trait PreformatAttachmentExt { + /// Creates a new attachment, with the inner attachment data preformatted. + /// + /// This can be useful, as the preformatted attachment is a newly allocated + /// object and additionally is [`Send`]+[`Sync`]. + /// + /// See [`PreformattedAttachment`] for more information. + /// + /// [`PreformattedAttachment`](crate::preformatted::PreformattedAttachment) + #[track_caller] + #[must_use] + fn preformat(&self) -> ReportAttachment; +} + +impl PreformatAttachmentExt for ReportAttachment { + fn preformat(&self) -> ReportAttachment { + self.as_ref().preformat() + } +} + +impl<'a, A: ?Sized> PreformatAttachmentExt for ReportAttachmentMut<'a, A> { + fn preformat(&self) -> ReportAttachment { + self.as_ref().preformat() + } +} + +impl<'a, A: ?Sized> PreformatAttachmentExt for ReportAttachmentRef<'a, A> { + fn preformat(&self) -> ReportAttachment { + ReportAttachment::new_custom::( + PreformattedAttachment::new_from_attachment(*self), + ) + } +} + +pub trait PreformatRootExt: Sized { + /// Extracts the context and returns it with a preformatted version of the + /// report. + /// + /// Returns a tuple: the original typed context and a new report with + /// [`PreformattedContext`](crate::preformatted::PreformattedContext) + /// containing the string representation. The preformatted report maintains + /// the same structure (children and attachments). Useful when you need + /// the typed value for processing and the formatted version for display. + /// + /// This is a lower-level method primarily for custom transformation logic. + /// Most users should use + /// [`context_transform`](Self::context_transform), + /// [`context_transform_nested`](Self::context_transform_nested), + /// or [`context_to`](Self::context_to) instead. + /// + /// See also: [`preformat`](Report::preformat) (formats entire hierarchy), + /// [`into_parts`](Report::into_parts) (extracts without formatting), + /// [`current_context`](crate::ReportRef::current_context) (reference + /// without extraction). + /// + /// # Examples + /// + /// ``` + /// # use rootcause::{preformatted::PreformattedContext, prelude::*}; + /// # #[derive(Debug)] + /// struct MyError { + /// code: u32 + /// } + /// # impl std::fmt::Display for MyError { + /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "error {}", self.code) } + /// # } + /// + /// let report: Report = report!(MyError { code: 500 }); + /// let (context, preformatted): (MyError, Report) = report.preformat_root(); + /// ``` + #[track_caller] + #[must_use] + fn preformat_root(self) -> (C, Report) + where + PreformattedContext: markers::ObjectMarkerFor; +} + +pub trait ContextTransformNestedExt: Sized { + type Output; + + /// Transforms the context and nests the original report as a preformatted + /// child. + /// + /// Creates a new parent node with fresh hook data (location, backtrace), + /// but the original context type is lost—the child becomes + /// [`PreformattedContext`] and cannot be downcast. + /// + /// [`PreformattedContext`]: crate::preformatted::PreformattedContext + /// + /// # Examples + /// + /// ``` + /// # use rootcause::prelude::*; + /// # #[derive(Debug)] + /// # struct LibError; + /// # impl std::fmt::Display for LibError { + /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "lib error") } + /// # } + /// # #[derive(Debug)] + /// enum AppError { + /// Lib(LibError) + /// } + /// # impl std::fmt::Display for AppError { + /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "app error") } + /// # } + /// + /// let lib_report: Report = report!(LibError); + /// let app_report: Report = lib_report.context_transform_nested(AppError::Lib); + /// ``` + /// + /// # See Also + /// + /// - [`context_transform()`](Report::context_transform) - Transforms + /// without nesting + /// - [`context()`](Report::context) - Adds new parent, preserves child's + /// type + /// - [`preformat_root()`](Report::preformat_root) - Lower-level operation + /// used internally + /// - [`examples/context_methods.rs`] - Comparison guide + /// + /// [`examples/context_methods.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/context_methods.rs + #[track_caller] + #[must_use] + fn context_transform_nested(self, f: F) -> Self::Output + where + F: FnOnce(C) -> D, + D: markers::ObjectMarkerFor + core::fmt::Display + core::fmt::Debug, + PreformattedContext: markers::ObjectMarkerFor; +} + +impl PreformatReportExt for Report +where + O: ReportOwnershipMarker, +{ + fn preformat(&self) -> Report { + self.as_ref().preformat() + } +} + +impl<'a, C: ?Sized, T> PreformatReportExt for ReportMut<'a, C, T> { + fn preformat(&self) -> Report { + self.as_ref().preformat() + } +} + +impl<'a, C: ?Sized, O, T> PreformatReportExt for ReportRef<'a, C, O, T> { + fn preformat(&self) -> Report { + let preformatted_context = PreformattedContext::new_from_context(*self); + Report::from_parts_unhooked::( + preformatted_context, + self.children() + .iter() + .map(|sub_report| sub_report.preformat()) + .collect(), + self.attachments() + .iter() + .map(|attachment| attachment.preformat().into_dynamic()) + .collect(), + ) + } +} + +impl PreformatRootExt for Report { + fn preformat_root(self) -> (C, Report) + where + PreformattedContext: markers::ObjectMarkerFor, + { + let preformatted = PreformattedContext::new_from_context(self.as_ref()); + let (context, children, attachments) = self.into_parts(); + + ( + context, + Report::from_parts_unhooked::( + preformatted, + children, + attachments, + ), + ) + } +} + +impl ContextTransformNestedExt for Report { + type Output = Report; + + fn context_transform_nested(self, f: F) -> Report + where + F: FnOnce(C) -> D, + D: markers::ObjectMarkerFor + core::fmt::Display + core::fmt::Debug, + PreformattedContext: markers::ObjectMarkerFor, + { + let (context, report) = self.preformat_root(); + report.context_custom::(f(context)) + } +} + +impl ContextTransformNestedExt for Result> { + type Output = Result>; + + fn context_transform_nested(self, f: F) -> Result> + where + F: FnOnce(C) -> D, + D: markers::ObjectMarkerFor + core::fmt::Display + core::fmt::Debug, + PreformattedContext: markers::ObjectMarkerFor, + { + match self { + Ok(value) => Ok(value), + Err(report) => { + let (context, report) = report.preformat_root(); + Err(report.context_custom::(f(context))) + } + } + } +} + +#[cfg(test)] +mod tests { + use rootcause::{ + ReportRef, + markers::{Local, Mutable, SendSync, Uncloneable}, + prelude::*, + }; + + use super::*; + + #[test] + fn test_preformat() { + #[derive(Default)] + struct NonSendSyncError(core::cell::Cell<()>); + let non_send_sync_error = NonSendSyncError::default(); + let report = report!(non_send_sync_error); + let report_ref: ReportRef<'_, NonSendSyncError, Uncloneable, Local> = report.as_ref(); + let preformatted: Report = report_ref.preformat(); + assert_eq!(alloc::format!("{report}"), alloc::format!("{preformatted}")); + } +} diff --git a/src/preformatted.rs b/rootcause-preformat/src/preformatted.rs similarity index 90% rename from src/preformatted.rs rename to rootcause-preformat/src/preformatted.rs index d3a2296..0e2886e 100644 --- a/src/preformatted.rs +++ b/rootcause-preformat/src/preformatted.rs @@ -80,12 +80,14 @@ use alloc::{format, string::String}; use core::any::TypeId; -use rootcause_internals::handlers::{ - AttachmentFormattingStyle, AttachmentHandler, ContextFormattingStyle, ContextHandler, +use rootcause::{ + ReportRef, + handlers::{ + AttachmentFormattingStyle, AttachmentHandler, ContextFormattingStyle, ContextHandler, + }, + report_attachment::ReportAttachmentRef, }; -use crate::{ReportRef, report_attachment::ReportAttachmentRef}; - /// A context that has been preformatted into `String`s for both /// `Display` and `Debug`. /// @@ -146,11 +148,10 @@ impl PreformattedContext { display: format!("{}", report.format_current_context()), debug: format!("{:?}", report.format_current_context()), display_preferred_formatting_style: report.preferred_context_formatting_style( - rootcause_internals::handlers::FormattingFunction::Display, - ), - debug_preferred_formatting_style: report.preferred_context_formatting_style( - rootcause_internals::handlers::FormattingFunction::Debug, + rootcause::handlers::FormattingFunction::Display, ), + debug_preferred_formatting_style: report + .preferred_context_formatting_style(rootcause::handlers::FormattingFunction::Debug), } } @@ -249,12 +250,10 @@ impl PreformattedAttachment { original_type_id: attachment.inner_type_id(), display: format!("{}", attachment.format_inner()), debug: format!("{:?}", attachment.format_inner()), - display_preferred_formatting_style: attachment.preferred_formatting_style( - rootcause_internals::handlers::FormattingFunction::Display, - ), - debug_preferred_formatting_style: attachment.preferred_formatting_style( - rootcause_internals::handlers::FormattingFunction::Debug, - ), + display_preferred_formatting_style: attachment + .preferred_formatting_style(rootcause::handlers::FormattingFunction::Display), + debug_preferred_formatting_style: attachment + .preferred_formatting_style(rootcause::handlers::FormattingFunction::Debug), } } @@ -323,13 +322,13 @@ impl ContextHandler for PreformattedHandler { fn preferred_formatting_style( value: &PreformattedContext, - report_formatting_function: rootcause_internals::handlers::FormattingFunction, + report_formatting_function: rootcause::handlers::FormattingFunction, ) -> ContextFormattingStyle { match report_formatting_function { - rootcause_internals::handlers::FormattingFunction::Display => { + rootcause::handlers::FormattingFunction::Display => { value.display_preferred_formatting_style } - rootcause_internals::handlers::FormattingFunction::Debug => { + rootcause::handlers::FormattingFunction::Debug => { value.debug_preferred_formatting_style } } @@ -353,13 +352,13 @@ impl AttachmentHandler for PreformattedHandler { fn preferred_formatting_style( value: &PreformattedAttachment, - report_formatting_function: rootcause_internals::handlers::FormattingFunction, + report_formatting_function: rootcause::handlers::FormattingFunction, ) -> AttachmentFormattingStyle { match report_formatting_function { - rootcause_internals::handlers::FormattingFunction::Display => { + rootcause::handlers::FormattingFunction::Display => { value.display_preferred_formatting_style } - rootcause_internals::handlers::FormattingFunction::Debug => { + rootcause::handlers::FormattingFunction::Debug => { value.debug_preferred_formatting_style } } diff --git a/src/hooks/attachment_formatter.rs b/src/hooks/attachment_formatter.rs index 428fccc..5415435 100644 --- a/src/hooks/attachment_formatter.rs +++ b/src/hooks/attachment_formatter.rs @@ -179,7 +179,6 @@ use crate::{ ReportRef, hooks::{HookData, use_hooks}, markers::{Dynamic, Local, Uncloneable}, - preformatted::PreformattedAttachment, report_attachment::ReportAttachmentRef, }; @@ -333,21 +332,15 @@ trait StoredHook: 'static + Send + Sync + core::fmt::Debug { formatter: &mut fmt::Formatter<'_>, ) -> fmt::Result; - fn display_preformatted( - &self, - attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - attachment_parent: Option>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result; - - fn debug_preformatted( - &self, - attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - attachment_parent: Option>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result; - - fn preferred_formatting_style( + /// Determines the preferred formatting style for this attachment. + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. The type `A` stored in the attachment matches the `A` from type + /// `Hook` this is implemented for. + unsafe fn preferred_formatting_style( &self, attachment: ReportAttachmentRef<'_, Dynamic>, report_formatting_function: FormattingFunction, @@ -359,8 +352,7 @@ trait StoredHook: 'static + Send + Sync + core::fmt::Debug { /// /// This trait allows you to override the default formatting behavior for /// attachments of type `A`. You can customize both Display and Debug -/// formatting, as well as handle preformatted attachments and specify preferred -/// formatting styles. +/// formatting, as well as specify preferred formatting styles. /// /// # Type Parameters /// @@ -449,49 +441,6 @@ pub trait AttachmentFormatterHook: 'static + Send + Sync { fmt::Display::fmt(&attachment.format_inner_unhooked(), formatter) } - /// Formats a preformatted attachment using Display formatting. - /// - /// This method handles attachments that have been preformatted (typically - /// done using [`ReportRef::preformat`]). The default implementation - /// delegates to the attachment's unhooked Display formatting. - /// - /// # Arguments - /// - /// * `attachment` - Reference to the preformatted attachment - /// * `attachment_parent` - Optional context about the parent report - /// * `formatter` - The formatter to write output to - /// - /// # Examples - /// - /// ``` - /// use rootcause::{ - /// hooks::attachment_formatter::{AttachmentFormatterHook, AttachmentParent}, - /// preformatted::PreformattedAttachment, - /// report_attachment::ReportAttachmentRef, - /// }; - /// - /// struct MyFormatter; - /// impl AttachmentFormatterHook for MyFormatter { - /// fn display_preformatted( - /// &self, - /// attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - /// _parent: Option>, - /// f: &mut core::fmt::Formatter<'_>, - /// ) -> core::fmt::Result { - /// write!(f, "[Preformatted] {}", attachment.format_inner_unhooked()) - /// } - /// } - /// ``` - fn display_preformatted( - &self, - attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - attachment_parent: Option>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - let _ = attachment_parent; - fmt::Display::fmt(&attachment.format_inner_unhooked(), formatter) - } - /// Formats the attachment using Debug formatting. /// /// This method is called when the attachment needs to be displayed in a @@ -534,53 +483,6 @@ pub trait AttachmentFormatterHook: 'static + Send + Sync { fmt::Debug::fmt(&attachment.format_inner_unhooked(), formatter) } - /// Formats a preformatted attachment using Debug formatting. - /// - /// This method handles attachments that have been preformatted (typically - /// done using [`ReportRef::preformat`]). The default implementation - /// delegates to the attachment's unhooked Debug formatting. - /// - /// # Arguments - /// - /// * `attachment` - Reference to the preformatted attachment - /// * `attachment_parent` - Optional context about the parent report - /// * `formatter` - The formatter to write output to - /// - /// # Examples - /// - /// ``` - /// use rootcause::{ - /// hooks::attachment_formatter::{AttachmentFormatterHook, AttachmentParent}, - /// preformatted::PreformattedAttachment, - /// report_attachment::ReportAttachmentRef, - /// }; - /// - /// struct MyFormatter; - /// impl AttachmentFormatterHook for MyFormatter { - /// fn debug_preformatted( - /// &self, - /// attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - /// _parent: Option>, - /// f: &mut core::fmt::Formatter<'_>, - /// ) -> core::fmt::Result { - /// write!( - /// f, - /// "[Preformatted Debug] {:?}", - /// attachment.format_inner_unhooked() - /// ) - /// } - /// } - /// ``` - fn debug_preformatted( - &self, - attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - attachment_parent: Option>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - let _ = attachment_parent; - fmt::Debug::fmt(&attachment.format_inner_unhooked(), formatter) - } - /// Determines the preferred formatting style for this attachment. /// /// This method allows the formatter to specify how the attachment should be @@ -590,8 +492,7 @@ pub trait AttachmentFormatterHook: 'static + Send + Sync { /// /// # Arguments /// - /// * `attachment` - Reference to the attachment (as [`Dynamic`] as it can - /// be either `A` or a [`PreformattedAttachment`]) + /// * `attachment` - Reference to the attachment /// * `report_formatting_function` - Whether the overall report uses Display /// or Debug formatting /// @@ -622,7 +523,7 @@ pub trait AttachmentFormatterHook: 'static + Send + Sync { /// ``` fn preferred_formatting_style( &self, - attachment: ReportAttachmentRef<'_, Dynamic>, + attachment: ReportAttachmentRef<'_, A>, report_formatting_function: FormattingFunction, ) -> AttachmentFormattingStyle { attachment.preferred_formatting_style_unhooked(report_formatting_function) @@ -657,31 +558,14 @@ where self.hook.debug(attachment, attachment_parent, formatter) } - fn display_preformatted( - &self, - attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - attachment_parent: Option>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - self.hook - .display_preformatted(attachment, attachment_parent, formatter) - } - - fn debug_preformatted( - &self, - attachment: ReportAttachmentRef<'_, PreformattedAttachment>, - attachment_parent: Option>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - self.hook - .debug_preformatted(attachment, attachment_parent, formatter) - } - - fn preferred_formatting_style( + unsafe fn preferred_formatting_style( &self, attachment: ReportAttachmentRef<'_, Dynamic>, report_formatting_function: FormattingFunction, ) -> AttachmentFormattingStyle { + // SAFETY: + // 1. Guaranteed by the caller + let attachment = unsafe { attachment.downcast_attachment_unchecked::() }; self.hook .preferred_formatting_style(attachment, report_formatting_function) } @@ -696,12 +580,6 @@ pub(crate) fn display_attachment( if let Some(hook_data) = hook_data { let attachment_formatters: &HookMap = &hook_data.attachment_formatters; - if let Some(attachment) = attachment.downcast_attachment::() - && let Some(hook) = attachment_formatters.get(attachment.inner().original_type_id()) - { - return hook.display_preformatted(attachment, attachment_parent, formatter); - } - if let Some(hook) = attachment_formatters.get(attachment.inner_type_id()) { // SAFETY: // 1. The call to `get` guarantees that the returned hook is of type `Hook() - && let Some(hook) = attachment_formatters.get(attachment.inner().original_type_id()) - { - return hook.debug_preformatted(attachment, attachment_parent, formatter); - } - if let Some(hook) = attachment_formatters.get(attachment.inner_type_id()) { // SAFETY: // 1. The call to `get` guarantees that the returned hook is of type `Hook| { if let Some(hook_data) = hook_data { let attachment_formatters: &HookMap = &hook_data.attachment_formatters; - if let Some(inner) = attachment.downcast_inner::() - && let Some(hook) = attachment_formatters.get(inner.original_type_id()) - { - return hook.preferred_formatting_style(attachment, report_formatting_function); - } if let Some(hook) = attachment_formatters.get(attachment.inner_type_id()) { - return hook.preferred_formatting_style(attachment, report_formatting_function); + // SAFETY: + // 1. The call to `get` guarantees that the returned hook is of type `Hook`, and `TypeId::of() == attachment.inner_type_id()`. Therefore the + // type `A` stored in the attachment matches the `A` from type `Hook`. + unsafe { + return hook.preferred_formatting_style(attachment, report_formatting_function); + } } } attachment.preferred_formatting_style_unhooked(report_formatting_function) diff --git a/src/hooks/context_formatter.rs b/src/hooks/context_formatter.rs index 65c10cd..2daf0bb 100644 --- a/src/hooks/context_formatter.rs +++ b/src/hooks/context_formatter.rs @@ -122,7 +122,6 @@ use crate::{ ReportRef, hooks::{HookData, use_hooks}, markers::{Dynamic, Local, Uncloneable}, - preformatted::PreformattedContext, }; #[derive(Default)] @@ -225,19 +224,15 @@ trait StoredHook: 'static + Send + Sync + core::fmt::Debug { formatter: &mut fmt::Formatter<'_>, ) -> fmt::Result; - fn display_preformatted( - &self, - report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result; - - fn debug_preformatted( - &self, - report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result; - - fn preferred_context_formatting_style( + /// Determines the preferred formatting style for this context. + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. The type `C` stored in the context matches the `C` from type `Hook` this is implemented for. + unsafe fn preferred_context_formatting_style( &self, report: ReportRef<'_, Dynamic, Uncloneable, Local>, report_formatting_function: FormattingFunction, @@ -249,8 +244,7 @@ trait StoredHook: 'static + Send + Sync + core::fmt::Debug { /// /// This trait allows you to override the default formatting behavior for /// contexts (the main error types) of type `C`. You can customize both Display -/// and Debug formatting, handle preformatted contexts, and specify preferred -/// formatting styles. +/// and Debug formatting, and specify preferred formatting styles. /// /// # Type Parameters /// @@ -346,51 +340,6 @@ pub trait ContextFormatterHook: 'static + Send + Sync { fmt::Display::fmt(&report.format_current_context_unhooked(), formatter) } - /// Formats a preformatted context using Display formatting. - /// - /// This method handles contexts that have been preformatted (typically done - /// using [`ReportRef::preformat`] for performance or consistency reasons). - /// The default implementation delegates to the context's unhooked - /// Display formatting. - /// - /// # Arguments - /// - /// * `report` - Reference to the report containing the preformatted context - /// * `formatter` - The formatter to write output to - /// - /// # Examples - /// - /// ``` - /// use rootcause::{ - /// ReportRef, - /// hooks::context_formatter::ContextFormatterHook, - /// markers::{Local, Uncloneable}, - /// preformatted::PreformattedContext, - /// }; - /// - /// struct MyFormatter; - /// impl ContextFormatterHook for MyFormatter { - /// fn display_preformatted( - /// &self, - /// report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - /// f: &mut core::fmt::Formatter<'_>, - /// ) -> core::fmt::Result { - /// write!( - /// f, - /// "[Preformatted] {}", - /// report.format_current_context_unhooked() - /// ) - /// } - /// } - /// ``` - fn display_preformatted( - &self, - report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - fmt::Display::fmt(&report.format_current_context_unhooked(), formatter) - } - /// Formats the context using Debug formatting. /// /// This method is called when the context needs to be displayed in a @@ -430,50 +379,6 @@ pub trait ContextFormatterHook: 'static + Send + Sync { fmt::Debug::fmt(&report.format_current_context_unhooked(), formatter) } - /// Formats a preformatted context using Debug formatting. - /// - /// This method handles preformatted contexts when debug formatting is - /// requested. The default implementation delegates to the context's - /// unhooked Debug formatting. - /// - /// # Arguments - /// - /// * `report` - Reference to the report containing the preformatted context - /// * `formatter` - The formatter to write output to - /// - /// # Examples - /// - /// ``` - /// use rootcause::{ - /// ReportRef, - /// hooks::context_formatter::ContextFormatterHook, - /// markers::{Local, Uncloneable}, - /// preformatted::PreformattedContext, - /// }; - /// - /// struct MyFormatter; - /// impl ContextFormatterHook for MyFormatter { - /// fn debug_preformatted( - /// &self, - /// report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - /// f: &mut core::fmt::Formatter<'_>, - /// ) -> core::fmt::Result { - /// write!( - /// f, - /// "[Preformatted Debug] {:?}", - /// report.format_current_context_unhooked() - /// ) - /// } - /// } - /// ``` - fn debug_preformatted( - &self, - report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - fmt::Debug::fmt(&report.format_current_context_unhooked(), formatter) - } - /// Determines the preferred formatting style for this context. /// /// This method allows the formatter to specify how the context should be @@ -483,8 +388,7 @@ pub trait ContextFormatterHook: 'static + Send + Sync { /// /// # Arguments /// - /// * `report` - Reference to the report (as [`Dynamic`] as it can be either - /// a `C` or a [`PreformattedContext`]) + /// * `report` - Reference to the report /// * `report_formatting_function` - Whether the overall report uses Display /// or Debug formatting /// @@ -495,14 +399,14 @@ pub trait ContextFormatterHook: 'static + Send + Sync { /// ReportRef, /// handlers::{ContextFormattingStyle, FormattingFunction}, /// hooks::context_formatter::ContextFormatterHook, - /// markers::{Dynamic, Local, Uncloneable}, + /// markers::{Local, Uncloneable}, /// }; /// /// struct MyFormatter; /// impl ContextFormatterHook for MyFormatter { /// fn preferred_context_formatting_style( /// &self, - /// _report: ReportRef<'_, Dynamic, Uncloneable, Local>, + /// _report: ReportRef<'_, C, Uncloneable, Local>, /// _function: FormattingFunction, /// ) -> ContextFormattingStyle { /// ContextFormattingStyle { @@ -515,7 +419,7 @@ pub trait ContextFormatterHook: 'static + Send + Sync { /// ``` fn preferred_context_formatting_style( &self, - report: ReportRef<'_, Dynamic, Uncloneable, Local>, + report: ReportRef<'_, C, Uncloneable, Local>, report_formatting_function: FormattingFunction, ) -> ContextFormattingStyle { report.preferred_context_formatting_style_unhooked(report_formatting_function) @@ -549,27 +453,14 @@ where self.hook.debug(report, formatter) } - fn display_preformatted( - &self, - report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - self.hook.display_preformatted(report, formatter) - } - - fn debug_preformatted( - &self, - report: ReportRef<'_, PreformattedContext, Uncloneable, Local>, - formatter: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - self.hook.debug_preformatted(report, formatter) - } - - fn preferred_context_formatting_style( + unsafe fn preferred_context_formatting_style( &self, report: ReportRef<'_, Dynamic, Uncloneable, Local>, report_formatting_function: FormattingFunction, ) -> ContextFormattingStyle { + // SAFETY: + // 1. Guaranteed by the caller + let report = unsafe { report.downcast_report_unchecked::() }; self.hook .preferred_context_formatting_style(report, report_formatting_function) } @@ -582,12 +473,6 @@ pub(crate) fn display_context( use_hooks(|hook_data: Option<&HookData>| { if let Some(hook_data) = hook_data { let context_formatters: &HookMap = &hook_data.context_formatters; - if let Some(report) = report.downcast_report::() - && let Some(hook) = - context_formatters.get(report.current_context().original_type_id()) - { - return hook.display_preformatted(report, formatter); - } if let Some(hook) = context_formatters.get(report.current_context_type_id()) { // SAFETY: @@ -611,12 +496,6 @@ pub(crate) fn debug_context( use_hooks(|hook_data: Option<&HookData>| { if let Some(hook_data) = hook_data { let context_formatters: &HookMap = &hook_data.context_formatters; - if let Some(report) = report.downcast_report::() - && let Some(hook) = - context_formatters.get(report.current_context().original_type_id()) - { - return hook.debug_preformatted(report, formatter); - } if let Some(hook) = context_formatters.get(report.current_context_type_id()) { // SAFETY: @@ -649,14 +528,15 @@ pub(crate) fn get_preferred_context_formatting_style( if let Some(hook_data) = hook_data { let context_formatters: &HookMap = &hook_data.context_formatters; - if let Some(current_context) = report.downcast_current_context::() - && let Some(hook) = context_formatters.get(current_context.original_type_id()) - { - return hook.preferred_context_formatting_style(report, report_formatting_function); - } - if let Some(hook) = context_formatters.get(report.current_context_type_id()) { - return hook.preferred_context_formatting_style(report, report_formatting_function); + // SAFETY: + // 1. The call to `get` guarantees that the returned hook is of type `Hook`, and `TypeId::of() == report.current_context_type_id()`. Therefore + // the type `C` stored in the context matches the `C` from type `Hook`. + unsafe { + return hook + .preferred_context_formatting_style(report, report_formatting_function); + } } } report.preferred_context_formatting_style_unhooked(report_formatting_function) diff --git a/src/lib.rs b/src/lib.rs index a5c831a..ae759a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -318,7 +318,7 @@ //! allocating a new root node is to call [`Report::context`]. //! - From `Report<*, *, *>` to `Report`: -//! - You can preformat the entire [`Report`] using [`Report::preformat`]. +//! - You can preformat the entire [`Report`] using [`PreformatReportExt::preformat`]. //! This creates an entirely new [`Report`] that has the same structure and //! will look the same as the current one if printed, but all contexts and //! attachments will be replaced with a [`PreformattedContext`] version. @@ -332,7 +332,8 @@ //! handling libraries in the Rust ecosystem, including [`anyhow`], //! [`thiserror`], and [`error-stack`]. //! -//! [`PreformattedContext`]: crate::preformatted::PreformattedContext +//! [`PreformatReportExt::preformat`]: https://docs.rs/rootcause-preformat/latest/rootcause_preformat/trait.PreformatReportExt.html#method.preformat +//! [`PreformattedContext`]: https://docs.rs/rootcause-preformat/latest/rootcause_preformat/struct.PreformattedContext.html //! [`Mutable`]: crate::markers::Mutable //! [`Cloneable`]: crate::markers::Cloneable //! [`SendSync`]: crate::markers::SendSync @@ -351,7 +352,6 @@ mod macros; pub mod handlers; pub mod hooks; pub mod markers; -pub mod preformatted; pub mod compat; pub mod option_ext; diff --git a/src/report/mut_.rs b/src/report/mut_.rs index 68a7d27..509a9c5 100644 --- a/src/report/mut_.rs +++ b/src/report/mut_.rs @@ -3,9 +3,8 @@ use core::any::TypeId; use rootcause_internals::handlers::{ContextFormattingStyle, FormattingFunction}; use crate::{ - Report, ReportIter, ReportRef, handlers, - markers::{self, Cloneable, Dynamic, Local, Mutable, SendSync, Uncloneable}, - preformatted::PreformattedContext, + ReportIter, ReportRef, handlers, + markers::{self, Cloneable, Dynamic, Local, SendSync, Uncloneable}, report_attachment::ReportAttachment, report_attachments::ReportAttachments, report_collection::ReportCollection, @@ -699,30 +698,6 @@ impl<'a, C: ?Sized, T> ReportMut<'a, C, T> { self.as_ref().iter_sub_reports() } - /// Creates a new report, which has the same structure as the current - /// report, but has all the contexts and attachments preformatted. - /// - /// This can be useful, as the new report is mutable because it was just - /// created, and additionally the new report is [`Send`]+[`Sync`]. - /// - /// # Examples - /// ``` - /// # use rootcause::{prelude::*, ReportMut, preformatted::PreformattedContext}; - /// # #[derive(Default)] - /// # struct NonSendSyncError(core::cell::Cell<()>); - /// # let non_send_sync_error = NonSendSyncError::default(); - /// # let mut report = report!(non_send_sync_error); - /// let report_mut: ReportMut<'_, NonSendSyncError, markers::Local> = report.as_mut(); - /// let preformatted: Report = - /// report_mut.preformat(); - /// assert_eq!(format!("{report}"), format!("{preformatted}")); - /// ``` - #[track_caller] - #[must_use] - pub fn preformat(&self) -> Report { - self.as_ref().preformat() - } - /// Returns the [`TypeId`] of the current context. /// /// # Examples diff --git a/src/report/owned.rs b/src/report/owned.rs index 93ad950..d46dcae 100644 --- a/src/report/owned.rs +++ b/src/report/owned.rs @@ -9,7 +9,6 @@ use crate::{ ReportConversion, ReportIter, ReportMut, ReportRef, handlers::{self, ContextHandler}, markers::{self, Cloneable, Dynamic, Local, Mutable, SendSync, Uncloneable}, - preformatted::{self, PreformattedContext}, report_attachment::ReportAttachment, report_attachments::ReportAttachments, report_collection::ReportCollection, @@ -530,13 +529,13 @@ impl Report { /// /// # See Also /// - /// - [`context_transform_nested()`](Report::context_transform_nested) - - /// Wraps original as preformatted child + /// - [`ContextTransformNestedExt::context_transform_nested`] - Wraps original as preformatted child /// - [`context()`](Report::context) - Adds new parent context /// - [`context_to()`](Report::context_to) - Uses /// [`ReportConversion`](crate::ReportConversion) trait /// - [`examples/context_methods.rs`] - Comparison guide /// + /// [`ContextTransformNestedExt::context_transform_nested`]: https://docs.rs/rootcause-preformat/latest/rootcause_preformat/trait.ContextTransformNestedExt.html#method.context_transform_nested /// [`examples/context_methods.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/context_methods.rs #[track_caller] pub fn context_transform(self, f: F) -> Report @@ -549,110 +548,6 @@ impl Report { Report::from_parts_unhooked::(new_context, children, attachments) } - - /// Transforms the context and nests the original report as a preformatted - /// child. - /// - /// Creates a new parent node with fresh hook data (location, backtrace), - /// but the original context type is lost—the child becomes - /// [`PreformattedContext`] and cannot be downcast. - /// - /// [`PreformattedContext`]: crate::preformatted::PreformattedContext - /// - /// # Examples - /// - /// ``` - /// # use rootcause::prelude::*; - /// # #[derive(Debug)] - /// # struct LibError; - /// # impl std::fmt::Display for LibError { - /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "lib error") } - /// # } - /// # #[derive(Debug)] - /// enum AppError { - /// Lib(LibError) - /// } - /// # impl std::fmt::Display for AppError { - /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "app error") } - /// # } - /// - /// let lib_report: Report = report!(LibError); - /// let app_report: Report = lib_report.context_transform_nested(AppError::Lib); - /// ``` - /// - /// # See Also - /// - /// - [`context_transform()`](Report::context_transform) - Transforms - /// without nesting - /// - [`context()`](Report::context) - Adds new parent, preserves child's - /// type - /// - [`preformat_root()`](Report::preformat_root) - Lower-level operation - /// used internally - /// - [`examples/context_methods.rs`] - Comparison guide - /// - /// [`examples/context_methods.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/context_methods.rs - #[track_caller] - pub fn context_transform_nested(self, f: F) -> Report - where - F: FnOnce(C) -> D, - D: markers::ObjectMarkerFor + core::fmt::Display + core::fmt::Debug, - PreformattedContext: markers::ObjectMarkerFor, - { - let (context, report) = self.preformat_root(); - report.context_custom::(f(context)) - } - - /// Extracts the context and returns it with a preformatted version of the - /// report. - /// - /// Returns a tuple: the original typed context and a new report with - /// [`PreformattedContext`](crate::preformatted::PreformattedContext) - /// containing the string representation. The preformatted report maintains - /// the same structure (children and attachments). Useful when you need - /// the typed value for processing and the formatted version for display. - /// - /// This is a lower-level method primarily for custom transformation logic. - /// Most users should use - /// [`context_transform`](Self::context_transform), - /// [`context_transform_nested`](Self::context_transform_nested), - /// or [`context_to`](Self::context_to) instead. - /// - /// See also: [`preformat`](Report::preformat) (formats entire hierarchy), - /// [`into_parts`](Report::into_parts) (extracts without formatting), - /// [`current_context`](crate::ReportRef::current_context) (reference - /// without extraction). - /// - /// # Examples - /// - /// ``` - /// # use rootcause::{preformatted::PreformattedContext, prelude::*}; - /// # #[derive(Debug)] - /// struct MyError { - /// code: u32 - /// } - /// # impl std::fmt::Display for MyError { - /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "error {}", self.code) } - /// # } - /// - /// let report: Report = report!(MyError { code: 500 }); - /// let (context, preformatted): (MyError, Report) = report.preformat_root(); - /// ``` - pub fn preformat_root(self) -> (C, Report) - where - PreformattedContext: markers::ObjectMarkerFor, - { - let preformatted = PreformattedContext::new_from_context(self.as_ref()); - let (context, children, attachments) = self.into_parts(); - - ( - context, - Report::from_parts_unhooked::( - preformatted, - children, - attachments, - ), - ) - } } impl Report { @@ -960,7 +855,9 @@ impl Report { /// - Allocate a new root node using e.g. [`Report::context`]. /// - If there is a single unique owner of the report, you can use /// [`Report::try_into_mutable`]. - /// - Preformat the root node using [`Report::preformat`]. + /// - Preformat the root node using [`PreformatReportExt::preformat`]. + /// + /// [`PreformatReportExt::preformat`]: https://docs.rs/rootcause-preformat/latest/rootcause_preformat/trait.PreformatReportExt.html#method.preformat /// /// # Examples /// ``` @@ -1237,28 +1134,6 @@ impl Report { self.as_uncloneable_ref().iter_sub_reports() } - /// Creates a new report, which has the same structure as the current - /// report, but has all the contexts and attachments preformatted. - /// - /// This can be useful, as the new report is mutable because it was just - /// created, and additionally the new report is [`Send`]+[`Sync`]. - /// - /// # Examples - /// ``` - /// # use rootcause::{prelude::*, preformatted::PreformattedContext, ReportRef, markers::{Uncloneable, Mutable, SendSync, Local}}; - /// # #[derive(Default)] - /// # struct NonSendSyncError(core::cell::Cell<()>); - /// # let non_send_sync_error = NonSendSyncError::default(); - /// let mut report: Report = report!(non_send_sync_error); - /// let preformatted: Report = report.preformat(); - /// assert_eq!(format!("{report}"), format!("{preformatted}")); - /// ``` - #[track_caller] - #[must_use] - pub fn preformat(&self) -> Report { - self.as_uncloneable_ref().preformat() - } - /// Returns the [`TypeId`] of the current context. /// /// # Examples diff --git a/src/report/ref_.rs b/src/report/ref_.rs index a3fa501..78826ac 100644 --- a/src/report/ref_.rs +++ b/src/report/ref_.rs @@ -5,8 +5,7 @@ use rootcause_internals::handlers::{ContextFormattingStyle, FormattingFunction}; use crate::{ Report, ReportIter, - markers::{Cloneable, Dynamic, Local, Mutable, SendSync, Uncloneable}, - preformatted::{self, PreformattedContext}, + markers::{Cloneable, Dynamic, Local, SendSync, Uncloneable}, report_attachments::ReportAttachments, report_collection::ReportCollection, util::format_helper, @@ -558,40 +557,6 @@ impl<'a, C: ?Sized, O, T> ReportRef<'a, C, O, T> { ReportIter::from_raw(stack) } - /// Creates a new report, which has the same structure as the current - /// report, but has all the contexts and attachments preformatted. - /// - /// This can be useful, as the new report is mutable because it was just - /// created, and additionally the new report is [`Send`]+[`Sync`]. - /// - /// # Examples - /// ``` - /// # use rootcause::{prelude::*, preformatted::PreformattedContext, ReportRef, markers::{Uncloneable, Mutable, SendSync, Local}}; - /// # #[derive(Default)] - /// # struct NonSendSyncError(core::cell::Cell<()>); - /// # let non_send_sync_error = NonSendSyncError::default(); - /// # let report = report!(non_send_sync_error); - /// let report_ref: ReportRef<'_, NonSendSyncError, Uncloneable, Local> = report.as_ref(); - /// let preformatted: Report = report_ref.preformat(); - /// assert_eq!(format!("{report}"), format!("{preformatted}")); - /// ``` - #[track_caller] - #[must_use] - pub fn preformat(self) -> Report { - let preformatted_context = PreformattedContext::new_from_context(self); - Report::from_parts_unhooked::( - preformatted_context, - self.children() - .iter() - .map(|sub_report| sub_report.preformat()) - .collect(), - self.attachments() - .iter() - .map(|attachment| attachment.preformat().into_dynamic()) - .collect(), - ) - } - /// Returns the [`TypeId`] of the current context. /// /// # Examples @@ -1169,21 +1134,4 @@ mod tests { static_assertions::assert_not_impl_any!(Report: From>); static_assertions::assert_not_impl_any!(Report: From>); } - - #[test] - fn test_preformat() { - use crate::{ - ReportRef, - markers::{Local, Mutable, SendSync, Uncloneable}, - preformatted::PreformattedContext, - prelude::*, - }; - #[derive(Default)] - struct NonSendSyncError(core::cell::Cell<()>); - let non_send_sync_error = NonSendSyncError::default(); - let report = report!(non_send_sync_error); - let report_ref: ReportRef<'_, NonSendSyncError, Uncloneable, Local> = report.as_ref(); - let preformatted: Report = report_ref.preformat(); - assert_eq!(alloc::format!("{report}"), alloc::format!("{preformatted}")); - } } diff --git a/src/report_attachment/mut_.rs b/src/report_attachment/mut_.rs index 9f5cd70..fca8f5a 100644 --- a/src/report_attachment/mut_.rs +++ b/src/report_attachment/mut_.rs @@ -2,11 +2,7 @@ use core::any::TypeId; use rootcause_internals::handlers::{AttachmentFormattingStyle, FormattingFunction}; -use crate::{ - markers::{Dynamic, SendSync}, - preformatted::PreformattedAttachment, - report_attachment::{ReportAttachment, ReportAttachmentRef}, -}; +use crate::{markers::Dynamic, report_attachment::ReportAttachmentRef}; /// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field /// an unsafe field and remove this module. @@ -416,20 +412,6 @@ impl<'a, A: ?Sized> ReportAttachmentMut<'a, A> { self.as_raw_ref() .preferred_formatting_style(report_formatting_function) } - - /// Creates a new attachment, with the inner attachment data preformatted. - /// - /// This can be useful, as the preformatted attachment is a newly allocated - /// object and additionally is [`Send`]+[`Sync`]. - /// - /// See [`PreformattedAttachment`] for more information. - /// - /// [`PreformattedAttachment`](crate::preformatted::PreformattedAttachment) - #[track_caller] - #[must_use] - pub fn preformat(&self) -> ReportAttachment { - self.as_ref().preformat() - } } impl<'a> ReportAttachmentMut<'a, Dynamic> { diff --git a/src/report_attachment/owned.rs b/src/report_attachment/owned.rs index 4268ce9..8796bcd 100644 --- a/src/report_attachment/owned.rs +++ b/src/report_attachment/owned.rs @@ -8,7 +8,6 @@ use rootcause_internals::{ use crate::{ handlers::{self, AttachmentHandler}, markers::{self, Dynamic, Local, SendSync}, - preformatted::PreformattedAttachment, report_attachment::{ReportAttachmentMut, ReportAttachmentRef}, }; @@ -386,22 +385,6 @@ impl ReportAttachment { // this type's invariant unsafe { ReportAttachmentMut::from_raw(raw) } } - - /// Creates a new attachment, with the inner attachment data preformatted. - /// - /// This can be useful, as the preformatted attachment is a newly allocated - /// object and additionally is [`Send`]+[`Sync`]. - /// - /// See [`PreformattedAttachment`] for more information. - /// - /// [`PreformattedAttachment`](crate::preformatted::PreformattedAttachment) - #[must_use] - #[track_caller] - pub fn preformat(&self) -> ReportAttachment { - // For implementation reasons, the actual formatting works on - // ReportAttachmentRef - self.as_ref().preformat() - } } impl ReportAttachment { diff --git a/src/report_attachment/ref_.rs b/src/report_attachment/ref_.rs index 1c18934..d58d63c 100644 --- a/src/report_attachment/ref_.rs +++ b/src/report_attachment/ref_.rs @@ -2,12 +2,7 @@ use core::any::TypeId; use rootcause_internals::handlers::{AttachmentFormattingStyle, FormattingFunction}; -use crate::{ - markers::{Dynamic, SendSync}, - preformatted::{self, PreformattedAttachment}, - report_attachment::ReportAttachment, - util::format_helper, -}; +use crate::{markers::Dynamic, util::format_helper}; /// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field /// an unsafe field and remove this module. @@ -329,22 +324,6 @@ impl<'a, A: ?Sized> ReportAttachmentRef<'a, A> { self.as_raw_ref() .preferred_formatting_style(report_formatting_function) } - - /// Creates a new attachment, with the inner attachment data preformatted. - /// - /// This can be useful, as the preformatted attachment is a newly allocated - /// object and additionally is [`Send`]+[`Sync`]. - /// - /// See [`PreformattedAttachment`] for more information. - /// - /// [`PreformattedAttachment`](crate::preformatted::PreformattedAttachment) - #[track_caller] - #[must_use] - pub fn preformat(self) -> ReportAttachment { - ReportAttachment::new_custom::( - PreformattedAttachment::new_from_attachment(self), - ) - } } impl<'a> ReportAttachmentRef<'a, Dynamic> { diff --git a/src/result_ext.rs b/src/result_ext.rs index b69f570..1e11e09 100644 --- a/src/result_ext.rs +++ b/src/result_ext.rs @@ -305,44 +305,6 @@ pub trait ResultExt { F: FnOnce(E::Context) -> C, C: Send + Sync + core::fmt::Display + core::fmt::Debug; - /// Transforms the error's context while nesting the original report as a - /// child. - /// - /// If `Err`, converts to a [`Report`], preformats it, and wraps it as a - /// child under the new context. Report creation hooks run again, - /// capturing fresh hook data. - /// See [`Report::context_transform_nested`](crate::Report::context_transform_nested) for details. - /// - /// See also: [`local_context_transform_nested`](ResultExt::local_context_transform_nested) (non-`Send + Sync` version), - /// [`context_transform`](ResultExt::context_transform) (preserves - /// structure), [`examples/context_methods.rs`] (comparison guide). - /// - /// [`examples/context_methods.rs`]: https://github.com/rootcause-rs/rootcause/blob/main/examples/context_methods.rs - /// - /// # Examples - /// - /// ``` - /// # use std::io; - /// # use rootcause::prelude::*; - /// #[derive(Debug)] - /// enum AppError { - /// Io(io::Error) - /// } - /// # impl std::fmt::Display for AppError { - /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "error") } - /// # } - /// - /// let result: Result> = - /// std::fs::read_to_string("config.toml").context_transform_nested(AppError::Io); - /// ``` - #[track_caller] - fn context_transform_nested(self, f: F) -> Result> - where - E: IntoReport, - E::Context: Sized, - F: FnOnce(E::Context) -> C, - C: Send + Sync + core::fmt::Display + core::fmt::Debug; - /// Converts the error into a [`Report`] and adds the provided attachment to /// the [`Report`]. /// @@ -785,39 +747,6 @@ pub trait ResultExt { F: FnOnce(E::Context) -> C, C: core::fmt::Display + core::fmt::Debug; - /// Non-`Send + Sync` version of - /// [`context_transform_nested`](ResultExt::context_transform_nested). - /// - /// Transforms the error's context while nesting the original report as a - /// child. Produces a local (non-thread-safe) [`Report`]. Report - /// creation hooks run again. - /// - /// # Examples - /// - /// ``` - /// # use std::io; - /// use std::rc::Rc; - /// use rootcause::prelude::*; - /// - /// #[derive(Debug)] - /// enum AppError { - /// Io(Rc) // Rc is not Send + Sync - /// } - /// # impl std::fmt::Display for AppError { - /// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "error") } - /// # } - /// - /// let result: Result> = - /// std::fs::read_to_string("config.toml").local_context_transform_nested(|e| AppError::Io(Rc::new(e))); - /// ``` - #[track_caller] - fn local_context_transform_nested(self, f: F) -> Result> - where - E: IntoReport, - E::Context: Sized, - F: FnOnce(E::Context) -> C, - C: core::fmt::Display + core::fmt::Debug; - /// Converts the error into a local (non-thread-safe) [`Report`] and adds /// the provided attachment to the [`Report`]. /// @@ -1069,20 +998,6 @@ impl ResultExt for Result { } } - #[inline] - fn context_transform_nested(self, f: F) -> Result> - where - E: IntoReport, - E::Context: Sized, - F: FnOnce(E::Context) -> C, - C: Send + Sync + core::fmt::Display + core::fmt::Debug, - { - match self { - Ok(v) => Ok(v), - Err(e) => Err(e.into_report().context_transform_nested(f)), - } - } - #[inline] fn attach(self, attachment: A) -> Result> where @@ -1225,20 +1140,6 @@ impl ResultExt for Result { } } - #[inline] - fn local_context_transform_nested(self, f: F) -> Result> - where - E: IntoReport, - E::Context: Sized, - F: FnOnce(E::Context) -> C, - C: core::fmt::Display + core::fmt::Debug, - { - match self { - Ok(v) => Ok(v), - Err(e) => Err(e.into_report().context_transform_nested(f)), - } - } - #[inline] fn local_attach(self, attachment: A) -> Result::Context, Mutable, Local>> where