use alloc::{boxed::Box, fmt};
use core::{any::TypeId, marker::PhantomData};
use hashbrown::HashMap;
use rootcause_internals::handlers::{ContextFormattingStyle, FormattingFunction};
use crate::{
ReportRef,
hooks::{HookData, use_hooks},
markers::{Dynamic, Local, Uncloneable},
preformatted::PreformattedContext,
};
#[derive(Default)]
pub(crate) struct HookMap {
map: HashMap<TypeId, Box<dyn StoredHook>, rustc_hash::FxBuildHasher>,
}
impl core::fmt::Debug for HookMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.map.values().fmt(f)
}
}
impl HookMap {
fn get(&self, type_id: TypeId) -> Option<&dyn StoredHook> {
Some(&**self.map.get(&type_id)?)
}
pub(crate) fn insert<C, H>(&mut self, hook: H)
where
C: 'static,
H: ContextFormatterHook<C>,
{
let hook: Hook<C, H> = Hook {
hook,
_hooked_type: PhantomData,
};
let hook: Box<Hook<C, H>> = Box::new(hook);
self.map.insert(TypeId::of::<C>(), hook);
}
}
struct Hook<C, H>
where
C: 'static,
{
hook: H,
_hooked_type: PhantomData<fn(C) -> C>,
}
impl<C, H> core::fmt::Debug for Hook<C, H>
where
C: 'static,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ContextFormattingHook<{}, {}>",
core::any::type_name::<C>(),
core::any::type_name::<H>(),
)
}
}
trait StoredHook: 'static + Send + Sync + core::fmt::Debug {
unsafe fn display(
&self,
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result;
unsafe fn debug(
&self,
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
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(
&self,
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
report_formatting_function: FormattingFunction,
) -> ContextFormattingStyle;
}
pub trait ContextFormatterHook<C>: 'static + Send + Sync {
fn display(
&self,
report: ReportRef<'_, C, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
fmt::Display::fmt(&report.format_current_context_unhooked(), formatter)
}
fn display_preformatted(
&self,
report: ReportRef<'_, PreformattedContext, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
fmt::Display::fmt(&report.format_current_context_unhooked(), formatter)
}
fn debug(
&self,
report: ReportRef<'_, C, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
fmt::Debug::fmt(&report.format_current_context_unhooked(), formatter)
}
fn debug_preformatted(
&self,
report: ReportRef<'_, PreformattedContext, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
fmt::Debug::fmt(&report.format_current_context_unhooked(), formatter)
}
fn preferred_context_formatting_style(
&self,
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
report_formatting_function: FormattingFunction,
) -> ContextFormattingStyle {
report.preferred_context_formatting_style_unhooked(report_formatting_function)
}
}
impl<C, H> StoredHook for Hook<C, H>
where
C: 'static,
H: ContextFormatterHook<C>,
{
unsafe fn display(
&self,
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let report = unsafe { report.downcast_report_unchecked::<C>() };
self.hook.display(report, formatter)
}
unsafe fn debug(
&self,
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let report = unsafe { report.downcast_report_unchecked::<C>() };
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(
&self,
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
report_formatting_function: FormattingFunction,
) -> ContextFormattingStyle {
self.hook
.preferred_context_formatting_style(report, report_formatting_function)
}
}
pub(crate) fn display_context(
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
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::<PreformattedContext>()
&& 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()) {
unsafe {
return hook.display(report, formatter);
}
}
}
fmt::Display::fmt(&report.format_current_context_unhooked(), formatter)
})
}
pub(crate) fn debug_context(
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
formatter: &mut fmt::Formatter<'_>,
) -> fmt::Result {
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::<PreformattedContext>()
&& 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()) {
unsafe {
return hook.debug(report, formatter);
}
}
}
fmt::Debug::fmt(&report.format_current_context_unhooked(), formatter)
})
}
pub(crate) fn get_preferred_context_formatting_style(
report: ReportRef<'_, Dynamic, Uncloneable, Local>,
report_formatting_function: FormattingFunction,
) -> ContextFormattingStyle {
use_hooks(|hook_data: Option<&HookData>| {
if let Some(hook_data) = hook_data {
let context_formatters: &HookMap = &hook_data.context_formatters;
if let Some(current_context) = report.downcast_current_context::<PreformattedContext>()
&& 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);
}
}
report.preferred_context_formatting_style_unhooked(report_formatting_function)
})
}