#[cfg(test)]
mod tests;
use std::sync::OnceLock;
pub use tera::Context;
pub use tera::Value;
use crate::core::New;
use crate::mime_type::MimeType;
use crate::range::Range;
use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
static ENGINE: OnceLock<TeraEngine> = OnceLock::new();
#[derive(Debug)]
pub struct TeraEngine {
inner: tera::Tera,
}
impl TeraEngine {
pub fn from_glob(pattern: &str) -> Result<Self, String> {
let tera = tera::Tera::new(pattern)
.map_err(|e| format!("template engine init failed: {}", e))?;
Ok(TeraEngine { inner: tera })
}
pub fn from_dir(dir: &str) -> Result<Self, String> {
let pattern = format!("{}/**/*", dir.trim_end_matches('/'));
Self::from_glob(&pattern)
}
pub fn from_raw(templates: &[(&str, &str)]) -> Result<Self, String> {
let mut tera = tera::Tera::default();
for (name, content) in templates {
tera.add_raw_template(name, content)
.map_err(|e| format!("failed to add template '{}': {}", name, e))?;
}
Ok(TeraEngine { inner: tera })
}
pub fn render(&self, template_name: &str, ctx: &Context) -> Result<String, String> {
self.inner
.render(template_name, ctx)
.map_err(|e| format!("render '{}' failed: {}", template_name, e))
}
pub fn response(&self, template_name: &str, ctx: &Context) -> Result<Response, String> {
let html = self.render(template_name, ctx)?;
Ok(html_response(html))
}
}
pub fn init(dir: &str) -> Result<(), String> {
let engine = TeraEngine::from_dir(dir)?;
ENGINE
.set(engine)
.map_err(|_| "template engine already initialized".to_string())
}
pub fn init_from_env() -> Result<(), String> {
let dir = std::env::var(crate::entry_point::Config::RWS_CONFIG_TEMPLATE_DIR)
.unwrap_or_else(|_| {
crate::entry_point::Config::RWS_CONFIG_TEMPLATE_DIR_DEFAULT_VALUE.to_string()
});
init(&dir)
}
pub fn global() -> &'static TeraEngine {
ENGINE
.get()
.expect("template engine not initialized — call template::init() at startup")
}
pub fn render(template_name: &str, ctx: &Context) -> Result<Response, String> {
global().response(template_name, ctx)
}
fn html_response(html: String) -> Response {
let mut r = Response::new();
r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
r.content_range_list = vec![Range::get_content_range(
html.into_bytes(),
MimeType::TEXT_HTML.to_string(),
)];
r
}