#![deny(missing_docs)]
mod bindings;
mod callback;
pub mod console;
mod droppable_value;
mod value;
#[cfg(test)]
mod tests;
use std::{convert::TryFrom, error, fmt};
pub use callback::{Arguments, Callback};
pub use value::*;
#[derive(PartialEq, Debug)]
pub enum ExecutionError {
InputWithZeroBytes,
Conversion(ValueError),
Internal(String),
Exception(JsValue),
OutOfMemory,
#[doc(hidden)]
__NonExhaustive,
}
impl fmt::Display for ExecutionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ExecutionError::*;
match self {
InputWithZeroBytes => write!(f, "Invalid script input: code contains zero byte (\\0)"),
Conversion(e) => e.fmt(f),
Internal(e) => write!(f, "Internal error: {}", e),
Exception(e) => write!(f, "{:?}", e),
OutOfMemory => write!(f, "Out of memory: runtime memory limit exceeded"),
__NonExhaustive => unreachable!(),
}
}
}
impl error::Error for ExecutionError {}
impl From<ValueError> for ExecutionError {
fn from(v: ValueError) -> Self {
ExecutionError::Conversion(v)
}
}
#[derive(Debug)]
pub enum ContextError {
RuntimeCreationFailed,
ContextCreationFailed,
Execution(ExecutionError),
#[doc(hidden)]
__NonExhaustive,
}
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ContextError::*;
match self {
RuntimeCreationFailed => write!(f, "Could not create runtime"),
ContextCreationFailed => write!(f, "Could not create context"),
Execution(e) => e.fmt(f),
__NonExhaustive => unreachable!(),
}
}
}
impl error::Error for ContextError {}
pub struct ContextBuilder {
memory_limit: Option<usize>,
console_backend: Option<Box<dyn console::ConsoleBackend>>,
}
impl ContextBuilder {
fn new() -> Self {
Self {
memory_limit: None,
console_backend: None,
}
}
pub fn memory_limit(self, max_bytes: usize) -> Self {
let mut s = self;
s.memory_limit = Some(max_bytes);
s
}
pub fn console<B>(mut self, backend: B) -> Self
where
B: console::ConsoleBackend,
{
self.console_backend = Some(Box::new(backend));
self
}
pub fn build(self) -> Result<Context, ContextError> {
let wrapper = bindings::ContextWrapper::new(self.memory_limit)?;
if let Some(be) = self.console_backend {
wrapper.set_console(be).map_err(ContextError::Execution)?;
}
Ok(Context::from_wrapper(wrapper))
}
}
pub struct Context {
wrapper: bindings::ContextWrapper,
}
impl Context {
fn from_wrapper(wrapper: bindings::ContextWrapper) -> Self {
Self { wrapper }
}
pub fn builder() -> ContextBuilder {
ContextBuilder::new()
}
pub fn new() -> Result<Self, ContextError> {
let wrapper = bindings::ContextWrapper::new(None)?;
Ok(Self::from_wrapper(wrapper))
}
pub fn reset(self) -> Result<Self, ContextError> {
let wrapper = self.wrapper.reset()?;
Ok(Self { wrapper })
}
pub fn eval(&self, code: &str) -> Result<JsValue, ExecutionError> {
let value_raw = self.wrapper.eval(code)?;
let value = value_raw.to_value()?;
Ok(value)
}
pub fn eval_as<R>(&self, code: &str) -> Result<R, ExecutionError>
where
R: TryFrom<JsValue>,
R::Error: Into<ValueError>,
{
let value_raw = self.wrapper.eval(code)?;
let value = value_raw.to_value()?;
let ret = R::try_from(value).map_err(|e| e.into())?;
Ok(ret)
}
pub fn set_global<V>(&self, name: &str, value: V) -> Result<(), ExecutionError>
where
V: Into<JsValue>,
{
let global = self.wrapper.global()?;
global.set_property(name, value.into())?;
Ok(())
}
pub fn call_function(
&self,
function_name: &str,
args: impl IntoIterator<Item = impl Into<JsValue>>,
) -> Result<JsValue, ExecutionError> {
let qargs = args
.into_iter()
.map(|arg| self.wrapper.serialize_value(arg.into()))
.collect::<Result<Vec<_>, _>>()?;
let global = self.wrapper.global()?;
let func_obj = global.property(function_name)?;
if !func_obj.is_object() {
return Err(ExecutionError::Internal(format!(
"Could not find function '{}' in global scope: does not exist, or not an object",
function_name
)));
}
let value = self.wrapper.call_function(func_obj, qargs)?.to_value()?;
Ok(value)
}
pub fn add_callback<F>(
&self,
name: &str,
callback: impl Callback<F> + 'static,
) -> Result<(), ExecutionError> {
self.wrapper.add_callback(name, callback)
}
}