Skip to main content

redbcli/
lib.rs

1use std::collections::HashMap;
2use std::io::Write;
3pub mod dynread;
4pub mod flags;
5pub mod pretty_print;
6pub mod redbcontrol;
7
8macro_rules! write_io {
9    ($func_name:ident, $prefix:literal) => {
10        pub fn $func_name(data: String) -> Result<(), String> {
11            let out_data = format!("{}-> {} \n", $prefix, data);
12            write!(std::io::stdout(), "{}", out_data).map_err(|e| e.to_string())?;
13            std::io::stdout().flush().map_err(|e| e.to_string())?;
14            Ok(())
15        }
16    };
17}
18
19write_io!(write_io_error, "error:");
20write_io!(write_io_success, "success:");
21write_io!(write_io_info, "info:");
22
23#[derive(Debug, Default)]
24pub struct TableMeta {
25    pub name: String,
26    pub key_type: String,
27    pub value_type: String,
28}
29
30#[derive(Debug, Default)]
31pub struct TableInfo {
32    pub tables: Vec<TableMeta>,
33}
34
35#[derive(Debug, Default)]
36pub struct KvInfo {
37    pub kvdatas: Vec<(String, String)>,
38    pub key_type: String,
39    pub value_type: String,
40}
41
42pub type StrKvData = HashMap<String, String>;
43
44/// Serializes table rows (formatted key/value cells) into a deterministic
45/// pretty-printed JSON object, ready for editing in vim.
46pub fn rows_to_json(rows: &[(String, String)]) -> Result<String, String> {
47    let mut map = serde_json::Map::new();
48    for (k, v) in rows {
49        map.insert(k.clone(), serde_json::Value::String(v.clone()));
50    }
51    serde_json::to_string_pretty(&serde_json::Value::Object(map)).map_err(|e| e.to_string())
52}
53
54/// Parses edited JSON back into rows. Keys are the formatted cell strings;
55/// values must be JSON strings.
56pub fn json_to_rows(json: &str) -> Result<Vec<(String, String)>, String> {
57    let value: serde_json::Value =
58        serde_json::from_str(json).map_err(|e| format!("invalid json: {e}"))?;
59    let object = value
60        .as_object()
61        .ok_or_else(|| "edited data must be a json object".to_string())?;
62    let mut rows = Vec::with_capacity(object.len());
63    for (k, v) in object {
64        let text = v
65            .as_str()
66            .ok_or_else(|| format!("value for key '{k}' must be a json string"))?;
67        rows.push((k.clone(), text.to_string()));
68    }
69    Ok(rows)
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn json_roundtrip_is_deterministic() {
78        let rows = vec![
79            ("zebra".to_string(), "2".to_string()),
80            ("apple".to_string(), "1".to_string()),
81        ];
82        let json = rows_to_json(&rows).unwrap();
83        assert_eq!(json, "{\n  \"apple\": \"1\",\n  \"zebra\": \"2\"\n}");
84        let parsed = json_to_rows(&json).unwrap();
85        assert_eq!(
86            parsed,
87            vec![
88                ("apple".to_string(), "1".to_string()),
89                ("zebra".to_string(), "2".to_string())
90            ]
91        );
92    }
93
94    #[test]
95    fn json_rejects_non_object() {
96        assert!(json_to_rows("[1, 2]").is_err());
97        assert!(json_to_rows("\"str\"").is_err());
98    }
99
100    #[test]
101    fn json_rejects_non_string_values() {
102        let err = json_to_rows("{\"a\": 42}").unwrap_err();
103        assert!(err.contains("must be a json string"), "got: {err}");
104    }
105
106    #[test]
107    fn json_preserves_tuple_and_hex_cells() {
108        let rows = vec![
109            ("(1, 2)".to_string(), "0x01abff".to_string()),
110            ("(3, 4)".to_string(), "100".to_string()),
111        ];
112        let parsed = json_to_rows(&rows_to_json(&rows).unwrap()).unwrap();
113        assert_eq!(parsed, rows);
114    }
115}