pub use config::*;
pub use rquickjs as quickjs;
pub use runtime::Runtime;
use std::str;
mod config;
mod runtime;
use anyhow::{Error, Result, anyhow};
use rquickjs::{
Ctx, Error as JSError, Exception, FromJs, String as JSString, Value, convert, prelude::Rest,
qjs,
};
#[cfg(feature = "messagepack")]
pub mod messagepack;
#[cfg(feature = "json")]
pub mod json;
mod apis;
pub struct Args<'js>(Ctx<'js>, Rest<Value<'js>>);
impl<'js> Args<'js> {
pub fn hold(cx: Ctx<'js>, args: Rest<Value<'js>>) -> Self {
Self(cx, args)
}
pub fn release(self) -> (Ctx<'js>, Rest<Value<'js>>) {
(self.0, self.1)
}
}
#[macro_export]
macro_rules! hold_and_release {
($cx:expr, $args:expr) => {
Args::hold($cx, $args).release()
};
}
#[macro_export]
macro_rules! hold {
($cx:expr, $args:expr) => {
Args::hold($cx, $args)
};
}
pub fn from_js_error(ctx: Ctx<'_>, e: JSError) -> Error {
if e.is_exception() {
let val = ctx.catch();
if let Some(exception) = val.clone().into_exception() {
anyhow!("{exception}")
} else {
anyhow!(val_to_string(&ctx, val).unwrap_or_else(|_| "Internal error".to_string()))
}
} else {
Into::into(e)
}
}
pub fn to_js_error(cx: Ctx, e: Error) -> JSError {
match e.downcast::<JSError>() {
Ok(e) => e,
Err(e) => {
if e.to_string()
.contains("JSError: Exception generated by QuickJS")
{
return JSError::Exception;
}
cx.throw(Value::from_exception(
Exception::from_message(cx.clone(), &e.to_string())
.expect("creating an exception to succeed"),
))
}
}
}
pub fn to_string_lossy<'js>(cx: &Ctx<'js>, string: &JSString<'js>, error: JSError) -> String {
let mut len: qjs::size_t = 0;
let ptr =
unsafe { qjs::JS_ToCStringLen2(cx.as_raw().as_ptr(), &mut len, string.as_raw(), false) };
let buffer = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) };
let mut utf8_error = match error {
JSError::Utf8(e) => e,
_ => unreachable!("expected Utf8 error"),
};
let mut res = String::new();
let mut buffer = buffer;
loop {
let (valid, after_valid) = buffer.split_at(utf8_error.valid_up_to());
res.push_str(unsafe { str::from_utf8_unchecked(valid) });
res.push(char::REPLACEMENT_CHARACTER);
let lone_surrogate = matches!(after_valid, [0xED, 0xA0..=0xBF, 0x80..=0xBF, ..]);
let error_len = if lone_surrogate {
3
} else {
utf8_error
.error_len()
.expect("Error length should always be available on underlying buffer")
};
buffer = &after_valid[error_len..];
match str::from_utf8(buffer) {
Ok(valid) => {
res.push_str(valid);
break;
}
Err(e) => utf8_error = e,
}
}
res
}
pub fn val_to_string<'js>(this: &Ctx<'js>, val: Value<'js>) -> Result<String> {
if let Some(symbol) = val.as_symbol() {
if let Some(description) = symbol.description()?.into_string() {
let description = description
.to_string()
.unwrap_or_else(|e| to_string_lossy(this, &description, e));
Ok(format!("Symbol({description})"))
} else {
Ok("Symbol()".into())
}
} else {
let stringified = <convert::Coerced<JSString>>::from_js(this, val).map(|string| {
string
.to_string()
.unwrap_or_else(|e| to_string_lossy(this, &string.0, e))
})?;
Ok(stringified)
}
}