Skip to main content

pick/formats/
toml_format.rs

1use crate::error::PickError;
2use serde_json::Value;
3
4pub fn parse(input: &str) -> Result<Value, PickError> {
5    let toml_value: toml::Value = input
6        .parse()
7        .map_err(|e: toml::de::Error| PickError::ParseError("TOML".into(), e.to_string()))?;
8    toml_to_json(toml_value)
9}
10
11fn toml_to_json(v: toml::Value) -> Result<Value, PickError> {
12    match v {
13        toml::Value::String(s) => Ok(Value::String(s)),
14        toml::Value::Integer(i) => Ok(Value::Number(i.into())),
15        toml::Value::Float(f) => Ok(serde_json::Number::from_f64(f)
16            .map(Value::Number)
17            .unwrap_or(Value::Null)),
18        toml::Value::Boolean(b) => Ok(Value::Bool(b)),
19        toml::Value::Datetime(dt) => Ok(Value::String(dt.to_string())),
20        toml::Value::Array(arr) => {
21            let items: Result<Vec<Value>, _> = arr.into_iter().map(toml_to_json).collect();
22            Ok(Value::Array(items?))
23        }
24        toml::Value::Table(table) => {
25            let mut map = serde_json::Map::new();
26            for (k, v) in table {
27                map.insert(k, toml_to_json(v)?);
28            }
29            Ok(Value::Object(map))
30        }
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use serde_json::json;
38
39    #[test]
40    fn parse_simple_kv() {
41        let v = parse("name = \"pick\"\nversion = \"0.1.0\"").unwrap();
42        assert_eq!(v["name"], json!("pick"));
43        assert_eq!(v["version"], json!("0.1.0"));
44    }
45
46    #[test]
47    fn parse_section() {
48        let input = "[package]\nname = \"pick\"\nversion = \"0.1.0\"";
49        let v = parse(input).unwrap();
50        assert_eq!(v["package"]["name"], json!("pick"));
51    }
52
53    #[test]
54    fn parse_nested_tables() {
55        let input = "[server]\nhost = \"localhost\"\n\n[server.tls]\nenabled = true";
56        let v = parse(input).unwrap();
57        assert_eq!(v["server"]["host"], json!("localhost"));
58        assert_eq!(v["server"]["tls"]["enabled"], json!(true));
59    }
60
61    #[test]
62    fn parse_array_of_tables() {
63        let input = "[[items]]\nname = \"a\"\n\n[[items]]\nname = \"b\"";
64        let v = parse(input).unwrap();
65        assert_eq!(v["items"][0]["name"], json!("a"));
66        assert_eq!(v["items"][1]["name"], json!("b"));
67    }
68
69    #[test]
70    fn parse_inline_table() {
71        let input = "point = { x = 1, y = 2 }";
72        let v = parse(input).unwrap();
73        assert_eq!(v["point"]["x"], json!(1));
74        assert_eq!(v["point"]["y"], json!(2));
75    }
76
77    #[test]
78    fn parse_array() {
79        let input = "ports = [8080, 8443, 9090]";
80        let v = parse(input).unwrap();
81        assert_eq!(v["ports"], json!([8080, 8443, 9090]));
82    }
83
84    #[test]
85    fn parse_boolean() {
86        let input = "debug = true\nrelease = false";
87        let v = parse(input).unwrap();
88        assert_eq!(v["debug"], json!(true));
89        assert_eq!(v["release"], json!(false));
90    }
91
92    #[test]
93    fn parse_float() {
94        let input = "pi = 3.14159";
95        let v = parse(input).unwrap();
96        assert!((v["pi"].as_f64().unwrap() - 3.14159).abs() < 1e-10);
97    }
98
99    #[test]
100    fn parse_datetime() {
101        let input = "created = 2024-01-15T10:30:00Z";
102        let v = parse(input).unwrap();
103        assert!(v["created"].is_string());
104    }
105
106    #[test]
107    fn parse_multiline_string() {
108        let input = "desc = \"\"\"\nhello\nworld\"\"\"";
109        let v = parse(input).unwrap();
110        assert!(v["desc"].as_str().unwrap().contains("hello"));
111    }
112
113    #[test]
114    fn parse_integer() {
115        let input = "count = 42\nneg = -10";
116        let v = parse(input).unwrap();
117        assert_eq!(v["count"], json!(42));
118        assert_eq!(v["neg"], json!(-10));
119    }
120
121    #[test]
122    fn parse_invalid() {
123        assert!(parse("not valid toml [[[").is_err());
124    }
125
126    #[test]
127    fn parse_nan_float() {
128        let input = "val = nan";
129        let v = parse(input).unwrap();
130        assert_eq!(v["val"], Value::Null); // NaN can't be represented in JSON
131    }
132}