Skip to main content

wae_https/template/
mod.rs

1//! 模板渲染模块
2//!
3//! 提供基于 Askama 模板引擎的渲染支持。
4//!
5//! # 特性
6//!
7//! - 编译时模板检查,类型安全
8//! - 自动 HTML 转义,防止 XSS 攻击
9//! - 可配置模板目录和扩展名
10
11use http::{Response, StatusCode, header};
12use std::path::PathBuf;
13
14use crate::{Body, full_body};
15
16pub use askama;
17
18/// 模板配置
19#[derive(Debug, Clone)]
20pub struct TemplateConfig {
21    /// 模板根目录
22    pub template_dir: PathBuf,
23    /// 模板文件扩展名
24    pub extension: String,
25    /// 是否启用缓存(生产环境建议启用)
26    pub enable_cache: bool,
27}
28
29impl Default for TemplateConfig {
30    fn default() -> Self {
31        Self { template_dir: PathBuf::from("templates"), extension: "html".to_string(), enable_cache: true }
32    }
33}
34
35impl TemplateConfig {
36    /// 创建新的模板配置
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// 设置模板目录
42    pub fn with_template_dir(mut self, dir: impl Into<PathBuf>) -> Self {
43        self.template_dir = dir.into();
44        self
45    }
46
47    /// 设置模板扩展名
48    pub fn with_extension(mut self, ext: impl Into<String>) -> Self {
49        self.extension = ext.into();
50        self
51    }
52
53    /// 设置是否启用缓存
54    pub fn with_cache(mut self, enable: bool) -> Self {
55        self.enable_cache = enable;
56        self
57    }
58}
59
60/// 模板渲染器
61///
62/// 提供模板渲染的核心功能。
63///
64/// # 示例
65///
66/// ```ignore
67/// use wae_https::template::{TemplateConfig, TemplateRenderer};
68///
69/// let config = TemplateConfig::new()
70///     .with_template_dir("views");
71///
72/// let renderer = TemplateRenderer::new(config);
73/// ```
74#[derive(Debug, Clone)]
75pub struct TemplateRenderer {
76    config: TemplateConfig,
77}
78
79impl TemplateRenderer {
80    /// 创建新的模板渲染器
81    pub fn new(config: TemplateConfig) -> Self {
82        Self { config }
83    }
84
85    /// 使用默认配置创建模板渲染器
86    pub fn default_renderer() -> Self {
87        Self::new(TemplateConfig::default())
88    }
89
90    /// 获取配置引用
91    pub fn config(&self) -> &TemplateConfig {
92        &self.config
93    }
94
95    /// 获取模板目录路径
96    pub fn template_dir(&self) -> &PathBuf {
97        &self.config.template_dir
98    }
99}
100
101impl Default for TemplateRenderer {
102    fn default() -> Self {
103        Self::default_renderer()
104    }
105}
106
107/// 模板渲染错误
108#[derive(Debug)]
109pub enum TemplateError {
110    /// 模板未找到
111    NotFound(String),
112    /// 渲染错误
113    RenderError(String),
114    /// IO 错误
115    IoError(String),
116}
117
118impl std::fmt::Display for TemplateError {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        match self {
121            TemplateError::NotFound(name) => write!(f, "Template not found: {}", name),
122            TemplateError::RenderError(msg) => write!(f, "Template render error: {}", msg),
123            TemplateError::IoError(msg) => write!(f, "Template IO error: {}", msg),
124        }
125    }
126}
127
128impl std::error::Error for TemplateError {}
129
130/// HTML 模板响应包装器
131///
132/// 用于将 Askama 模板包装为 HTTP 响应。
133/// 支持自动设置 Content-Type 头。
134pub struct HtmlTemplate<T> {
135    /// 内部模板
136    inner: T,
137}
138
139impl<T> HtmlTemplate<T> {
140    /// 创建新的 HTML 模板包装器
141    pub fn new(template: T) -> Self {
142        Self { inner: template }
143    }
144
145    /// 获取内部模板引用
146    pub fn inner(&self) -> &T {
147        &self.inner
148    }
149
150    /// 消费包装器,返回内部模板
151    pub fn into_inner(self) -> T {
152        self.inner
153    }
154}
155
156impl<T> From<T> for HtmlTemplate<T> {
157    fn from(template: T) -> Self {
158        Self::new(template)
159    }
160}
161
162impl<T> HtmlTemplate<T>
163where
164    T: askama::Template,
165{
166    /// 转换为 HTTP 响应
167    pub fn into_response(self) -> Response<Body> {
168        match self.inner.render() {
169            Ok(html) => Response::builder()
170                .status(StatusCode::OK)
171                .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
172                .body(full_body(html))
173                .unwrap(),
174            Err(e) => {
175                tracing::error!("Template render error: {}", e);
176                Response::builder()
177                    .status(StatusCode::INTERNAL_SERVER_ERROR)
178                    .header(header::CONTENT_TYPE, "text/plain; charset=utf-8")
179                    .body(full_body(format!("Template error: {}", e)))
180                    .unwrap()
181            }
182        }
183    }
184}
185
186/// 模板响应构建器
187///
188/// 提供便捷的模板响应构建方法。
189pub struct TemplateResponse;
190
191impl TemplateResponse {
192    /// 返回 HTML 模板响应
193    pub fn html<T: askama::Template>(template: T) -> Response<Body> {
194        HtmlTemplate::new(template).into_response()
195    }
196
197    /// 返回带自定义状态码的 HTML 模板响应
198    pub fn html_with_status<T: askama::Template>(template: T, status: StatusCode) -> Response<Body> {
199        match template.render() {
200            Ok(html) => Response::builder()
201                .status(status)
202                .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
203                .body(full_body(html))
204                .unwrap(),
205            Err(e) => {
206                tracing::error!("Template render error: {}", e);
207                Response::builder()
208                    .status(StatusCode::INTERNAL_SERVER_ERROR)
209                    .header(header::CONTENT_TYPE, "text/plain; charset=utf-8")
210                    .body(full_body(format!("Template error: {}", e)))
211                    .unwrap()
212            }
213        }
214    }
215}