phs/
script.rs

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                        // Aplica o pré-processamento para spread syntax
148                        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        // Test with a non-Bearer value
343        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)); // sobrescreve o valor de a
375            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        // Verifica se a estrutura está correta
428        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        // Verifica se a estrutura está correta
462        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            // Verifica o usuário completo
507            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            // Verifica as permissões combinadas
518            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            // Verifica o total
530            assert_eq!(obj.get("total_permissions").unwrap(), &Value::from(5i64));
531        } else {
532            panic!("Result should be an object");
533        }
534    }
535}