pick/formats/
toml_format.rs1use 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); }
132}