1use crate::context::Context;
2use crate::variable::Variable;
3use phlow_sdk::prelude::*;
4use rhai::{
5 plugin::*,
6 serde::{from_dynamic, to_dynamic},
7 Engine, EvalAltResult, ParseError, Scope, AST,
8};
9use std::{collections::HashMap, sync::Arc};
10
11#[derive(Debug)]
12pub enum ScriptError {
13 EvalError(Box<EvalAltResult>),
14 InvalidType(Value),
15 CompileError(String, ParseError),
16}
17
18#[derive(Debug, Clone)]
19pub struct Script {
20 map_extracted: Value,
21 map_index_ast: HashMap<usize, AST>,
22 engine: Arc<Engine>,
23}
24
25impl Script {
26 pub fn try_build(engine: Arc<Engine>, script: &Value) -> Result<Self, ScriptError> {
27 let mut map_index_ast = HashMap::new();
28 let mut counter = 0;
29 let map_extracted =
30 Self::extract_primitives(&engine, &script, &mut map_index_ast, &mut counter)?;
31
32 Ok(Self {
33 map_extracted,
34 map_index_ast,
35 engine,
36 })
37 }
38
39 pub fn to_code_string(code: &str) -> String {
40 let code = code.trim();
41 if code.starts_with("{{") && code.ends_with("}}") {
42 code[2..code.len() - 2].to_string()
43 } else if code.parse::<i128>().is_ok()
44 || code.parse::<f64>().is_ok()
45 || code == "true".to_string()
46 || code == "false".to_string()
47 || code == "null".to_string()
48 || code == "undefined".to_string()
49 {
50 code.to_string()
51 } else {
52 format!("`{}`", code)
53 }
54 }
55
56 pub fn evaluate(&self, context: &Context) -> Result<Value, ScriptError> {
57 let mut scope = Scope::new();
58
59 let steps: Dynamic = to_dynamic(context.steps.clone()).map_err(ScriptError::EvalError)?;
60 let main: Dynamic = to_dynamic(context.main.clone()).map_err(ScriptError::EvalError)?;
61 let payload: Dynamic =
62 to_dynamic(context.payload.clone()).map_err(ScriptError::EvalError)?;
63 let input: Dynamic = to_dynamic(context.input.clone()).map_err(ScriptError::EvalError)?;
64
65 scope.push_constant("steps", steps);
66 scope.push_constant("main", main);
67 scope.push_constant("payload", payload);
68 scope.push_constant("input", input);
69
70 let mut result_map: HashMap<usize, Value> = HashMap::new();
71
72 for (key, value) in self.map_index_ast.iter() {
73 let value = self
74 .engine
75 .eval_ast_with_scope(&mut scope, &value)
76 .map_err(ScriptError::EvalError)?;
77
78 result_map.insert(*key, from_dynamic(&value).map_err(ScriptError::EvalError)?);
79 }
80
81 let result = Self::replace_primitives(&self.map_extracted, &result_map);
82
83 Ok(result)
84 }
85
86 pub fn evaluate_variable(&self, context: &Context) -> Result<Variable, ScriptError> {
87 let value = self.evaluate(context)?;
88 Ok(Variable::new(value))
89 }
90
91 fn extract_primitives(
92 engine: &Engine,
93 value: &Value,
94 map_index_ast: &mut HashMap<usize, AST>,
95 counter: &mut usize,
96 ) -> Result<Value, ScriptError> {
97 match value {
98 Value::Object(map) => {
99 let mut new_map = HashMap::new();
100
101 for (key, value) in map.iter() {
102 let item = Self::extract_primitives(engine, value, map_index_ast, counter)?;
103 new_map.insert(key.to_string(), item);
104 }
105
106 Ok(Value::from(new_map))
107 }
108 Value::Array(array) => {
109 let mut new_array = Vec::new();
110 for value in array.into_iter() {
111 let item = Self::extract_primitives(engine, value, map_index_ast, counter)?;
112
113 new_array.push(item);
114 }
115
116 Ok(Value::from(new_array))
117 }
118 _ => {
119 let code = Self::to_code_string(&value.to_string());
120
121 let ast = match engine.compile(&code) {
122 Ok(ast) => ast,
123 Err(err) => return Err(ScriptError::CompileError(code.clone(), err)),
124 };
125 map_index_ast.insert(*counter, ast);
126
127 let result = Value::from(*counter);
128 *counter += 1;
129
130 Ok(result)
131 }
132 }
133 }
134
135 fn replace_primitives(map_extracted: &Value, result: &HashMap<usize, Value>) -> Value {
136 match map_extracted {
137 Value::Object(map) => {
138 let mut new_map = HashMap::new();
139 for (key, value) in map.iter() {
140 new_map.insert(key.to_string(), Self::replace_primitives(value, result));
141 }
142 Value::from(new_map)
143 }
144 Value::Array(array) => {
145 let mut new_array = Vec::new();
146 for value in array.into_iter() {
147 new_array.push(Self::replace_primitives(value, result));
148 }
149 Value::from(new_array)
150 }
151 _ => {
152 let index = match map_extracted.to_i64() {
153 Some(index) => index as usize,
154 None => panic!("Index not found"),
155 };
156 let value = match result.get(&index) {
157 Some(value) => value.clone(),
158 None => panic!("Index not found"),
159 };
160
161 value
162 }
163 }
164 }
165}
166
167#[cfg(test)]
168mod test {
169 use crate::{id::ID, step_worker::StepWorker};
170
171 use super::*;
172 use phs::build_engine;
173 use std::collections::HashMap;
174
175 #[test]
176 fn test_payload_execute() {
177 let script: &str = r#"{{
178 let a = 10;
179 let b = 20;
180 a + b
181 }}"#;
182
183 let context = Context::new();
184 let engine = build_engine(None);
185 let payload = Script::try_build(engine, &script.to_value()).unwrap();
186
187 let result = payload.evaluate(&context).unwrap();
188 assert_eq!(result, Value::from(30i64));
189 }
190
191 #[test]
192 fn test_payload_json() {
193 let script = r#"{{
194 let a = 10;
195 let b = 20;
196 let c = "hello";
197
198 #{
199 a: a,
200 b: b,
201 sum: a + b
202 }
203 }}"#;
204
205 let context = Context::new();
206 let engine = build_engine(None);
207 let payload = Script::try_build(engine, &script.to_value()).unwrap();
208
209 let result = payload.evaluate(&context).unwrap();
210 let expected = Value::from({
211 let mut map = HashMap::new();
212 map.insert("a".to_string(), Value::from(10i64));
213 map.insert("b".to_string(), Value::from(20i64));
214 map.insert("sum".to_string(), Value::from(30i64));
215 map
216 });
217
218 assert_eq!(result, expected);
219 }
220
221 #[test]
222 fn test_payload_execute_variable() {
223 let script = "hello world";
224
225 let context = Context::new();
226 let engine = build_engine(None);
227 let payload = Script::try_build(engine, &script.to_value()).unwrap();
228
229 let variable = payload.evaluate_variable(&context).unwrap();
230 assert_eq!(variable, Variable::new(Value::from("hello world")));
231 }
232
233 #[test]
234 fn test_payload_execute_variable_context() {
235 let script = r#"{{
236 let a = payload.a;
237 let b = payload.b;
238 a + b
239 }}"#;
240
241 let context = Context::from_payload(Value::from({
242 let mut map = HashMap::new();
243 map.insert("a".to_string(), Value::from(10i64));
244 map.insert("b".to_string(), Value::from(20i64));
245 map
246 }));
247
248 let engine = build_engine(None);
249 let payload = Script::try_build(engine, &script.to_value()).unwrap();
250
251 let variable = payload.evaluate_variable(&context).unwrap();
252 assert_eq!(variable, Variable::new(Value::from(30i64)));
253 }
254
255 #[test]
256 fn test_payload_execute_variable_context_params() {
257 let script = r#"{{payload.a}}"#;
258
259 let context = Context::from_payload(Value::from({
260 let mut map = HashMap::new();
261 map.insert("a".to_string(), Value::from(10i64));
262 map.insert("b".to_string(), Value::from(20i64));
263 map
264 }));
265
266 let engine = build_engine(None);
267 let payload = Script::try_build(engine, &script.to_value()).unwrap();
268
269 let variable = payload.evaluate_variable(&context).unwrap();
270 assert_eq!(variable, Variable::new(Value::from(10i64)));
271 }
272
273 #[test]
274 fn test_payload_execute_variable_step() {
275 let script = r#"{{
276 let a = steps.me.a;
277 let b = steps.me.b;
278
279 a + b
280 }}"#;
281 let step = StepWorker {
282 id: ID::from("me"),
283 ..Default::default()
284 };
285
286 let mut context = Context::new();
287 context.add_step_id_output(step.get_id().clone(), {
288 let mut map = HashMap::new();
289 map.insert("a".to_string(), Value::from(10i64));
290 map.insert("b".to_string(), Value::from(20i64));
291 map.to_value()
292 });
293
294 let engine = build_engine(None);
295 let payload = Script::try_build(engine, &script.to_value()).unwrap();
296
297 let variable = payload.evaluate_variable(&context).unwrap();
298
299 assert_eq!(variable, Variable::new(Value::from(30i64)));
300 }
301}