Skip to main content

template_model/
message.rs

1use serde_json::Value;
2
3/// Wraps `value` in single quotes: `foo` becomes `'foo'`.
4///
5/// This is the single convention for surfacing an identifier, property name, or
6/// literal inside a message. It never emits double quotes, so the rendered
7/// message survives JSON serialization without `\"` escaping.
8pub fn quote(value: impl AsRef<str>) -> String {
9    format!("'{}'", value.as_ref())
10}
11
12/// Renders a JSON value for display inside a message.
13///
14/// Strings are single-quoted; numbers, booleans, and null render as their bare
15/// literal. Arrays and objects recurse so their nested strings are single-quoted
16/// too, unlike `serde_json::to_string`, which would emit double quotes and force
17/// `\"` escaping in the final JSON report.
18pub fn render_value(value: &Value) -> String {
19    match value {
20        Value::String(s) => quote(s),
21        Value::Null => "null".to_string(),
22        Value::Bool(b) => b.to_string(),
23        Value::Number(n) => n.to_string(),
24        Value::Array(items) => render_value_list(items),
25        Value::Object(map) => {
26            let entries =
27                map.iter().map(|(k, v)| format!("{}: {}", quote(k), render_value(v))).collect::<Vec<_>>().join(", ");
28            format!("{{{}}}", entries)
29        }
30    }
31}
32
33/// Renders a slice of JSON values as a bracketed list, e.g. `['a', 'b']` or
34/// `[1, 2]`, with each element formatted by [`render_value`].
35pub fn render_value_list(values: &[Value]) -> String {
36    format!("[{}]", values.iter().map(render_value).collect::<Vec<_>>().join(", "))
37}
38
39/// Renders a sequence of string-like items as a single-quoted, bracketed list,
40/// e.g. `['Enabled', 'Suspended']`. Use this for enum candidates, valid
41/// attribute names, and other lists of plain strings.
42pub fn render_str_list<I, S>(items: I) -> String
43where
44    I: IntoIterator<Item = S>,
45    S: AsRef<str>,
46{
47    let rendered = items.into_iter().map(|item| quote(item.as_ref())).collect::<Vec<_>>().join(", ");
48    format!("[{}]", rendered)
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use serde_json::json;
55
56    #[test]
57    fn quote_wraps_in_single_quotes() {
58        assert_eq!(quote("foo"), "'foo'");
59        assert_eq!(quote(String::from("bar")), "'bar'");
60    }
61
62    #[test]
63    fn render_value_single_quotes_strings() {
64        assert_eq!(render_value(&json!("hello")), "'hello'");
65    }
66
67    #[test]
68    fn render_value_renders_scalars_bare() {
69        assert_eq!(render_value(&json!(42)), "42");
70        assert_eq!(render_value(&json!(3.5)), "3.5");
71        assert_eq!(render_value(&json!(true)), "true");
72        assert_eq!(render_value(&json!(null)), "null");
73    }
74
75    #[test]
76    fn render_value_array_uses_single_quotes_not_double() {
77        let rendered = render_value(&json!(["a", "b"]));
78        assert_eq!(rendered, "['a', 'b']");
79        assert!(!rendered.contains('"'), "rendered list must not contain double quotes");
80    }
81
82    #[test]
83    fn render_value_object_uses_single_quotes() {
84        let rendered = render_value(&json!({"BucketName": "shared"}));
85        assert_eq!(rendered, "{'BucketName': 'shared'}");
86        assert!(!rendered.contains('"'));
87    }
88
89    #[test]
90    fn render_value_nested_mixed_types() {
91        assert_eq!(render_value(&json!([1, "two", true])), "[1, 'two', true]");
92    }
93
94    #[test]
95    fn render_value_list_matches_render_value_of_array() {
96        let values = vec![json!("x"), json!("y")];
97        assert_eq!(render_value_list(&values), "['x', 'y']");
98    }
99
100    #[test]
101    fn render_str_list_single_quotes_each_item() {
102        assert_eq!(render_str_list(["Enabled", "Suspended"]), "['Enabled', 'Suspended']");
103        assert_eq!(render_str_list(Vec::<String>::new()), "[]");
104    }
105}