rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use minijinja::{Environment, ErrorKind, Value};

use super::defaultfilters::register_default_filters;

#[derive(Debug)]
pub struct TemplateEngine {
    env: Environment<'static>,
}

impl TemplateEngine {
    pub fn new() -> Self {
        let mut env = Environment::new();
        register_default_filters(&mut env);

        Self { env }
    }

    pub fn add_template(&mut self, name: &str, source: &str) {
        self.env
            .add_template_owned(name.to_string(), source.to_string())
            .unwrap_or_else(|error| panic!("failed to add template `{name}`: {error}"));
    }

    pub fn render(&self, name: &str, context: Value) -> Result<String, TemplateError> {
        let template = self
            .env
            .get_template(name)
            .map_err(|error| match error.kind() {
                ErrorKind::TemplateNotFound => TemplateError::NotFound(name.to_string()),
                _ => TemplateError::Render(error),
            })?;
        template.render(context).map_err(TemplateError::from)
    }
}

impl Default for TemplateEngine {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, thiserror::Error)]
pub enum TemplateError {
    #[error("Template not found: {0}")]
    NotFound(String),
    #[error(transparent)]
    Render(#[from] minijinja::Error),
}

#[cfg(test)]
mod tests {
    use super::*;
    use minijinja::context;

    #[test]
    fn renders_registered_templates() {
        let mut engine = TemplateEngine::new();
        engine.add_template("hello.html", "Hello {{ name }}!");

        let rendered = engine
            .render("hello.html", context!(name => "World"))
            .expect("rendered template");

        assert_eq!(rendered, "Hello World!");
    }

    #[test]
    fn reports_missing_templates() {
        let engine = TemplateEngine::new();
        let error = engine
            .render("missing.html", context! {})
            .expect_err("missing template error");
        assert!(matches!(error, TemplateError::NotFound(name) if name == "missing.html"));
    }

    #[test]
    fn renders_templates_with_registered_default_filters() {
        let mut engine = TemplateEngine::new();
        engine.add_template(
            "filters.html",
            "{{ title|slugify }} {{ name|upper }} countr{{ amount|pluralize(\"y,ies\") }} {{ size|filesizeformat }}",
        );

        let rendered = engine
            .render(
                "filters.html",
                context!(title => "Hello Rust World", name => "rjango", amount => 2, size => 1536),
            )
            .expect("rendered template with filters");

        assert_eq!(rendered, "hello-rust-world RJANGO countries 1.5 KB");
    }
}