use crate::{Handler, MiddleWareHandler, Next, Request, Response, Result, SilentError, StatusCode};
use async_trait::async_trait;
use serde::Serialize;
use serde_json::Value;
use std::sync::Arc;
use tera::{Context, Tera};
#[derive(Debug, Clone)]
pub struct TemplateResponse {
template: String,
data: Value,
}
impl<T: Serialize, S: Into<String>> From<(S, T)> for TemplateResponse {
fn from((template, data): (S, T)) -> Self {
let template = template.into();
let data = serde_json::to_value(data).unwrap_or(Value::Null);
TemplateResponse { template, data }
}
}
impl From<TemplateResponse> for Response {
fn from(value: TemplateResponse) -> Self {
let mut res = Response::empty();
res.extensions.insert(value);
res
}
}
pub struct TemplateMiddleware {
pub template: Arc<Tera>,
}
impl TemplateMiddleware {
pub fn try_new(template_path: &str) -> Result<Self> {
let template = Arc::new(Tera::new(template_path).map_err(|e| {
SilentError::business_error(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to load templates: {e}"),
)
})?);
Ok(TemplateMiddleware { template })
}
pub fn new(template_path: &str) -> Self {
Self::try_new(template_path).expect("Failed to load templates")
}
}
#[async_trait]
impl MiddleWareHandler for TemplateMiddleware {
async fn handle(&self, req: Request, next: &Next) -> Result<Response> {
let mut res = next.call(req).await?;
let template = res.extensions.get::<TemplateResponse>().ok_or_else(|| {
SilentError::business_error(
StatusCode::INTERNAL_SERVER_ERROR,
"template response missing",
)
})?;
res.set_body(
self.template
.render(
&template.template,
&Context::from_serialize(&template.data).map_err(|e| {
SilentError::business_error(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to render template: {e}"),
)
})?,
)
.map_err(|e| {
SilentError::business_error(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to render template: {e}"),
)
})?
.into(),
);
res.set_typed_header(headers::ContentType::html());
Ok(res)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::Route;
use crate::{Handler, Request};
use bytes::Bytes;
use http_body_util::BodyExt;
#[test]
fn test_template_response_from_tuple() {
let resp = TemplateResponse::from(("index.html", serde_json::json!({"key": "val"})));
assert_eq!(resp.template, "index.html");
assert_eq!(resp.data["key"], "val");
}
#[test]
fn test_template_response_from_string_template_name() {
let resp = TemplateResponse::from(("page.html".to_string(), vec![1, 2, 3]));
assert_eq!(resp.template, "page.html");
assert!(resp.data.is_array());
}
#[test]
fn test_template_response_clone() {
let resp = TemplateResponse::from(("t.html", "data"));
let cloned = resp.clone();
assert_eq!(resp.template, cloned.template);
assert_eq!(resp.data, cloned.data);
}
#[test]
fn test_template_response_into_response() {
let template_resp = TemplateResponse::from(("t.html", "hello"));
let response: Response = template_resp.into();
assert!(response.extensions.get::<TemplateResponse>().is_some());
}
#[test]
fn test_try_new_invalid_glob_pattern() {
let result = TemplateMiddleware::try_new("[invalid glob");
assert!(result.is_err());
}
#[test]
#[should_panic(expected = "Failed to load templates")]
fn test_new_invalid_glob_panics() {
let _ = TemplateMiddleware::new("[invalid glob");
}
#[tokio::test]
async fn test_handle_missing_template_response() {
let mut tera = Tera::default();
tera.add_raw_template("t.html", "hi").unwrap();
let mid = TemplateMiddleware {
template: Arc::new(tera),
};
let route = Route::default()
.get(|_req: Request| async { Ok(Response::text("no template")) })
.hook(mid);
let mut req = Request::empty();
req.set_remote("127.0.0.1:8080".parse().unwrap());
let res = route.call(req).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_handle_unknown_template_name() {
let mut tera = Tera::default();
tera.add_raw_template("known.html", "ok").unwrap();
let mid = TemplateMiddleware {
template: Arc::new(tera),
};
let route = Route::default()
.get(|_req: Request| async { Ok(TemplateResponse::from(("unknown.html", "data"))) })
.hook(mid);
let mut req = Request::empty();
req.set_remote("127.0.0.1:8080".parse().unwrap());
let res = route.call(req).await;
assert!(res.is_err());
}
#[tokio::test]
async fn test_handle_sets_content_type_html() {
let mut tera = Tera::default();
tera.add_raw_template("t.html", "<p>{{ v }}</p>").unwrap();
let mid = TemplateMiddleware {
template: Arc::new(tera),
};
let route = Route::default()
.get(|_req: Request| async {
Ok(TemplateResponse::from((
"t.html",
serde_json::json!({"v": "x"}),
)))
})
.hook(mid);
let mut req = Request::empty();
req.set_remote("127.0.0.1:8080".parse().unwrap());
let res = route.call(req).await.unwrap();
let ct = res
.headers()
.get(http::header::CONTENT_TYPE)
.unwrap()
.to_str()
.unwrap()
.to_string();
assert!(ct.contains("text/html"));
}
#[derive(Serialize)]
struct Temp {
name: String,
}
#[tokio::test]
async fn templates_test() {
let mut tera = Tera::default();
tera.add_raw_template("index.html", "<h1>{{ name }}</h1>")
.unwrap();
let temp_middleware = TemplateMiddleware {
template: Arc::new(tera),
};
let route = Route::default()
.get(|_req: Request| async {
let temp = Temp {
name: "templates".to_string(),
};
Ok(TemplateResponse::from(("index.html".to_string(), temp)))
})
.hook(temp_middleware);
let mut req = Request::empty();
req.set_remote("127.0.0.1:8080".parse().unwrap());
assert_eq!(
route
.call(req)
.await
.unwrap()
.body
.frame()
.await
.unwrap()
.unwrap()
.data_ref()
.unwrap(),
&Bytes::from("<h1>templates</h1>")
);
}
}