boa_engine/interop/
into_js_arguments.rs

1use boa_engine::value::TryFromJs;
2use boa_engine::{Context, JsNativeError, JsObject, JsResult, JsValue, NativeObject};
3use boa_gc::{GcRef, GcRefMut};
4use std::ops::Deref;
5
6/// Create a Rust value from a JS argument. This trait is used to
7/// convert arguments from JS to Rust types. It allows support
8/// for optional arguments or rest arguments.
9pub trait TryFromJsArgument<'a>: Sized {
10    /// Try to convert a JS argument into a Rust value, returning the
11    /// value and the rest of the arguments to be parsed.
12    ///
13    /// # Errors
14    /// Any parsing errors that may occur during the conversion.
15    fn try_from_js_argument(
16        this: &'a JsValue,
17        rest: &'a [JsValue],
18        context: &mut Context,
19    ) -> JsResult<(Self, &'a [JsValue])>;
20}
21
22impl<'a, T: TryFromJs> TryFromJsArgument<'a> for T {
23    fn try_from_js_argument(
24        _: &'a JsValue,
25        rest: &'a [JsValue],
26        context: &mut Context,
27    ) -> JsResult<(Self, &'a [JsValue])> {
28        match rest.split_first() {
29            Some((first, rest)) => Ok((first.try_js_into(context)?, rest)),
30            None => T::try_from_js(&JsValue::undefined(), context).map(|v| (v, rest)),
31        }
32    }
33}
34
35/// An argument that would be ignored in a JS function. This is equivalent of typing
36/// `()` in Rust functions argument, but more explicit.
37#[derive(Debug, Clone, Copy)]
38pub struct Ignore;
39
40impl<'a> TryFromJsArgument<'a> for Ignore {
41    fn try_from_js_argument(
42        _this: &'a JsValue,
43        rest: &'a [JsValue],
44        _: &mut Context,
45    ) -> JsResult<(Self, &'a [JsValue])> {
46        Ok((Ignore, &rest[1..]))
47    }
48}
49
50/// An argument that when used in a JS function will empty the list
51/// of JS arguments as `JsValue`s. This can be used for having the
52/// rest of the arguments in a function. It should be the last
53/// argument of your function, before the `Context` argument if any.
54///
55/// For example,
56/// ```
57/// # use boa_engine::{Context, JsValue, IntoJsFunctionCopied};
58/// # use boa_engine::interop::JsRest;
59/// # let mut context = Context::default();
60/// let sums = (|args: JsRest, context: &mut Context| -> i32 {
61///     args.iter()
62///         .map(|i| i.try_js_into::<i32>(context).unwrap())
63///         .sum::<i32>()
64/// })
65/// .into_js_function_copied(&mut context);
66///
67/// let result = sums
68///     .call(
69///         &JsValue::undefined(),
70///         &[JsValue::from(1), JsValue::from(2), JsValue::from(3)],
71///         &mut context,
72///     )
73///     .unwrap();
74/// assert_eq!(result, JsValue::new(6));
75/// ```
76#[derive(Debug, Clone)]
77pub struct JsRest<'a>(pub &'a [JsValue]);
78
79#[allow(unused)]
80impl<'a> JsRest<'a> {
81    /// Consumes the `JsRest` and returns the inner list of `JsValue`.
82    #[must_use]
83    pub fn into_inner(self) -> &'a [JsValue] {
84        self.0
85    }
86
87    /// Transforms the `JsRest` into a `Vec<JsValue>`.
88    #[must_use]
89    pub fn to_vec(self) -> Vec<JsValue> {
90        self.0.to_vec()
91    }
92
93    /// Returns an iterator over the inner list of `JsValue`.
94    pub fn iter(&self) -> impl Iterator<Item = &JsValue> {
95        self.0.iter()
96    }
97
98    /// Returns the length of the inner list of `JsValue`.
99    #[must_use]
100    pub fn len(&self) -> usize {
101        self.0.len()
102    }
103
104    /// Returns `true` if the inner list of `JsValue` is empty.
105    #[must_use]
106    pub fn is_empty(&self) -> bool {
107        self.0.is_empty()
108    }
109}
110
111impl<'a> From<&'a [JsValue]> for JsRest<'a> {
112    fn from(values: &'a [JsValue]) -> Self {
113        Self(values)
114    }
115}
116
117impl<'a> IntoIterator for JsRest<'a> {
118    type Item = &'a JsValue;
119    type IntoIter = std::slice::Iter<'a, JsValue>;
120
121    fn into_iter(self) -> Self::IntoIter {
122        self.into_inner().iter()
123    }
124}
125
126/// An argument that when used in a JS function will capture all
127/// the arguments that can be converted to `T`. The first argument
128/// that cannot be converted to `T` will stop the conversion.
129///
130/// For example,
131/// ```
132/// # use boa_engine::{Context, JsValue, IntoJsFunctionCopied};
133/// # use boa_engine::interop::JsAll;
134/// # let mut context = Context::default();
135/// let sums = (|args: JsAll<i32>, context: &mut Context| -> i32 {
136///     args.iter().sum()
137/// })
138/// .into_js_function_copied(&mut context);
139///
140/// let result = sums
141///     .call(
142///         &JsValue::undefined(),
143///         &[
144///             JsValue::from(1),
145///             JsValue::from(2),
146///             JsValue::from(3),
147///             JsValue::from(true),
148///             JsValue::from(4),
149///         ],
150///         &mut context,
151///     )
152///     .unwrap();
153/// assert_eq!(result, JsValue::new(6));
154/// ```
155#[derive(Debug, Clone)]
156pub struct JsAll<T: TryFromJs>(pub Vec<T>);
157
158impl<T: TryFromJs> JsAll<T> {
159    /// Consumes the `JsAll` and returns the inner list of `T`.
160    #[must_use]
161    pub fn into_inner(self) -> Vec<T> {
162        self.0
163    }
164
165    /// Returns an iterator over the inner list of `T`.
166    pub fn iter(&self) -> impl Iterator<Item = &T> {
167        self.0.iter()
168    }
169
170    /// Returns a mutable iterator over the inner list of `T`.
171    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
172        self.0.iter_mut()
173    }
174
175    /// Returns the length of the inner list of `T`.
176    #[must_use]
177    pub fn len(&self) -> usize {
178        self.0.len()
179    }
180
181    /// Returns `true` if the inner list of `T` is empty.
182    #[must_use]
183    pub fn is_empty(&self) -> bool {
184        self.0.is_empty()
185    }
186}
187
188impl<'a, T: TryFromJs> TryFromJsArgument<'a> for JsAll<T> {
189    fn try_from_js_argument(
190        _this: &'a JsValue,
191        mut rest: &'a [JsValue],
192        context: &mut Context,
193    ) -> JsResult<(Self, &'a [JsValue])> {
194        let mut values = Vec::new();
195
196        while !rest.is_empty() {
197            match rest[0].try_js_into(context) {
198                Ok(value) => {
199                    values.push(value);
200                    rest = &rest[1..];
201                }
202                Err(_) => break,
203            }
204        }
205        Ok((JsAll(values), rest))
206    }
207}
208
209/// Captures the `this` value in a JS function. Although this can be
210/// specified multiple times as argument, it will always be filled
211/// with clone of the same value.
212#[derive(Debug, Clone)]
213pub struct JsThis<T: TryFromJs>(pub T);
214
215impl<'a, T: TryFromJs> TryFromJsArgument<'a> for JsThis<T> {
216    fn try_from_js_argument(
217        this: &'a JsValue,
218        rest: &'a [JsValue],
219        context: &mut Context,
220    ) -> JsResult<(Self, &'a [JsValue])> {
221        Ok((JsThis(this.try_js_into(context)?), rest))
222    }
223}
224
225impl<T: TryFromJs> Deref for JsThis<T> {
226    type Target = T;
227
228    fn deref(&self) -> &Self::Target {
229        &self.0
230    }
231}
232
233/// Captures a class instance from the `this` value in a JS function. The class
234/// will be a non-mutable reference of Rust type `T`, if it is an instance of `T`.
235///
236/// To have more flexibility on the parsing of the `this` value, you can use the
237/// [`JsThis`] capture instead.
238#[derive(Debug, Clone)]
239pub struct JsClass<T: NativeObject> {
240    inner: JsObject<T>,
241}
242
243impl<T: NativeObject> JsClass<T> {
244    /// Returns the inner object directly.
245    #[must_use]
246    pub fn inner(&self) -> JsObject<T> {
247        self.inner.clone()
248    }
249
250    /// Borrow a reference to the class instance of type `T`.
251    ///
252    /// # Panics
253    ///
254    /// Panics if the object is currently borrowed.
255    ///
256    /// This does not panic if the type is wrong, as the type is checked
257    /// during the construction of the `JsClass` instance.
258    #[must_use]
259    pub fn borrow(&self) -> GcRef<'_, T> {
260        GcRef::map(self.inner.borrow(), |obj| obj.data())
261    }
262
263    /// Borrow a mutable reference to the class instance of type `T`.
264    ///
265    /// # Panics
266    ///
267    /// Panics if the object is currently mutably borrowed.
268    #[must_use]
269    pub fn borrow_mut(&self) -> GcRefMut<'_, T> {
270        GcRefMut::map(self.inner.borrow_mut(), |obj| obj.data_mut())
271    }
272}
273
274impl<T: NativeObject + Clone> JsClass<T> {
275    /// Clones the inner class instance.
276    ///
277    /// # Panics
278    ///
279    /// Panics if the inner object is currently borrowed mutably.
280    #[must_use]
281    pub fn clone_inner(&self) -> T {
282        self.inner.borrow().data().clone()
283    }
284}
285
286impl<'a, T: NativeObject + 'static> TryFromJsArgument<'a> for JsClass<T> {
287    fn try_from_js_argument(
288        this: &'a JsValue,
289        rest: &'a [JsValue],
290        _context: &mut Context,
291    ) -> JsResult<(Self, &'a [JsValue])> {
292        let inner = this
293            .as_object()
294            .and_then(|o| o.clone().downcast::<T>().ok())
295            .ok_or_else(|| JsNativeError::typ().with_message("invalid this for class method"))?;
296
297        Ok((JsClass { inner }, rest))
298    }
299}
300
301/// Captures a [`ContextData`] data from the [`Context`] as a JS function argument,
302/// based on its type.
303///
304/// The host defined type must implement [`Clone`], otherwise the borrow
305/// checker would not be able to ensure the safety of the context while
306/// making the function call. Because of this, it is recommended to use
307/// types that are cheap to clone.
308///
309/// For example,
310/// ```
311/// # use boa_engine::{Context, Finalize, JsData, JsValue, Trace, IntoJsFunctionCopied};
312/// # use boa_engine::interop::ContextData;
313///
314/// #[derive(Clone, Debug, Finalize, JsData, Trace)]
315/// struct CustomHostDefinedStruct {
316///     #[unsafe_ignore_trace]
317///     pub counter: usize,
318/// }
319/// let mut context = Context::default();
320/// context.insert_data(CustomHostDefinedStruct { counter: 123 });
321/// let f = (|ContextData(host): ContextData<CustomHostDefinedStruct>| host.counter + 1)
322///     .into_js_function_copied(&mut context);
323///
324/// assert_eq!(
325///     f.call(&JsValue::undefined(), &[], &mut context),
326///     Ok(JsValue::new(124))
327/// );
328/// ```
329#[derive(Debug, Clone)]
330pub struct ContextData<T: Clone>(pub T);
331
332impl<'a, T: NativeObject + Clone> TryFromJsArgument<'a> for ContextData<T> {
333    fn try_from_js_argument(
334        _this: &'a JsValue,
335        rest: &'a [JsValue],
336        context: &mut Context,
337    ) -> JsResult<(Self, &'a [JsValue])> {
338        match context.get_data::<T>() {
339            Some(value) => Ok((ContextData(value.clone()), rest)),
340            None => Err(JsNativeError::typ()
341                .with_message("Context data not found")
342                .into()),
343        }
344    }
345}