Skip to main content

jigs_log/
tree.rs

1use std::fmt::Write;
2
3use jigs_trace::Entry;
4
5/// Render entries as a depth-indented tree with status mark and duration.
6#[must_use]
7pub fn render_tree(entries: &[Entry]) -> String {
8    let labels: Vec<String> = entries
9        .iter()
10        .map(|e| {
11            let indent = if e.depth == 0 {
12                String::new()
13            } else {
14                format!("{}└─ ", "  ".repeat(e.depth - 1))
15            };
16            format!("{}{}", indent, e.name())
17        })
18        .collect();
19    let width = labels.iter().map(|l| l.chars().count()).max().unwrap_or(0);
20
21    let mut out = String::new();
22    for (label, e) in labels.iter().zip(entries) {
23        let pad = width - label.chars().count();
24        let mark = if e.ok { "ok" } else { "err" };
25        let detail = match &e.error {
26            Some(msg) => format!("ERROR: {msg}"),
27            None => format!("{:?}", e.duration),
28        };
29        let _ = writeln!(out, "{}{}  {}  {}", label, " ".repeat(pad), mark, detail);
30    }
31    out
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use jigs_core::JigMeta;
38    use std::time::Duration;
39
40    fn meta(name: &'static str) -> &'static JigMeta {
41        Box::leak(Box::new(JigMeta {
42            name,
43            file: "",
44            line: 0,
45            kind: "Response",
46            input: "Request",
47            input_type: "",
48            output_type: "",
49            is_async: false,
50            module: "",
51            chain: &[],
52        }))
53    }
54
55    fn entry(name: &'static str, depth: usize, ok: bool, err: Option<&str>) -> Entry {
56        Entry::new(
57            meta(name),
58            depth,
59            Duration::from_micros(100),
60            ok,
61            err.map(|s| s.to_string()),
62        )
63    }
64
65    #[test]
66    fn empty_input_renders_empty_string() {
67        assert_eq!(render_tree(&[]), "");
68    }
69
70    #[test]
71    fn nested_entries_indent_by_depth() {
72        let entries = [
73            entry("handle", 0, true, None),
74            entry("inner", 1, true, None),
75        ];
76        let out = render_tree(&entries);
77        assert!(out.contains("handle"));
78        let inner_line = out.lines().nth(1).unwrap();
79        assert!(inner_line.starts_with("└─ inner"));
80    }
81
82    #[test]
83    fn errors_are_rendered_with_message() {
84        let entries = [entry("step", 0, false, Some("boom"))];
85        let out = render_tree(&entries);
86        assert!(out.contains("err"));
87        assert!(out.contains("ERROR: boom"));
88    }
89}