1use std::borrow::Cow;
4use std::path::Path;
5use std::rc::Rc;
6
7use deno_core::{JsRuntime, OpState, RuntimeOptions, ZeroCopyBuf};
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11use crate::{AnyError, JsValue};
12
13pub struct Script {
18 runtime: JsRuntime,
19 last_rid: u32,
20}
21
22impl Script {
23 const DEFAULT_FILENAME: &'static str = "sandboxed.js";
24
25 pub fn from_string(js_code: &str) -> Result<Self, AnyError> {
29 let all_code = "const console = { log: function(expr) { Deno.core.print(expr + '\\n', false); } };".to_string() + js_code;
31
32 Self::create_script(&all_code, Self::DEFAULT_FILENAME)
33 }
34
35 pub fn from_file(file: impl AsRef<Path>) -> Result<Self, AnyError> {
39 let filename = file
40 .as_ref()
41 .file_name()
42 .and_then(|s| s.to_str())
43 .unwrap_or(Self::DEFAULT_FILENAME)
44 .to_owned();
45
46 match std::fs::read_to_string(file) {
47 Ok(js_code) => Self::create_script(&js_code, &filename),
48 Err(e) => Err(AnyError::from(e)),
49 }
50 }
51
52 pub fn call<P, R>(&mut self, fn_name: &str, args: &P) -> Result<R, AnyError>
57 where
58 P: Serialize,
59 R: DeserializeOwned,
60 {
61 let json_args = serde_json::to_value(args)?;
62 let json_result = self.call_json(fn_name, &json_args)?;
63 let result: R = serde_json::from_value(json_result)?;
64
65 Ok(result)
66 }
67
68 pub(crate) fn call_json(&mut self, fn_name: &str, args: &JsValue) -> Result<JsValue, AnyError> {
69 let js_code = format!("{{
74 let __rust_result = {f}({a});
75 if (typeof __rust_result === 'undefined')
76 __rust_result = null;
77
78 Deno.core.ops();
79 Deno.core.opSync(\"__rust_return\", __rust_result);\
80 }}", f = fn_name, a = args);
81
82 self.runtime.execute(Self::DEFAULT_FILENAME, &js_code)?;
83
84 let state_rc = self.runtime.op_state();
85 let mut state = state_rc.borrow_mut();
86 let table = &mut state.resource_table;
87
88 let entry: Rc<ResultResource> = table.take(self.last_rid).expect("Resource entry must be present");
90 let extracted = Rc::try_unwrap(entry).expect("Rc must hold single strong ref to resource entry");
91 self.last_rid += 1;
92
93 Ok(extracted.json_value)
94 }
95
96 fn create_script(js_code: &str, js_filename: &str) -> Result<Self, AnyError> {
97 let options = RuntimeOptions::default();
98
99 let mut runtime = JsRuntime::new(options);
100 runtime.execute(js_filename, &js_code)?;
101 runtime.register_op("__rust_return", deno_core::op_sync(Self::op_return));
102
103 Ok(Script { runtime, last_rid: 0 })
104 }
105
106 fn op_return(
107 state: &mut OpState,
108 args: JsValue,
109 _buf: Option<ZeroCopyBuf>,
110 ) -> Result<JsValue, AnyError> {
111 let entry = ResultResource { json_value: args };
112 let resource_table = &mut state.resource_table;
113 let _rid = resource_table.add(entry);
114 Ok(serde_json::Value::Null)
117 }
118}
119
120#[derive(Debug)]
121struct ResultResource {
122 json_value: JsValue
123}
124
125impl deno_core::Resource for ResultResource {
127 fn name(&self) -> Cow<str> {
128 "__rust_Result".into()
129 }
130}