Skip to main content

c12_parser/
yaml_format.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted};
4
5/// Parses a YAML string into a value, capturing outer whitespace only.
6pub fn parse_yaml<T>(
7    text: &str,
8    options: Option<FormatOptions>,
9) -> Result<Formatted<T>, serde_yaml::Error>
10where
11    T: DeserializeOwned,
12{
13    let mut opts = options.unwrap_or_default();
14    // Comments are not preserved; indentation is not preserved in the JS version.
15    opts.preserve_indentation = false;
16    let value = serde_yaml::from_str(text)?;
17    Ok(Formatted::new(text, value, &opts))
18}
19
20/// Stringifies a YAML value with preserved outer whitespace.
21pub fn stringify_yaml<T>(
22    formatted: &Formatted<T>,
23    options: Option<FormatOptions>,
24) -> Result<String, serde_yaml::Error>
25where
26    T: Serialize,
27{
28    let _opts = options.unwrap_or_default();
29
30    // We let serde_yaml handle inner indentation and only restore the
31    // outer whitespace captured during parsing.
32    let yaml_str = serde_yaml::to_string(&formatted.value)?;
33
34    Ok(format!(
35        "{}{}{}",
36        formatted.format.whitespace_start, yaml_str, formatted.format.whitespace_end
37    ))
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use serde_json::Value as JsonValue;
44
45    const YAML_FIXTURE: &str = r#"
46types:
47  boolean: true
48  integer: 1
49  float: 3.14
50  string: hello
51  array:
52    - 1
53    - 2
54    - 3
55  object:
56    key: value
57  'null': null
58  date: 1979-05-27T15:32:00.000Z
59"#;
60
61    fn strip_line_comments(s: &str, prefix: &str) -> String {
62        s.lines()
63            .map(|line| {
64                if let Some(pos) = line.find(prefix) {
65                    &line[..pos]
66                } else {
67                    line
68                }
69            })
70            .collect::<Vec<_>>()
71            .join("\n")
72    }
73
74    #[test]
75    fn yaml_parse_ok() {
76        let formatted = parse_yaml::<serde_yaml::Value>(YAML_FIXTURE, None).unwrap();
77        let root = formatted.value;
78
79        let types = root
80            .get("types")
81            .expect("types key should exist")
82            .as_mapping()
83            .expect("types should be a mapping");
84
85        assert_eq!(
86            types.get(&serde_yaml::Value::String("boolean".into())),
87            Some(&serde_yaml::Value::Bool(true))
88        );
89        assert_eq!(
90            types.get(&serde_yaml::Value::String("integer".into())),
91            Some(&serde_yaml::Value::Number(1.into()))
92        );
93        assert_eq!(
94            types.get(&serde_yaml::Value::String("float".into())),
95            Some(&serde_yaml::Value::Number(serde_yaml::Number::from(3.14)))
96        );
97        assert_eq!(
98            types.get(&serde_yaml::Value::String("string".into())),
99            Some(&serde_yaml::Value::String("hello".into()))
100        );
101        assert_eq!(
102            types.get(&serde_yaml::Value::String("array".into())),
103            Some(&serde_yaml::Value::Sequence(vec![
104                serde_yaml::Value::Number(1.into()),
105                serde_yaml::Value::Number(2.into()),
106                serde_yaml::Value::Number(3.into()),
107            ]))
108        );
109        // `'null'` is a string key whose value is YAML null.
110        assert_eq!(
111            types.get(&serde_yaml::Value::String("null".into())),
112            Some(&serde_yaml::Value::Null)
113        );
114        assert_eq!(
115            types.get(&serde_yaml::Value::String("date".into())),
116            Some(&serde_yaml::Value::String(
117                "1979-05-27T15:32:00.000Z".into()
118            ))
119        );
120    }
121
122    #[test]
123    fn yaml_stringify_exact_without_comments_normalized_indent() {
124        let formatted = parse_yaml::<JsonValue>(YAML_FIXTURE, None).unwrap();
125        let out = stringify_yaml(&formatted, None).unwrap();
126
127        let without_comments = strip_line_comments(YAML_FIXTURE, "#");
128        let expected_val: serde_yaml::Value = serde_yaml::from_str(&without_comments).unwrap();
129
130        let out_val: serde_yaml::Value = serde_yaml::from_str(&out).unwrap();
131        assert_eq!(out_val, expected_val);
132    }
133
134    #[test]
135    fn yaml_preserves_outer_whitespace() {
136        let text = " \ntypes:\n  key: value\n\n";
137        let formatted = parse_yaml::<JsonValue>(text, None).unwrap();
138        let out = stringify_yaml(&formatted, None).unwrap();
139
140        assert!(out.starts_with(" \n"));
141        assert!(out.ends_with("\n\n"));
142    }
143}