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 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 tokio::task::spawn(async move {
90 let result = (call_clone)(value);
91 let _ = tx.send(result);
92 });
93
94 rx.blocking_recv().unwrap_or(Value::Null) }) 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}