1use serde_json::Value;
7
8pub fn escape_markdown_cell(value: &str) -> String {
9 value.replace('|', "\\|").replace('\n', " ")
10}
11
12pub fn value_to_display_string(value: &Value) -> String {
13 match value {
14 Value::Null => String::new(),
15 Value::String(s) => s.clone(),
16 Value::Number(n) => n.to_string(),
17 Value::Bool(b) => b.to_string(),
18 other => other.to_string(),
19 }
20}
21
22pub fn rows_to_markdown(columns: &[String], rows: &[Vec<Value>]) -> String {
24 if columns.is_empty() {
25 return String::new();
26 }
27 let mut out = String::new();
28 out.push('|');
29 for col in columns {
30 out.push(' ');
31 out.push_str(&escape_markdown_cell(col));
32 out.push_str(" |");
33 }
34 out.push('\n');
35 out.push('|');
36 for _ in columns {
37 out.push_str(" --- |");
38 }
39 out.push('\n');
40 for row in rows {
41 out.push('|');
42 for (idx, _) in columns.iter().enumerate() {
43 let cell = row.get(idx).map(value_to_display_string).unwrap_or_default();
44 out.push(' ');
45 out.push_str(&escape_markdown_cell(&cell));
46 out.push_str(" |");
47 }
48 out.push('\n');
49 }
50 out
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use serde_json::json;
57
58 #[test]
59 fn markdown_table_renders_headers_and_rows() {
60 let md = rows_to_markdown(
61 &["id".into(), "name".into()],
62 &[vec![json!(1), json!("alice")], vec![json!(2), json!("bob")]],
63 );
64 assert!(md.contains("| id | name |"));
65 assert!(md.contains("| 1 | alice |"));
66 assert!(md.contains("| 2 | bob |"));
67 }
68
69 #[test]
70 fn markdown_escapes_pipes() {
71 let md = rows_to_markdown(&["x".into()], &[vec![json!("a|b")]]);
72 assert!(md.contains(r"a\|b"));
73 }
74}