use askama::Template;
use axum::{
http::StatusCode,
response::{Html, IntoResponse, Response},
};
pub mod extractor;
pub mod framework;
pub mod helpers;
pub mod registry;
pub use extractor::*;
pub use framework::{FrameworkTemplateError, FrameworkTemplates};
pub use helpers::*;
pub use registry::TemplateRegistry;
pub trait HxTemplate: Template {
fn render_htmx(self, is_htmx: bool) -> Response
where
Self: Sized,
{
match self.render() {
Ok(html) => {
if is_htmx {
let partial = extractor::extract_main_content(&html);
Html(partial.into_owned()).into_response()
} else {
Html(html).into_response()
}
}
Err(err) => {
tracing::error!("Template rendering error: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Template rendering failed",
)
.into_response()
}
}
}
fn render_html(self) -> Response
where
Self: Sized,
{
match self.render() {
Ok(html) => Html(html).into_response(),
Err(err) => {
tracing::error!("Template rendering error: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Template rendering failed",
)
.into_response()
}
}
}
fn render_partial(self) -> Response
where
Self: Sized,
{
match self.render() {
Ok(html) => {
let partial = extractor::extract_main_content(&html);
Html(partial.into_owned()).into_response()
}
Err(err) => {
tracing::error!("Template rendering error: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Template rendering failed",
)
.into_response()
}
}
}
fn render_oob(self, target_id: &str, swap_strategy: Option<&str>) -> Response
where
Self: Sized,
{
match self.render() {
Ok(html) => {
let swap_attr = swap_strategy.unwrap_or("true");
let oob_html = format!(
r#"<div id="{target_id}" hx-swap-oob="{swap_attr}">{html}</div>"#
);
Html(oob_html).into_response()
}
Err(err) => {
tracing::error!("Template rendering error: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Template rendering failed",
)
.into_response()
}
}
}
fn render_oob_str(self, target_id: &str, swap_strategy: Option<&str>) -> Result<String, askama::Error>
where
Self: Sized,
{
let html = self.render()?;
let swap_attr = swap_strategy.unwrap_or("true");
Ok(format!(
r#"<div id="{target_id}" hx-swap-oob="{swap_attr}">{html}</div>"#
))
}
}
impl<T> HxTemplate for T where T: Template {}
#[cfg(test)]
mod tests {
use super::*;
use askama::Template;
#[derive(Template)]
#[template(source = "<h1>{{ title }}</h1>", ext = "html")]
struct TestTemplate {
title: String,
}
#[test]
fn test_render_html() {
let template = TestTemplate {
title: "Hello".to_string(),
};
let response = template.render_html();
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn test_render_htmx_full_page() {
let template = TestTemplate {
title: "Hello".to_string(),
};
let response = template.render_htmx(false);
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn test_render_htmx_partial() {
let template = TestTemplate {
title: "Hello".to_string(),
};
let response = template.render_htmx(true);
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn test_render_oob() {
let template = TestTemplate {
title: "Updated".to_string(),
};
let response = template.render_oob("my-element", None);
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn test_render_oob_with_strategy() {
let template = TestTemplate {
title: "Replaced".to_string(),
};
let response = template.render_oob("my-element", Some("outerHTML"));
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn test_render_oob_str() {
let template = TestTemplate {
title: "Content".to_string(),
};
let oob_str = template.render_oob_str("target-id", None).unwrap();
assert!(oob_str.contains(r#"id="target-id""#));
assert!(oob_str.contains(r#"hx-swap-oob="true""#));
assert!(oob_str.contains("<h1>Content</h1>"));
}
#[test]
fn test_render_oob_str_with_strategy() {
let template = TestTemplate {
title: "Content".to_string(),
};
let oob_str = template.render_oob_str("target-id", Some("innerHTML")).unwrap();
assert!(oob_str.contains(r#"hx-swap-oob="innerHTML""#));
}
}