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