Skip to main content

c12_parser/
toml.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted, wrap_whitespace};
4
5/// Parses a TOML string into a value, capturing outer whitespace only.
6pub fn parse_toml<T>(
7    text: &str,
8    options: Option<FormatOptions>,
9) -> Result<Formatted<T>, toml::de::Error>
10where
11    T: DeserializeOwned,
12{
13    let opts = FormatOptions {
14        preserve_indentation: false,
15        ..options.unwrap_or_default()
16    };
17    let value = toml::from_str(text)?;
18    Ok(Formatted::new(text, value, &opts))
19}
20
21/// Stringifies a TOML value with preserved outer whitespace.
22pub fn stringify_toml<T>(
23    formatted: &Formatted<T>,
24    _options: Option<FormatOptions>,
25) -> Result<String, toml::ser::Error>
26where
27    T: Serialize,
28{
29    let toml_str = toml::to_string(&formatted.value)?;
30    Ok(wrap_whitespace(&toml_str, &formatted.format))
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::format::strip_line_comments;
37
38    /// Sample document used by parse/stringify tests.
39    const FIXTURE: &str = r#"
40[types]
41boolean = true
42integer = 1
43float = 3.14
44string = "hello"
45array = [ 1, 2, 3 ]
46null = "null"
47date = "1979-05-27T15:32:00.000Z"
48
49[types.object]
50key = "value"
51"#;
52
53    #[test]
54    fn parse_ok() {
55        #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
56        struct Types {
57            boolean: bool,
58            integer: i64,
59            float: f64,
60            string: String,
61            array: Vec<i64>,
62            null: String,
63            date: String,
64            object: Object,
65        }
66
67        #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
68        struct Object {
69            key: String,
70        }
71
72        #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
73        struct Root {
74            types: Types,
75        }
76
77        let formatted = parse_toml::<Root>(FIXTURE, None).unwrap();
78        assert_eq!(formatted.value.types.boolean, true);
79        assert_eq!(formatted.value.types.integer, 1);
80        assert!((formatted.value.types.float - 3.14).abs() < f64::EPSILON);
81        assert_eq!(formatted.value.types.string, "hello");
82        assert_eq!(formatted.value.types.array, vec![1, 2, 3]);
83        assert_eq!(formatted.value.types.null, "null");
84        assert_eq!(formatted.value.types.date, "1979-05-27T15:32:00.000Z");
85        assert_eq!(formatted.value.types.object.key, "value");
86    }
87
88    #[test]
89    fn stringify_roundtrip() {
90        #[derive(serde::Deserialize, serde::Serialize)]
91        struct Root {
92            types: std::collections::HashMap<String, toml::Value>,
93        }
94        let formatted = parse_toml::<Root>(FIXTURE, None).unwrap();
95        let out = stringify_toml(&formatted, None).unwrap();
96
97        let without_comments = strip_line_comments(FIXTURE, "#");
98        let expected = without_comments.trim();
99
100        let expected_val: toml::Value = toml::from_str(expected).unwrap();
101        let out_val: toml::Value = toml::from_str(out.trim()).unwrap();
102        assert_eq!(out_val, expected_val);
103    }
104
105    #[test]
106    fn preserves_outer_whitespace() {
107        let text = " \n[section]\nkey = 1\n\n";
108        #[derive(serde::Deserialize, serde::Serialize)]
109        struct Sectioned {
110            section: std::collections::HashMap<String, toml::Value>,
111        }
112
113        let formatted = parse_toml::<Sectioned>(text, None).unwrap();
114        let out = stringify_toml(&formatted, None).unwrap();
115
116        assert!(out.starts_with(" \n"));
117        assert!(out.ends_with("\n\n"));
118    }
119}