js_sandbox_ios/
lib.rs

1// Copyright (c) 2020-2023 js-sandbox contributors. Zlib license.
2
3// Note: the crate documentation is copied to README.md using cargo-readme (see CI)
4// Alternatives:
5// * once stable: #![feature(external_doc)] #![doc(include = "../README.md")]
6// * doc_comment crate + doctest!("../README.md");  -- works for running doc-tests, but not for doc on crate level
7
8//! `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]
9//! for serialization.
10//!
11//! This library's primary focus is **embedding JS as a scripting language into Rust**. It does not provide all possible integrations between the two
12//! languages, and is not tailored to JS's biggest domain as a client/server side language of the web.
13//!
14//! Instead, `js-sandbox` focuses on calling standalone JS code from Rust, and tries to remain as simple as possible in doing so.
15//! 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
16//! external mods.
17//!
18//! This library is in early development, with a basic but powerful API. The API may still evolve considerably.
19//!
20//! # Examples
21//!
22//! ## Print from JavaScript
23//!
24//! The _Hello World_ example -- print something using JavaScript -- is one line, as it should be:
25//! ```rust
26//! # #[allow(clippy::needless_doctest_main)]
27//! fn main() {
28//! 	js_sandbox_ios::eval_json("console.log('Hello Rust from JS')").expect("JS runs");
29//! }
30//! ```
31//!
32//! ## Call a JS function
33//!
34//! A very basic application calls a JavaScript function `sub()` from Rust. It passes an argument and accepts a return value, both serialized via JSON:
35//!
36//! ```rust
37//! use js_sandbox_ios::{Script, AnyError};
38//!
39//! fn main() -> Result<(), AnyError> {
40//!     let js_code = "function sub(a, b) { return a - b; }";
41//!     let mut script = Script::from_string(js_code)?;
42//!
43//!     let result: i32 = script.call("sub", (7, 5))?;
44//!
45//!     assert_eq!(result, 2);
46//!     Ok(())
47//! }
48//! ```
49//!
50//! An example that serializes a JSON object (Rust -> JS) and formats a string (JS -> Rust):
51//!
52//! ```rust
53//! use js_sandbox_ios::{Script, AnyError};
54//! use serde::Serialize;
55//!
56//! #[derive(Serialize)]
57//! struct Person {
58//!     name: String,
59//!     age: u8,
60//! }
61//!
62//! fn main() -> Result<(), AnyError> {
63//!     let src = r#"
64//!         function toString(person) {
65//!             return "A person named " + person.name + " of age " + person.age;
66//!         }"#;
67//!
68//! 	  let mut script = Script::from_string(src)?;
69//!
70//! 	  let person = Person { name: "Roger".to_string(), age: 42 };
71//! 	  let result: String = script.call("toString", (person,))?;
72//!
73//! 	  assert_eq!(result, "A person named Roger of age 42");
74//! 	  Ok(())
75//! }
76//! ```
77//!
78//! ## Load JS file
79//!
80//! JavaScript files can be loaded from any `Path` at runtime (e.g. 3rd party mods).
81//!
82//! If you want to statically embed UTF-8 encoded files in the Rust binary, you can alternatively use the
83//! [`std::include_str`](https://doc.rust-lang.org/std/macro.include_str.html) macro.
84//!
85//! ```rust,no_run
86//! # macro_rules! include_str { ( $($tt:tt)* ) => { "" } }
87//! use js_sandbox_ios::Script;
88//!
89//! fn main() {
90//! 	// (1) at runtime:
91//! 	let mut script = Script::from_file("script.js").expect("load + init succeeds");
92//!
93//! 	// (2) at compile time:
94//! 	let code: &'static str = include_str!("script.js");
95//! 	let mut script = Script::from_string(code).expect("init succeeds");
96//!
97//! 	// use script as usual
98//! }
99//! ```
100//!
101//! ## Maintain state in JavaScript
102//!
103//! It is possible to initialize a stateful JS script, and then use functions to modify that state over time.
104//! This example appends a string in two calls, and then gets the result in a third call:
105//!
106//! ```rust
107//! use js_sandbox_ios::{Script, AnyError};
108//!
109//! fn main() -> Result<(), AnyError> {
110//! 	let src = r#"
111//!         var total = '';
112//!         function append(str) { total += str; }
113//!         function get()       { return total; }"#;
114//!
115//! 	let mut script = Script::from_string(src)?;
116//!
117//! 	let _: () = script.call("append", ("hello",))?;
118//! 	let _: () = script.call("append", (" world",))?;
119//! 	let result: String = script.call("get", ())?;
120//!
121//! 	assert_eq!(result, "hello world");
122//! 	Ok(())
123//! }
124//! ```
125//!
126//! ## Call a script with timeout
127//!
128//! The JS code may contain long- or forever-running loops that block Rust code. It is possible to set
129//! a timeout, after which JavaScript execution is aborted.
130//!
131//! ```rust
132//! use js_sandbox_ios::{Script, JsError};
133//!
134//! fn main() -> Result<(), JsError> {
135//! 	use std::time::Duration;
136//! 	let js_code = "function run_forever() { for(;;) {} }";
137//! 	let mut script = Script::from_string(js_code)?
138//! 		.with_timeout(Duration::from_millis(1000));
139//!
140//! 	let result: Result<String, JsError> = script.call("run_forever", ());
141//!
142//! 	assert_eq!(
143//! 		result.unwrap_err().to_string(),
144//! 		"Uncaught Error: execution terminated".to_string()
145//! 	);
146//!
147//! 	Ok(())
148//! }
149//! ```
150//!
151//! [Deno]: https://deno.land
152//! [serde_json]: https://docs.serde.rs/serde_json
153
154pub use call_args::CallArgs;
155pub use js_sandbox_macros_ios::js_api;
156pub use script::*;
157pub use util::eval_json;
158
159/// Represents a value passed to or from JavaScript.
160///
161/// Currently aliased as serde_json's Value type.
162pub type JsValue = serde_json::Value;
163
164/// Error occuring during script execution
165pub use js_error::JsError;
166
167/// Polymorphic error type able to represent different error domains.
168///
169/// Currently reusing [anyhow::Error](../anyhow/enum.Error.html), this type may change slightly in the future depending on js-sandbox's needs.
170// use through deno_core, to make sure same version of anyhow crate is used
171pub type AnyError = deno_core::error::AnyError;
172
173/// Wrapper type representing a result that can result in a JS runtime error
174pub type JsResult<T> = Result<T, JsError>;
175
176mod call_args;
177mod js_error;
178mod script;
179mod util;