handlebars/helpers/
scripting.rs1use std::collections::{BTreeMap, HashMap};
2
3use crate::context::Context;
4use crate::error::{RenderError, RenderErrorReason};
5use crate::helpers::HelperDef;
6use crate::json::value::{PathAndJson, ScopedJson};
7use crate::registry::Registry;
8use crate::render::{Helper, RenderContext};
9
10use rhai::serde::{from_dynamic, to_dynamic};
11use rhai::{Dynamic, Engine, Scope, AST};
12
13use serde_json::value::Value as Json;
14
15pub(crate) struct ScriptHelper {
16 pub(crate) script: AST,
17}
18
19#[inline]
20fn call_script_helper<'reg: 'rc, 'rc>(
21 params: &[PathAndJson<'rc>],
22 hash: &BTreeMap<&'reg str, PathAndJson<'rc>>,
23 engine: &Engine,
24 script: &AST,
25) -> Result<ScopedJson<'rc>, RenderError> {
26 let params: Dynamic = to_dynamic(params.iter().map(|p| p.value()).collect::<Vec<&Json>>())
27 .map_err(RenderErrorReason::from)?;
28
29 let hash: Dynamic = to_dynamic(
30 hash.iter()
31 .map(|(k, v)| ((*k).to_owned(), v.value()))
32 .collect::<HashMap<String, &Json>>(),
33 )
34 .map_err(RenderErrorReason::from)?;
35
36 let mut scope = Scope::new();
37 scope.push_dynamic("params", params);
38 scope.push_dynamic("hash", hash);
39
40 let result = engine
41 .eval_ast_with_scope::<Dynamic>(&mut scope, script)
42 .map_err(RenderErrorReason::from)?;
43
44 let result_json: Json = from_dynamic(&result).map_err(RenderErrorReason::from)?;
45
46 Ok(ScopedJson::Derived(result_json))
47}
48
49impl HelperDef for ScriptHelper {
50 fn call_inner<'reg: 'rc, 'rc>(
51 &self,
52 h: &Helper<'rc>,
53 reg: &'reg Registry<'reg>,
54 _ctx: &'rc Context,
55 _rc: &mut RenderContext<'reg, 'rc>,
56 ) -> Result<ScopedJson<'rc>, RenderError> {
57 call_script_helper(h.params(), h.hash(), ®.engine, &self.script)
58 }
59}
60
61#[cfg(test)]
62mod test {
63 use super::*;
64 use crate::json::value::{PathAndJson, ScopedJson};
65 use rhai::Engine;
66
67 #[test]
68 fn test_dynamic_convert() {
69 let j0 = json! {
70 [{"name": "tomcat"}, {"name": "jetty"}]
71 };
72
73 let d0 = to_dynamic(j0).unwrap();
74 assert_eq!("array", d0.type_name());
75
76 let j1 = json!({
77 "name": "tomcat",
78 "value": 4000,
79 });
80
81 let d1 = to_dynamic(j1).unwrap();
82 assert_eq!("map", d1.type_name());
83 }
84
85 #[test]
86 fn test_to_json() {
87 let d0 = Dynamic::from("tomcat".to_owned());
88
89 assert_eq!(
90 Json::String("tomcat".to_owned()),
91 from_dynamic::<Json>(&d0).unwrap()
92 );
93 }
94
95 #[test]
96 fn test_script_helper_value_access() {
97 let engine = Engine::new();
98
99 let script = "let plen = len(params); let p0 = params[0]; let hlen = len(hash); let hme = hash[\"me\"]; plen + \",\" + p0 + \",\" + hlen + \",\" + hme";
100 let ast = engine.compile(script).unwrap();
101
102 let params = vec![PathAndJson::new(None, ScopedJson::Derived(json!(true)))];
103
104 let mut hash = BTreeMap::new();
105 hash.insert(
106 "me",
107 PathAndJson::new(None, ScopedJson::Derived(json!("no"))),
108 );
109 hash.insert(
110 "you",
111 PathAndJson::new(None, ScopedJson::Derived(json!("yes"))),
112 );
113
114 let result = call_script_helper(¶ms, &hash, &engine, &ast)
115 .unwrap()
116 .render();
117 assert_eq!("1,true,2,no", &result);
118 }
119}