#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ErrorStackEntry {
pub error: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub attachments: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub backtrace: Option<String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ErrorStack {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub stack: Vec<ErrorStackEntry>,
}
impl ErrorStack {
pub fn from_error_stack<T: error_stack::Context>(report: error_stack::Report<T>) -> Self {
let mut stack_entries: Vec<ErrorStackEntry> = Vec::new();
let mut current_attachments: Vec<String> = Vec::new();
let global_backtrace = {
let mut backtrace_iter = report.frames().filter_map(|frame| {
frame
.sources()
.iter()
.find_map(|source| source.downcast_ref::<std::backtrace::Backtrace>())
});
backtrace_iter.next().map(|bt| bt.to_string())
};
for frame in report.frames() {
match frame.kind() {
error_stack::FrameKind::Context(context) => {
if !current_attachments.is_empty()
&& !stack_entries.is_empty()
&& let Some(last_entry) = stack_entries.last_mut()
{
last_entry.attachments.append(&mut current_attachments);
}
let backtrace = if stack_entries.is_empty() {
global_backtrace.clone()
} else {
None
};
stack_entries.push(ErrorStackEntry {
error: context.to_string(),
attachments: vec![],
backtrace,
});
}
error_stack::FrameKind::Attachment(attachment_kind) => {
if let error_stack::AttachmentKind::Printable(printable) = attachment_kind {
current_attachments.push(printable.to_string());
}
}
}
}
if !current_attachments.is_empty()
&& !stack_entries.is_empty()
&& let Some(last_entry) = stack_entries.last_mut()
{
last_entry.attachments.extend(current_attachments);
}
Self {
stack: stack_entries,
}
}
}
impl<T: error_stack::Context> From<error_stack::Report<T>> for ErrorStack {
fn from(report: error_stack::Report<T>) -> Self {
Self::from_error_stack(report)
}
}