Files
madbase/functions/src/deno_runtime.rs.bak
Vlad Durnea cffdf8af86
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped
wip:milestone 0 fixes
2026-03-15 12:35:42 +02:00

276 lines
12 KiB
Rust

2: ```rust
4: 2: use deno_core::{JsRuntime, v8};
5: 3: use serde_json::Value;
6: 4:
7: 5: use std::collections::HashMap;
8: 6: use std::fs;
9: 7:
10: 8: pub struct DenoRuntime {
11: 9: // We create a new runtime for each execution to ensure isolation
12: 10: // In a production environment, we might want to pool runtimes or use isolates more efficiently
13: 11: }
14: 12:
15: 13: impl DenoRuntime {
16: 14: pub fn new() -> Self {
17: 15: Self {}
18: 16: }
19: 17:
20: 18: pub async fn execute(&self, code: String, payload: Option<Value>, headers: HashMap<String, String>) -> Result<(String, String, u16, HashMap<String, String>)> {
21: 19: let (tx, rx) = tokio::sync::oneshot::channel();
22: 20:
23: 21: std::thread::spawn(move || {
24: 22: let rt = tokio::runtime::Builder::new_current_thread()
25: 23: .enable_all()
26: 24: .build()
27: 25: .unwrap();
28: 26:
29: 27: let local = tokio::task::LocalSet::new();
30: 28: let result = local.block_on(&rt, async { Self::execute_inner(code, payload, headers).await });
31: 29: let _ = tx.send(result);
32: 30: });
33: 31:
34: 32: tokio::time::timeout(std::time::Duration::from_secs(30), rx)
35: 33: .await
36: 34: .map_err(|_| anyhow::anyhow!("Deno execution timed out after 30s"))?
37: 35: .map_err(|_| anyhow::anyhow!("Deno execution thread panicked"))?
38: 36: }
39: 37:
40: 38: async fn execute_inner(code: String, payload: Option<Value>, headers: HashMap<String, String>) -> Result<(String, String, u16, HashMap<String, String>)> {
41: 39: // Initialize JS Runtime with module support
42: 40: let mut runtime = JsRuntime::new(deno_core::RuntimeOptions {
43: 41: module_loader: Some(std::rc::Rc::new(deno_core::FsModuleLoader)),
44: 42: ..Default::default()
45: 43: });
46: 44:
47: 45: // 1. Inject Preamble (Polyfills for Deno.serve, Request, Response, Headers)
48: 46: let preamble = r#"
49: 47: globalThis.console = {
50: 48: log: (...args) => {
51: 49: Deno.core.print(args.map(a => String(a)).join(" ") + "\n");
52: 50: },
53: 51: error: (...args) => {
54: 52: Deno.core.print("[ERROR] " + args.map(a => String(a)).join(" ") + "\n", true);
55: 53: }
56: 54: };
57: 55:
58: 56: class Headers {
59: 57: constructor(init) {
60: 58: this.map = new Map();
61: 59: if (init) {
62: 60: if (init instanceof Headers) {
63: 61: init.forEach((v, k) => this.map.set(k.toLowerCase(), v));
64: 62: } else if (Array.isArray(init)) {
65: 63: init.forEach(([k, v]) => this.map.set(k.toLowerCase(), v));
66: 64: } else {
67: 65: Object.entries(init).forEach(([k, v]) => this.map.set(k.toLowerCase(), v));
68: 66: }
69: 67: }
70: 68: }
71: 69: get(key) { return this.map.get(key.toLowerCase()) || null; }
72: 70: set(key, value) { this.map.set(key.toLowerCase(), value); }
73: 71: has(key) { return this.map.has(key.toLowerCase()); }
74: 72: forEach(callback) { this.map.forEach(callback); }
75: 73: entries() { return this.map.entries(); }
76: 74: }
77: 75: globalThis.Headers = Headers;
78: 76:
79: 77: globalThis.Deno = {
80: 78: serve: (handler) => {
81: 79: globalThis._handler = handler;
82: 80: },
83: 81: core: Deno.core,
84: 82: env: {
85: 83: get: (key) => {
86: 84: return globalThis._env ? globalThis._env[key] : null;
87: 85: },
88: 86: toObject: () => {
89: 87: return globalThis._env || {};
90: 88: }
91: 89: }
92: 90: };
93: 91:
94: 92: class Response {
95: 93: constructor(body, init) {
96: 94: this.body = body;
97: 95: this.status = init?.status || 200;
98: 96: this.headers = new Headers(init?.headers);
99: 97: }
100: 98: async text() { return String(this.body); }
101: 99: async json() { return JSON.parse(this.body); }
102: 100: }
103: 101: globalThis.Response = Response;
104: 102:
105: 103: class Request {
106: 104: constructor(url, init) {
107: 105: this.url = url;
108: 106: this.method = init?.method || "GET";
109: 107: this._body = init?.body;
110: 108: this.headers = new Headers(init?.headers);
111: 109: }
112: 110: async json() { return typeof this._body === 'string' ? JSON.parse(this._body) : this._body; }
113: 111: async text() { return typeof this._body === 'string' ? this._body : JSON.stringify(this._body); }
114: 112: }
115: 113: globalThis.Request = Request;
116: 114: "#;
117: 115:
118: 116: tracing::info!("DenoRuntime: executing preamble");
119: 117: runtime.execute_script("<preamble>", preamble.to_string())?;
120: 118:
121: 119: let payload_json = serde_json::to_string(&payload.unwrap_or(serde_json::json!({})))?;
122: 120: let headers_json = serde_json::to_string(&headers)?;
123: 121:
124: 122: let module_code = format!(r#"
125: 123: // User script
126: 124: {code}
127: 125:
128: 126: // Invocation logic
129: 127: async function invoke() {{
130: 128: if (!globalThis._handler) {{
131: 129: return {{ error: "No handler registered via Deno.serve" }};
132: 130: }}
133: 131: try {{
134: 132: const req = new Request("http://localhost", {{
135: 133: method: "POST",
136: 134: body: {payload_json},
137: 135: headers: {headers_json}
138: 136: }});
139: 137: const res = await globalThis._handler(req);
140: 138: const text = await res.text();
141: 139:
142: 140: const resHeaders = {{}};
143: 141: if (res.headers && typeof res.headers.forEach === 'function') {{
144: 142: res.headers.forEach((v, k) => resHeaders[k] = v);
145: 143: }}
146: 144:
147: 145: return {{
148: 146: result: text,
149: 147: headers: resHeaders,
150: 148: status: res.status
151: 149: }};
152: 150: }} catch (e) {{
153: 151: return {{ error: String(e) }};
154: 152: }}
155: 153: }}
156: 154:
157: 155: globalThis._result = await invoke();
158: 156: "#);
159: 157:
160: 158: let temp_path = format!("/tmp/deno_main_{}.js", uuid::Uuid::new_v4());
161: 159: fs::write(&temp_path, module_code)?;
162: 160:
163: 161: let specifier = deno_core::resolve_url(&format!("file://{}", temp_path))?;
164: 162:
165: 163: tracing::info!("DenoRuntime: loading main module from {}", temp_path);
166: 164: let mod_id = runtime.load_main_es_module(&specifier).await?;
167: 165:
168: 166: tracing::info!("DenoRuntime: evaluating module");
169: 167: let receiver = runtime.mod_evaluate(mod_id);
170: 168:
171: 169: // Wait for module execution to finish and drain event loop
172: 170: runtime.run_event_loop(deno_core::PollEventLoopOptions::default()).await?;
173: 171: receiver.await?;
174: 172: tracing::info!("DenoRuntime: module evaluated");
175: 173:
176: 174: // Clean up temp file
177: 175: let _ = fs::remove_file(&temp_path);
178: 176:
179: 177: // Extract result
180: 178: let result_val = runtime.execute_script("<extract>", "globalThis._result".to_string())?;
181: 179: let scope = &mut runtime.handle_scope();
182: 180: let local = v8::Local::new(scope, result_val);
183: 181: let deserialized_value: Value = deno_core::serde_v8::from_v8(scope, local)?;
184: 182:
185: 183: let stdout = if let Some(res) = deserialized_value.get("result") {
186: 184: res.as_str().unwrap_or("").to_string()
187: 185: } else {
188: 186: String::new()
189: 187: };
190: 188:
191: 189: let stderr = if let Some(err) = deserialized_value.get("error") {
192: 190: err.as_str().unwrap_or("Unknown error").to_string()
193: 191: } else {
194: 192: String::new()
195: 193: };
196: 194:
197: 195: let status = if let Some(s) = deserialized_value.get("status") {
198: 196: s.as_u64().unwrap_or(200) as u16
199: 197: } else {
200: 198: 200
201: 199: };
202: 200:
203: 201: let mut headers = HashMap::new();
204: 202: if let Some(h) = deserialized_value.get("headers") {
205: 203: if let Some(obj) = h.as_object() {
206: 204: for (k, v) in obj {
207: 205: if let Some(s) = v.as_str() {
208: 206: headers.insert(k.clone(), s.to_string());
209: 207: }
210: 208: }
211: 209: }
212: 210: }
213: 211:
214: 212: Ok((stdout, stderr, status, headers))
215: 213: }
216: 214: }
217: 215:
218: 216: #[cfg(test)]
219: 217: mod tests {
220: 218: use super::*;
221: 219: use std::collections::HashMap;
222: 220:
223: 221: #[tokio::test]
224: 222: async fn test_deno_runtime_simple_execution() {
225: 223: let runtime = DenoRuntime::new();
226: 224: let code = r#"
227: 225: Deno.serve((req) => {
228: 226: return new Response("Hello from MadBase");
229: 227: });
230: 228: "#;
231: 229:
232: 230: let (stdout, stderr, status, _headers) = runtime.execute(code.to_string(), None, HashMap::new())
233: 231: .await
234: 232: .expect("Execution failed");
235: 233:
236: 234: assert_eq!(stdout, "Hello from MadBase");
237: 235: assert_eq!(stderr, "");
238: 236: assert_eq!(status, 200);
239: 237: }
240: 238:
241: 239: #[tokio::test]
242: 240: async fn test_deno_runtime_async_promise() {
243: 241: let runtime = DenoRuntime::new();
244: 242: let code = r#"
245: 243: Deno.serve(async (req) => {
246: 244: await Promise.resolve();
247: 245: return new Response("Promise OK");
248: 246: });
249: 247: "#;
250: 248:
251: 249: let (stdout, _stderr, status, _) = runtime.execute(code.to_string(), None, HashMap::new())
252: 250: .await
253: 251: .expect("Execution failed");
254: 252:
255: 253: assert_eq!(stdout, "Promise OK");
256: 254: assert_eq!(status, 200);
257: 255: }
258: 256:
259: 257: #[tokio::test]
260: 258: async fn test_deno_runtime_error_handling() {
261: 259: let runtime = DenoRuntime::new();
262: 260: let code = r#"
263: 261: Deno.serve((req) => {
264: 262: throw new Error("Custom Error");
265: 263: });
266: 264: "#;
267: 265:
268: 266: let (stdout, stderr, _status, _) = runtime.execute(code.to_string(), None, HashMap::new())
269: 267: .await
270: 268: .expect("Execution failed");
271: 269:
272: 270: assert_eq!(stdout, "");
273: 271: assert!(stderr.contains("Custom Error"));
274: 272: }
275: 273: }
276: ```