use boa_engine::value::TryFromJs;
use boa_engine::{Context, JsNativeError, JsObject, JsResult, JsValue, NativeObject};
use boa_gc::{GcRef, GcRefMut};
use std::ops::Deref;
pub trait TryFromJsArgument<'a>: Sized {
fn try_from_js_argument(
this: &'a JsValue,
rest: &'a [JsValue],
context: &mut Context,
) -> JsResult<(Self, &'a [JsValue])>;
}
impl<'a, T: TryFromJs> TryFromJsArgument<'a> for T {
fn try_from_js_argument(
_: &'a JsValue,
rest: &'a [JsValue],
context: &mut Context,
) -> JsResult<(Self, &'a [JsValue])> {
match rest.split_first() {
Some((first, rest)) => Ok((first.try_js_into(context)?, rest)),
None => T::try_from_js(&JsValue::undefined(), context).map(|v| (v, rest)),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Ignore;
impl<'a> TryFromJsArgument<'a> for Ignore {
fn try_from_js_argument(
_this: &'a JsValue,
rest: &'a [JsValue],
_: &mut Context,
) -> JsResult<(Self, &'a [JsValue])> {
Ok((Ignore, &rest[1..]))
}
}
#[derive(Debug, Clone)]
pub struct JsRest<'a>(pub &'a [JsValue]);
#[allow(unused)]
impl<'a> JsRest<'a> {
#[must_use]
pub fn into_inner(self) -> &'a [JsValue] {
self.0
}
#[must_use]
pub fn to_vec(self) -> Vec<JsValue> {
self.0.to_vec()
}
pub fn iter(&self) -> impl Iterator<Item = &JsValue> {
self.0.iter()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'a> From<&'a [JsValue]> for JsRest<'a> {
fn from(values: &'a [JsValue]) -> Self {
Self(values)
}
}
impl<'a> IntoIterator for JsRest<'a> {
type Item = &'a JsValue;
type IntoIter = std::slice::Iter<'a, JsValue>;
fn into_iter(self) -> Self::IntoIter {
self.into_inner().iter()
}
}
#[derive(Debug, Clone)]
pub struct JsAll<T: TryFromJs>(pub Vec<T>);
impl<T: TryFromJs> JsAll<T> {
#[must_use]
pub fn into_inner(self) -> Vec<T> {
self.0
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.0.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.0.iter_mut()
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'a, T: TryFromJs> TryFromJsArgument<'a> for JsAll<T> {
fn try_from_js_argument(
_this: &'a JsValue,
mut rest: &'a [JsValue],
context: &mut Context,
) -> JsResult<(Self, &'a [JsValue])> {
let mut values = Vec::new();
while !rest.is_empty() {
match rest[0].try_js_into(context) {
Ok(value) => {
values.push(value);
rest = &rest[1..];
}
Err(_) => break,
}
}
Ok((JsAll(values), rest))
}
}
#[derive(Debug, Clone)]
pub struct JsThis<T: TryFromJs>(pub T);
impl<'a, T: TryFromJs> TryFromJsArgument<'a> for JsThis<T> {
fn try_from_js_argument(
this: &'a JsValue,
rest: &'a [JsValue],
context: &mut Context,
) -> JsResult<(Self, &'a [JsValue])> {
Ok((JsThis(this.try_js_into(context)?), rest))
}
}
impl<T: TryFromJs> Deref for JsThis<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct JsClass<T: NativeObject> {
inner: JsObject<T>,
}
impl<T: NativeObject> JsClass<T> {
#[must_use]
pub fn inner(&self) -> JsObject<T> {
self.inner.clone()
}
#[must_use]
pub fn borrow(&self) -> GcRef<'_, T> {
GcRef::map(self.inner.borrow(), |obj| obj.data())
}
#[must_use]
pub fn borrow_mut(&self) -> GcRefMut<'_, T> {
GcRefMut::map(self.inner.borrow_mut(), |obj| obj.data_mut())
}
}
impl<T: NativeObject + Clone> JsClass<T> {
#[must_use]
pub fn clone_inner(&self) -> T {
self.inner.borrow().data().clone()
}
}
impl<'a, T: NativeObject + 'static> TryFromJsArgument<'a> for JsClass<T> {
fn try_from_js_argument(
this: &'a JsValue,
rest: &'a [JsValue],
_context: &mut Context,
) -> JsResult<(Self, &'a [JsValue])> {
let inner = this
.as_object()
.and_then(|o| o.clone().downcast::<T>().ok())
.ok_or_else(|| JsNativeError::typ().with_message("invalid this for class method"))?;
Ok((JsClass { inner }, rest))
}
}
#[derive(Debug, Clone)]
pub struct ContextData<T: Clone>(pub T);
impl<'a, T: NativeObject + Clone> TryFromJsArgument<'a> for ContextData<T> {
fn try_from_js_argument(
_this: &'a JsValue,
rest: &'a [JsValue],
context: &mut Context,
) -> JsResult<(Self, &'a [JsValue])> {
match context.get_data::<T>() {
Some(value) => Ok((ContextData(value.clone()), rest)),
None => Err(JsNativeError::typ()
.with_message("Context data not found")
.into()),
}
}
}