moduforge_rules_engine/handler/function/
function.rs

1use std::hash::{DefaultHasher, Hash, Hasher};
2use std::sync::Arc;
3
4use crate::handler::function::error::{FunctionError, FunctionResult, ResultExt};
5use crate::handler::function::listener::{RuntimeEvent, RuntimeListener};
6use crate::handler::function::module::console::{Console, Log};
7use crate::handler::function::module::ModuleLoader;
8use crate::handler::function::serde::JsValue;
9use rquickjs::promise::MaybePromise;
10use rquickjs::{
11    async_with, AsyncContext, AsyncRuntime, CatchResultExt, Ctx, Module,
12};
13use serde::{Deserialize, Serialize};
14use moduforge_rules_expression::variable::Variable;
15
16pub struct FunctionConfig {
17    pub listeners: Option<Vec<Box<dyn RuntimeListener>>>,
18}
19
20pub struct Function {
21    rt: Arc<AsyncRuntime>,
22    ctx: AsyncContext,
23    listeners: Vec<Box<dyn RuntimeListener>>,
24    module_loader: ModuleLoader,
25}
26
27impl Function {
28    pub async fn create<'js>(config: FunctionConfig) -> FunctionResult<Self> {
29        let module_loader = ModuleLoader::new();
30        let rt = Arc::new(AsyncRuntime::new()?);
31
32        rt.set_loader(module_loader.clone(), module_loader.clone()).await;
33
34        let ctx = AsyncContext::full(&rt).await?;
35        let this = Self {
36            rt,
37            ctx,
38            module_loader,
39            listeners: config.listeners.unwrap_or_default(),
40        };
41
42        this.dispatch_event(RuntimeEvent::Startup).await?;
43        Ok(this)
44    }
45
46    async fn dispatch_event_inner(
47        &self,
48        ctx: &Ctx<'_>,
49        event: RuntimeEvent,
50    ) -> FunctionResult {
51        for listener in &self.listeners {
52            if let Err(err) =
53                listener.on_event(ctx.clone(), event.clone()).await
54            {
55                return Err(err.into());
56            };
57        }
58
59        Ok(())
60    }
61
62    async fn dispatch_event(
63        &self,
64        event: RuntimeEvent,
65    ) -> FunctionResult {
66        async_with!(&self.ctx => |ctx| {
67            self.dispatch_event_inner(&ctx, event).await
68        })
69        .await
70    }
71
72    pub fn context(&self) -> &AsyncContext {
73        &self.ctx
74    }
75
76    pub fn runtime(&self) -> &AsyncRuntime {
77        &self.rt
78    }
79
80    pub fn suggest_module_name<'a>(
81        &self,
82        name: &str,
83        source: &str,
84    ) -> String {
85        let declarative_name = format!("node:{name}");
86
87        if self.module_loader.has_module(&declarative_name) {
88            let content_hash = create_content_hash(source);
89            format!("node:{name}.{content_hash:x}")
90        } else {
91            declarative_name
92        }
93    }
94
95    pub async fn register_module(
96        &self,
97        name: &str,
98        source: &str,
99    ) -> FunctionResult {
100        let maybe_error: Option<FunctionError> = async_with!(&self.ctx => |ctx| {
101            if let Err(err) = Module::declare(ctx.clone(), name.as_bytes().to_vec(), source.as_bytes().to_vec()).catch(&ctx) {
102                return Some(err.into())
103            }
104
105            return None;
106        }).await;
107        if let Some(err) = maybe_error {
108            return Err(err);
109        }
110
111        self.module_loader.add_module(name.to_string());
112        Ok(())
113    }
114
115    pub async fn call_handler(
116        &self,
117        name: &str,
118        data: JsValue,
119    ) -> FunctionResult<HandlerResponse> {
120        let k: FunctionResult<HandlerResponse> = async_with!(&self.ctx => |ctx| {
121            self.dispatch_event_inner(&ctx, RuntimeEvent::SoftReset).await?;
122
123            let m: rquickjs::Object = Module::import(&ctx, name).catch(&ctx)?.into_future().await.catch(&ctx)?;
124            let handler: rquickjs::Function = m.get("handler").catch(&ctx)?;
125
126            let handler_promise: MaybePromise = handler.call((data, 5)).catch(&ctx)?;
127            let handler_result = handler_promise.into_future::<JsValue>().await.catch(&ctx)?;
128
129            let console = Console::from_context(&ctx).or_throw(&ctx)?;
130            let logs = console.logs.into_inner();
131
132            Ok(HandlerResponse { data: handler_result.0, logs })
133        })
134        .await;
135
136        Ok(k?)
137    }
138
139    pub async fn extract_logs(&self) -> Vec<Log> {
140        let logs: Option<Vec<Log>> = async_with!(&self.ctx => |ctx| {
141            let console = Console::from_context(&ctx).ok()?;
142            Some(console.logs.into_inner())
143        })
144        .await;
145
146        logs.unwrap_or_default()
147    }
148}
149
150#[derive(Serialize, Deserialize)]
151pub struct HandlerResponse {
152    pub logs: Vec<Log>,
153    pub data: Variable,
154}
155
156fn create_content_hash(content: &str) -> u64 {
157    let mut hasher = DefaultHasher::new();
158    content.hash(&mut hasher);
159    hasher.finish()
160}