use super::ScriptingBackend;
use crate::core::{Error, Result, Value};
use boa_engine::object::builtins::JsArray;
#[cfg(feature = "js")]
use boa_engine::{Context, JsString, JsValue, Source};
#[cfg(feature = "js")]
pub struct BoaBackend {
}
#[cfg(feature = "js")]
impl BoaBackend {
pub fn new() -> Self {
Self {}
}
}
impl Default for BoaBackend {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "js")]
fn create_secure_context() -> Context {
Context::default()
}
#[cfg(feature = "js")]
impl ScriptingBackend for BoaBackend {
fn name(&self) -> &'static str {
"boa"
}
fn supported_languages(&self) -> &[&'static str] {
&["boa", "deno", "javascript", "js", "typescript", "ts"]
}
fn execute(&self, code: &str, args: &[Value], param_names: &[&str]) -> Result<Value> {
let mut context = create_secure_context();
let js_array = JsArray::new(&mut context);
for (i, arg) in args.iter().enumerate() {
let js_value = match arg {
Value::Null(_) => JsValue::null(),
Value::Integer(i) => JsValue::new(*i as i32), Value::Float(f) => JsValue::rational(*f),
Value::Text(s) => JsValue::new(JsString::from(s.as_ref())),
Value::Boolean(b) => JsValue::new(*b),
Value::Timestamp(ts) => JsValue::new(JsString::from(ts.to_rfc3339())),
Value::Json(j) => JsValue::new(JsString::from(j.to_string())),
};
js_array
.set(i as u32, js_value, false, &mut context)
.map_err(|e| Error::internal(format!("Failed to set array element: {:?}", e)))?;
}
context
.register_global_property::<JsString, JsValue>(
JsString::from("arguments"),
js_array.into(),
Default::default(),
)
.map_err(|e| Error::internal(format!("Failed to set arguments array: {}", e)))?;
for (i, arg) in args.iter().enumerate() {
let param_name = param_names[i];
let js_value = match arg {
Value::Null(_) => JsValue::null(),
Value::Integer(i) => JsValue::new(*i as i32),
Value::Float(f) => JsValue::rational(*f),
Value::Text(s) => JsValue::new(JsString::from(s.as_ref())),
Value::Boolean(b) => JsValue::new(*b),
Value::Timestamp(ts) => JsValue::new(JsString::from(ts.to_rfc3339())),
Value::Json(j) => JsValue::new(JsString::from(j.to_string())),
};
context
.register_global_property::<JsString, JsValue>(
JsString::from(param_name),
js_value,
Default::default(),
)
.map_err(|e| {
Error::internal(format!("Failed to set argument {}: {}", param_name, e))
})?;
}
let wrapped_code = format!(
r#"
(function() {{
{}
}}).apply(null, arguments)
"#,
code
);
let source = Source::from_bytes(&wrapped_code);
let result = context
.eval(source)
.map_err(|e| Error::internal(format!("Function execution failed: {:?}", e)))?;
if result.is_null() || result.is_undefined() {
Ok(Value::Null(crate::core::types::DataType::Null))
} else if let Some(b) = result.as_boolean() {
Ok(Value::Boolean(b))
} else if let Some(s) = result.as_string() {
Ok(Value::Text(s.to_std_string().unwrap_or_default().into()))
} else if let Some(i) = result.as_number() {
if i.fract() == 0.0 && i >= i32::MIN as f64 && i <= i32::MAX as f64 {
Ok(Value::Integer(i as i64))
} else {
Ok(Value::Float(i))
}
} else if let Some(bi) = result.as_bigint() {
Ok(Value::Integer(bi.to_string().parse().unwrap_or(0)))
} else {
let json_value = result
.to_json(&mut context)
.unwrap_or(Some(serde_json::Value::String(
result.display().to_string(),
)))
.unwrap_or(serde_json::Value::String(result.display().to_string()));
Ok(Value::Json(json_value.to_string().into()))
}
}
fn validate_code(&self, code: &str) -> Result<()> {
let mut context = create_secure_context();
let source = Source::from_bytes(code);
match boa_engine::Script::parse(source, None, &mut context) {
Ok(_) => Ok(()),
Err(e) => Err(Error::internal(format!("Boa syntax error: {:?}", e))),
}
}
}
#[cfg(not(feature = "js"))]
pub struct BoaBackend;
#[cfg(not(feature = "js"))]
impl BoaBackend {
pub fn new() -> Self {
Self
}
}
#[cfg(not(feature = "js"))]
impl ScriptingBackend for BoaBackend {
fn name(&self) -> &'static str {
"boa"
}
fn supported_languages(&self) -> &[&'static str] {
&["boa", "deno", "javascript", "js", "typescript", "ts"]
}
fn execute(&self, _code: &str, _args: &[Value], _param_names: &[&str]) -> Result<Value> {
Err(Error::internal(
"Boa backend not enabled. Use --features js to enable JavaScript/TypeScript support",
))
}
fn validate_code(&self, _code: &str) -> Result<()> {
Err(Error::internal(
"Boa backend not enabled. Use --features js to enable JavaScript/TypeScript support",
))
}
}