1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
use std::collections::{BTreeMap, HashMap};

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

use rhai::serde::{from_dynamic, to_dynamic};
use rhai::{Dynamic, Engine, Scope, AST};

use serde_json::value::Value as Json;

pub(crate) struct ScriptHelper {
    pub(crate) script: AST,
}

#[inline]
fn call_script_helper<'reg: 'rc, 'rc>(
    params: &[PathAndJson<'reg, 'rc>],
    hash: &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
    engine: &Engine,
    script: &AST,
) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
    let params: Dynamic = to_dynamic(params.iter().map(|p| p.value()).collect::<Vec<&Json>>())?;

    let hash: Dynamic = to_dynamic(
        hash.iter()
            .map(|(k, v)| ((*k).to_owned(), v.value()))
            .collect::<HashMap<String, &Json>>(),
    )?;

    let mut scope = Scope::new();
    scope.push_dynamic("params", params);
    scope.push_dynamic("hash", hash);

    let result = engine
        .eval_ast_with_scope::<Dynamic>(&mut scope, script)
        .map_err(RenderError::from)?;

    let result_json: Json = from_dynamic(&result)?;

    Ok(ScopedJson::Derived(result_json))
}

impl HelperDef for ScriptHelper {
    fn call_inner<'reg: 'rc, 'rc>(
        &self,
        h: &Helper<'reg, 'rc>,
        reg: &'reg Registry<'reg>,
        _ctx: &'rc Context,
        _rc: &mut RenderContext<'reg, 'rc>,
    ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
        call_script_helper(h.params(), h.hash(), &reg.engine, &self.script)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::json::value::{PathAndJson, ScopedJson};
    use rhai::Engine;

    #[test]
    fn test_dynamic_convert() {
        let j0 = json! {
            [{"name": "tomcat"}, {"name": "jetty"}]
        };

        let d0 = to_dynamic(&j0).unwrap();
        assert_eq!("array", d0.type_name());

        let j1 = json!({
            "name": "tomcat",
            "value": 4000,
        });

        let d1 = to_dynamic(&j1).unwrap();
        assert_eq!("map", d1.type_name());
    }

    #[test]
    fn test_to_json() {
        let d0 = Dynamic::from("tomcat".to_owned());

        assert_eq!(
            Json::String("tomcat".to_owned()),
            from_dynamic::<Json>(&d0).unwrap()
        );
    }

    #[test]
    fn test_script_helper_value_access() {
        let engine = Engine::new();

        let script = "let plen = len(params); let p0 = params[0]; let hlen = len(hash); let hme = hash[\"me\"]; plen + \",\" + p0 + \",\" + hlen + \",\" + hme";
        let ast = engine.compile(&script).unwrap();

        let params = vec![PathAndJson::new(None, ScopedJson::Derived(json!(true)))];
        let hash = btreemap! {
            "me" => PathAndJson::new(None, ScopedJson::Derived(json!("no"))),
            "you" => PathAndJson::new(None, ScopedJson::Derived(json!("yes"))),
        };

        let result = call_script_helper(&params, &hash, &engine, &ast)
            .unwrap()
            .render();
        assert_eq!("1,true,2,no", &result);
    }
}