use std::error::Error;
use crate::console::{Console, ConsoleOptions};
use crate::panel::Panel;
use crate::protocol::Renderable;
use crate::r#box::HEAVY;
use crate::segment::Segment;
use crate::style::Style;
use crate::text::Text;
pub struct Traceback {
message: String,
causes: Vec<String>,
}
impl Traceback {
pub fn new(error: &dyn Error) -> Self {
let mut causes = Vec::new();
let mut source = error.source();
while let Some(err) = source {
causes.push(err.to_string());
source = err.source();
}
Traceback {
message: error.to_string(),
causes,
}
}
pub fn from_message(message: impl Into<String>) -> Self {
Traceback {
message: message.into(),
causes: Vec::new(),
}
}
fn content(&self) -> Text {
let error_style = Style::parse("bold red").expect("valid style");
let label_style = Style::parse("dim").expect("valid style");
let cause_style = Style::parse("red").expect("valid style");
let mut text = Text::new("");
text.append(&self.message, Some(error_style.into()));
if !self.causes.is_empty() {
text.append("\n\nCaused by:", Some(label_style.into()));
for (index, cause) in self.causes.iter().enumerate() {
text.append(&format!("\n {}: ", index + 1), None);
text.append(cause, Some(cause_style.clone().into()));
}
}
text
}
fn panel(&self) -> Panel {
Panel::new(Box::new(self.content()))
.box_set(HEAVY)
.border_style(Style::parse("red").expect("valid style"))
.title("Traceback (most recent call last)")
}
}
impl Renderable for Traceback {
fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
self.panel().rich_render(console, options)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::ColorSystem;
use std::fmt;
#[derive(Debug)]
struct Inner;
impl fmt::Display for Inner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "disk full")
}
}
impl Error for Inner {}
#[derive(Debug)]
struct Outer;
static INNER: Inner = Inner;
impl fmt::Display for Outer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "failed to save file")
}
}
impl Error for Outer {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&INNER)
}
}
fn render(tb: &Traceback) -> String {
Console::builder()
.force_terminal(true)
.color_system(Some(ColorSystem::Truecolor))
.width(40)
.no_color(false)
.build()
.render_to_string(tb)
}
#[test]
fn renders_error_chain() {
let out = render(&Traceback::new(&Outer));
assert!(out.contains("Traceback (most recent call last)"));
assert!(out.contains("failed to save file"));
assert!(out.contains("Caused by:"));
assert!(out.contains("disk full"));
assert!(out.contains("\x1b[31m"), "expected red border/message");
}
#[test]
fn from_message_has_no_chain() {
let out = render(&Traceback::from_message("something broke"));
assert!(out.contains("something broke"));
assert!(!out.contains("Caused by:"));
}
}