phlow_engine/
engine.rs

1use crate::repositories::{Repositories, RepositoryFunction};
2use phlow_sdk::{tokio, valu3};
3use regex::Regex;
4use rhai::serde::from_dynamic;
5use rhai::{Dynamic, Engine};
6use std::sync::Arc;
7use std::sync::OnceLock;
8use tokio::runtime::Runtime;
9use tokio::sync::oneshot;
10use valu3::value::Value;
11
12fn build_engine() -> Engine {
13    let mut engine = Engine::new();
14
15    // Define operadores personalizados
16    match engine.register_custom_operator("starts_with", 80) {
17        Ok(engine) => engine.register_fn("start_withs", |x: String, y: String| x.starts_with(&y)),
18        Err(_) => {
19            panic!("Error on register custom operator starts_with");
20        }
21    };
22
23    match engine.register_custom_operator("ends_with", 81) {
24        Ok(engine) => engine.register_fn("ends_with", |x: String, y: String| x.ends_with(&y)),
25        Err(_) => {
26            panic!("Error on register custom operator ends_with");
27        }
28    };
29
30    match engine.register_custom_operator("search", 82) {
31        Ok(engine) => engine.register_fn("search", |x: String, y: String| match Regex::new(&x) {
32            Ok(re) => re.is_match(&y),
33            Err(_) => false,
34        }),
35        Err(_) => {
36            panic!("Error on register custom operator search");
37        }
38    };
39
40    engine
41}
42
43static RUNTIME: OnceLock<Runtime> = OnceLock::new();
44
45pub fn build_engine_sync(repositories: Option<Repositories>) -> Engine {
46    let mut engine = build_engine();
47    let rt = RUNTIME.get_or_init(|| match Runtime::new() {
48        Ok(rt) => rt,
49        Err(e) => panic!("Error creating runtime: {:?}", e),
50    });
51
52    if let Some(repositories) = repositories {
53        for (key, call) in repositories.repositories {
54            let call: RepositoryFunction = Arc::new(move |value: Value| -> Value {
55                let call_clone = Arc::clone(&call);
56                let (tx, rx) = oneshot::channel();
57
58                rt.spawn(async move {
59                    let result = (call_clone)(value);
60                    let _ = tx.send(result);
61                });
62
63                rx.blocking_recv().unwrap_or(Value::Null)
64            }) as RepositoryFunction;
65
66            engine.register_fn(key.clone(), move |dynamic: Dynamic| {
67                let value: Value = match from_dynamic(&dynamic) {
68                    Ok(value) => value,
69                    Err(_) => Value::Null,
70                };
71                call(value)
72            });
73        }
74    }
75
76    engine
77}
78
79pub fn build_engine_async(repositories: Option<Repositories>) -> Arc<Engine> {
80    let mut engine = build_engine();
81
82    if let Some(repositories) = repositories {
83        for (key, call) in repositories.repositories {
84            let call: RepositoryFunction = Arc::new(move |value: Value| -> Value {
85                let call_clone = Arc::clone(&call);
86                let (tx, rx) = oneshot::channel();
87
88                // Executa a chamada assíncrona corretamente sem criar um novo runtime
89                tokio::task::spawn(async move {
90                    let result = (call_clone)(value);
91                    let _ = tx.send(result);
92                });
93
94                // Usa tokio::runtime::Handle::current() para evitar erro de runtime
95                rx.blocking_recv().unwrap_or(Value::Null) // Aguarda sem criar outro runtime
96            }) as RepositoryFunction;
97
98            engine.register_fn(key.clone(), move |dynamic: Dynamic| {
99                let value: Value = match from_dynamic(&dynamic) {
100                    Ok(value) => value,
101                    Err(_) => Value::Null,
102                };
103                call(value)
104            });
105        }
106    }
107
108    Arc::new(engine)
109}
110
111#[cfg(test)]
112mod tests {
113    use crate::plugin;
114
115    use super::*;
116    use std::collections::HashMap;
117    use std::sync::Arc;
118    use valu3::value::Value;
119
120    #[test]
121    fn test_custom_operators() {
122        let engine = build_engine_async(None);
123
124        let result: bool = engine.eval(r#""hello" starts_with "he""#).unwrap();
125        assert!(result);
126
127        let result: bool = engine.eval(r#""world" ends_with "ld""#).unwrap();
128        assert!(result);
129
130        let result: bool = engine.eval(r#""\\d+" search "123""#).unwrap();
131        assert!(result);
132    }
133
134    #[test]
135    fn test_repository_function() {
136        let mut repositories = HashMap::new();
137
138        let mock_function: Arc<dyn Fn(Value) -> Value + Send + Sync> = plugin!(|value| {
139            if let Value::String(s) = value {
140                Value::from(format!("{}-processed", s))
141            } else {
142                Value::Null
143            }
144        });
145
146        repositories.insert("process".to_string(), mock_function);
147
148        let repos = Repositories { repositories };
149        let engine = build_engine_sync(Some(repos));
150
151        let result: Value = engine.eval(r#"process("data")"#).unwrap();
152
153        assert_eq!(result, Value::from("data-processed"));
154    }
155}