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::{Dynamic, Engine, Scope, AST};
use serde_json::value::{Map, Number, Value as Json};
pub struct ScriptHelper {
pub(crate) script: AST,
}
#[inline]
fn call_script_helper<'reg: 'rc, 'rc>(
params: &Vec<PathAndJson<'reg, 'rc>>,
hash: &BTreeMap<&'reg str, PathAndJson<'reg, 'rc>>,
engine: &Engine,
script: &AST,
) -> Result<Option<ScopedJson<'reg, 'rc>>, RenderError> {
let params: Dynamic = params
.iter()
.map(|p| to_dynamic(p.value()))
.collect::<Vec<Dynamic>>()
.into();
let hash: Dynamic = hash
.iter()
.map(|(k, v)| ((*k).to_owned(), to_dynamic(v.value())))
.collect::<HashMap<String, Dynamic>>()
.into();
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 = to_json(&result);
Ok(Some(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<Option<ScopedJson<'reg, 'rc>>, RenderError> {
call_script_helper(h.params(), h.hash(), ®.engine, &self.script)
}
}
fn to_dynamic(j: &Json) -> Dynamic {
match j {
Json::Number(n) => Dynamic::from(n.clone()),
Json::Bool(b) => Dynamic::from(*b),
Json::Null => Dynamic::from(()),
Json::String(s) => Dynamic::from(s.clone()),
Json::Array(ref v) => {
let dyn_vec: Vec<Dynamic> = v.iter().map(|i| to_dynamic(i)).collect();
Dynamic::from(dyn_vec)
}
Json::Object(ref o) => {
let dyn_map: HashMap<String, Dynamic> = o
.iter()
.map(|(k, v)| ((*k).to_owned(), to_dynamic(v)))
.collect();
Dynamic::from(dyn_map)
}
}
}
fn to_json(d: &Dynamic) -> Json {
if let Ok(s) = d.as_str() {
return Json::String(s.to_owned());
}
if let Ok(i) = d.as_int() {
return Json::Number(Number::from(i));
}
if let Ok(b) = d.as_bool() {
return Json::Bool(b);
}
if d.type_name() == "array" {
let v = d
.downcast_ref::<Vec<Dynamic>>()
.unwrap()
.iter()
.map(to_json)
.collect::<Vec<Json>>();
return Json::Array(v);
}
if d.type_name() == "map" {
let m = d
.downcast_ref::<HashMap<String, Dynamic>>()
.unwrap()
.iter()
.map(|(k, v)| (k.clone(), to_json(v)))
.collect::<Map<String, Json>>();
return Json::Object(m);
}
return Json::Null;
}
#[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);
assert_eq!("array", d0.type_name());
let j1 = json!({
"name": "tomcat",
"value": 4000,
});
let d1 = to_dynamic(&j1);
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()), to_json(&d0));
}
#[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(¶ms, &hash, &engine, &ast)
.unwrap()
.unwrap()
.render();
assert_eq!("1,true,2,no", &result);
}
}