Skip to main content

matrixcode_core/workflow/
template.rs

1//! Template Rendering Engine
2//!
3//! 简单的模板渲染引擎,支持 {{var}} 格式的变量替换。
4
5use anyhow::{Context, Result};
6use regex::Regex;
7use std::collections::HashMap;
8
9/// 模板渲染器
10#[derive(Debug)]
11pub struct TemplateRenderer {
12    /// 变量模式:{{var}}
13    var_pattern: Regex,
14    /// 嵌套访问模式:{{var.field}}
15    nested_pattern: Regex,
16    /// 默认值模式:{{var|default}}
17    default_pattern: Regex,
18}
19
20impl Default for TemplateRenderer {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl TemplateRenderer {
27    /// 创建新的模板渲染器
28    pub fn new() -> Self {
29        Self {
30            var_pattern: Regex::new(r"\{\{(\w+)\}\}").unwrap(),
31            nested_pattern: Regex::new(r"\{\{(\w+(?:\.\w+)+)\}\}").unwrap(),
32            default_pattern: Regex::new(r"\{\{(\w+)\|([^}]+)\}\}").unwrap(),
33        }
34    }
35
36    /// 渲染模板字符串
37    ///
38    /// 支持以下语法:
39    /// - `{{var}}` - 简单变量替换
40    /// - `{{var.field}}` - 嵌套访问
41    /// - `{{var|default}}` - 带默认值
42    pub fn render(
43        &self,
44        template: &str,
45        variables: &HashMap<String, serde_json::Value>,
46    ) -> Result<String> {
47        let mut result = template.to_string();
48
49        // 先处理嵌套访问(更具体的模式)
50        result = self.render_nested(&result, variables)?;
51
52        // 处理带默认值的情况
53        result = self.render_with_default(&result, variables)?;
54
55        // 最后处理简单变量
56        result = self.render_simple(&result, variables)?;
57
58        Ok(result)
59    }
60
61    /// 渲染简单变量 {{var}}
62    fn render_simple(
63        &self,
64        template: &str,
65        variables: &HashMap<String, serde_json::Value>,
66    ) -> Result<String> {
67        let mut result = template.to_string();
68
69        for cap in self.var_pattern.captures_iter(template) {
70            let full_match = cap.get(0).unwrap().as_str();
71            let var_name = cap.get(1).unwrap().as_str();
72
73            if let Some(value) = variables.get(var_name) {
74                let replacement = self.value_to_string(value)?;
75                result = result.replace(full_match, &replacement);
76            }
77        }
78
79        Ok(result)
80    }
81
82    /// 渲染嵌套访问 {{var.field}}
83    fn render_nested(
84        &self,
85        template: &str,
86        variables: &HashMap<String, serde_json::Value>,
87    ) -> Result<String> {
88        let mut result = template.to_string();
89
90        for cap in self.nested_pattern.captures_iter(template) {
91            let full_match = cap.get(0).unwrap().as_str();
92            let path = cap.get(1).unwrap().as_str();
93
94            if let Some(value) = self.resolve_path(path, variables)? {
95                let replacement = self.value_to_string(&value)?;
96                result = result.replace(full_match, &replacement);
97            }
98        }
99
100        Ok(result)
101    }
102
103    /// 渲染带默认值 {{var|default}}
104    fn render_with_default(
105        &self,
106        template: &str,
107        variables: &HashMap<String, serde_json::Value>,
108    ) -> Result<String> {
109        let mut result = template.to_string();
110
111        for cap in self.default_pattern.captures_iter(template) {
112            let full_match = cap.get(0).unwrap().as_str();
113            let var_name = cap.get(1).unwrap().as_str();
114            let default_value = cap.get(2).unwrap().as_str();
115
116            let replacement = match variables.get(var_name) {
117                Some(value) => self.value_to_string(value)?,
118                None => default_value.to_string(),
119            };
120            result = result.replace(full_match, &replacement);
121        }
122
123        Ok(result)
124    }
125
126    /// 解析路径(如 var.field.subfield)
127    fn resolve_path(
128        &self,
129        path: &str,
130        variables: &HashMap<String, serde_json::Value>,
131    ) -> Result<Option<serde_json::Value>> {
132        let parts: Vec<&str> = path.split('.').collect();
133        if parts.is_empty() {
134            return Ok(None);
135        }
136
137        let first = parts[0];
138        let mut current = variables.get(first).cloned();
139
140        for part in parts.iter().skip(1) {
141            match current {
142                Some(serde_json::Value::Object(map)) => {
143                    current = map.get(*part).cloned();
144                }
145                _ => return Ok(None),
146            }
147        }
148
149        Ok(current)
150    }
151
152    /// 将 JSON 值转换为字符串
153    fn value_to_string(&self, value: &serde_json::Value) -> Result<String> {
154        match value {
155            serde_json::Value::Null => Ok(String::new()),
156            serde_json::Value::Bool(b) => Ok(b.to_string()),
157            serde_json::Value::Number(n) => Ok(n.to_string()),
158            serde_json::Value::String(s) => Ok(s.clone()),
159            serde_json::Value::Array(arr) => {
160                serde_json::to_string(arr).with_context(|| "Failed to stringify array")
161            }
162            serde_json::Value::Object(obj) => {
163                serde_json::to_string(obj).with_context(|| "Failed to stringify object")
164            }
165        }
166    }
167
168    /// 从模板中提取所有变量名
169    pub fn extract_variables(&self, template: &str) -> Vec<String> {
170        let mut vars = Vec::new();
171
172        for cap in self.var_pattern.captures_iter(template) {
173            vars.push(cap.get(1).unwrap().as_str().to_string());
174        }
175
176        for cap in self.nested_pattern.captures_iter(template) {
177            let path = cap.get(1).unwrap().as_str();
178            if let Some(first) = path.split('.').next() {
179                vars.push(first.to_string());
180            }
181        }
182
183        for cap in self.default_pattern.captures_iter(template) {
184            vars.push(cap.get(1).unwrap().as_str().to_string());
185        }
186
187        vars.sort();
188        vars.dedup();
189        vars
190    }
191
192    /// 检查模板是否包含未解析的变量
193    pub fn has_unresolved(&self, rendered: &str) -> bool {
194        self.var_pattern.is_match(rendered)
195            || self.nested_pattern.is_match(rendered)
196            || self.default_pattern.is_match(rendered)
197    }
198
199    /// 渲染参数 HashMap
200    ///
201    /// 对每个参数值,如果是字符串则渲染模板,否则保持原值
202    pub fn render_params(
203        &self,
204        params: &HashMap<String, serde_json::Value>,
205        variables: &HashMap<String, serde_json::Value>,
206    ) -> Result<serde_json::Value> {
207        let mut rendered = HashMap::new();
208        for (key, value) in params {
209            let rendered_value = if let serde_json::Value::String(s) = value {
210                let rendered_str = self.render(s, variables)?;
211                serde_json::Value::String(rendered_str)
212            } else {
213                value.clone()
214            };
215            rendered.insert(key.clone(), rendered_value);
216        }
217        Ok(serde_json::Value::Object(rendered.into_iter().collect()))
218    }
219}
220
221/// 便捷渲染函数
222pub fn render(template: &str, variables: &HashMap<String, serde_json::Value>) -> Result<String> {
223    let renderer = TemplateRenderer::new();
224    renderer.render(template, variables)
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use serde_json::json;
231
232    #[test]
233    fn test_simple_variable() {
234        let renderer = TemplateRenderer::new();
235        let mut vars = HashMap::new();
236        vars.insert("name".to_string(), json!("Alice"));
237
238        let result = renderer.render("Hello, {{name}}!", &vars).unwrap();
239        assert_eq!(result, "Hello, Alice!");
240    }
241
242    #[test]
243    fn test_multiple_variables() {
244        let renderer = TemplateRenderer::new();
245        let mut vars = HashMap::new();
246        vars.insert("first".to_string(), json!("John"));
247        vars.insert("last".to_string(), json!("Doe"));
248
249        let result = renderer.render("{{first}} {{last}}", &vars).unwrap();
250        assert_eq!(result, "John Doe");
251    }
252
253    #[test]
254    fn test_nested_access() {
255        let renderer = TemplateRenderer::new();
256        let mut vars = HashMap::new();
257        vars.insert(
258            "user".to_string(),
259            json!({
260                "name": "Bob",
261                "age": 30
262            }),
263        );
264
265        let result = renderer
266            .render("Name: {{user.name}}, Age: {{user.age}}", &vars)
267            .unwrap();
268        assert_eq!(result, "Name: Bob, Age: 30");
269    }
270
271    #[test]
272    fn test_default_value() {
273        let renderer = TemplateRenderer::new();
274        let vars = HashMap::new();
275
276        let result = renderer.render("Hello, {{name|Guest}}!", &vars).unwrap();
277        assert_eq!(result, "Hello, Guest!");
278    }
279
280    #[test]
281    fn test_extract_variables() {
282        let renderer = TemplateRenderer::new();
283        let template = "{{name}} is {{age}} years old, user: {{user.name}}";
284
285        let vars = renderer.extract_variables(template);
286        assert!(vars.contains(&"name".to_string()));
287        assert!(vars.contains(&"age".to_string()));
288        assert!(vars.contains(&"user".to_string()));
289    }
290
291    #[test]
292    fn test_number_value() {
293        let renderer = TemplateRenderer::new();
294        let mut vars = HashMap::new();
295        vars.insert("count".to_string(), json!(42));
296
297        let result = renderer.render("Count: {{count}}", &vars).unwrap();
298        assert_eq!(result, "Count: 42");
299    }
300}