Skip to main content

c12_parser/
toml_format.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted};
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 mut opts = options.unwrap_or_default();
14    // Match JS version: comments/indentation are not preserved, but whitespace is.
15    opts.preserve_indentation = false;
16    let value = toml::from_str(text)?;
17    Ok(Formatted::new(text, value, &opts))
18}
19
20/// Stringifies a TOML value with preserved outer whitespace.
21pub fn stringify_toml<T>(
22    formatted: &Formatted<T>,
23    _options: Option<FormatOptions>,
24) -> Result<String, toml::ser::Error>
25where
26    T: Serialize,
27{
28    let toml_str = toml::to_string(&formatted.value)?;
29    Ok(format!(
30        "{}{}{}",
31        formatted.format.whitespace_start, toml_str, formatted.format.whitespace_end
32    ))
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38
39    const TOML_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    fn strip_line_comments(s: &str, prefix: &str) -> String {
54        s.lines()
55            .map(|line| {
56                if let Some(pos) = line.find(prefix) {
57                    &line[..pos]
58                } else {
59                    line
60                }
61            })
62            .collect::<Vec<_>>()
63            .join("\n")
64    }
65
66    #[test]
67    fn toml_parse_ok() {
68        #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
69        struct Types {
70            boolean: bool,
71            integer: i64,
72            float: f64,
73            string: String,
74            array: Vec<i64>,
75            null: String,
76            date: String,
77            object: Object,
78        }
79
80        #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
81        struct Object {
82            key: String,
83        }
84
85        #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
86        struct Root {
87            types: Types,
88        }
89
90        let formatted = parse_toml::<Root>(TOML_FIXTURE, None).unwrap();
91        assert_eq!(formatted.value.types.boolean, true);
92        assert_eq!(formatted.value.types.integer, 1);
93        assert!((formatted.value.types.float - 3.14).abs() < f64::EPSILON);
94        assert_eq!(formatted.value.types.string, "hello");
95        assert_eq!(formatted.value.types.array, vec![1, 2, 3]);
96        assert_eq!(formatted.value.types.null, "null");
97        assert_eq!(formatted.value.types.date, "1979-05-27T15:32:00.000Z");
98        assert_eq!(formatted.value.types.object.key, "value");
99    }
100
101    #[test]
102    fn toml_stringify_exact_without_comments_trimmed() {
103        #[derive(serde::Deserialize, serde::Serialize)]
104        struct Root {
105            types: std::collections::HashMap<String, toml::Value>,
106        }
107        let formatted = parse_toml::<Root>(TOML_FIXTURE, None).unwrap();
108        let out = stringify_toml(&formatted, None).unwrap();
109
110        let without_comments = strip_line_comments(TOML_FIXTURE, "#");
111        let expected = without_comments.trim();
112
113        let expected_val: toml::Value = toml::from_str(expected).unwrap();
114        let out_val: toml::Value = toml::from_str(out.trim()).unwrap();
115        assert_eq!(out_val, expected_val);
116    }
117
118    #[test]
119    fn toml_preserves_outer_whitespace() {
120        let text = " \n[section]\nkey = 1\n\n";
121        #[derive(serde::Deserialize, serde::Serialize)]
122        struct Sectioned {
123            section: std::collections::HashMap<String, toml::Value>,
124        }
125
126        let formatted = parse_toml::<Sectioned>(text, None).unwrap();
127        let out = stringify_toml(&formatted, None).unwrap();
128
129        assert!(out.starts_with(" \n"));
130        assert!(out.ends_with("\n\n"));
131    }
132}