c12_parser/
toml_format.rs1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted};
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 mut opts = options.unwrap_or_default();
14 opts.preserve_indentation = false;
16 let value = toml::from_str(text)?;
17 Ok(Formatted::new(text, value, &opts))
18}
19
20pub 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}