use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use core::fmt;
use core::hash::{Hash, Hasher};
use core::panic::Location;
use crate::AtCrateInfo;
use crate::context::{AtContext, AtContextRef};
use crate::trace::{AtFrame, AtFrameOwned, AtTrace, AtTraceBoxed};
pub struct At<E> {
error: E,
trace: AtTraceBoxed,
}
impl<E> At<E> {
#[inline]
pub const fn wrap(error: E) -> Self {
Self {
error,
trace: AtTraceBoxed::new(),
}
}
#[inline]
pub fn from_parts(error: E, trace: AtTrace) -> Self {
let mut boxed = AtTraceBoxed::new();
boxed.set(trace);
Self {
error,
trace: boxed,
}
}
fn ensure_trace(&mut self) -> &mut AtTrace {
self.trace.get_or_insert_mut()
}
#[track_caller]
#[inline]
pub fn at(mut self) -> Self {
let loc = Location::caller();
let trace = self.trace.get_or_insert_mut();
let _ = trace.try_push(loc);
self
}
#[track_caller]
#[inline]
pub fn at_fn<F: Fn()>(mut self, _marker: F) -> Self {
let full_name = core::any::type_name::<F>();
let name = full_name.strip_suffix("::{{closure}}").unwrap_or(full_name);
let loc = Location::caller();
let trace = self.trace.get_or_insert_mut();
let _ = trace.try_push(loc);
let context = AtContext::FunctionName(name);
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
pub fn at_named(mut self, name: &'static str) -> Self {
let loc = Location::caller();
let trace = self.trace.get_or_insert_mut();
let _ = trace.try_push(loc);
let context = AtContext::FunctionName(name);
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
pub fn at_str(mut self, msg: &'static str) -> Self {
let loc = Location::caller();
let context = AtContext::Text(Cow::Borrowed(msg));
let trace = self.trace.get_or_insert_mut();
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
pub fn at_string(mut self, f: impl FnOnce() -> String) -> Self {
let loc = Location::caller();
let context = AtContext::Text(Cow::Owned(f()));
let trace = self.trace.get_or_insert_mut();
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
pub fn at_data<T: fmt::Display + Send + Sync + 'static>(
mut self,
f: impl FnOnce() -> T,
) -> Self {
let loc = Location::caller();
let ctx = f();
let context = AtContext::Display(Box::new(ctx));
let trace = self.trace.get_or_insert_mut();
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
pub fn at_debug<T: fmt::Debug + Send + Sync + 'static>(
mut self,
f: impl FnOnce() -> T,
) -> Self {
let loc = Location::caller();
let ctx = f();
let context = AtContext::Debug(Box::new(ctx));
let trace = self.trace.get_or_insert_mut();
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
pub fn at_aside_error<Err: core::error::Error + Send + Sync + 'static>(
mut self,
err: Err,
) -> Self {
let loc = Location::caller();
let context = AtContext::Error(Box::new(err));
let trace = self.trace.get_or_insert_mut();
trace.try_add_context(loc, context);
self
}
#[deprecated(
since = "0.1.4",
note = "Renamed to `at_aside_error()`. The attached error is diagnostic context \
only — it is NOT part of the `.source()` chain."
)]
#[track_caller]
#[inline]
pub fn at_error<Err: core::error::Error + Send + Sync + 'static>(mut self, err: Err) -> Self {
let loc = Location::caller();
let context = AtContext::Error(Box::new(err));
let trace = self.trace.get_or_insert_mut();
trace.try_add_context(loc, context);
self
}
#[track_caller]
#[inline]
pub fn at_crate(mut self, info: &'static AtCrateInfo) -> Self {
let loc = Location::caller();
let trace = self.trace.get_or_insert_mut();
trace.try_add_crate_boundary(loc, info);
self
}
#[doc(hidden)]
#[inline]
pub fn at_skipped_frames(mut self) -> Self {
let trace = self.trace.get_or_insert_mut();
let _ = trace.try_push_skipped();
self
}
#[inline]
pub fn set_crate_info(mut self, info: &'static AtCrateInfo) -> Self {
let trace = self.trace.get_or_insert_mut();
trace.set_crate_info(info);
self
}
#[inline]
pub fn crate_info(&self) -> Option<&'static AtCrateInfo> {
self.trace.as_ref().and_then(|t| t.crate_info())
}
#[inline]
pub fn error(&self) -> &E {
&self.error
}
#[inline]
pub fn error_mut(&mut self) -> &mut E {
&mut self.error
}
#[deprecated(
since = "0.1.4",
note = "Discards the trace. Use `decompose()` to get both error and trace, \
or `map_error()` to convert types while preserving the trace."
)]
#[inline]
pub fn into_inner(self) -> E {
self.error
}
#[inline]
pub fn decompose(mut self) -> (E, Option<AtTrace>) {
let trace = self.trace.take();
(self.error, trace)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.trace.is_empty()
}
#[inline]
#[allow(dead_code)] pub(crate) fn locations(&self) -> impl Iterator<Item = &'static Location<'static>> + '_ {
self.trace
.as_ref()
.into_iter()
.flat_map(|t| t.iter())
.flatten() }
#[inline]
#[allow(dead_code)] pub(crate) fn first_location(&self) -> Option<&'static Location<'static>> {
self.locations().next()
}
#[inline]
#[allow(dead_code)] pub(crate) fn last_location(&self) -> Option<&'static Location<'static>> {
self.locations().last()
}
#[inline]
#[allow(dead_code)] pub(crate) fn trace_ref(&self) -> Option<&AtTrace> {
self.trace.as_ref()
}
#[inline]
pub fn contexts(&self) -> impl Iterator<Item = AtContextRef<'_>> {
self.trace.as_ref().into_iter().flat_map(|t| t.contexts())
}
#[inline]
pub fn frames(&self) -> impl Iterator<Item = AtFrame<'_>> {
self.trace.frames()
}
#[inline]
pub fn frame_count(&self) -> usize {
self.trace.as_ref().map_or(0, |t| t.frame_count())
}
#[inline]
pub fn at_pop(&mut self) -> Option<AtFrameOwned> {
self.trace.as_mut()?.pop()
}
#[inline]
pub fn at_push(&mut self, segment: AtFrameOwned) {
self.ensure_trace().push(segment);
}
#[inline]
pub fn at_first_pop(&mut self) -> Option<AtFrameOwned> {
self.trace.as_mut()?.pop_first()
}
#[inline]
pub fn at_first_insert(&mut self, segment: AtFrameOwned) {
self.ensure_trace().push_first(segment);
}
#[inline]
pub fn take_trace(&mut self) -> Option<AtTrace> {
self.trace.take()
}
#[inline]
pub fn set_trace(&mut self, trace: AtTrace) {
self.trace.set(trace);
}
#[inline]
pub fn map_error<E2, F>(self, f: F) -> At<E2>
where
F: FnOnce(E) -> E2,
{
At {
error: f(self.error),
trace: self.trace,
}
}
#[inline]
pub fn into_traceable<E2, F>(mut self, f: F) -> E2
where
F: FnOnce(E) -> E2,
E2: crate::trace::AtTraceable,
{
let mut new_err = f(self.error);
if let Some(trace) = self.trace.take() {
*new_err.trace_mut() = trace;
}
new_err
}
}
impl<E: fmt::Debug> fmt::Debug for At<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Error: {:?}", self.error)?;
let Some(trace) = self.trace.as_ref() else {
return Ok(());
};
writeln!(f)?;
for (i, loc_opt) in trace.iter().enumerate() {
match loc_opt {
Some(loc) => {
writeln!(f, " at {}:{}", loc.file(), loc.line())?;
for context in trace.contexts_at(i) {
match context {
AtContext::Text(msg) => writeln!(f, " ╰─ {}", msg)?,
AtContext::FunctionName(name) => writeln!(f, " ╰─ in {}", name)?,
AtContext::Debug(t) => writeln!(f, " ╰─ {:?}", &**t)?,
AtContext::Display(t) => writeln!(f, " ╰─ {}", &**t)?,
AtContext::Error(e) => writeln!(f, " ╰─ caused by: {}", e)?,
AtContext::Crate(_) => {} }
}
}
None => {
writeln!(f, " [...]")?;
}
}
}
Ok(())
}
}
impl<E: fmt::Debug> At<E> {
#[inline]
pub fn display_with_meta(&self) -> impl fmt::Display + '_ {
DisplayWithMeta { traced: self }
}
}
struct DisplayWithMeta<'a, E> {
traced: &'a At<E>,
}
impl<E: fmt::Debug> fmt::Display for DisplayWithMeta<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Error: {:?}", self.traced.error)?;
let Some(trace) = self.traced.trace.as_ref() else {
return Ok(());
};
let initial_crate = trace.crate_info();
if let Some(info) = initial_crate {
writeln!(f, " crate: {}", info.name())?;
}
writeln!(f)?;
let mut link_template: Option<String> = initial_crate.and_then(build_link_base);
for (i, loc_opt) in trace.iter().enumerate() {
for context in trace.contexts_at(i) {
if let AtContext::Crate(info) = context {
link_template = build_link_base(info);
}
}
match loc_opt {
Some(loc) => {
write_location_meta(f, loc, link_template.as_deref())?;
for context in trace.contexts_at(i) {
match context {
AtContext::Text(msg) => writeln!(f, " ╰─ {}", msg)?,
AtContext::FunctionName(name) => writeln!(f, " ╰─ in {}", name)?,
AtContext::Debug(t) => writeln!(f, " ╰─ {:?}", &**t)?,
AtContext::Display(t) => writeln!(f, " ╰─ {}", &**t)?,
AtContext::Error(e) => writeln!(f, " ╰─ caused by: {}", e)?,
AtContext::Crate(_) => {} }
}
}
None => {
writeln!(f, " [...]")?;
}
}
}
Ok(())
}
}
fn build_link_base(info: &AtCrateInfo) -> Option<String> {
match (info.repo(), info.commit()) {
(Some(repo), Some(commit)) => {
let repo = repo.trim_end_matches('/');
let path = info.crate_path().unwrap_or("");
let format = info.link_format();
let mut result =
String::with_capacity(format.len() + repo.len() + commit.len() + path.len());
let mut chars = format.chars().peekable();
while let Some(c) = chars.next() {
if c == '{' {
let mut placeholder = String::new();
while let Some(&next) = chars.peek() {
if next == '}' {
chars.next(); break;
}
placeholder.push(chars.next().unwrap());
}
match placeholder.as_str() {
"repo" => result.push_str(repo),
"commit" => result.push_str(commit),
"path" => result.push_str(path),
other => {
result.push('{');
result.push_str(other);
result.push('}');
}
}
} else {
result.push(c);
}
}
Some(result)
}
_ => None,
}
}
fn write_location_meta(
f: &mut fmt::Formatter<'_>,
loc: &'static Location<'static>,
link_template: Option<&str>,
) -> fmt::Result {
writeln!(f, " at {}:{}", loc.file(), loc.line())?;
if let Some(template) = link_template {
let file = loc.file().replace('\\', "/");
let line = loc.line();
let link = template
.replace("{file}", &file)
.replace("{line}", &line.to_string());
writeln!(f, " {}", link)?;
}
Ok(())
}
impl<E: fmt::Display> At<E> {
#[inline]
pub fn full_trace(&self) -> impl fmt::Display + '_ {
AtFullTraceDisplay { at: self }
}
#[inline]
pub fn last_error_trace(&self) -> impl fmt::Display + '_ {
AtLastErrorTraceDisplay { at: self }
}
#[inline]
pub fn last_error(&self) -> impl fmt::Display + '_ {
AtLastErrorDisplay { at: self }
}
}
struct AtFullTraceDisplay<'a, E> {
at: &'a At<E>,
}
impl<E: fmt::Display> fmt::Display for AtFullTraceDisplay<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.at.error)?;
if let Some(trace) = self.at.trace.as_ref() {
for frame in trace.frames() {
if let Some(loc) = frame.location() {
write!(f, "\n at {}:{}:{}", loc.file(), loc.line(), loc.column())?;
} else {
write!(f, "\n [...]")?;
}
for ctx in frame.contexts() {
if let Some(text) = ctx.as_text() {
write!(f, "\n {}", text)?;
} else if let Some(fn_name) = ctx.as_function_name() {
write!(f, "\n in {}", fn_name)?;
} else if let Some(err) = ctx.as_error() {
write!(f, "\n caused by: {}", err)?;
let mut source = err.source();
let mut depth = 2;
while let Some(src) = source {
let indent = " ".repeat(depth);
write!(f, "\n{}caused by: {}", indent, src)?;
source = src.source();
depth += 1;
}
} else {
write!(f, "\n {}", ctx)?;
}
}
}
}
Ok(())
}
}
struct AtLastErrorTraceDisplay<'a, E> {
at: &'a At<E>,
}
impl<E: fmt::Display> fmt::Display for AtLastErrorTraceDisplay<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.at.error)?;
if let Some(trace) = self.at.trace.as_ref() {
for frame in trace.frames() {
if let Some(loc) = frame.location() {
write!(f, "\n at {}:{}:{}", loc.file(), loc.line(), loc.column())?;
} else {
write!(f, "\n [...]")?;
}
}
}
Ok(())
}
}
struct AtLastErrorDisplay<'a, E> {
at: &'a At<E>,
}
impl<E: fmt::Display> fmt::Display for AtLastErrorDisplay<'_, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.at.error)
}
}
impl<E: fmt::Display> fmt::Display for At<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)
}
}
impl<E: core::error::Error> core::error::Error for At<E> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.error.source()
}
}
impl<E> From<E> for At<E> {
#[inline]
fn from(error: E) -> Self {
At::wrap(error)
}
}
impl<E: PartialEq> PartialEq for At<E> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.error == other.error
}
}
impl<E: Eq> Eq for At<E> {}
impl<E: Hash> Hash for At<E> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.error.hash(state);
}
}
impl<E> AsRef<E> for At<E> {
#[inline]
fn as_ref(&self) -> &E {
&self.error
}
}