rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use minijinja::Value;
use std::collections::HashMap;

pub struct Context {
    inner: HashMap<String, Value>,
}

impl Context {
    #[must_use]
    pub fn new() -> Self {
        Self {
            inner: HashMap::new(),
        }
    }

    pub fn from_iter<I, K, V>(iter: I) -> Self
    where
        I: IntoIterator<Item = (K, V)>,
        K: Into<String>,
        V: Into<Value>,
    {
        let mut context = Self::new();
        for (key, value) in iter {
            context.insert(key, value);
        }
        context
    }

    pub fn insert(&mut self, key: impl Into<String>, value: impl Into<Value>) {
        self.inner.insert(key.into(), value.into());
    }

    pub fn update(&mut self, other: Context) {
        self.inner.extend(other.inner);
    }

    #[must_use]
    pub fn get(&self, key: &str) -> Option<&Value> {
        self.inner.get(key)
    }

    pub fn remove(&mut self, key: &str) -> Option<Value> {
        self.inner.remove(key)
    }

    #[must_use]
    pub fn contains_key(&self, key: &str) -> bool {
        self.inner.contains_key(key)
    }

    #[must_use]
    pub fn len(&self) -> usize {
        self.inner.len()
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    #[must_use]
    pub fn into_value(self) -> Value {
        Value::from_serialize(self.inner)
    }
}

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

#[cfg(test)]
mod tests {
    use minijinja::Environment;

    use super::*;

    #[test]
    fn builds_minijinja_values() {
        let mut context = Context::new();
        context.insert("name", "Rjango");
        context.insert("count", 3);

        let mut env = Environment::new();
        env.add_template("show.txt", "{{ name }} {{ count }}")
            .expect("valid template");

        let rendered = env
            .get_template("show.txt")
            .expect("template exists")
            .render(context.into_value())
            .expect("context renders");

        assert_eq!(rendered, "Rjango 3");
    }

    #[test]
    fn context_from_iter_builds_values() {
        let context =
            Context::from_iter([("name", Value::from("Rjango")), ("count", Value::from(2))]);

        assert_eq!(context.get("name"), Some(&Value::from("Rjango")));
        assert_eq!(context.get("count"), Some(&Value::from(2)));
        assert!(context.contains_key("name"));
    }

    #[test]
    fn context_update_merges() {
        let mut primary = Context::from_iter([("name", Value::from("Rjango"))]);
        let secondary =
            Context::from_iter([("name", Value::from("Updated")), ("count", Value::from(3))]);

        primary.update(secondary);

        assert_eq!(primary.get("name"), Some(&Value::from("Updated")));
        assert_eq!(primary.get("count"), Some(&Value::from(3)));
    }

    #[test]
    fn context_get_and_remove() {
        let mut context = Context::from_iter([("name", Value::from("Rjango"))]);

        assert_eq!(context.get("name"), Some(&Value::from("Rjango")));
        assert_eq!(context.remove("name"), Some(Value::from("Rjango")));
        assert_eq!(context.get("name"), None);
        assert!(!context.contains_key("name"));
    }

    #[test]
    fn context_len_and_empty() {
        let mut context = Context::new();
        assert!(context.is_empty());
        assert_eq!(context.len(), 0);

        context.insert("name", "Rjango");

        assert!(!context.is_empty());
        assert_eq!(context.len(), 1);
    }
}