sinan 0.1.0

A Boilerplate for Rapid Axum Web Service Deployment.
Documentation
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};

use dotenvy::dotenv;
use eyre::Result;
use serde_json::{from_value, Value};
use tera::{Context, Error, Function, Tera};

use crate::contracts::Facade;
use crate::facades::Container;

#[derive(Clone, Debug)]
pub struct Template(Tera);

impl Facade for Template {
    fn new(container: &Container) -> Self {
        let mut tera = Tera::default();
        tera.register_function("env", env());

        Self(tera)
    }
}

fn env() -> impl Function {
    Box::new(
        move |args: &HashMap<String, Value>| -> tera::Result<Value> {
            let name = match args.get("name") {
                Some(val) => match from_value::<String>(val.clone()) {
                    Ok(v) => v,
                    Err(_) => {
                        return Err(Error::msg(format!(
                            "Function `env` received name={} but `name` can only be a string",
                            val
                        )));
                    }
                },
                None => {
                    return Err(Error::msg(
                        "Function `env` didn't receive a `name` argument",
                    ))
                }
            };

            dotenv().ok();

            match std::env::var(&name).ok() {
                Some(res) => Ok(Value::String(res)),
                None => match args.get("default") {
                    Some(default) => Ok(default.clone()),
                    None => Err(Error::msg(format!(
                        "Environment variable `{}` not found",
                        &name
                    ))),
                },
            }
        },
    )
}

impl Deref for Template {
    type Target = Tera;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Template {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Template {
    pub fn render(&mut self, input: &str, context: &Value) -> Result<String> {
        let text = self.render_str(input, &Context::from_serialize(context)?)?;

        Ok(text)
    }
}