1use crate::preprocessor::SpreadPreprocessor;
2use crate::variable::Variable;
3use regex::Regex;
4use rhai::{
5 AST, Engine, EvalAltResult, ParseError, Scope,
6 serde::{from_dynamic, to_dynamic},
7};
8use std::{collections::HashMap, fmt::Display, sync::Arc};
9use valu3::prelude::*;
10
11type Context = HashMap<String, Value>;
12
13#[derive(Debug)]
14pub enum ScriptError {
15 EvalError(Box<EvalAltResult>),
16 InvalidType(Value),
17 CompileError(String, ParseError),
18}
19
20impl Display for ScriptError {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 ScriptError::EvalError(err) => write!(f, "Eval error: {}", err),
24 ScriptError::InvalidType(value) => write!(f, "Invalid type: {}", value),
25 ScriptError::CompileError(code, err) => write!(f, "Compile error: {}: {}", code, err),
26 }
27 }
28}
29
30#[derive(Debug, Clone)]
31pub struct Script {
32 map_extracted: Value,
33 map_index_ast: HashMap<usize, AST>,
34 engine: Arc<Engine>,
35}
36
37impl Script {
38 pub fn try_build(engine: Arc<Engine>, script: &Value) -> Result<Self, ScriptError> {
39 let mut map_index_ast = HashMap::new();
40 let mut counter = 0;
41 let map_extracted =
42 Self::extract_primitives(&engine, &script, &mut map_index_ast, &mut counter)?;
43
44 Ok(Self {
45 map_extracted,
46 map_index_ast,
47 engine,
48 })
49 }
50
51 pub fn evaluate_from_scope(&self, scope: &mut Scope) -> Result<Value, ScriptError> {
52 Self::default_scope(scope)?;
53
54 let mut result_map: HashMap<usize, Value> = HashMap::new();
55
56 for (key, value) in self.map_index_ast.iter() {
57 let value = self
58 .engine
59 .eval_ast_with_scope(scope, &value)
60 .map_err(ScriptError::EvalError)?;
61
62 result_map.insert(*key, from_dynamic(&value).map_err(ScriptError::EvalError)?);
63 }
64
65 let result = Self::replace_primitives(&self.map_extracted, &result_map);
66
67 Ok(result)
68 }
69
70 pub fn evaluate(&self, context: &Context) -> Result<Value, ScriptError> {
71 let mut scope = Scope::new();
72
73 for (key, value) in context.iter() {
74 let value = to_dynamic(value).map_err(ScriptError::EvalError)?;
75 scope.push_constant(key, value);
76 }
77
78 self.evaluate_from_scope(&mut scope)
79 }
80
81 pub fn evaluate_without_context(&self) -> Result<Value, ScriptError> {
82 self.evaluate(&Context::new())
83 }
84
85 pub fn evaluate_variable(&self, context: &Context) -> Result<Variable, ScriptError> {
86 let value = self.evaluate(context)?;
87 Ok(Variable::new(value))
88 }
89
90 fn default_scope(scope: &mut Scope) -> Result<(), ScriptError> {
91 let envs = {
92 let envs = std::env::vars()
93 .map(|(key, value)| (key, value))
94 .collect::<HashMap<String, String>>();
95
96 to_dynamic(envs).map_err(ScriptError::EvalError)?
97 };
98
99 scope.push_constant("envs", envs);
100
101 Ok(())
102 }
103
104 fn replace_null_safe(code: &str) -> String {
105 let re = Regex::new(r"\bnull\b").unwrap();
106 re.replace_all(code, "()").to_string()
107 }
108
109 fn extract_primitives(
110 engine: &Engine,
111 value: &Value,
112 map_index_ast: &mut HashMap<usize, AST>,
113 counter: &mut usize,
114 ) -> Result<Value, ScriptError> {
115 match value {
116 Value::Object(map) => {
117 let mut new_map = HashMap::new();
118
119 for (key, value) in map.iter() {
120 let item = Self::extract_primitives(engine, value, map_index_ast, counter)?;
121 new_map.insert(key.to_string(), item);
122 }
123
124 Ok(Value::from(new_map))
125 }
126 Value::Array(array) => {
127 let mut new_array = Vec::new();
128 for value in array.into_iter() {
129 let item = Self::extract_primitives(engine, value, map_index_ast, counter)?;
130
131 new_array.push(item);
132 }
133
134 Ok(Value::from(new_array))
135 }
136 _ => {
137 let value_string = value.to_string();
138
139 let data_string = {
140 let code = value_string.trim();
141
142 if code.starts_with("{{") && code.ends_with("}}") {
143 let code = code[2..code.len() - 2].to_string();
144
145 let code_fixed = Self::replace_null_safe(&code);
146
147 let preprocessor = SpreadPreprocessor::new();
149 preprocessor.process(&code_fixed)
150 } else if code.parse::<i128>().is_ok()
151 || code.parse::<f64>().is_ok()
152 || code == "true".to_string()
153 || code == "false".to_string()
154 || code == "undefined".to_string()
155 {
156 code.to_string()
157 } else if code == "null".to_string() {
158 "()".to_string()
159 } else {
160 value.to_json_inline()
161 }
162 };
163
164 let ast = match engine.compile(&data_string) {
165 Ok(ast) => ast,
166 Err(err) => {
167 return Err(ScriptError::CompileError(data_string, err));
168 }
169 };
170 map_index_ast.insert(*counter, ast);
171 let result = counter.to_value();
172
173 *counter += 1;
174
175 Ok(result)
176 }
177 }
178 }
179
180 fn replace_primitives(map_extracted: &Value, result: &HashMap<usize, Value>) -> Value {
181 match map_extracted {
182 Value::Object(map) => {
183 let mut new_map = HashMap::new();
184 for (key, value) in map.iter() {
185 new_map.insert(key.to_string(), Self::replace_primitives(value, result));
186 }
187 Value::from(new_map)
188 }
189 Value::Array(array) => {
190 let mut new_array = Vec::new();
191 for value in array.into_iter() {
192 new_array.push(Self::replace_primitives(value, result));
193 }
194 Value::from(new_array)
195 }
196 _ => {
197 let index = match map_extracted.to_i64() {
198 Some(index) => index as usize,
199 None => panic!("Index not found"),
200 };
201 let value = match result.get(&index) {
202 Some(value) => value.clone(),
203 None => panic!("Index not found"),
204 };
205
206 value
207 }
208 }
209 }
210}
211
212#[cfg(test)]
213mod test {
214 use super::*;
215 use crate::build_engine;
216 use std::collections::HashMap;
217
218 #[test]
219 fn test_payload_execute() {
220 let script: &str = r#"{{
221 let a = 10;
222 let b = 20;
223 a + b
224 }}"#;
225
226 let context = Context::new();
227 let engine = build_engine(None);
228 let payload = Script::try_build(engine, &script.to_value()).unwrap();
229
230 let result = payload.evaluate(&context).unwrap();
231 assert_eq!(result, Value::from(30i64));
232 }
233
234 #[test]
235 fn test_payload_json() {
236 let script = r#"{{
237 let a = 10;
238 let b = 20;
239 let c = "hello";
240
241 {
242 a: a,
243 b: b,
244 sum: a + b
245 }
246 }}"#;
247
248 let context = Context::new();
249 let engine = build_engine(None);
250 let payload = Script::try_build(engine, &script.to_value()).unwrap();
251
252 let result = payload.evaluate(&context).unwrap();
253 let expected = Value::from({
254 let mut map = HashMap::new();
255 map.insert("a".to_string(), Value::from(10i64));
256 map.insert("b".to_string(), Value::from(20i64));
257 map.insert("sum".to_string(), Value::from(30i64));
258 map
259 });
260
261 assert_eq!(result, expected);
262 }
263
264 #[test]
265 fn test_payload_execute_variable() {
266 let script = "hello world";
267
268 let context = Context::new();
269 let engine = build_engine(None);
270 let payload = Script::try_build(engine, &script.to_value()).unwrap();
271
272 let variable = payload.evaluate_variable(&context).unwrap();
273 assert_eq!(variable, Variable::new(Value::from("hello world")));
274 }
275
276 #[test]
277 fn test_payload_execute_variable_context() {
278 let script = r#"{{
279 let a = payload.a;
280 let b = payload.b;
281 a + b
282 }}"#;
283
284 let context = HashMap::from([(
285 "payload".to_string(),
286 HashMap::from([
287 ("a".to_string(), Value::from(10i64)),
288 ("b".to_string(), Value::from(20i64)),
289 ])
290 .to_value(),
291 )]);
292
293 let engine = build_engine(None);
294 let payload = Script::try_build(engine, &script.to_value()).unwrap();
295
296 let variable = payload.evaluate_variable(&context).unwrap();
297 assert_eq!(variable, Variable::new(Value::from(30i64)));
298 }
299
300 #[test]
301 fn test_payload_execute_variable_context_params() {
302 let script = r#"{{payload.a}}"#;
303
304 let context = HashMap::from([(
305 "payload".to_string(),
306 HashMap::from([
307 ("a".to_string(), Value::from(10i64)),
308 ("b".to_string(), Value::from(20i64)),
309 ])
310 .to_value(),
311 )]);
312
313 let engine = build_engine(None);
314 let payload = Script::try_build(engine, &script.to_value()).unwrap();
315
316 let variable = payload.evaluate_variable(&context).unwrap();
317 assert_eq!(variable, Variable::new(Value::from(10i64)));
318 }
319
320 #[test]
321 fn test_payload_execute_starts_with_bearer() {
322 let script = r#"{{
323 main.headers.authorization.starts_with("Bearer ")
324 }}"#;
325
326 let context = HashMap::from([(
327 "main".to_string(),
328 HashMap::from([(
329 "headers".to_string(),
330 HashMap::from([("authorization".to_string(), Value::from("Bearer 123456"))])
331 .to_value(),
332 )])
333 .to_value(),
334 )]);
335
336 let engine = build_engine(None);
337 let payload = Script::try_build(engine.clone(), &script.to_value()).unwrap();
338
339 let variable = payload.evaluate_variable(&context).unwrap();
340 assert_eq!(variable, Variable::new(Value::from(true)));
341
342 let context = HashMap::from([(
344 "main".to_string(),
345 HashMap::from([(
346 "headers".to_string(),
347 HashMap::from([("authorization".to_string(), Value::from("Basic 123456"))])
348 .to_value(),
349 )])
350 .to_value(),
351 )]);
352
353 let payload = Script::try_build(engine.clone(), &script.to_value()).unwrap();
354 let variable = payload.evaluate_variable(&context).unwrap();
355 assert_eq!(variable, Variable::new(Value::from(false)));
356 }
357
358 #[test]
359 fn test_object_spread_syntax() {
360 let script = r#"{{
361 let a = {x: 1, y: 2};
362 let b = {z: 3};
363 {...a, y: 20, ...b, w: 4}
364 }}"#;
365
366 let context = Context::new();
367 let engine = build_engine(None);
368 let payload = Script::try_build(engine, &script.to_value()).unwrap();
369
370 let result = payload.evaluate(&context).unwrap();
371 let expected = Value::from({
372 let mut map = HashMap::new();
373 map.insert("x".to_string(), Value::from(1i64));
374 map.insert("y".to_string(), Value::from(20i64)); map.insert("z".to_string(), Value::from(3i64));
376 map.insert("w".to_string(), Value::from(4i64));
377 map
378 });
379
380 assert_eq!(result, expected);
381 }
382
383 #[test]
384 fn test_array_spread_syntax() {
385 let script = r#"{{
386 let a = [1, 2];
387 let b = [5, 6];
388 [...a, 3, 4, ...b, 7]
389 }}"#;
390
391 let context = Context::new();
392 let engine = build_engine(None);
393 let payload = Script::try_build(engine, &script.to_value()).unwrap();
394
395 let result = payload.evaluate(&context).unwrap();
396 let expected = Value::from(vec![
397 Value::from(1i64),
398 Value::from(2i64),
399 Value::from(3i64),
400 Value::from(4i64),
401 Value::from(5i64),
402 Value::from(6i64),
403 Value::from(7i64),
404 ]);
405
406 assert_eq!(result, expected);
407 }
408
409 #[test]
410 fn test_nested_spread_syntax() {
411 let script = r#"{{
412 let user = {name: "John", age: 30};
413 let meta = {id: 1, verified: true};
414
415 {
416 ...user,
417 profile: meta
418 }
419 }}"#;
420
421 let context = Context::new();
422 let engine = build_engine(None);
423 let payload = Script::try_build(engine, &script.to_value()).unwrap();
424
425 let result = payload.evaluate(&context).unwrap();
426
427 if let Value::Object(obj) = result {
429 assert_eq!(obj.get("name").unwrap(), &Value::from("John"));
430 assert_eq!(obj.get("age").unwrap(), &Value::from(30i64));
431
432 if let Some(Value::Object(profile)) = obj.get("profile") {
433 assert_eq!(profile.get("id").unwrap(), &Value::from(1i64));
434 assert_eq!(profile.get("verified").unwrap(), &Value::from(true));
435 } else {
436 panic!("Profile should be an object");
437 }
438 } else {
439 panic!("Result should be an object");
440 }
441 }
442
443 #[test]
444 fn test_nested_invert_spread_syntax() {
445 let script = r#"{{
446 let user = {name: "John", age: 30};
447 let meta = {id: 1, verified: true};
448
449 {
450 profile: meta,
451 ...user,
452 }
453 }}"#;
454
455 let context = Context::new();
456 let engine = build_engine(None);
457 let payload = Script::try_build(engine, &script.to_value()).unwrap();
458
459 let result = payload.evaluate(&context).unwrap();
460
461 if let Value::Object(obj) = result {
463 assert_eq!(obj.get("name").unwrap(), &Value::from("John"));
464 assert_eq!(obj.get("age").unwrap(), &Value::from(30i64));
465
466 if let Some(Value::Object(profile)) = obj.get("profile") {
467 assert_eq!(profile.get("id").unwrap(), &Value::from(1i64));
468 assert_eq!(profile.get("verified").unwrap(), &Value::from(true));
469 } else {
470 panic!("Profile should be an object");
471 }
472 } else {
473 panic!("Result should be an object");
474 }
475 }
476
477 #[test]
478 fn test_complete_spread_example() {
479 let script = r#"{{
480 // Dados de exemplo
481 let user_base = {id: 1, name: "João"};
482 let user_extra = {email: "joao@email.com", active: true};
483 let permissions = ["read", "write"];
484 let admin_permissions = ["admin", "delete"];
485
486 // Usando spread para combinar objetos
487 let complete_user = {...user_base, ...user_extra, role: "user"};
488
489 // Usando spread para combinar arrays
490 let all_permissions = [...permissions, "update", ...admin_permissions];
491
492 {
493 user: complete_user,
494 permissions: all_permissions,
495 total_permissions: all_permissions.len()
496 }
497 }}"#;
498
499 let context = Context::new();
500 let engine = build_engine(None);
501 let payload = Script::try_build(engine, &script.to_value()).unwrap();
502
503 let result = payload.evaluate(&context).unwrap();
504
505 if let Value::Object(obj) = result {
506 if let Some(Value::Object(user)) = obj.get("user") {
508 assert_eq!(user.get("id").unwrap(), &Value::from(1i64));
509 assert_eq!(user.get("name").unwrap(), &Value::from("João"));
510 assert_eq!(user.get("email").unwrap(), &Value::from("joao@email.com"));
511 assert_eq!(user.get("active").unwrap(), &Value::from(true));
512 assert_eq!(user.get("role").unwrap(), &Value::from("user"));
513 } else {
514 panic!("User should be an object");
515 }
516
517 if let Some(Value::Array(permissions)) = obj.get("permissions") {
519 assert_eq!(permissions.len(), 5);
520 assert_eq!(permissions.get(0).unwrap(), &Value::from("read"));
521 assert_eq!(permissions.get(1).unwrap(), &Value::from("write"));
522 assert_eq!(permissions.get(2).unwrap(), &Value::from("update"));
523 assert_eq!(permissions.get(3).unwrap(), &Value::from("admin"));
524 assert_eq!(permissions.get(4).unwrap(), &Value::from("delete"));
525 } else {
526 panic!("Permissions should be an array");
527 }
528
529 assert_eq!(obj.get("total_permissions").unwrap(), &Value::from(5i64));
531 } else {
532 panic!("Result should be an object");
533 }
534 }
535}