1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted, wrap_whitespace};
4
5pub 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
21pub 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 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}