gotpl/
lib.rs

1use serde::Serialize;
2use std::error::Error;
3use std::ffi::{CStr, CString, NulError};
4use std::fmt::{self, Display, Formatter};
5use std::marker::PhantomData;
6// use std::os::raw::c_char; // 添加这个导入
7
8#[cfg(not(docsrs))]
9mod goffi {
10    #![allow(non_snake_case)]
11    #![allow(non_camel_case_types)]
12    #![allow(non_upper_case_globals)]
13    #![allow(unused)]
14    // include rust bindings generated by bindgen
15    include!(concat!(env!("OUT_DIR"), "/api_bindings.rs"));
16}
17
18#[cfg(docsrs)]
19mod goffi {
20    use std::os::raw::c_char;
21
22    #[repr(C)]
23    pub struct RenderResult {
24        pub output: *mut c_char, // 改为 c_char
25        pub error: *mut c_char,  // 改为 c_char
26    }
27
28    extern "C" {
29        pub fn RenderTemplate(
30            template_content: *mut c_char, // 改为 c_char
31            json_data: *mut c_char,        // 改为 c_char
32            escape_html: bool,
33            use_missing_key_zero: bool,
34        ) -> RenderResult;
35        pub fn FreeResultString(s: *mut c_char); // 改为 c_char
36    }
37}
38
39#[derive(Debug)]
40pub enum RenderError {
41    InvalidCString(NulError),
42    JsonSerialization(serde_json::Error),
43    GoExecution(String),
44}
45
46impl Display for RenderError {
47    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
48        match self {
49            RenderError::InvalidCString(e) => {
50                write!(f, "Failed to convert string to C-compatible string: {}", e)
51            }
52            RenderError::JsonSerialization(e) => {
53                write!(f, "Failed to serialize data to JSON: {}", e)
54            }
55            RenderError::GoExecution(e) => write!(f, "Go template execution error: {}", e),
56        }
57    }
58}
59
60impl Error for RenderError {
61    fn source(&self) -> Option<&(dyn Error + 'static)> {
62        match self {
63            RenderError::InvalidCString(e) => Some(e),
64            RenderError::JsonSerialization(e) => Some(e),
65            RenderError::GoExecution(_) => None,
66        }
67    }
68}
69
70impl From<NulError> for RenderError {
71    fn from(err: NulError) -> Self {
72        RenderError::InvalidCString(err)
73    }
74}
75
76impl From<serde_json::Error> for RenderError {
77    fn from(err: serde_json::Error) -> Self {
78        RenderError::JsonSerialization(err)
79    }
80}
81
82struct OwnedGoResult(goffi::RenderResult);
83
84impl Drop for OwnedGoResult {
85    fn drop(&mut self) {
86        unsafe {
87            goffi::FreeResultString(self.0.output);
88            goffi::FreeResultString(self.0.error);
89        }
90    }
91}
92
93/// Go Template Renderer
94pub struct TemplateRenderer<'a, T: Serialize> {
95    template_content: &'a str,
96    data: &'a T,
97    escape_html: bool,
98    use_missing_key_zero: bool,
99    _marker: PhantomData<&'a T>,
100}
101
102impl<'a, T: Serialize> TemplateRenderer<'a, T> {
103    /// Creates a new template renderer.
104    ///
105    /// # Arguments
106    /// * `template_content` - Go template content as a string slice.
107    /// * `data` - Data to be injected into the template, must implement `Serialize`.
108    pub fn new(template_content: &'a str, data: &'a T) -> Self {
109        Self {
110            template_content,
111            data,
112            escape_html: false,
113            use_missing_key_zero: false,
114            _marker: PhantomData,
115        }
116    }
117
118    /// Sets whether to escape HTML in the output.
119    ///
120    /// Defaults to `false`.
121    pub fn escape_html(mut self, escape: bool) -> Self {
122        self.escape_html = escape;
123        self
124    }
125
126    /// Sets whether to treat missing keys as zero values.
127    ///
128    /// Go Template's `missingkey=zero` option.
129    pub fn use_missing_key_zero(mut self, use_zero: bool) -> Self {
130        self.use_missing_key_zero = use_zero;
131        self
132    }
133
134    /// Executes the template rendering.
135    ///
136    /// # Returns
137    /// Ok(String) if rendering was successful, Err(RenderError) otherwise.
138    pub fn render(self) -> Result<String, RenderError> {
139        // Prepare inputs
140        let c_template = CString::new(self.template_content)?;
141        let json_data_string = serde_json::to_string(self.data)?;
142        let c_json_data = CString::new(json_data_string)?;
143
144        // Call Go function - 注意这里的转换
145        let result = unsafe {
146            OwnedGoResult(goffi::RenderTemplate(
147                c_template.into_raw(),  // 使用 into_raw() 而不是 as_ptr() as *mut i8
148                c_json_data.into_raw(), // 使用 into_raw() 而不是 as_ptr() as *mut i8
149                self.escape_html,
150                self.use_missing_key_zero,
151            ))
152        };
153
154        // Process result
155        let output = unsafe {
156            let output_str = CStr::from_ptr(result.0.output)
157                .to_string_lossy()
158                .into_owned();
159            // 重新获取所有权以便后续正确释放
160            let _ = CString::from_raw(result.0.output);
161            output_str
162        };
163
164        let error = unsafe {
165            let error_str = CStr::from_ptr(result.0.error)
166                .to_string_lossy()
167                .into_owned();
168            // 重新获取所有权以便后续正确释放
169            let _ = CString::from_raw(result.0.error);
170            error_str
171        };
172
173        if !error.is_empty() {
174            Err(RenderError::GoExecution(error))
175        } else {
176            Ok(output)
177        }
178    }
179}
180
181// 为方便使用添加的便捷函数
182impl<'a, T: Serialize> TemplateRenderer<'a, T> {
183    /// 快速渲染模板的便捷方法
184    pub fn render_quick(template: &'a str, data: &'a T) -> Result<String, RenderError> {
185        Self::new(template, data).render()
186    }
187
188    /// 渲染HTML转义的模板
189    pub fn render_html(template: &'a str, data: &'a T) -> Result<String, RenderError> {
190        Self::new(template, data).escape_html(true).render()
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use serde::Serialize;
198
199    #[derive(Serialize, Debug)]
200    struct SimpleData {
201        name: String,
202        age: u32,
203        active: bool,
204    }
205
206    #[derive(Serialize)]
207    struct NestedData {
208        user: SimpleData,
209        website: String,
210    }
211
212    #[derive(Serialize)]
213    struct EmptyData {}
214
215    // 基础功能测试
216    #[test]
217    fn test_basic_template_rendering() {
218        let data = SimpleData {
219            name: "Alice".to_string(),
220            age: 30,
221            active: true,
222        };
223
224        let template = "Hello, {{.name}}! You are {{.age}} years old.";
225        let result = TemplateRenderer::render_quick(template, &data).unwrap();
226
227        assert_eq!(result, "Hello, Alice! You are 30 years old.");
228    }
229
230    // HTML 转义测试
231    #[test]
232    fn test_html_escaping() {
233        let data = SimpleData {
234            name: "<script>alert('xss')</script>".to_string(),
235            age: 25,
236            active: false,
237        };
238
239        let template = "Welcome, {{.name}}";
240
241        // 不转义 HTML
242        let result_no_escape = TemplateRenderer::new(template, &data)
243            .escape_html(false)
244            .render()
245            .unwrap();
246        assert_eq!(result_no_escape, "Welcome, <script>alert('xss')</script>");
247
248        // 转义 HTML
249        let result_escape = TemplateRenderer::new(template, &data)
250            .escape_html(true)
251            .render()
252            .unwrap();
253        assert!(result_escape.contains("&lt;script&gt;"));
254        assert!(result_escape.contains("&lt;/script&gt;"));
255    }
256
257    // 嵌套数据结构测试
258    #[test]
259    fn test_nested_data() {
260        let user_data = SimpleData {
261            name: "Bob".to_string(),
262            age: 35,
263            active: true,
264        };
265
266        let data = NestedData {
267            user: user_data,
268            website: "example.com".to_string(),
269        };
270
271        let template = "User: {{.user.name}}, Website: {{.website}}";
272        let result = TemplateRenderer::render_quick(template, &data).unwrap();
273
274        assert_eq!(result, "User: Bob, Website: example.com");
275    }
276
277    // 布尔值和条件测试
278    #[test]
279    fn test_boolean_conditional() {
280        let data = SimpleData {
281            name: "Charlie".to_string(),
282            age: 40,
283            active: true,
284        };
285
286        let template = "{{.name}} is {{if .active}}active{{else}}inactive{{end}}";
287        let result = TemplateRenderer::render_quick(template, &data).unwrap();
288
289        assert_eq!(result, "Charlie is active");
290    }
291
292    // 缺失键处理测试
293    #[test]
294    fn test_missing_key_handling() {
295        let data = SimpleData {
296            name: "David".to_string(),
297            age: 28,
298            active: false,
299        };
300
301        // 测试访问不存在的字段
302        let template = "Name: {{.name}}, Missing: {{.nonexistent}}";
303
304        // 使用 zero 行为
305        let result_zero = TemplateRenderer::new(template, &data)
306            .use_missing_key_zero(true)
307            .render()
308            .unwrap();
309
310        // 在 missingkey=zero 模式下,不存在的字段应该为空
311        assert_eq!(result_zero, "Name: David, Missing: ");
312
313        // 默认模式下可能会出错(取决于 Go 模板的默认行为)
314        // 这里我们只测试 zero 模式,因为默认模式的行为可能因配置而异
315    }
316
317    // 空数据测试
318    #[test]
319    fn test_empty_data() {
320        let data = EmptyData {};
321        let template = "Static content";
322        let result = TemplateRenderer::render_quick(template, &data).unwrap();
323
324        assert_eq!(result, "Static content");
325    }
326
327    // 复杂模板语法测试
328    #[test]
329    fn test_complex_template() {
330        let data = SimpleData {
331            name: "Eve".to_string(),
332            age: 32,
333            active: true,
334        };
335
336        let template = r#"
337User Profile:
338-----------
339Name: {{.name}}
340Age: {{.age}}
341Status: {{if .active}}Active{{else}}Inactive{{end}}
342
343{{range $i, $e := .}}
344Field {{$i}}: {{$e}}
345{{end}}
346"#;
347
348        let result = TemplateRenderer::render_quick(template, &data).unwrap();
349        assert!(result.contains("Name: Eve"));
350        assert!(result.contains("Age: 32"));
351        assert!(result.contains("Status: Active"));
352    }
353
354    // 错误处理测试
355    #[test]
356    fn test_error_handling() {
357        let data = SimpleData {
358            name: "Frank".to_string(),
359            age: 45,
360            active: false,
361        };
362
363        // 无效的模板语法
364        let invalid_template = "Hello, {{.name}"; // 缺少闭合括号
365        let result = TemplateRenderer::render_quick(invalid_template, &data);
366
367        assert!(result.is_err());
368        if let Err(RenderError::GoExecution(err)) = result {
369            assert!(err.contains("template") || err.contains("parse") || err.contains("execute"));
370        } else {
371            panic!("Expected GoExecution error");
372        }
373    }
374
375    // 便捷方法测试
376    #[test]
377    fn test_convenience_methods() {
378        let data = SimpleData {
379            name: "Grace".to_string(),
380            age: 29,
381            active: true,
382        };
383
384        let template = "Hello, {{.name}}";
385
386        // 测试 render_quick
387        let quick_result = TemplateRenderer::render_quick(template, &data).unwrap();
388        assert_eq!(quick_result, "Hello, Grace");
389
390        // 测试 render_html
391        let html_result = TemplateRenderer::render_html(template, &data).unwrap();
392        assert_eq!(html_result, "Hello, Grace"); // 没有特殊字符时应该相同
393    }
394
395    // 特殊字符测试
396    #[test]
397    fn test_special_characters() {
398        let data = SimpleData {
399            name: "O'Reilly".to_string(),
400            age: 50,
401            active: false,
402        };
403
404        let template = "Name: {{.name}}, Quote: \"test\"";
405        let result = TemplateRenderer::render_quick(template, &data).unwrap();
406
407        assert!(result.contains("O'Reilly"));
408        assert!(result.contains("Quote: \"test\""));
409    }
410
411    // 边界值测试
412    #[test]
413    fn test_edge_cases() {
414        // 空字符串
415        let empty_data = SimpleData {
416            name: "".to_string(),
417            age: 0,
418            active: false,
419        };
420
421        let template = "Empty name: '{{.name}}'";
422        let result = TemplateRenderer::render_quick(template, &empty_data).unwrap();
423        assert_eq!(result, "Empty name: ''");
424
425        // 很长的字符串
426        let long_name = "A".repeat(1000);
427        let long_data = SimpleData {
428            name: long_name.clone(),
429            age: 99,
430            active: true,
431        };
432
433        let template = "Long: {{.name}}";
434        let result = TemplateRenderer::render_quick(template, &long_data).unwrap();
435        assert_eq!(result, format!("Long: {}", long_name));
436    }
437
438    // 序列化错误测试
439    #[test]
440    fn test_serialization_error() {
441        // 创建一个无法序列化的数据类型
442        struct UnserializableData;
443
444        impl Serialize for UnserializableData {
445            fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
446            where
447                S: serde::Serializer,
448            {
449                use serde::ser::Error;
450                Err(S::Error::custom("Intentionally failed serialization"))
451            }
452        }
453
454        let data = UnserializableData;
455        let template = "Test {{.field}}";
456        let result = TemplateRenderer::render_quick(template, &data);
457
458        assert!(result.is_err());
459        if let Err(RenderError::JsonSerialization(_)) = result {
460            // 期望的序列化错误
461        } else {
462            panic!("Expected JsonSerialization error");
463        }
464    }
465}