deft_quick_js/
lib.rs

1//! quick-js is a a Rust wrapper for [QuickJS](https://bellard.org/quickjs/), a new Javascript
2//! engine by Fabrice Bellard.
3//!
4//! It enables easy and straight-forward execution of modern Javascript from Rust.
5//!
6//! ## Limitations
7//!
8//! * Building on Windows requires the `x86_64-pc-windows-gnu` toolchain
9//!
10//! ## Quickstart:
11//!
12//! ```rust
13//! use deft_quick_js::{Context, JsValue};
14//!
15//! let context = Context::new().unwrap();
16//!
17//! // Eval.
18//!
19//! let value = context.eval("1 + 2").unwrap();
20//! assert_eq!(value, JsValue::Int(3));
21//!
22//! let value = context.eval_as::<String>(" var x = 100 + 250; x.toString() ").unwrap();
23//! assert_eq!(&value, "350");
24//!
25//! // Callbacks.
26//!
27//! context.add_callback("myCallback", |a: i32, b: i32| a + b).unwrap();
28//!
29//! context.eval(r#"
30//!     // x will equal 30
31//!     var x = myCallback(10, 20);
32//! "#).unwrap();
33//! ```
34
35// #![deny(missing_docs)]
36
37extern crate core;
38
39pub mod bindings;
40mod callback;
41pub mod console;
42mod value;
43
44#[cfg(test)]
45mod tests;
46pub mod loader;
47pub mod exception;
48
49use std::{convert::TryFrom, error, fmt};
50use std::any::Any;
51use libquickjs_sys::{JS_EVAL_TYPE_GLOBAL, JS_EVAL_TYPE_MODULE};
52use loader::JsModuleLoader;
53
54pub use self::{
55    callback::{Arguments, Callback},
56    value::*,
57};
58
59pub use libquickjs_sys;
60use crate::exception::HostPromiseRejectionTracker;
61
62/// Error on Javascript execution.
63#[derive(Debug)]
64pub enum ExecutionError {
65    /// Code to be executed contained zero-bytes.
66    InputWithZeroBytes,
67    /// Value conversion failed. (either input arguments or result value).
68    Conversion(ValueError),
69    /// Internal error.
70    Internal(String),
71    /// JS Exception was thrown.
72    Exception(JsValue),
73    /// JS Runtime exceeded the memory limit.
74    OutOfMemory,
75    #[doc(hidden)]
76    __NonExhaustive,
77}
78
79impl fmt::Display for ExecutionError {
80    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81        use ExecutionError::*;
82        match self {
83            InputWithZeroBytes => write!(f, "Invalid script input: code contains zero byte (\\0)"),
84            Conversion(e) => e.fmt(f),
85            Internal(e) => write!(f, "Internal error: {}", e),
86            Exception(e) => write!(f, "{:?}", e),
87            OutOfMemory => write!(f, "Out of memory: runtime memory limit exceeded"),
88            __NonExhaustive => unreachable!(),
89        }
90    }
91}
92
93impl error::Error for ExecutionError {}
94
95impl From<ValueError> for ExecutionError {
96    fn from(v: ValueError) -> Self {
97        ExecutionError::Conversion(v)
98    }
99}
100
101/// Error on context creation.
102#[derive(Debug)]
103pub enum ContextError {
104    /// Runtime could not be created.
105    RuntimeCreationFailed,
106    /// Context could not be created.
107    ContextCreationFailed,
108    /// Execution error while building.
109    Execution(ExecutionError),
110    #[doc(hidden)]
111    __NonExhaustive,
112}
113
114impl fmt::Display for ContextError {
115    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116        use ContextError::*;
117        match self {
118            RuntimeCreationFailed => write!(f, "Could not create runtime"),
119            ContextCreationFailed => write!(f, "Could not create context"),
120            Execution(e) => e.fmt(f),
121            __NonExhaustive => unreachable!(),
122        }
123    }
124}
125
126impl error::Error for ContextError {}
127
128/// A builder for [Context](Context).
129///
130/// Create with [Context::builder](Context::builder).
131pub struct ContextBuilder {
132    memory_limit: Option<usize>,
133    console_backend: Option<Box<dyn console::ConsoleBackend>>,
134    module_loader: Option<Box<dyn JsModuleLoader>>,
135}
136
137impl ContextBuilder {
138    fn new() -> Self {
139        Self {
140            memory_limit: None,
141            console_backend: None,
142            module_loader: None,
143        }
144    }
145
146    /// Sets the memory limit of the Javascript runtime (in bytes).
147    ///
148    /// If the limit is exceeded, methods like `eval` will return
149    /// a `Err(ExecutionError::Exception(JsValue::Null))`
150    // TODO: investigate why we don't get a proper exception message here.
151    pub fn memory_limit(self, max_bytes: usize) -> Self {
152        let mut s = self;
153        s.memory_limit = Some(max_bytes);
154        s
155    }
156
157    /// Set a console handler that will proxy `console.{log,trace,debug,...}`
158    /// calls.
159    ///
160    /// The given argument must implement the [console::ConsoleBackend] trait.
161    ///
162    /// A very simple logger could look like this:
163    pub fn console<B>(mut self, backend: B) -> Self
164    where
165        B: console::ConsoleBackend,
166    {
167        self.console_backend = Some(Box::new(backend));
168        self
169    }
170
171    /// Set js module loader
172    pub fn module_loader<L>(mut self, loader: L) -> Self
173    where
174        L: JsModuleLoader,
175    {
176        self.module_loader = Some(Box::new(loader));
177        self
178    }
179
180    /// Finalize the builder and build a JS Context.
181    pub fn build(self) -> Result<Context, ContextError> {
182        let mut wrapper = bindings::ContextWrapper::new(self.memory_limit)?;
183        if let Some(be) = self.console_backend {
184            wrapper.set_console(be).map_err(ContextError::Execution)?;
185        }
186        if let Some(ml) = self.module_loader {
187            wrapper.set_module_loader(ml);
188        }
189        Ok(Context::from_wrapper(wrapper))
190    }
191}
192
193/// Context is a wrapper around a QuickJS Javascript context.
194/// It is the primary way to interact with the runtime.
195///
196/// For each `Context` instance a new instance of QuickJS
197/// runtime is created. It means that it is safe to use
198/// different contexts in different threads, but each
199/// `Context` instance must be used only from a single thread.
200pub struct Context {
201    wrapper: bindings::ContextWrapper,
202}
203
204impl Context {
205    fn from_wrapper(wrapper: bindings::ContextWrapper) -> Self {
206        Self { wrapper }
207    }
208
209    /// Create a `ContextBuilder` that allows customization of JS Runtime settings.
210    ///
211    /// For details, see the methods on `ContextBuilder`.
212    ///
213    /// ```rust
214    /// let _context = deft_quick_js::Context::builder()
215    ///     .memory_limit(100_000)
216    ///     .build()
217    ///     .unwrap();
218    /// ```
219    pub fn builder() -> ContextBuilder {
220        ContextBuilder::new()
221    }
222
223    /// Create a new Javascript context with default settings.
224    pub fn new() -> Result<Self, ContextError> {
225        let wrapper = bindings::ContextWrapper::new(None)?;
226        Ok(Self::from_wrapper(wrapper))
227    }
228
229    /// Reset the Javascript engine.
230    ///
231    /// All state and callbacks will be removed.
232    pub fn reset(self) -> Result<Self, ContextError> {
233        let wrapper = self.wrapper.reset()?;
234        Ok(Self { wrapper })
235    }
236
237    /// Evaluates Javascript code and returns the value of the final expression.
238    ///
239    /// **Promises**:
240    /// If the evaluated code returns a Promise, the event loop
241    /// will be executed until the promise is finished. The final value of
242    /// the promise will be returned, or a `ExecutionError::Exception` if the
243    /// promise failed.
244    ///
245    /// ```rust
246    /// use deft_quick_js::{Context, JsValue};
247    /// let context = Context::new().unwrap();
248    ///
249    /// let value = context.eval(" 1 + 2 + 3 ");
250    /// assert_eq!(
251    ///     value,
252    ///     Ok(JsValue::Int(6)),
253    /// );
254    ///
255    /// let value = context.eval(r#"
256    ///     function f() { return 55 * 3; }
257    ///     let y = f();
258    ///     var x = y.toString() + "!"
259    ///     x
260    /// "#);
261    /// assert_eq!(
262    ///     value,
263    ///     Ok(JsValue::String("165!".to_string())),
264    /// );
265    /// ```
266    pub fn eval(&self, code: &str, filename: &str) -> Result<JsValue, ExecutionError> {
267        let value_raw = self.wrapper.eval(code, JS_EVAL_TYPE_GLOBAL, filename)?;
268        let value = value_raw.to_value()?;
269        Ok(value)
270    }
271
272    /// Eval as module
273    pub fn eval_module(&self, code: &str, filename: &str) -> Result<JsValue, ExecutionError> {
274        let value_raw = self.wrapper.eval(code, JS_EVAL_TYPE_MODULE, filename)?;
275        let value = value_raw.to_value()?;
276        Ok(value)
277    }
278
279    /// Evaluates Javascript code and returns the value of the final expression
280    /// as a Rust type.
281    ///
282    /// **Promises**:
283    /// If the evaluated code returns a Promise, the event loop
284    /// will be executed until the promise is finished. The final value of
285    /// the promise will be returned, or a `ExecutionError::Exception` if the
286    /// promise failed.
287    ///
288    /// ```rust
289    /// use deft_quick_js::{Context};
290    /// let context = Context::new().unwrap();
291    ///
292    /// let res = context.eval_as::<bool>(" 100 > 10 ");
293    /// assert_eq!(
294    ///     res,
295    ///     Ok(true),
296    /// );
297    ///
298    /// let value: i32 = context.eval_as(" 10 + 10 ").unwrap();
299    /// assert_eq!(
300    ///     value,
301    ///     20,
302    /// );
303    /// ```
304    pub fn eval_as<R>(&self, code: &str, filename: &str) -> Result<R, ExecutionError>
305    where
306        R: TryFrom<JsValue>,
307        R::Error: Into<ValueError>,
308    {
309        let value_raw = self.wrapper.eval(code, JS_EVAL_TYPE_GLOBAL,filename)?;
310        let value = value_raw.to_value()?;
311        let ret = R::try_from(value).map_err(|e| e.into())?;
312        Ok(ret)
313    }
314
315    /// Set a global variable.
316    ///
317    /// ```rust
318    /// use deft_quick_js::{Context, JsValue};
319    /// let context = Context::new().unwrap();
320    ///
321    /// context.set_global("someGlobalVariable", 42).unwrap();
322    /// let value = context.eval_as::<i32>("someGlobalVariable").unwrap();
323    /// assert_eq!(
324    ///     value,
325    ///     42,
326    /// );
327    /// ```
328    pub fn set_global<V>(&self, name: &str, value: V) -> Result<(), ExecutionError>
329    where
330        V: Into<JsValue>,
331    {
332        let global = self.wrapper.global()?;
333        let v = self.wrapper.serialize_value(value.into())?;
334        global.set_property(name, v)?;
335        Ok(())
336    }
337
338    /// Call a global function in the Javascript namespace.
339    ///
340    /// **Promises**:
341    /// If the evaluated code returns a Promise, the event loop
342    /// will be executed until the promise is finished. The final value of
343    /// the promise will be returned, or a `ExecutionError::Exception` if the
344    /// promise failed.
345    ///
346    /// ```rust
347    /// use deft_quick_js::{Context, JsValue};
348    /// let context = Context::new().unwrap();
349    ///
350    /// let res = context.call_function("encodeURIComponent", vec!["a=b"]);
351    /// assert_eq!(
352    ///     res,
353    ///     Ok(JsValue::String("a%3Db".to_string())),
354    /// );
355    /// ```
356    pub fn call_function(
357        &self,
358        function_name: &str,
359        args: impl IntoIterator<Item = impl Into<JsValue>>,
360    ) -> Result<JsValue, ExecutionError> {
361        let qargs = args
362            .into_iter()
363            .map(|arg| self.wrapper.serialize_value(arg.into()))
364            .collect::<Result<Vec<_>, _>>()?;
365
366        let global = self.wrapper.global()?;
367        let func = global
368            .property_require(function_name)?
369            .try_into_function()?;
370        let v = self.wrapper.call_function(func, qargs)?.to_value()?;
371        Ok(v)
372    }
373
374    /// Call a js function
375    pub fn call_js_function(
376        &self,
377        function: impl Into<JsValue>,
378        args: impl IntoIterator<Item = impl Into<JsValue>>,
379    ) -> Result<JsValue, ExecutionError> {
380        let qargs = args
381            .into_iter()
382            .map(|arg| self.wrapper.serialize_value(arg.into()))
383            .collect::<Result<Vec<_>, _>>()?;
384        let func = self.wrapper.serialize_value(function.into())?.try_into_function()?;
385        let v = self.wrapper.call_function(func, qargs)?.to_value()?;
386        Ok(v)
387    }
388
389    /// Add a global JS function that is backed by a Rust function or closure.
390    ///
391    /// The callback must satisfy several requirements:
392    /// * accepts 0 - 5 arguments
393    /// * each argument must be convertible from a JsValue
394    /// * must return a value
395    /// * the return value must either:
396    ///   - be convertible to JsValue
397    ///   - be a Result<T, E> where T is convertible to JsValue
398    ///     if Err(e) is returned, a Javascript exception will be raised
399    ///
400    /// ```rust
401    /// use deft_quick_js::{Context, JsValue};
402    /// let context = Context::new().unwrap();
403    ///
404    /// // Register a closue as a callback under the "add" name.
405    /// // The 'add' function can now be called from Javascript code.
406    /// context.add_callback("add", |a: i32, b: i32| { a + b }).unwrap();
407    ///
408    /// // Now we try out the 'add' function via eval.
409    /// let output = context.eval_as::<i32>(" add( 3 , 4 ) ").unwrap();
410    /// assert_eq!(
411    ///     output,
412    ///     7,
413    /// );
414    /// ```
415    pub fn add_callback<F>(
416        &self,
417        name: &str,
418        callback: impl Callback<F> + 'static,
419    ) -> Result<(), ExecutionError> {
420        self.wrapper.add_callback(name, callback)
421    }
422    
423    pub fn execute_pending_job(&self) -> Result<bool, ExecutionError> {
424        self.wrapper.execute_pending_job()
425    }
426
427    /// Execute module
428    pub fn execute_module(&self, module_name: &str) -> Result<(), ExecutionError> {
429        self.wrapper.execute_module(module_name)
430    }
431
432    pub fn set_promise_rejection_tracker<F: HostPromiseRejectionTracker + 'static>(&mut self, tracker: F) {
433        self.wrapper.set_host_promise_rejection_tracker(tracker);
434    }
435
436}