Skip to main content

c12_parser/
yaml.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted, wrap_whitespace};
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 opts = FormatOptions {
14        preserve_indentation: false,
15        ..options.unwrap_or_default()
16    };
17    let value = serde_yaml::from_str(text)?;
18    Ok(Formatted::new(text, value, &opts))
19}
20
21/// Stringifies a YAML value with preserved outer whitespace.
22pub fn stringify_yaml<T>(
23    formatted: &Formatted<T>,
24    _options: Option<FormatOptions>,
25) -> Result<String, serde_yaml::Error>
26where
27    T: Serialize,
28{
29    // We let serde_yaml handle inner indentation and only restore the
30    // outer whitespace captured during parsing.
31    let yaml_str = serde_yaml::to_string(&formatted.value)?;
32    Ok(wrap_whitespace(&yaml_str, &formatted.format))
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38    use crate::format::strip_line_comments;
39    use serde_json::Value as JsonValue;
40
41    /// Sample document used by parse/stringify tests.
42    const FIXTURE: &str = r#"
43types:
44  boolean: true
45  integer: 1
46  float: 3.14
47  string: hello
48  array:
49    - 1
50    - 2
51    - 3
52  object:
53    key: value
54  'null': null
55  date: 1979-05-27T15:32:00.000Z
56"#;
57
58    #[test]
59    fn parse_ok() {
60        let formatted = parse_yaml::<serde_yaml::Value>(FIXTURE, None).unwrap();
61        let root = formatted.value;
62
63        let types = root
64            .get("types")
65            .expect("types key should exist")
66            .as_mapping()
67            .expect("types should be a mapping");
68
69        assert_eq!(
70            types.get(&serde_yaml::Value::String("boolean".into())),
71            Some(&serde_yaml::Value::Bool(true))
72        );
73        assert_eq!(
74            types.get(&serde_yaml::Value::String("integer".into())),
75            Some(&serde_yaml::Value::Number(1.into()))
76        );
77        assert_eq!(
78            types.get(&serde_yaml::Value::String("float".into())),
79            Some(&serde_yaml::Value::Number(serde_yaml::Number::from(3.14)))
80        );
81        assert_eq!(
82            types.get(&serde_yaml::Value::String("string".into())),
83            Some(&serde_yaml::Value::String("hello".into()))
84        );
85        assert_eq!(
86            types.get(&serde_yaml::Value::String("array".into())),
87            Some(&serde_yaml::Value::Sequence(vec![
88                serde_yaml::Value::Number(1.into()),
89                serde_yaml::Value::Number(2.into()),
90                serde_yaml::Value::Number(3.into()),
91            ]))
92        );
93        // `'null'` is a string key whose value is YAML null.
94        assert_eq!(
95            types.get(&serde_yaml::Value::String("null".into())),
96            Some(&serde_yaml::Value::Null)
97        );
98        assert_eq!(
99            types.get(&serde_yaml::Value::String("date".into())),
100            Some(&serde_yaml::Value::String(
101                "1979-05-27T15:32:00.000Z".into()
102            ))
103        );
104    }
105
106    #[test]
107    fn stringify_roundtrip() {
108        let formatted = parse_yaml::<JsonValue>(FIXTURE, None).unwrap();
109        let out = stringify_yaml(&formatted, None).unwrap();
110
111        let without_comments = strip_line_comments(FIXTURE, "#");
112        let expected_val: serde_yaml::Value = serde_yaml::from_str(&without_comments).unwrap();
113
114        let out_val: serde_yaml::Value = serde_yaml::from_str(&out).unwrap();
115        assert_eq!(out_val, expected_val);
116    }
117
118    #[test]
119    fn preserves_outer_whitespace() {
120        let text = " \ntypes:\n  key: value\n\n";
121        let formatted = parse_yaml::<JsonValue>(text, None).unwrap();
122        let out = stringify_yaml(&formatted, None).unwrap();
123
124        assert!(out.starts_with(" \n"));
125        assert!(out.ends_with("\n\n"));
126    }
127}