use alloc::{boxed::Box, string::ToString, vec::Vec};
use core::{fmt, fmt::Write, marker::PhantomData, panic::Location};
#[cfg(all(nightly, feature = "std"))]
use std::backtrace::{Backtrace, BacktraceStatus};
#[cfg(feature = "spantrace")]
use tracing_error::{SpanTrace, SpanTraceStatus};
#[cfg(nightly)]
use crate::iter::{RequestRef, RequestValue};
#[cfg(all(nightly, any(feature = "std", feature = "spantrace")))]
use crate::{context::temporary_provider, provider::request_ref};
use crate::{
iter::{Frames, FramesMut},
AttachmentKind, Context, Frame, FrameKind,
};
#[must_use]
#[repr(transparent)]
pub struct Report<C> {
inner: Box<ReportImpl>,
_context: PhantomData<C>,
}
impl<C> Report<C> {
pub(crate) fn from_frame(
frame: Frame,
#[cfg(all(nightly, feature = "std"))] backtrace: Option<Backtrace>,
#[cfg(feature = "spantrace")] span_trace: Option<SpanTrace>,
) -> Self {
Self {
inner: Box::new(ReportImpl {
frame,
#[cfg(all(nightly, feature = "std"))]
backtrace,
#[cfg(feature = "spantrace")]
span_trace,
}),
_context: PhantomData,
}
}
#[track_caller]
pub fn new(context: C) -> Self
where
C: Context,
{
#[cfg(all(nightly, any(feature = "std", feature = "spantrace")))]
let provider = temporary_provider(&context);
#[cfg(all(nightly, feature = "std"))]
let backtrace = if request_ref::<Backtrace, _>(&provider).is_some() {
None
} else {
Some(Backtrace::capture())
};
#[cfg(all(nightly, feature = "spantrace"))]
let span_trace = if request_ref::<SpanTrace, _>(&provider).is_some() {
None
} else {
Some(SpanTrace::capture())
};
#[cfg(all(not(nightly), feature = "spantrace"))]
let span_trace = Some(SpanTrace::capture());
#[cfg(all(nightly, any(feature = "std", feature = "spantrace")))]
drop(provider);
Self::from_frame(
Frame::from_context(context, Location::caller(), None),
#[cfg(all(nightly, feature = "std"))]
backtrace,
#[cfg(feature = "spantrace")]
span_trace,
)
}
#[track_caller]
pub fn attach<A>(self, attachment: A) -> Self
where
A: Send + Sync + 'static,
{
Self::from_frame(
Frame::from_attachment(
attachment,
Location::caller(),
Some(Box::new(self.inner.frame)),
),
#[cfg(all(nightly, feature = "std"))]
self.inner.backtrace,
#[cfg(feature = "spantrace")]
self.inner.span_trace,
)
}
#[track_caller]
pub fn attach_printable<A>(self, attachment: A) -> Self
where
A: fmt::Display + fmt::Debug + Send + Sync + 'static,
{
Self::from_frame(
Frame::from_printable_attachment(
attachment,
Location::caller(),
Some(Box::new(self.inner.frame)),
),
#[cfg(all(nightly, feature = "std"))]
self.inner.backtrace,
#[cfg(feature = "spantrace")]
self.inner.span_trace,
)
}
#[track_caller]
pub fn change_context<T>(self, context: T) -> Report<T>
where
T: Context,
{
Report::from_frame(
Frame::from_context(
context,
Location::caller(),
Some(Box::new(self.inner.frame)),
),
#[cfg(all(nightly, feature = "std"))]
self.inner.backtrace,
#[cfg(feature = "spantrace")]
self.inner.span_trace,
)
}
#[must_use]
#[cfg(all(nightly, feature = "std"))]
pub fn backtrace(&self) -> Option<&Backtrace> {
let backtrace = self.inner.backtrace.as_ref().unwrap_or_else(|| {
self.request_ref::<Backtrace>()
.next()
.expect("Backtrace is not available")
});
if backtrace.status() == BacktraceStatus::Captured {
Some(backtrace)
} else {
None
}
}
#[must_use]
#[cfg(feature = "spantrace")]
pub fn span_trace(&self) -> Option<&SpanTrace> {
#[cfg(not(nightly))]
let span_trace = self.inner.span_trace.as_ref()?;
#[cfg(nightly)]
let span_trace = self
.inner
.span_trace
.as_ref()
.or_else(|| self.request_ref::<SpanTrace>().next())?;
if span_trace.status() == SpanTraceStatus::CAPTURED {
Some(span_trace)
} else {
None
}
}
pub const fn frames(&self) -> Frames<'_> {
Frames::new(self)
}
pub fn frames_mut(&mut self) -> FramesMut<'_> {
FramesMut::new(self)
}
#[cfg(nightly)]
pub const fn request_ref<T: ?Sized + Send + Sync + 'static>(&self) -> RequestRef<'_, T> {
RequestRef::new(self)
}
#[cfg(nightly)]
pub const fn request_value<T: Send + Sync + 'static>(&self) -> RequestValue<'_, T> {
RequestValue::new(self)
}
#[must_use]
pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
self.frames().any(Frame::is::<T>)
}
#[must_use]
pub fn downcast_ref<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.frames().find_map(Frame::downcast_ref::<T>)
}
#[must_use]
pub fn downcast_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
self.frames_mut().find_map(Frame::downcast_mut::<T>)
}
pub(crate) const fn frame(&self) -> &Frame {
&self.inner.frame
}
pub(crate) fn frame_mut(&mut self) -> &mut Frame {
&mut self.inner.frame
}
}
impl<T: Context> Report<T> {
#[must_use]
pub fn current_context(&self) -> &T
where
T: Send + Sync + 'static,
{
self.downcast_ref().unwrap_or_else(|| {
unreachable!(
"Report does not contain a context. This is considered a bug and should be \
reported to https://github.com/hashintel/hash/issues/new"
);
})
}
}
impl<Context> fmt::Display for Report<Context> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "hooks")]
if let Some(display_hook) = Report::display_hook() {
return display_hook(self.generalized(), fmt);
}
for (index, frame) in self
.frames()
.filter_map(|frame| match frame.kind() {
FrameKind::Context(context) => Some(context.to_string()),
FrameKind::Attachment(AttachmentKind::Printable(attachment)) => {
Some(attachment.to_string())
}
FrameKind::Attachment(AttachmentKind::Opaque(_)) => None,
})
.enumerate()
{
if index == 0 {
fmt::Display::fmt(&frame, fmt)?;
if !fmt.alternate() {
break;
}
} else {
write!(fmt, ": {frame}")?;
}
}
Ok(())
}
}
impl<Context> fmt::Debug for Report<Context> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "hooks")]
if let Some(debug_hook) = Report::debug_hook() {
return debug_hook(self.generalized(), fmt);
}
if fmt.alternate() {
let mut debug = fmt.debug_struct("Report");
debug.field("frames", &self.frames());
#[cfg(all(nightly, feature = "std"))]
debug.field("backtrace", &self.backtrace());
#[cfg(feature = "spantrace")]
debug.field("span_trace", &self.span_trace());
debug.finish()
} else {
let mut context_idx = -1;
let mut attachments: Vec<_> = Vec::new();
let mut opaque_attachments = 0;
for frame in self.frames() {
match frame.kind() {
FrameKind::Context(context) => {
if context_idx == -1 {
write!(fmt, "{context}")?;
} else {
if context_idx == 0 {
fmt.write_str("\n\nCaused by:")?;
}
write!(fmt, "\n {context_idx}: {context}")?;
}
write!(fmt, "\n at {}", frame.location())?;
#[allow(clippy::iter_with_drain)] for attachment in attachments.drain(..) {
write!(fmt, "\n - {attachment}")?;
}
if opaque_attachments > 0 {
write!(
fmt,
"\n - {opaque_attachments} additional opaque attachment"
)?;
if opaque_attachments > 1 {
fmt.write_char('s')?;
}
opaque_attachments = 0;
}
context_idx += 1;
}
FrameKind::Attachment(AttachmentKind::Printable(attachment)) => {
attachments.push(attachment);
}
FrameKind::Attachment(AttachmentKind::Opaque(_)) => {
opaque_attachments += 1;
}
}
}
#[cfg(all(nightly, feature = "std"))]
if let Some(backtrace) = self.backtrace() {
write!(fmt, "\n\nStack backtrace:\n{backtrace}")?;
}
#[cfg(feature = "spantrace")]
if let Some(span_trace) = self.span_trace() {
write!(fmt, "\n\nSpan trace:\n{span_trace}")?;
}
Ok(())
}
}
}
pub struct ReportImpl {
pub(super) frame: Frame,
#[cfg(all(nightly, feature = "std"))]
backtrace: Option<Backtrace>,
#[cfg(feature = "spantrace")]
span_trace: Option<SpanTrace>,
}