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
276 lines
12 KiB
Rust
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: ```
|
|
|