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