use alloc::{boxed::Box, vec, vec::Vec};
use core::{fmt, fmt::Display, marker::PhantomData, mem, panic::Location};
#[cfg(all(rust_1_65, feature = "std"))]
use std::backtrace::{Backtrace, BacktraceStatus};
#[cfg(feature = "std")]
use std::process::ExitCode;
#[cfg(feature = "spantrace")]
use tracing_error::{SpanTrace, SpanTraceStatus};
#[cfg(nightly)]
use crate::iter::{RequestRef, RequestValue};
use crate::{
iter::{Frames, FramesMut},
Context, Frame,
};
#[must_use]
#[repr(transparent)]
pub struct Report<C> {
#[allow(clippy::box_collection)]
pub(super) frames: Box<Vec<Frame>>,
_context: PhantomData<fn() -> *const C>,
}
impl<C> Report<C> {
#[inline]
#[track_caller]
pub fn new(context: C) -> Self
where
C: Context,
{
Self::from_frame(Frame::from_context(
context,
Location::caller(),
Box::new([]),
))
}
#[track_caller]
pub(crate) fn from_frame(frame: Frame) -> Self {
#[cfg(nightly)]
let location = core::any::request_ref::<Location>(&frame)
.is_none()
.then_some(Location::caller());
#[cfg(not(nightly))]
let location = Some(Location::caller());
#[cfg(all(nightly, feature = "std"))]
let backtrace = core::any::request_ref::<Backtrace>(&frame)
.filter(|backtrace| backtrace.status() == BacktraceStatus::Captured)
.is_none()
.then(Backtrace::capture);
#[cfg(all(rust_1_65, not(nightly), feature = "std"))]
let backtrace = Some(Backtrace::capture());
#[cfg(all(nightly, feature = "spantrace"))]
let span_trace = core::any::request_ref::<SpanTrace>(&frame)
.filter(|span_trace| span_trace.status() == SpanTraceStatus::CAPTURED)
.is_none()
.then(SpanTrace::capture);
#[cfg(all(not(nightly), feature = "spantrace"))]
let span_trace = Some(SpanTrace::capture());
#[allow(unused_mut)]
let mut report = Self {
frames: Box::new(vec![frame]),
_context: PhantomData,
};
if let Some(location) = location {
report = report.attach(*location);
}
#[cfg(all(rust_1_65, feature = "std"))]
if let Some(backtrace) =
backtrace.filter(|bt| matches!(bt.status(), BacktraceStatus::Captured))
{
report = report.attach(backtrace);
}
#[cfg(feature = "spantrace")]
if let Some(span_trace) = span_trace.filter(|st| st.status() == SpanTraceStatus::CAPTURED) {
report = report.attach(span_trace);
}
report
}
#[allow(missing_docs)]
#[must_use]
#[cfg(all(nightly, feature = "std"))]
#[deprecated(
since = "0.2.0",
note = "a report might contain multiple backtraces, use `request_ref::<Backtrace>()` \
instead"
)]
pub fn backtrace(&self) -> Option<&Backtrace> {
self.request_ref::<Backtrace>().next()
}
#[allow(missing_docs)]
#[must_use]
#[cfg(feature = "spantrace")]
#[cfg_attr(
nightly,
deprecated(
since = "0.2.0",
note = "a report might contain multiple spantraces, use `request_ref::<SpanTrace>()` \
instead"
)
)]
#[cfg_attr(
not(nightly),
deprecated(
since = "0.2.0",
note = "a report might contain multiple spantraces, use \
`frames().filter(Frame::downcast_ref::<SpanTrace>)` instead"
)
)]
pub fn span_trace(&self) -> Option<&SpanTrace> {
#[cfg(nightly)]
return self.request_ref::<SpanTrace>().next();
#[cfg(not(nightly))]
return self.downcast_ref::<SpanTrace>();
}
pub fn extend_one(&mut self, mut report: Self) {
self.frames.append(&mut report.frames);
}
#[track_caller]
pub fn attach<A>(mut self, attachment: A) -> Self
where
A: Send + Sync + 'static,
{
let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
self.frames.push(Frame::from_attachment(
attachment,
Location::caller(),
old_frames.into_boxed_slice(),
));
self
}
#[track_caller]
pub fn attach_printable<A>(mut self, attachment: A) -> Self
where
A: fmt::Display + fmt::Debug + Send + Sync + 'static,
{
let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
self.frames.push(Frame::from_printable_attachment(
attachment,
Location::caller(),
old_frames.into_boxed_slice(),
));
self
}
#[track_caller]
pub fn change_context<T>(mut self, context: T) -> Report<T>
where
T: Context,
{
let old_frames = mem::replace(self.frames.as_mut(), Vec::with_capacity(1));
let context_frame = vec![Frame::from_context(
context,
Location::caller(),
old_frames.into_boxed_slice(),
)];
self.frames.push(Frame::from_attachment(
*Location::caller(),
Location::caller(),
context_frame.into_boxed_slice(),
));
Report {
frames: self.frames,
_context: PhantomData,
}
}
#[must_use]
pub fn current_frames(&self) -> &[Frame] {
&self.frames
}
pub fn frames(&self) -> Frames<'_> {
Frames::new(&self.frames)
}
pub fn frames_mut(&mut self) -> FramesMut<'_> {
FramesMut::new(&mut self.frames)
}
#[cfg(nightly)]
pub fn request_ref<T: ?Sized + Send + Sync + 'static>(&self) -> RequestRef<'_, T> {
RequestRef::new(&self.frames)
}
#[cfg(nightly)]
pub fn request_value<T: Send + Sync + 'static>(&self) -> RequestValue<'_, T> {
RequestValue::new(&self.frames)
}
#[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>)
}
#[must_use]
pub fn current_context(&self) -> &C
where
C: Display + 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"
);
})
}
}
#[cfg(feature = "std")]
impl<Context> std::process::Termination for Report<Context> {
fn report(self) -> ExitCode {
#[cfg(not(nightly))]
return ExitCode::FAILURE;
#[cfg(nightly)]
self.request_ref::<ExitCode>()
.next()
.copied()
.unwrap_or(ExitCode::FAILURE)
}
}
impl<Context> FromIterator<Report<Context>> for Option<Report<Context>> {
fn from_iter<T: IntoIterator<Item = Report<Context>>>(iter: T) -> Self {
let mut iter = iter.into_iter();
let mut base = iter.next()?;
for rest in iter {
base.extend_one(rest);
}
Some(base)
}
}
impl<Context> Extend<Self> for Report<Context> {
fn extend<T: IntoIterator<Item = Self>>(&mut self, iter: T) {
for item in iter {
self.extend_one(item);
}
}
}