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