Skip to main content

prosaic_project/
template.rs

1//! `templates/*.toml` schema.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct TemplateFile {
7    pub key: String,
8    #[serde(default)]
9    pub description: String,
10    #[serde(default)]
11    pub slots_required: Vec<String>,
12    #[serde(default)]
13    pub slots_optional: Vec<String>,
14    pub variants: Vec<Variant>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Variant {
19    /// "low" | "medium" | "high"
20    #[serde(default = "default_salience")]
21    pub salience: String,
22    /// BCP-47 lang code; defaults to project language if unspecified.
23    #[serde(default)]
24    pub language: Option<String>,
25    /// Free-form author-defined style tag, e.g. "executive" or "technical".
26    #[serde(default)]
27    pub style: Option<String>,
28    #[serde(default)]
29    pub description: String,
30    pub body: String,
31}
32
33fn default_salience() -> String {
34    "medium".to_string()
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn parse_minimal_template() {
43        let toml_str = r#"
44            key = "code.modified"
45            variants = [
46              { body = "{name|refer} was modified" },
47            ]
48        "#;
49        let t: TemplateFile = toml::from_str(toml_str).unwrap();
50        assert_eq!(t.key, "code.modified");
51        assert_eq!(t.variants.len(), 1);
52        assert_eq!(t.variants[0].salience, "medium");
53        assert_eq!(t.variants[0].body, "{name|refer} was modified");
54        assert!(t.variants[0].language.is_none());
55        assert!(t.variants[0].style.is_none());
56    }
57
58    #[test]
59    fn parse_multi_variant_with_salience() {
60        let toml_str = r#"
61            key = "code.modified"
62            description = "Render a 'class X was modified' event."
63            slots_required = ["name"]
64            slots_optional = ["consumer_count"]
65
66            [[variants]]
67            salience = "low"
68            body = "{name|refer} was modified"
69
70            [[variants]]
71            salience = "medium"
72            body = """{name|refer} was modified, affecting {consumer_count}"""
73
74            [[variants]]
75            salience = "high"
76            language = "en"
77            style = "executive"
78            body = "{name|refer} has been substantially modified"
79        "#;
80        let t: TemplateFile = toml::from_str(toml_str).unwrap();
81        assert_eq!(t.variants.len(), 3);
82        assert_eq!(t.variants[0].salience, "low");
83        assert_eq!(t.variants[1].salience, "medium");
84        assert_eq!(t.variants[2].salience, "high");
85        assert_eq!(t.variants[2].language.as_deref(), Some("en"));
86        assert_eq!(t.variants[2].style.as_deref(), Some("executive"));
87        assert_eq!(t.slots_required, vec!["name"]);
88        assert_eq!(t.slots_optional, vec!["consumer_count"]);
89    }
90
91    #[test]
92    fn parse_multi_language_variants() {
93        let toml_str = r#"
94            key = "code.modified"
95
96            [[variants]]
97            salience = "medium"
98            language = "en"
99            body = "{name} was modified"
100
101            [[variants]]
102            salience = "medium"
103            language = "es"
104            body = "{name} fue modificado"
105        "#;
106        let t: TemplateFile = toml::from_str(toml_str).unwrap();
107        assert_eq!(t.variants.len(), 2);
108        assert_eq!(t.variants[0].language.as_deref(), Some("en"));
109        assert_eq!(t.variants[1].language.as_deref(), Some("es"));
110    }
111
112    #[test]
113    fn parse_style_variants() {
114        let toml_str = r#"
115            key = "code.modified"
116
117            [[variants]]
118            salience = "medium"
119            style = "executive"
120            body = "{name} changed"
121        "#;
122        let t: TemplateFile = toml::from_str(toml_str).unwrap();
123        assert_eq!(t.variants[0].style.as_deref(), Some("executive"));
124    }
125
126    #[test]
127    fn missing_key_errors() {
128        let toml_str = r#"
129            variants = [{ body = "x" }]
130        "#;
131        let res = toml::from_str::<TemplateFile>(toml_str);
132        assert!(res.is_err(), "expected error for missing `key` field");
133    }
134
135    #[test]
136    fn missing_variant_body_errors() {
137        let toml_str = r#"
138            key = "x"
139            [[variants]]
140            salience = "medium"
141        "#;
142        let res = toml::from_str::<TemplateFile>(toml_str);
143        assert!(
144            res.is_err(),
145            "expected error for missing variant `body` field"
146        );
147    }
148
149    #[test]
150    fn round_trip_serialize() {
151        let original: TemplateFile = toml::from_str(
152            r#"
153            key = "code.added"
154            description = "Add event."
155            variants = [
156              { salience = "medium", body = "{name|refer} was added" },
157            ]
158            "#,
159        )
160        .unwrap();
161        let serialized = toml::to_string(&original).unwrap();
162        let reparsed: TemplateFile = toml::from_str(&serialized).unwrap();
163        assert_eq!(reparsed.key, "code.added");
164        assert_eq!(reparsed.variants.len(), 1);
165        assert_eq!(reparsed.variants[0].body, "{name|refer} was added");
166    }
167}