quickjs_rs/
callback.rs

1use std::{convert::TryFrom, marker::PhantomData, panic::RefUnwindSafe};
2
3use crate::value::{JsValue, ValueError};
4
5pub trait IntoCallbackResult {
6    fn into_callback_res(self) -> Result<JsValue, String>;
7}
8
9impl<T: Into<JsValue>> IntoCallbackResult for T {
10    fn into_callback_res(self) -> Result<JsValue, String> {
11        Ok(self.into())
12    }
13}
14
15impl<T: Into<JsValue>, E: std::fmt::Display> IntoCallbackResult for Result<T, E> {
16    fn into_callback_res(self) -> Result<JsValue, String> {
17        match self {
18            Ok(v) => Ok(v.into()),
19            Err(e) => Err(e.to_string()),
20        }
21    }
22}
23
24/// The Callback trait is implemented for functions/closures that can be
25/// used as callbacks in the JS runtime.
26pub trait Callback<F>: RefUnwindSafe {
27    /// Returns the number of required Javascript arguments.
28    fn argument_count(&self) -> usize;
29
30    /// Execute the callback.
31    ///
32    /// Should return:
33    ///   - Err(_) if the JS values could not be converted
34    ///   - Ok(Err(_)) if an error ocurred while processing.
35    ///       The given error will be raised as a JS exception.
36    ///   - Ok(Ok(result)) when execution succeeded.
37    fn call(&self, args: Vec<JsValue>) -> Result<Result<JsValue, String>, ValueError>;
38}
39
40macro_rules! impl_callback {
41    (@call $len:literal $self:ident $args:ident ) => {
42        $self()
43    };
44
45    (@call $len:literal $self:ident $args:ident $( $arg:ident ),* ) => {
46        {
47            let mut iter = $args.into_iter();
48            $self(
49                $(
50                    $arg::try_from(iter.next().unwrap())?,
51                )*
52            )
53        }
54    };
55
56    [ $(  $len:literal : ( $( $arg:ident, )* ), )* ] => {
57        $(
58
59            impl<
60                $( $arg, )*
61                E,
62                R,
63                F,
64            > Callback<PhantomData<(
65                $( &$arg, )*
66                &E,
67                &R,
68                &F,
69            )>> for F
70            where
71                $( $arg: TryFrom<JsValue, Error = E>, )*
72                ValueError: From<E>,
73                R: IntoCallbackResult,
74                F: Fn( $( $arg, )*  ) -> R + Sized + RefUnwindSafe,
75            {
76                fn argument_count(&self) -> usize {
77                    $len
78                }
79
80                fn call(&self, args: Vec<JsValue>) -> Result<Result<JsValue, String>, ValueError> {
81                    if args.len() != $len {
82                        return Ok(Err(format!(
83                            "Invalid argument count: Expected {}, got {}",
84                            self.argument_count(),
85                            args.len()
86                        )));
87                    }
88
89                    let res = impl_callback!(@call $len self args $($arg),* );
90                    Ok(res.into_callback_res())
91                }
92            }
93        )*
94    };
95}
96
97impl<R, F> Callback<PhantomData<(&R, &F)>> for F
98where
99    R: IntoCallbackResult,
100    F: Fn() -> R + Sized + RefUnwindSafe,
101{
102    fn argument_count(&self) -> usize {
103        0
104    }
105
106    fn call(&self, args: Vec<JsValue>) -> Result<Result<JsValue, String>, ValueError> {
107        if !args.is_empty() {
108            return Ok(Err(format!(
109                "Invalid argument count: Expected 0, got {}",
110                args.len(),
111            )));
112        }
113
114        let res = self();
115        Ok(res.into_callback_res())
116    }
117}
118
119impl_callback![
120    1: (A1,),
121    2: (A1, A2,),
122    3: (A1, A2, A3,),
123    4: (A1, A2, A3, A4,),
124    5: (A1, A2, A3, A4, A5,),
125];
126
127/// A wrapper around Vec<JsValue>, used for vararg callbacks.
128///
129/// To create a callback with a variable number of arguments, a callback closure
130/// must take a single `Arguments` argument.
131pub struct Arguments(Vec<JsValue>);
132
133impl Arguments {
134    /// Unpack the arguments into a Vec.
135    pub fn into_vec(self) -> Vec<JsValue> {
136        self.0
137    }
138}
139
140impl<F> Callback<PhantomData<(&Arguments, &F)>> for F
141where
142    F: Fn(Arguments) + Sized + RefUnwindSafe,
143{
144    fn argument_count(&self) -> usize {
145        0
146    }
147
148    fn call(&self, args: Vec<JsValue>) -> Result<Result<JsValue, String>, ValueError> {
149        (self)(Arguments(args));
150        Ok(Ok(JsValue::Undefined))
151    }
152}
153
154impl<F, R> Callback<PhantomData<(&Arguments, &F, &R)>> for F
155where
156    R: IntoCallbackResult,
157    F: Fn(Arguments) -> R + Sized + RefUnwindSafe,
158{
159    fn argument_count(&self) -> usize {
160        0
161    }
162
163    fn call(&self, args: Vec<JsValue>) -> Result<Result<JsValue, String>, ValueError> {
164        let res = (self)(Arguments(args));
165        Ok(res.into_callback_res())
166    }
167}