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}