moduforge_rules_engine/handler/function/
function.rs1use 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}