handbar 0.0.3

Fork of Handlebars.
use serde_json::value::Value as Json;

use crate::context::Context;
use crate::error::RenderError;
use crate::helpers::HelperDef;
use crate::json::value::ScopedJson;
use crate::registry::Registry;
use crate::render::{Helper, RenderContext};

#[derive(Clone, Copy)]
pub struct AppendHelper;

impl HelperDef for AppendHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'rc>,
        _: &'reg Registry<'reg>,
        _: &'rc Context,
        _: &mut RenderContext<'reg, 'rc>,
    ) -> Result<ScopedJson<'rc>, RenderError> {
        let list = h
            .param(0)
            .ok_or_else(|| RenderError::new("Param not found for helper \"append\": list"))?
            .value()
            .as_array()
            .ok_or_else(|| {
                RenderError::new("Param invalid for helper \"append\": list must be array")
            })?;

        let value = h
            .param(1)
            .ok_or_else(|| RenderError::new("Param not found for helper \"append\": value"))?
            .value();

        let mut result = list.clone();
        result.push(value.to_owned());

        Ok(Json::Array(result).into())
    }
}

#[derive(Clone, Copy)]
pub struct PrependHelper;

impl HelperDef for PrependHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'rc>,
        _: &'reg Registry<'reg>,
        _: &'rc Context,
        _: &mut RenderContext<'reg, 'rc>,
    ) -> Result<ScopedJson<'rc>, RenderError> {
        let list = h
            .param(0)
            .ok_or_else(|| RenderError::new("Param not found for helper \"prepend\": list"))?
            .value()
            .as_array()
            .ok_or_else(|| {
                RenderError::new("Param invalid for helper \"prepend\": list must be array")
            })?;

        let value = h
            .param(1)
            .ok_or_else(|| RenderError::new("Param not found for helper \"prepend\": value"))?
            .value();

        let mut result = list.clone();
        result.insert(0, value.to_owned());

        Ok(Json::Array(result).into())
    }
}

#[derive(Clone, Copy)]
pub struct InsertHelper;

impl HelperDef for InsertHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'rc>,
        _: &'reg Registry<'reg>,
        _: &'rc Context,
        _: &mut RenderContext<'reg, 'rc>,
    ) -> Result<ScopedJson<'rc>, RenderError> {
        let collection = h
            .param(0)
            .ok_or_else(|| RenderError::new("Param not found for helper \"insert\": collection"))?
            .value();

        let position = h
            .param(1)
            .ok_or_else(|| RenderError::new("Param not found for helper \"insert\": position"))?
            .value();

        let item = h
            .param(2)
            .ok_or_else(|| RenderError::new("Param not found for helper \"insert\": item"))?
            .value();

        match (collection, position) {
            (Json::Array(list), Json::Number(index)) => {
                let index = index.as_u64().ok_or_else(|| {
                    RenderError::new(
                        "Param invalid for helper \"insert\": position must be positive number",
                    )
                })? as usize;

                if index > list.len() {
                    return Err(RenderError::new(
                        "Param invalid for helper \"insert\": position out of range",
                    ))
                }

                let mut result = list.clone();
                result.insert(index, item.to_owned());
                Ok(Json::Array(result).into())
            }
            (Json::Object(map), Json::String(key)) => {
                let mut result = map.clone();
                result.insert(key.clone(), item.to_owned());
                Ok(Json::Object(result).into())
            }
            _ => Err(RenderError::new(format!(
                "Param invalid for helper \"insert\": unsupported insertion tuple (collection: {:?}, position: {:?})",
                collection,
                position,
            ))),
        }
    }
}

#[derive(Clone, Copy)]
pub struct RemoveHelper;

impl HelperDef for RemoveHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'rc>,
        _: &'reg Registry<'reg>,
        _: &'rc Context,
        _: &mut RenderContext<'reg, 'rc>,
    ) -> Result<ScopedJson<'rc>, RenderError> {
        let collection = h
            .param(0)
            .ok_or_else(|| RenderError::new("Param not found for helper \"remove\": collection"))?
            .value();

        let position = h
            .param(1)
            .ok_or_else(|| RenderError::new("Param not found for helper \"remove\": position"))?
            .value();

        match (collection, position) {
            (Json::Array(list), Json::Number(index)) => {
                let index = index.as_u64().ok_or_else(|| {
                    RenderError::new(
                        "Param invalid for helper \"remove\": position must be positive number",
                    )
                })? as usize;

                if index >= list.len() {
                    return Err(RenderError::new(
                        "Param invalid for helper \"remove\": position out of range",
                    ))
                }

                let mut result = list.clone();
                result.remove(index);
                Ok(Json::Array(result).into())
            }
            (Json::Object(map), Json::String(key)) => {
                let mut result = map.clone();
                result.remove(key);
                Ok(Json::Object(result).into())
            }
            _ => Err(RenderError::new(format!(
                "Param invalid for helper \"remove\": unsupported insertion tuple (collection: {:?}, position: {:?})",
                collection,
                position,
            ))),
        }
    }
}

pub static APPEND_HELPER: AppendHelper = AppendHelper;
pub static PREPEND_HELPER: PrependHelper = PrependHelper;
pub static INSERT_HELPER: InsertHelper = InsertHelper;
pub static REMOVE_HELPER: RemoveHelper = RemoveHelper;

#[cfg(test)]
mod test {
    #[test]
    fn test_insert() {
        assert_renders![
            (
                r##"{{ json (insert {} "foo" "foo") }}"##,
                r##"{"foo":"foo"}"##
            ),
            (
                r##"{{ json (insert [1, 2] 0 "foo") }}"##,
                r##"["foo",1,2]"##
            ),
            (
                r##"{{ json (insert [1, 2] 2 "foo") }}"##,
                r##"[1,2,"foo"]"##
            ),
        ];
    }

    #[test]
    fn test_append() {
        assert_renders![(r##"{{ json (append [0, 1] 2) }}"##, r##"[0,1,2]"##)];
    }

    #[test]
    fn test_remove() {
        assert_renders![(r##"{{ json (remove [0, 1] 1) }}"##, r##"[0]"##)];
        assert_renders![(r##"{{ json (remove {"foo": "bar"} "foo") }}"##, r##"{}"##)];
    }
}