Skip to main content

jigs_log/
tree.rs

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