Skip to main content

wae_https/template/
mod.rs

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