js-sandbox

crates.io docs.rs

js-sandbox is a Rust library for executing JavaScript code from Rust in a secure sandbox. It is based on the [Deno] project and uses [serde_json] for serialization.

This library's primary focus is embedding JS as a scripting language into Rust. It does not provide all possible integrations between the two languages, and is not tailored to JS's biggest domain as a client/server side language of the web.

Instead, js-sandbox focuses on calling standalone JS code from Rust, and tries to remain as simple as possible in doing so. The typical use case is a core Rust application that integrates with scripts from external users, for example a plugin system or a game that runs external mods.

This library is in early development, with a basic but powerful API. The API may still evolve considerably.

Examples

Print from JavaScript

The Hello World example -- print something using JavaScript -- is one line, as it should be: rust fn main() { js_sandbox::eval_json("console.log('Hello Rust from JS')").expect("JS runs"); }

Call a JS function

A very basic application calls a JavaScript function sub() from Rust. It passes an argument and accepts a return value, both serialized via JSON:

```rust use js_sandbox::{Script, AnyError};

fn main() -> Result<(), AnyError> { let jscode = "function sub(a, b) { return a - b; }"; let mut script = Script::fromstring(js_code)?;

let result: i32 = script.call("sub", (7, 5))?;

assert_eq!(result, 2);
Ok(())

} ```

An example that serializes a JSON object (Rust -> JS) and formats a string (JS -> Rust):

```rust use js_sandbox::{Script, AnyError}; use serde::Serialize;

[derive(Serialize)]

struct Person { name: String, age: u8, }

fn main() -> Result<(), AnyError> { let src = r#" function toString(person) { return "A person named " + person.name + " of age " + person.age; }"#;

let mut script = Script::from_string(src)?;

let person = Person { name: "Roger".to_string(), age: 42 };
let result: String = script.call("toString", (person,))?;

assert_eq!(result, "A person named Roger of age 42");
Ok(())

} ```

Load JS file

JavaScript files can be loaded from any Path at runtime (e.g. 3rd party mods).

If you want to statically embed UTF-8 encoded files in the Rust binary, you can alternatively use the std::include_str macro.

```rust use js_sandbox::Script;

fn main() { // (1) at runtime: let mut script = Script::from_file("script.js").expect("load + init succeeds");

// (2) at compile time:
let code: &'static str = include_str!("script.js");
let mut script = Script::from_string(code).expect("init succeeds");

// use script as usual

} ```

Maintain state in JavaScript

It is possible to initialize a stateful JS script, and then use functions to modify that state over time. This example appends a string in two calls, and then gets the result in a third call:

```rust use js_sandbox::{Script, AnyError};

fn main() -> Result<(), AnyError> { let src = r#" var total = ''; function append(str) { total += str; } function get() { return total; }"#;

let mut script = Script::from_string(src)?;

let _: () = script.call("append", ("hello",))?;
let _: () = script.call("append", (" world",))?;
let result: String = script.call("get", ())?;

assert_eq!(result, "hello world");
Ok(())

} ```

Call a script with timeout

The JS code may contain long- or forever-running loops that block Rust code. It is possible to set a timeout, after which JavaScript execution is aborted.

```rust use js_sandbox::{Script, AnyError};

fn main() -> Result<(), AnyError> { use std::time::Duration; let jscode = "function runforever() { for(;;) {} }"; let mut script = Script::fromstring(jscode)? .withtimeout(Duration::frommillis(1000));

let result: Result<String, AnyError> = script.call("run_forever", ());

assert_eq!(
    result.unwrap_err().to_string(),
    "Uncaught Error: execution terminated".to_string()
);

Ok(())

} ```