diff --git a/.changes/plugin-refactor.md b/.changes/plugin-refactor.md index 8b7f88927..fa9cc5c26 100644 --- a/.changes/plugin-refactor.md +++ b/.changes/plugin-refactor.md @@ -3,5 +3,5 @@ --- Refactored the `Plugin` trait `initialize` and `extend_api` signatures. -`initialize` now takes the `App` as first argument, and `extend_api` takes a `InvokeResolver` as last argument. +`initialize` now takes the `App` as first argument, and `extend_api` takes an `Invoke` instead of `InvokeMessage`. This adds support to managed state on plugins. diff --git a/.changes/remove-with-window.md b/.changes/remove-with-window.md index 2aff7c756..36cabc9ca 100644 --- a/.changes/remove-with-window.md +++ b/.changes/remove-with-window.md @@ -2,4 +2,4 @@ "tauri": patch --- -Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `FromCommand` trait. +Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `CommandArg` trait. diff --git a/core/tauri-macros/src/command.rs b/core/tauri-macros/src/command.rs index bdc6a138a..74afbc330 100644 --- a/core/tauri-macros/src/command.rs +++ b/core/tauri-macros/src/command.rs @@ -63,7 +63,7 @@ pub fn generate_command(function: ItemFn) -> TokenStream { } } - let arg_name_ = arg_name.clone().unwrap(); + let arg_name_ = arg_name.unwrap(); let arg_name_s = arg_name_.to_string(); let arg_type = match arg_type { @@ -76,13 +76,14 @@ pub fn generate_command(function: ItemFn) -> TokenStream { } }; - invoke_args.append_all(quote! { - let #arg_name_ = match <#arg_type>::from_command(#fn_name_str, #arg_name_s, &message) { - Ok(value) => value, - Err(e) => return tauri::InvokeResponse::error(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string()) - }; + let item = quote!(::tauri::command::CommandItem { + name: #fn_name_str, + key: #arg_name_s, + message: &__message, }); - invoke_arg_names.push(arg_name_.clone()); + + invoke_args.append_all(quote!(let #arg_name_ = <#arg_type>::from_command(#item)?;)); + invoke_arg_names.push(arg_name_); invoke_arg_types.push(arg_type); } @@ -97,21 +98,19 @@ pub fn generate_command(function: ItemFn) -> TokenStream { // otherwise we wrap it with an `Ok()`, converting the return value to tauri::InvokeResponse // note that all types must implement `serde::Serialize`. let return_value = if returns_result { - quote! { - match #fn_name(#(#invoke_arg_names),*)#await_maybe { - Ok(value) => ::core::result::Result::<_, ()>::Ok(value).into(), - Err(e) => ::core::result::Result::<(), _>::Err(e).into(), - } - } + quote!(::core::result::Result::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe?)) } else { - quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe).into() } + quote! { ::core::result::Result::<_, ::tauri::InvokeError>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe) } }; + // double underscore prefix temporary until underlying scoping issue is fixed (planned) quote! { #function - #vis fn #fn_wrapper(message: ::tauri::InvokeMessage

, resolver: ::tauri::InvokeResolver

) { - use ::tauri::command::FromCommand; - resolver.respond_async(async move { + + #vis fn #fn_wrapper(invoke: ::tauri::Invoke

) { + use ::tauri::command::CommandArg; + let ::tauri::Invoke { message: __message, resolver: __resolver } = invoke; + __resolver.respond_async(async move { #invoke_args #return_value }) @@ -139,12 +138,12 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream { }); quote! { - move |message, resolver| { - let cmd = message.command().to_string(); - match cmd.as_str() { - #(stringify!(#fn_names) => #fn_wrappers(message, resolver),)* + move |invoke| { + let cmd = invoke.message.command(); + match cmd { + #(stringify!(#fn_names) => #fn_wrappers(invoke),)* _ => { - resolver.reject(format!("command {} not found", cmd)) + invoke.resolver.reject(format!("command {} not found", cmd)) }, } } diff --git a/core/tauri/src/command.rs b/core/tauri/src/command.rs index 9550a1a1b..31a053f13 100644 --- a/core/tauri/src/command.rs +++ b/core/tauri/src/command.rs @@ -4,297 +4,136 @@ //! Useful items for custom commands. +use crate::hooks::InvokeError; use crate::{InvokeMessage, Params}; use serde::de::Visitor; use serde::Deserializer; -/// A [`Deserializer`] wrapper around [`Value::get`]. -/// -/// If the key doesn't exist, an error will be returned if the deserialized type is not expecting -/// an optional item. If the key does exist, the value will be called with [`Value`]'s -/// [`Deserializer`] implementation. -struct KeyedValue<'de> { - command: &'de str, - key: &'de str, - value: &'de serde_json::Value, +/// Represents a custom command. +pub struct CommandItem<'a, P: Params> { + /// The name of the command, e.g. `handler` on `#[command] fn handler(value: u64)` + pub name: &'static str, + + /// The key of the command item, e.g. `value` on `#[command] fn handler(value: u64)` + pub key: &'static str, + + /// The [`InvokeMessage`] that was passed to this command. + pub message: &'a InvokeMessage

, } -macro_rules! kv_value { - ($s:ident) => {{ - use serde::de::Error; +/// Trait implemented by command arguments to derive a value from a [`CommandItem`]. +/// +/// # Command Arguments +/// +/// A command argument is any type that represents an item parsable from a [`CommandItem`]. Most +/// implementations will use the data stored in [`InvokeMessage`] since [`CommandItem`] is mostly a +/// wrapper around it. +/// +/// # Provided Implementations +/// +/// Tauri implements [`CommandArg`] automatically for a number of types. +/// * [`tauri::Window`] +/// * [`tauri::State`] +/// * `T where T: serde::Deserialize` +/// * Any type that implements `Deserialize` can automatically be used as a [`CommandArg`]. +pub trait CommandArg<'de, P: Params>: Sized { + /// Derives an instance of `Self` from the [`CommandItem`]. + /// + /// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`]. + fn from_command(command: CommandItem<'de, P>) -> Result; +} - match $s.value.get($s.key) { - Some(value) => value, - None => { - return Err(serde_json::Error::custom(format!( - "command {} missing required key `{}`", - $s.command, $s.key - ))) +/// Automatically implement [`CommandArg`] for any type that can be deserialized. +impl<'de, D: serde::Deserialize<'de>, P: Params> CommandArg<'de, P> for D { + fn from_command(command: CommandItem<'de, P>) -> Result { + let arg = command.key; + Self::deserialize(command).map_err(|e| crate::Error::InvalidArgs(arg, e).into()) + } +} + +/// Pass the result of [`serde_json::Value::get`] into [`serde_json::Value`]'s deserializer. +/// +/// Returns an error if the [`CommandItem`]'s key does not exist in the value. +macro_rules! pass { + ($fn:ident, $($arg:ident: $argt:ty),+) => { + fn $fn>(self, $($arg: $argt),*) -> Result { + use serde::de::Error; + + match self.message.payload.get(self.key) { + Some(value) => value.$fn($($arg),*), + None => { + Err(serde_json::Error::custom(format!( + "command {} missing required key {}", + self.name, self.key + ))) + } } } - }}; + } } -impl<'de> Deserializer<'de> for KeyedValue<'de> { +/// A [`Deserializer`] wrapper around [`CommandItem`]. +/// +/// If the key doesn't exist, an error will be returned if the deserialized type is not expecting +/// an optional item. If the key does exist, the value will be called with +/// [`Value`](serde_json::Value)'s [`Deserializer`] implementation. +impl<'de, P: Params> Deserializer<'de> for CommandItem<'de, P> { type Error = serde_json::Error; - fn deserialize_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_any(visitor) - } + pass!(deserialize_any, visitor: V); + pass!(deserialize_bool, visitor: V); + pass!(deserialize_i8, visitor: V); + pass!(deserialize_i16, visitor: V); + pass!(deserialize_i32, visitor: V); + pass!(deserialize_i64, visitor: V); + pass!(deserialize_u8, visitor: V); + pass!(deserialize_u16, visitor: V); + pass!(deserialize_u32, visitor: V); + pass!(deserialize_u64, visitor: V); + pass!(deserialize_f32, visitor: V); + pass!(deserialize_f64, visitor: V); + pass!(deserialize_char, visitor: V); + pass!(deserialize_str, visitor: V); + pass!(deserialize_string, visitor: V); + pass!(deserialize_bytes, visitor: V); + pass!(deserialize_byte_buf, visitor: V); - fn deserialize_bool(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_bool(visitor) - } - - fn deserialize_i8(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_i8(visitor) - } - - fn deserialize_i16(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_i16(visitor) - } - - fn deserialize_i32(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_i32(visitor) - } - - fn deserialize_i64(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_i64(visitor) - } - - fn deserialize_u8(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_u8(visitor) - } - - fn deserialize_u16(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_u16(visitor) - } - - fn deserialize_u32(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_u32(visitor) - } - - fn deserialize_u64(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_u64(visitor) - } - - fn deserialize_f32(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_f32(visitor) - } - - fn deserialize_f64(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_f64(visitor) - } - - fn deserialize_char(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_char(visitor) - } - - fn deserialize_str(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_str(visitor) - } - - fn deserialize_string(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_string(visitor) - } - - fn deserialize_bytes(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_bytes(visitor) - } - - fn deserialize_byte_buf(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_byte_buf(visitor) - } - - fn deserialize_option(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - match self.value.get(self.key) { + fn deserialize_option>(self, visitor: V) -> Result { + match self.message.payload.get(self.key) { Some(value) => value.deserialize_option(visitor), None => visitor.visit_none(), } } - fn deserialize_unit(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_unit(visitor) - } + pass!(deserialize_unit, visitor: V); + pass!(deserialize_unit_struct, name: &'static str, visitor: V); + pass!(deserialize_newtype_struct, name: &'static str, visitor: V); + pass!(deserialize_seq, visitor: V); + pass!(deserialize_tuple, len: usize, visitor: V); - fn deserialize_unit_struct( - self, - name: &'static str, - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_unit_struct(name, visitor) - } - - fn deserialize_newtype_struct( - self, - name: &'static str, - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_newtype_struct(name, visitor) - } - - fn deserialize_seq(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_seq(visitor) - } - - fn deserialize_tuple(self, len: usize, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_tuple(len, visitor) - } - - fn deserialize_tuple_struct( - self, + pass!( + deserialize_tuple_struct, name: &'static str, len: usize, - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_tuple_struct(name, len, visitor) - } + visitor: V + ); - fn deserialize_map(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_map(visitor) - } + pass!(deserialize_map, visitor: V); - fn deserialize_struct( - self, + pass!( + deserialize_struct, name: &'static str, fields: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_struct(name, fields, visitor) - } + visitor: V + ); - fn deserialize_enum( - self, + pass!( + deserialize_enum, name: &'static str, - variants: &'static [&'static str], - visitor: V, - ) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_enum(name, variants, visitor) - } + fields: &'static [&'static str], + visitor: V + ); - fn deserialize_identifier(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_identifier(visitor) - } - - fn deserialize_ignored_any(self, visitor: V) -> Result - where - V: Visitor<'de>, - { - kv_value!(self).deserialize_ignored_any(visitor) - } -} - -/// Trait implemented by command arguments to derive a value from a [`InvokeMessage`]. -/// [`tauri::Window`], [`tauri::State`] and types that implements [`Deserialize`] automatically implements this trait. -pub trait FromCommand<'de, P: Params>: Sized { - /// Derives an instance of `Self` from the [`InvokeMessage`]. - /// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`]. - /// - /// # Arguments - /// - `command`: the command value passed to invoke, e.g. `initialize` on `invoke('initialize', {})`. - /// - `key`: The name of the variable in the command handler, e.g. `value` on `#[command] fn handler(value: u64)` - /// - `message`: The [`InvokeMessage`] instance. - fn from_command( - command: &'de str, - key: &'de str, - message: &'de InvokeMessage

, - ) -> ::core::result::Result; -} - -impl<'de, D: serde::Deserialize<'de>, P: Params> FromCommand<'de, P> for D { - fn from_command( - command: &'de str, - key: &'de str, - message: &'de InvokeMessage

, - ) -> ::core::result::Result { - D::deserialize(KeyedValue { - command, - key, - value: &message.payload, - }) - } + pass!(deserialize_identifier, visitor: V); + pass!(deserialize_ignored_any, visitor: V); } diff --git a/core/tauri/src/endpoints.rs b/core/tauri/src/endpoints.rs index bd6cef097..fa04dadc2 100644 --- a/core/tauri/src/endpoints.rs +++ b/core/tauri/src/endpoints.rs @@ -4,8 +4,8 @@ use crate::{ api::{config::Config, PackageInfo}, - hooks::{InvokeMessage, InvokeResolver}, - Params, Window, + hooks::{InvokeError, InvokeMessage, InvokeResolver}, + Invoke, Params, Window, }; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; @@ -66,67 +66,46 @@ impl Module { cmd .run(package_info) .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() - }), - Self::Process(cmd) => resolver.respond_async(async move { - cmd - .run() - .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() - }), - Self::Fs(cmd) => resolver.respond_async(async move { - cmd - .run() - .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }), + Self::Process(cmd) => resolver + .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }), + Self::Fs(cmd) => resolver + .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }), Self::Window(cmd) => resolver.respond_async(async move { cmd .run(window) .await .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }), Self::Shell(cmd) => resolver.respond_async(async move { cmd .run(window) .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }), Self::Event(cmd) => resolver.respond_async(async move { cmd .run(window) .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }), Self::Internal(cmd) => resolver.respond_async(async move { cmd .run(window) .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() - }), - Self::Dialog(cmd) => resolver.respond_async(async move { - cmd - .run() - .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }), + Self::Dialog(cmd) => resolver + .respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }), Self::Cli(cmd) => { if let Some(cli_config) = config.tauri.cli.clone() { resolver.respond_async(async move { cmd .run(&cli_config) .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }) } } @@ -136,8 +115,7 @@ impl Module { cmd .run(identifier) .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }) } Self::Http(cmd) => resolver.respond_async(async move { @@ -145,32 +123,35 @@ impl Module { .run() .await .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }), Self::GlobalShortcut(cmd) => resolver.respond_async(async move { cmd .run(window) .and_then(|r| r.json) - .map_err(|e| e.to_string()) - .into() + .map_err(InvokeError::from) }), } } } -pub(crate) fn handle( +pub(crate) fn handle( module: String, - message: InvokeMessage, - resolver: InvokeResolver, + invoke: Invoke

, config: &Config, package_info: &PackageInfo, ) { - let mut payload = message.payload; + let Invoke { message, resolver } = invoke; + let InvokeMessage { + mut payload, + window, + .. + } = message; + if let JsonValue::Object(ref mut obj) = payload { obj.insert("module".to_string(), JsonValue::String(module)); } - let window = message.window; + match serde_json::from_value::(payload) { Ok(module) => module.run(window, resolver, config, package_info.clone()), Err(e) => resolver.reject(e.to_string()), diff --git a/core/tauri/src/endpoints/shell.rs b/core/tauri/src/endpoints/shell.rs index 5c2eaa81a..12fa9f2d5 100644 --- a/core/tauri/src/endpoints/shell.rs +++ b/core/tauri/src/endpoints/shell.rs @@ -27,8 +27,9 @@ pub enum Buffer { Raw(Vec), } +#[allow(clippy::unnecessary_wraps)] fn default_env() -> Option> { - Some(Default::default()) + Some(HashMap::default()) } #[allow(dead_code)] diff --git a/core/tauri/src/hooks.rs b/core/tauri/src/hooks.rs index b3efcab57..c9a1d1272 100644 --- a/core/tauri/src/hooks.rs +++ b/core/tauri/src/hooks.rs @@ -12,13 +12,13 @@ use serde_json::Value as JsonValue; use std::{future::Future, sync::Arc}; /// A closure that is run when the Tauri application is setting up. -pub type SetupHook = Box) -> Result<(), Box> + Send>; +pub type SetupHook

= Box) -> Result<(), Box> + Send>; /// A closure that is run everytime Tauri receives a message it doesn't explicitly handle. -pub type InvokeHandler = dyn Fn(InvokeMessage, InvokeResolver) + Send + Sync + 'static; +pub type InvokeHandler

= dyn Fn(Invoke

) + Send + Sync + 'static; /// A closure that is run once every time a window is created and loaded. -pub type OnPageLoad = dyn Fn(Window, PageLoadPayload) + Send + Sync + 'static; +pub type OnPageLoad

= dyn Fn(Window

, PageLoadPayload) + Send + Sync + 'static; /// The payload for the [`OnPageLoad`] hook. #[derive(Debug, Clone, Deserialize)] @@ -33,33 +33,67 @@ impl PageLoadPayload { } } +/// The message and resolver given to a custom command. +pub struct Invoke { + /// The message passed. + pub message: InvokeMessage

, + + /// The resolver of the message. + pub resolver: InvokeResolver

, +} + +/// Error response from an [`InvokeMessage`]. +#[derive(Debug)] +pub struct InvokeError(JsonValue); + +impl InvokeError { + /// Create an [`InvokeError`] as a string of the [`serde_json::Error`] message. + pub fn from_serde_json(error: serde_json::Error) -> Self { + Self(JsonValue::String(error.to_string())) + } +} + +impl From for InvokeError { + fn from(value: T) -> Self { + serde_json::to_value(value) + .map(Self) + .unwrap_or_else(Self::from_serde_json) + } +} + +impl From for InvokeError { + fn from(error: crate::Error) -> Self { + Self(JsonValue::String(error.to_string())) + } +} + /// Response from a [`InvokeMessage`] passed to the [`InvokeResolver`]. #[derive(Debug)] pub enum InvokeResponse { /// Resolve the promise. Ok(JsonValue), /// Reject the promise. - Err(JsonValue), + Err(InvokeError), } -impl From> for InvokeResponse { - fn from(result: Result) -> Self { - match result { - Result::Ok(t) => match serde_json::to_value(t) { - Ok(v) => Self::Ok(v), - Err(e) => Self::Err(JsonValue::String(e.to_string())), - }, - Result::Err(e) => Self::error(e), +impl InvokeResponse { + /// Turn a [`InvokeResponse`] back into a serializable result. + pub fn into_result(self) -> Result { + match self { + Self::Ok(v) => Ok(v), + Self::Err(e) => Err(e.0), } } } -impl InvokeResponse { - #[doc(hidden)] - pub fn error(value: T) -> Self { - match serde_json::to_value(value) { - Ok(v) => Self::Err(v), - Err(e) => Self::Err(JsonValue::String(e.to_string())), +impl From> for InvokeResponse { + fn from(result: Result) -> Self { + match result { + Ok(ok) => match serde_json::to_value(ok) { + Ok(value) => Self::Ok(value), + Err(err) => Self::Err(InvokeError::from_serde_json(err)), + }, + Err(err) => Self::Err(err), } } } @@ -72,19 +106,8 @@ pub struct InvokeResolver { pub(crate) error: String, } -/*impl Clone for InvokeResolver

{ - fn clone(&self) -> Self { - Self { - window: self.window.clone(), - main_thread: self.main_thread, - callback: self.callback.clone(), - error: self.error.clone(), - } - } -}*/ - -impl InvokeResolver { - pub(crate) fn new(window: Window, main_thread: bool, callback: String, error: String) -> Self { +impl InvokeResolver

{ + pub(crate) fn new(window: Window

, main_thread: bool, callback: String, error: String) -> Self { Self { window, main_thread, @@ -94,7 +117,11 @@ impl InvokeResolver { } /// Reply to the invoke promise with an async task. - pub fn respond_async + Send + 'static>(self, task: F) { + pub fn respond_async(self, task: F) + where + T: Serialize, + F: Future> + Send + 'static, + { if self.main_thread { crate::async_runtime::block_on(async move { Self::return_task(self.window, task, self.callback, self.error).await; @@ -107,25 +134,24 @@ impl InvokeResolver { } /// Reply to the invoke promise running the given closure. - pub fn respond_closure InvokeResponse>(self, f: F) { + pub fn respond_closure(self, f: F) + where + T: Serialize, + F: FnOnce() -> Result, + { Self::return_closure(self.window, f, self.callback, self.error) } /// Resolve the invoke promise with a value. pub fn resolve(self, value: S) { - Self::return_result( - self.window, - Result::::Ok(value).into(), - self.callback, - self.error, - ) + Self::return_result(self.window, Ok(value), self.callback, self.error) } /// Reject the invoke promise with a value. pub fn reject(self, value: S) { Self::return_result( self.window, - Result::<(), S>::Err(value).into(), + Result::<(), _>::Err(value.into()), self.callback, self.error, ) @@ -136,18 +162,21 @@ impl InvokeResolver { /// /// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value. /// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value. - pub async fn return_task + Send + 'static>( - window: Window, + pub async fn return_task( + window: Window

, task: F, success_callback: String, error_callback: String, - ) { + ) where + T: Serialize, + F: Future> + Send + 'static, + { let result = task.await; Self::return_closure(window, || result, success_callback, error_callback) } - pub(crate) fn return_closure InvokeResponse>( - window: Window, + pub(crate) fn return_closure Result>( + window: Window

, f: F, success_callback: String, error_callback: String, @@ -155,17 +184,14 @@ impl InvokeResolver { Self::return_result(window, f(), success_callback, error_callback) } - pub(crate) fn return_result( - window: Window, - response: InvokeResponse, + pub(crate) fn return_result( + window: Window

, + response: Result, success_callback: String, error_callback: String, ) { let callback_string = match format_callback_result( - match response { - InvokeResponse::Ok(t) => std::result::Result::Ok(t), - InvokeResponse::Err(e) => std::result::Result::Err(e), - }, + InvokeResponse::from(response).into_result(), success_callback, error_callback.clone(), ) { @@ -190,10 +216,10 @@ pub struct InvokeMessage { pub(crate) payload: JsonValue, } -impl InvokeMessage { +impl InvokeMessage

{ /// Create an new [`InvokeMessage`] from a payload send to a window. pub(crate) fn new( - window: Window, + window: Window

, state: Arc, command: String, payload: JsonValue, @@ -207,12 +233,38 @@ impl InvokeMessage { } /// The invoke command. + #[inline(always)] pub fn command(&self) -> &str { &self.command } /// The window that received the invoke. - pub fn window(&self) -> Window { + #[inline(always)] + pub fn window(&self) -> Window

{ self.window.clone() } + + /// A reference to window that received the invoke. + #[inline(always)] + pub fn window_ref(&self) -> &Window

{ + &self.window + } + + /// A reference to the payload the invoke received. + #[inline(always)] + pub fn payload(&self) -> &JsonValue { + &self.payload + } + + /// The state manager associated with the application + #[inline(always)] + pub fn state(&self) -> Arc { + self.state.clone() + } + + /// A reference to the state manager associated with application. + #[inline(always)] + pub fn state_ref(&self) -> &StateManager { + &self.state + } } diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 83ac2750f..0c9bcbe95 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -54,8 +54,8 @@ use std::{borrow::Borrow, collections::HashMap, path::PathBuf}; pub use { self::api::config::WindowUrl, self::hooks::{ - InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, PageLoadPayload, - SetupHook, + Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, + PageLoadPayload, SetupHook, }, self::runtime::app::{App, Builder}, self::runtime::flavors::wry::Wry, diff --git a/core/tauri/src/plugin.rs b/core/tauri/src/plugin.rs index e8c27fee6..c625f2952 100644 --- a/core/tauri/src/plugin.rs +++ b/core/tauri/src/plugin.rs @@ -4,11 +4,7 @@ //! Extend Tauri functionality. -use crate::{ - api::config::PluginConfig, - hooks::{InvokeMessage, InvokeResolver, PageLoadPayload}, - App, Params, Window, -}; +use crate::{api::config::PluginConfig, App, Invoke, PageLoadPayload, Params, Window}; use serde_json::Value as JsonValue; use std::collections::HashMap; @@ -16,13 +12,13 @@ use std::collections::HashMap; pub type Result = std::result::Result>; /// The plugin interface. -pub trait Plugin: Send { +pub trait Plugin: Send { /// The plugin name. Used as key on the plugin config object. fn name(&self) -> &'static str; /// Initialize the plugin. #[allow(unused_variables)] - fn initialize(&mut self, app: &App, config: JsonValue) -> Result<()> { + fn initialize(&mut self, app: &App

, config: JsonValue) -> Result<()> { Ok(()) } @@ -37,23 +33,23 @@ pub trait Plugin: Send { /// Callback invoked when the webview is created. #[allow(unused_variables)] - fn created(&mut self, window: Window) {} + fn created(&mut self, window: Window

) {} /// Callback invoked when the webview performs a navigation. #[allow(unused_variables)] - fn on_page_load(&mut self, window: Window, payload: PageLoadPayload) {} + fn on_page_load(&mut self, window: Window

, payload: PageLoadPayload) {} /// Add invoke_handler API extension commands. #[allow(unused_variables)] - fn extend_api(&mut self, message: InvokeMessage, resolver: InvokeResolver) {} + fn extend_api(&mut self, invoke: Invoke

) {} } /// Plugin collection type. -pub(crate) struct PluginStore { - store: HashMap<&'static str, Box>>, +pub(crate) struct PluginStore { + store: HashMap<&'static str, Box>>, } -impl Default for PluginStore { +impl Default for PluginStore

{ fn default() -> Self { Self { store: HashMap::new(), @@ -61,16 +57,16 @@ impl Default for PluginStore { } } -impl PluginStore { +impl PluginStore

{ /// Adds a plugin to the store. /// /// Returns `true` if a plugin with the same name is already in the store. - pub fn register + 'static>(&mut self, plugin: P) -> bool { + pub fn register + 'static>(&mut self, plugin: Plug) -> bool { self.store.insert(plugin.name(), Box::new(plugin)).is_some() } /// Initializes all plugins in the store. - pub(crate) fn initialize(&mut self, app: &App, config: &PluginConfig) -> crate::Result<()> { + pub(crate) fn initialize(&mut self, app: &App

, config: &PluginConfig) -> crate::Result<()> { self.store.values_mut().try_for_each(|plugin| { plugin .initialize( @@ -93,7 +89,7 @@ impl PluginStore { } /// Runs the created hook for all plugins in the store. - pub(crate) fn created(&mut self, window: Window) { + pub(crate) fn created(&mut self, window: Window

) { self .store .values_mut() @@ -101,27 +97,29 @@ impl PluginStore { } /// Runs the on_page_load hook for all plugins in the store. - pub(crate) fn on_page_load(&mut self, window: Window, payload: PageLoadPayload) { + pub(crate) fn on_page_load(&mut self, window: Window

, payload: PageLoadPayload) { self .store .values_mut() .for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone())) } - pub(crate) fn extend_api(&mut self, mut message: InvokeMessage, resolver: InvokeResolver) { - let command = message.command.replace("plugin:", ""); + pub(crate) fn extend_api(&mut self, mut invoke: Invoke

) { + let command = invoke.message.command.replace("plugin:", ""); let mut tokens = command.split('|'); // safe to unwrap: split always has a least one item let target = tokens.next().unwrap(); if let Some(plugin) = self.store.get_mut(target) { - message.command = tokens + invoke.message.command = tokens .next() .map(|c| c.to_string()) .unwrap_or_else(String::new); - plugin.extend_api(message, resolver); + plugin.extend_api(invoke); } else { - resolver.reject(format!("plugin {} not found", target)); + invoke + .resolver + .reject(format!("plugin {} not found", target)); } } } diff --git a/core/tauri/src/runtime/app.rs b/core/tauri/src/runtime/app.rs index ce0ecd979..092f47751 100644 --- a/core/tauri/src/runtime/app.rs +++ b/core/tauri/src/runtime/app.rs @@ -4,7 +4,7 @@ use crate::{ api::{assets::Assets, config::WindowUrl}, - hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload, SetupHook}, + hooks::{InvokeHandler, OnPageLoad, PageLoadPayload, SetupHook}, plugin::{Plugin, PluginStore}, runtime::{ flavors::wry::Wry, @@ -15,7 +15,7 @@ use crate::{ Dispatch, Runtime, }, sealed::{ManagerBase, RuntimeOrDispatch}, - Context, Manager, Params, StateManager, Window, + Context, Invoke, Manager, Params, StateManager, Window, }; use std::{collections::HashMap, sync::Arc}; @@ -142,7 +142,7 @@ where pub fn new() -> Self { Self { setup: Box::new(|_| Ok(())), - invoke_handler: Box::new(|_, _| ()), + invoke_handler: Box::new(|_| ()), on_page_load: Box::new(|_, _| ()), pending_windows: Default::default(), plugins: PluginStore::default(), @@ -154,8 +154,7 @@ where /// Defines the JS message handler callback. pub fn invoke_handler(mut self, invoke_handler: F) -> Self where - F: - Fn(InvokeMessage>, InvokeResolver>) + Send + Sync + 'static, + F: Fn(Invoke>) + Send + Sync + 'static, { self.invoke_handler = Box::new(invoke_handler); self diff --git a/core/tauri/src/runtime/manager.rs b/core/tauri/src/runtime/manager.rs index 679ac9b4e..8713d8348 100644 --- a/core/tauri/src/runtime/manager.rs +++ b/core/tauri/src/runtime/manager.rs @@ -10,7 +10,7 @@ use crate::{ PackageInfo, }, event::{Event, EventHandler, Listeners}, - hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload}, + hooks::{InvokeHandler, OnPageLoad, PageLoadPayload}, plugin::PluginStore, runtime::{ tag::{tags_to_javascript_array, Tag, TagRef, ToJsString}, @@ -22,7 +22,7 @@ use crate::{ Icon, Runtime, }, sealed::ParamsBase, - App, Context, Params, StateManager, Window, + App, Context, Invoke, Params, StateManager, Window, }; use serde::Serialize; use serde_json::Value as JsonValue; @@ -401,7 +401,7 @@ mod test { let manager: WindowManager> = WindowManager::with_handlers( context, PluginStore::default(), - Box::new(|_, _| ()), + Box::new(|_| ()), Box::new(|_, _| ()), Default::default(), StateManager::new(), @@ -416,8 +416,8 @@ mod test { } impl WindowManager

{ - pub fn run_invoke_handler(&self, message: InvokeMessage

, resolver: InvokeResolver

) { - (self.inner.invoke_handler)(message, resolver); + pub fn run_invoke_handler(&self, invoke: Invoke

) { + (self.inner.invoke_handler)(invoke); } pub fn run_on_page_load(&self, window: Window

, payload: PageLoadPayload) { @@ -430,13 +430,13 @@ impl WindowManager

{ .on_page_load(window, payload); } - pub fn extend_api(&self, message: InvokeMessage

, resolver: InvokeResolver

) { + pub fn extend_api(&self, invoke: Invoke

) { self .inner .plugins .lock() .expect("poisoned plugin store") - .extend_api(message, resolver); + .extend_api(invoke); } pub fn initialize_plugins(&self, app: &App

) -> crate::Result<()> { diff --git a/core/tauri/src/runtime/window.rs b/core/tauri/src/runtime/window.rs index d04bd611f..c4690777a 100644 --- a/core/tauri/src/runtime/window.rs +++ b/core/tauri/src/runtime/window.rs @@ -114,8 +114,9 @@ impl PartialEq for DetachedWindow { /// We want to export the runtime related window at the crate root, but not look like a re-export. pub(crate) mod export { use super::*; - use crate::command::FromCommand; + use crate::command::{CommandArg, CommandItem}; use crate::runtime::{manager::WindowManager, tag::TagRef}; + use crate::{Invoke, InvokeError}; use std::borrow::Borrow; /// A webview window managed by Tauri. @@ -167,13 +168,10 @@ pub(crate) mod export { } } - impl<'de, P: Params> FromCommand<'de, P> for Window

{ - fn from_command( - _: &'de str, - _: &'de str, - message: &'de InvokeMessage

, - ) -> Result { - Ok(message.window()) + impl<'de, P: Params> CommandArg<'de, P> for Window

{ + /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail. + fn from_command(command: CommandItem<'de, P>) -> Result { + Ok(command.message.window()) } } @@ -205,19 +203,14 @@ pub(crate) mod export { ); let resolver = InvokeResolver::new(self, payload.main_thread, payload.callback, payload.error); + let invoke = Invoke { message, resolver }; if let Some(module) = &payload.tauri_module { let module = module.to_string(); - crate::endpoints::handle( - module, - message, - resolver, - manager.config(), - manager.package_info(), - ); + crate::endpoints::handle(module, invoke, manager.config(), manager.package_info()); } else if command.starts_with("plugin:") { - manager.extend_api(message, resolver); + manager.extend_api(invoke); } else { - manager.run_invoke_handler(message, resolver); + manager.run_invoke_handler(invoke); } } } diff --git a/core/tauri/src/state.rs b/core/tauri/src/state.rs index 57a471034..c99207c71 100644 --- a/core/tauri/src/state.rs +++ b/core/tauri/src/state.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use crate::command::FromCommand; -use crate::{InvokeMessage, Params}; +use crate::command::{CommandArg, CommandItem}; +use crate::{InvokeError, Params}; use state::Container; /// A guard for a state value. @@ -34,13 +34,10 @@ impl Clone for State<'_, T> { } } -impl<'r, 'de: 'r, T: Send + Sync + 'static, P: Params> FromCommand<'de, P> for State<'r, T> { - fn from_command( - _: &'de str, - _: &'de str, - message: &'de InvokeMessage

, - ) -> Result { - Ok(message.state.get()) +impl<'r, 'de: 'r, T: Send + Sync + 'static, P: Params> CommandArg<'de, P> for State<'r, T> { + /// Grabs the [`State`] from the [`CommandItem`]. This will never fail. + fn from_command(command: CommandItem<'de, P>) -> Result { + Ok(command.message.state_ref().get()) } } diff --git a/examples/commands/src-tauri/src/main.rs b/examples/commands/src-tauri/src/main.rs index 9eefb0397..5e438dcdc 100644 --- a/examples/commands/src-tauri/src/main.rs +++ b/examples/commands/src-tauri/src/main.rs @@ -42,7 +42,7 @@ type Result = std::result::Result; #[tauri::command] fn simple_command_with_result(argument: String) -> Result { println!("{}", argument); - Ok(argument) + (!argument.is_empty()).then(|| argument).ok_or(()) } #[tauri::command] @@ -51,7 +51,7 @@ fn stateful_command_with_result( state: tauri::State<'_, MyState>, ) -> Result { println!("{:?} {:?}", argument, state.inner()); - Ok(argument.unwrap_or_else(|| "".to_string())) + argument.ok_or(()) } // Async commands