Skip to main content

allwright_plugin_sdk/
accessibility_yaml.rs

1//! A standard block-YAML encoding of JSON's data model. Quote every string using
2//! JSON escapes (also valid YAML) so YAML 1.1 and 1.2 parsers agree on types.
3use serde_json::Value;
4use std::fmt::Write;
5
6pub fn to_string(value: &Value) -> Result<String, String> {
7    let mut output = String::new();
8    write_value(value, 0, &mut output)?;
9    Ok(output)
10}
11
12fn scalar(value: &Value) -> Result<String, String> {
13    serde_json::to_string(value)
14        // YAML treats these as line breaks even inside quoted scalars.
15        .map(|text| {
16            text.replace('\u{0085}', "\\u0085")
17                .replace('\u{2028}', "\\u2028")
18                .replace('\u{2029}', "\\u2029")
19        })
20        .map_err(|error| error.to_string())
21}
22
23fn is_collection(value: &Value) -> bool {
24    match value {
25        Value::Array(items) => !items.is_empty(),
26        Value::Object(items) => !items.is_empty(),
27        _ => false,
28    }
29}
30
31fn write_entry(value: &Value, indent: usize, output: &mut String) -> Result<(), String> {
32    if is_collection(value) {
33        output.push('\n');
34        write_value(value, indent + 2, output)
35    } else {
36        writeln!(output, " {}", scalar(value)?).unwrap();
37        Ok(())
38    }
39}
40
41fn write_value(value: &Value, indent: usize, output: &mut String) -> Result<(), String> {
42    let padding = " ".repeat(indent);
43    match value {
44        Value::Object(items) if !items.is_empty() => {
45            for (key, child) in items {
46                write!(output, "{padding}{}:", scalar(&Value::String(key.clone()))?).unwrap();
47                write_entry(child, indent, output)?;
48            }
49        }
50        Value::Array(items) if !items.is_empty() => {
51            for child in items {
52                write!(output, "{padding}-").unwrap();
53                write_entry(child, indent, output)?;
54            }
55        }
56        _ => writeln!(output, "{padding}{}", scalar(value)?).unwrap(),
57    }
58    Ok(())
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use serde_json::json;
65
66    #[test]
67    fn scalar_types_survive_standard_yaml_parsing() {
68        let value = json!({"yes": ["yes", "NO", "on", "OFF", "2026-09-05", "12:34",
69            "true", "null", "001", "a\nb\r\tc", "\u{0085}\u{2028}\u{2029}", "😀",
70            "\\\"# - [] {} :", true, false, null, 2, 1.5, [], {}]});
71        let output = to_string(&value).unwrap();
72        assert!(output.contains("\"2026-09-05\""));
73        assert!(output.contains("\"yes\""));
74        assert_eq!(serde_yaml::from_str::<Value>(&output).unwrap(), value);
75    }
76}