tauri_wasm/
invoke.rs

1//! Types of tauri [commands].
2//!
3//! [commands]: https://v2.tauri.app/develop/calling-rust/#commands
4
5use {
6    crate::{error::Error, ext, string::ToStringValue},
7    js_sys::{ArrayBuffer, Uint8Array},
8    std::{
9        pin::Pin,
10        task::{Context, Poll},
11    },
12    wasm_bindgen::prelude::*,
13    wasm_bindgen_futures::JsFuture,
14};
15
16pub(crate) mod api {
17    use super::*;
18
19    /// Invokes a [command] on the backend.
20    ///
21    /// [command]: https://v2.tauri.app/develop/calling-rust/#commands
22    ///
23    /// This function returns a future-like object that
24    /// can be extended with additional properties.
25    /// See [`with_args`](Invoke::with_args) and
26    /// [`with_options`](Invoke::with_options) for details.
27    ///
28    /// # Example
29    ///
30    /// ```
31    /// # async fn e() -> Result<(), tauri_wasm::Error> {
32    /// use gloo::console;
33    ///
34    /// let message = tauri_wasm::invoke("connect").await?;
35    /// console::log!("connected to backend", message);
36    /// # Ok(())
37    /// # }
38    /// ```
39    #[inline]
40    pub fn invoke<C>(cmd: C) -> Invoke<C::Js>
41    where
42        C: ToStringValue,
43    {
44        let cmd = cmd.to_string_value();
45        let args = JsValue::UNDEFINED;
46        let opts = Options::empty();
47        Invoke { cmd, args, opts }
48    }
49}
50
51/// A type used to configure an [invoke](api::invoke) operation.
52pub struct Invoke<C, A = JsValue> {
53    cmd: C,
54    args: A,
55    opts: Options,
56}
57
58impl<C, A> Invoke<C, A> {
59    /// Invokes a [command] with arguments on the backend.
60    ///
61    /// [command]: https://v2.tauri.app/develop/calling-rust/#commands
62    ///
63    /// # Passing a serializable type
64    ///
65    /// To send a custom serializable type as arguments,
66    /// use the helper [`args`](crate::args) function.
67    ///
68    #[cfg_attr(feature = "serde", doc = "```")]
69    #[cfg_attr(not(feature = "serde"), doc = "```ignore")]
70    /// # async fn e() -> Result<(), tauri_wasm::Error> {
71    /// use {gloo::console, serde::Serialize};
72    ///
73    /// #[derive(Serialize)]
74    /// struct User<'str> {
75    ///     name: &'str str,
76    ///     pass: &'str str,
77    /// }
78    ///
79    /// let user = User {
80    ///     name: "anon",
81    ///     pass: "p@$$w0rD",
82    /// };
83    ///
84    /// let args = tauri_wasm::args(&user)?;
85    /// let message = tauri_wasm::invoke("login").with_args(args).await?;
86    /// console::log!("logged on backend", message);
87    /// # Ok(())
88    /// # }
89    /// ```
90    ///
91    /// # Passing a JS object
92    ///
93    /// Thanks to the `wasm_bindgen` attribute
94    /// you can convert your type into a JS value.
95    /// To pass the value as arguments implement the [`ToArgs`] trait.
96    ///
97    /// ```
98    /// # async fn e() -> Result<(), tauri_wasm::Error> {
99    /// use {
100    ///     gloo::console,
101    ///     tauri_wasm::invoke::ToArgs,
102    ///     wasm_bindgen::prelude::*,
103    /// };
104    ///
105    /// #[wasm_bindgen(getter_with_clone)]
106    /// struct User {
107    ///     name: String,
108    ///     pass: String,
109    /// }
110    ///
111    /// impl ToArgs for User {
112    ///     type Js = JsValue;
113    ///
114    ///     fn to_args(self) -> Self::Js {
115    ///         // wasm_bindgen attribute implements
116    ///         // convertion into JS value
117    ///         JsValue::from(self)
118    ///     }
119    /// }
120    ///
121    /// let user = User {
122    ///     name: "anon".to_owned(),
123    ///     pass: "p@$$w0rD".to_owned(),
124    /// };
125    ///
126    /// let message = tauri_wasm::invoke("login").with_args(user).await?;
127    /// console::log!("logged on backend", message);
128    /// # Ok(())
129    /// # }
130    /// ```
131    #[inline]
132    pub fn with_args<T>(self, args: T) -> Invoke<C, T::Js>
133    where
134        T: ToArgs,
135    {
136        let cmd = self.cmd;
137        let args = args.to_args();
138        let opts = self.opts;
139        Invoke { cmd, args, opts }
140    }
141
142    /// Invokes a [command] with options on the backend.
143    ///
144    /// [command]: https://v2.tauri.app/develop/calling-rust/#commands
145    ///
146    /// # Example
147    ///
148    #[cfg_attr(feature = "serde", doc = "```")]
149    #[cfg_attr(not(feature = "serde"), doc = "```ignore")]
150    /// # async fn e() -> Result<(), tauri_wasm::Error> {
151    /// use {gloo::console, tauri_wasm::invoke::Options};
152    ///
153    /// let opts = Options::from_record([
154    ///     ("secret", "2"),
155    ///     ("data", "3"),
156    /// ])?;
157    ///
158    /// let message = tauri_wasm::invoke("send").with_options(opts).await?;
159    /// console::log!("received from backend", message);
160    /// # Ok(())
161    /// # }
162    /// ```
163    #[inline]
164    pub fn with_options(self, opts: Options) -> Self {
165        Self { opts, ..self }
166    }
167}
168
169/// Represents the future of an [invoke](api::invoke) operation.
170pub struct InvokeFuture(JsFuture);
171
172impl InvokeFuture {
173    /// Returns the inner future.
174    #[inline]
175    pub fn into_future(self) -> JsFuture {
176        self.0
177    }
178}
179
180impl Future for InvokeFuture {
181    type Output = Result<JsValue, Error>;
182
183    #[inline]
184    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
185        let me = self.get_mut();
186        Pin::new(&mut me.0).poll(cx).map_err(Error)
187    }
188}
189
190impl<C, A> IntoFuture for Invoke<C, A>
191where
192    C: AsRef<JsValue>,
193    A: AsRef<JsValue>,
194{
195    type Output = Result<JsValue, Error>;
196    type IntoFuture = InvokeFuture;
197
198    #[inline]
199    fn into_future(self) -> Self::IntoFuture {
200        let promise = ext::invoke(self.cmd.as_ref(), self.args.as_ref(), self.opts);
201        InvokeFuture(JsFuture::from(promise))
202    }
203}
204
205/// Types that can be represented as arguments.
206pub trait ToArgs {
207    type Js: AsRef<JsValue>;
208    fn to_args(self) -> Self::Js;
209}
210
211impl ToArgs for ArrayBuffer {
212    type Js = JsValue;
213
214    #[inline]
215    fn to_args(self) -> Self::Js {
216        JsValue::from(self)
217    }
218}
219
220impl<'arr> ToArgs for &'arr ArrayBuffer {
221    type Js = &'arr JsValue;
222
223    #[inline]
224    fn to_args(self) -> Self::Js {
225        self
226    }
227}
228
229impl ToArgs for Uint8Array {
230    type Js = JsValue;
231
232    #[inline]
233    fn to_args(self) -> Self::Js {
234        JsValue::from(self)
235    }
236}
237
238impl<'arr> ToArgs for &'arr Uint8Array {
239    type Js = &'arr JsValue;
240
241    #[inline]
242    fn to_args(self) -> Self::Js {
243        self
244    }
245}
246
247impl ToArgs for &[u8] {
248    type Js = JsValue;
249
250    #[inline]
251    fn to_args(self) -> Self::Js {
252        Uint8Array::from(self).to_args()
253    }
254}
255
256impl<const N: usize> ToArgs for &[u8; N] {
257    type Js = JsValue;
258
259    #[inline]
260    fn to_args(self) -> Self::Js {
261        self.as_slice().to_args()
262    }
263}
264
265/// Invoke options.
266///
267/// To pass options to an invoke call, use the
268/// [`with_options`](Invoke::with_options) method.
269///
270/// You can create options from
271/// [headers](IntoHeaders::into_options).
272#[wasm_bindgen]
273pub struct Options {
274    pub(crate) headers: JsValue,
275}
276
277impl Options {
278    pub(crate) const fn empty() -> Self {
279        let headers = JsValue::UNDEFINED;
280        Self { headers }
281    }
282}
283
284#[wasm_bindgen]
285impl Options {
286    /// Returns options headers.
287    #[inline]
288    #[wasm_bindgen(getter)]
289    pub fn headers(self) -> JsValue {
290        self.headers
291    }
292}
293
294/// Types that can be converted into headers.
295pub trait IntoHeaders {
296    /// Converts the value into headers.
297    fn into_headers(self) -> Result<JsValue, Error>;
298
299    /// Converts the value into [options](Options).
300    #[inline]
301    fn into_options(self) -> Result<Options, Error>
302    where
303        Self: Sized,
304    {
305        let headers = self.into_headers()?;
306        Ok(Options { headers })
307    }
308}