Browse Source

refactor(core): merge invoke items into single struct, allow ? (#1683)

chip 4 years ago
parent
commit
1d6f418129

+ 1 - 1
.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.

+ 1 - 1
.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.

+ 21 - 22
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<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>, resolver: ::tauri::InvokeResolver<P>) {
-      use ::tauri::command::FromCommand;
-      resolver.respond_async(async move {
+
+    #vis fn #fn_wrapper<P: ::tauri::Params>(invoke: ::tauri::Invoke<P>) {
+      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))
         },
       }
     }

+ 103 - 264
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,
 
-macro_rules! kv_value {
-  ($s:ident) => {{
-    use serde::de::Error;
+  /// The key of the command item, e.g. `value` on `#[command] fn handler(value: u64)`
+  pub key: &'static str,
 
-    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
-        )))
-      }
-    }
-  }};
+  /// The [`InvokeMessage`] that was passed to this command.
+  pub message: &'a InvokeMessage<P>,
 }
 
-impl<'de> Deserializer<'de> for KeyedValue<'de> {
-  type Error = serde_json::Error;
-
-  fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_any(visitor)
-  }
-
-  fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_bool(visitor)
-  }
-
-  fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_i8(visitor)
-  }
-
-  fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_i16(visitor)
-  }
-
-  fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_i32(visitor)
-  }
-
-  fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_i64(visitor)
-  }
-
-  fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_u8(visitor)
-  }
-
-  fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_u16(visitor)
-  }
-
-  fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_u32(visitor)
-  }
-
-  fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_u64(visitor)
-  }
-
-  fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_f32(visitor)
-  }
-
-  fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_f64(visitor)
-  }
-
-  fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_char(visitor)
-  }
-
-  fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_str(visitor)
-  }
+/// 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<Self, InvokeError>;
+}
 
-  fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_string(visitor)
+/// 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<Self, InvokeError> {
+    let arg = command.key;
+    Self::deserialize(command).map_err(|e| crate::Error::InvalidArgs(arg, e).into())
   }
+}
 
-  fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_bytes(visitor)
+/// 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<V: Visitor<'de>>(self, $($arg: $argt),*) -> Result<V::Value, Self::Error> {
+      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
+          )))
+        }
+      }
+    }
   }
+}
 
-  fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_byte_buf(visitor)
-  }
+/// 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_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    match self.value.get(self.key) {
+  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_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
+    match self.message.payload.get(self.key) {
       Some(value) => value.deserialize_option(visitor),
       None => visitor.visit_none(),
     }
   }
 
-  fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_unit(visitor)
-  }
-
-  fn deserialize_unit_struct<V>(
-    self,
-    name: &'static str,
-    visitor: V,
-  ) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_unit_struct(name, visitor)
-  }
-
-  fn deserialize_newtype_struct<V>(
-    self,
-    name: &'static str,
-    visitor: V,
-  ) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_newtype_struct(name, visitor)
-  }
-
-  fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_seq(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_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_tuple(len, visitor)
-  }
-
-  fn deserialize_tuple_struct<V>(
-    self,
+  pass!(
+    deserialize_tuple_struct,
     name: &'static str,
     len: usize,
-    visitor: V,
-  ) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_tuple_struct(name, len, visitor)
-  }
+    visitor: V
+  );
 
-  fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_map(visitor)
-  }
+  pass!(deserialize_map, visitor: V);
 
-  fn deserialize_struct<V>(
-    self,
+  pass!(
+    deserialize_struct,
     name: &'static str,
     fields: &'static [&'static str],
-    visitor: V,
-  ) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_struct(name, fields, visitor)
-  }
+    visitor: V
+  );
 
-  fn deserialize_enum<V>(
-    self,
+  pass!(
+    deserialize_enum,
     name: &'static str,
-    variants: &'static [&'static str],
-    visitor: V,
-  ) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_enum(name, variants, visitor)
-  }
-
-  fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  where
-    V: Visitor<'de>,
-  {
-    kv_value!(self).deserialize_identifier(visitor)
-  }
-
-  fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
-  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<P>,
-  ) -> ::core::result::Result<Self, serde_json::Error>;
-}
+    fields: &'static [&'static str],
+    visitor: V
+  );
 
-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<P>,
-  ) -> ::core::result::Result<Self, serde_json::Error> {
-    D::deserialize(KeyedValue {
-      command,
-      key,
-      value: &message.payload,
-    })
-  }
+  pass!(deserialize_identifier, visitor: V);
+  pass!(deserialize_ignored_any, visitor: V);
 }

+ 27 - 46
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<M: Params>(
+pub(crate) fn handle<P: Params>(
   module: String,
-  message: InvokeMessage<M>,
-  resolver: InvokeResolver<M>,
+  invoke: Invoke<P>,
   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::<Module>(payload) {
     Ok(module) => module.run(window, resolver, config, package_info.clone()),
     Err(e) => resolver.reject(e.to_string()),

+ 2 - 1
core/tauri/src/endpoints/shell.rs

@@ -27,8 +27,9 @@ pub enum Buffer {
   Raw(Vec<u8>),
 }
 
+#[allow(clippy::unnecessary_wraps)]
 fn default_env() -> Option<HashMap<String, String>> {
-  Some(Default::default())
+  Some(HashMap::default())
 }
 
 #[allow(dead_code)]

+ 107 - 55
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<M> = Box<dyn Fn(&mut App<M>) -> Result<(), Box<dyn std::error::Error>> + Send>;
+pub type SetupHook<P> = Box<dyn Fn(&mut App<P>) -> Result<(), Box<dyn std::error::Error>> + Send>;
 
 /// A closure that is run everytime Tauri receives a message it doesn't explicitly handle.
-pub type InvokeHandler<M> = dyn Fn(InvokeMessage<M>, InvokeResolver<M>) + Send + Sync + 'static;
+pub type InvokeHandler<P> = dyn Fn(Invoke<P>) + Send + Sync + 'static;
 
 /// A closure that is run once every time a window is created and loaded.
-pub type OnPageLoad<M> = dyn Fn(Window<M>, PageLoadPayload) + Send + Sync + 'static;
+pub type OnPageLoad<P> = dyn Fn(Window<P>, 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<P: Params> {
+  /// The message passed.
+  pub message: InvokeMessage<P>,
+
+  /// The resolver of the message.
+  pub resolver: InvokeResolver<P>,
+}
+
+/// 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<T: Serialize> From<T> for InvokeError {
+  fn from(value: T) -> Self {
+    serde_json::to_value(value)
+      .map(Self)
+      .unwrap_or_else(Self::from_serde_json)
+  }
+}
+
+impl From<crate::Error> 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<T: Serialize, E: Serialize> From<Result<T, E>> for InvokeResponse {
-  fn from(result: Result<T, E>) -> 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<JsonValue, JsonValue> {
+    match self {
+      Self::Ok(v) => Ok(v),
+      Self::Err(e) => Err(e.0),
     }
   }
 }
 
-impl InvokeResponse {
-  #[doc(hidden)]
-  pub fn error<T: Serialize>(value: T) -> Self {
-    match serde_json::to_value(value) {
-      Ok(v) => Self::Err(v),
-      Err(e) => Self::Err(JsonValue::String(e.to_string())),
+impl<T: Serialize> From<Result<T, InvokeError>> for InvokeResponse {
+  fn from(result: Result<T, InvokeError>) -> 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<M: Params> {
   pub(crate) error: String,
 }
 
-/*impl<P: Params> Clone for InvokeResolver<P> {
-  fn clone(&self) -> Self {
-    Self {
-      window: self.window.clone(),
-      main_thread: self.main_thread,
-      callback: self.callback.clone(),
-      error: self.error.clone(),
-    }
-  }
-}*/
-
-impl<M: Params> InvokeResolver<M> {
-  pub(crate) fn new(window: Window<M>, main_thread: bool, callback: String, error: String) -> Self {
+impl<P: Params> InvokeResolver<P> {
+  pub(crate) fn new(window: Window<P>, main_thread: bool, callback: String, error: String) -> Self {
     Self {
       window,
       main_thread,
@@ -94,7 +117,11 @@ impl<M: Params> InvokeResolver<M> {
   }
 
   /// Reply to the invoke promise with an async task.
-  pub fn respond_async<F: Future<Output = InvokeResponse> + Send + 'static>(self, task: F) {
+  pub fn respond_async<T, F>(self, task: F)
+  where
+    T: Serialize,
+    F: Future<Output = Result<T, InvokeError>> + 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<M: Params> InvokeResolver<M> {
   }
 
   /// Reply to the invoke promise running the given closure.
-  pub fn respond_closure<F: FnOnce() -> InvokeResponse>(self, f: F) {
+  pub fn respond_closure<T, F>(self, f: F)
+  where
+    T: Serialize,
+    F: FnOnce() -> Result<T, InvokeError>,
+  {
     Self::return_closure(self.window, f, self.callback, self.error)
   }
 
   /// Resolve the invoke promise with a value.
   pub fn resolve<S: Serialize>(self, value: S) {
-    Self::return_result(
-      self.window,
-      Result::<S, ()>::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<S: Serialize>(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<M: Params> InvokeResolver<M> {
   ///
   /// 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<F: std::future::Future<Output = InvokeResponse> + Send + 'static>(
-    window: Window<M>,
+  pub async fn return_task<T, F>(
+    window: Window<P>,
     task: F,
     success_callback: String,
     error_callback: String,
-  ) {
+  ) where
+    T: Serialize,
+    F: Future<Output = Result<T, InvokeError>> + Send + 'static,
+  {
     let result = task.await;
     Self::return_closure(window, || result, success_callback, error_callback)
   }
 
-  pub(crate) fn return_closure<F: FnOnce() -> InvokeResponse>(
-    window: Window<M>,
+  pub(crate) fn return_closure<T: Serialize, F: FnOnce() -> Result<T, InvokeError>>(
+    window: Window<P>,
     f: F,
     success_callback: String,
     error_callback: String,
@@ -155,17 +184,14 @@ impl<M: Params> InvokeResolver<M> {
     Self::return_result(window, f(), success_callback, error_callback)
   }
 
-  pub(crate) fn return_result(
-    window: Window<M>,
-    response: InvokeResponse,
+  pub(crate) fn return_result<T: Serialize>(
+    window: Window<P>,
+    response: Result<T, InvokeError>,
     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<M: Params> {
   pub(crate) payload: JsonValue,
 }
 
-impl<M: Params> InvokeMessage<M> {
+impl<P: Params> InvokeMessage<P> {
   /// Create an new [`InvokeMessage`] from a payload send to a window.
   pub(crate) fn new(
-    window: Window<M>,
+    window: Window<P>,
     state: Arc<StateManager>,
     command: String,
     payload: JsonValue,
@@ -207,12 +233,38 @@ impl<M: Params> InvokeMessage<M> {
   }
 
   /// The invoke command.
+  #[inline(always)]
   pub fn command(&self) -> &str {
     &self.command
   }
 
   /// The window that received the invoke.
-  pub fn window(&self) -> Window<M> {
+  #[inline(always)]
+  pub fn window(&self) -> Window<P> {
     self.window.clone()
   }
+
+  /// A reference to window that received the invoke.
+  #[inline(always)]
+  pub fn window_ref(&self) -> &Window<P> {
+    &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<StateManager> {
+    self.state.clone()
+  }
+
+  /// A reference to the state manager associated with application.
+  #[inline(always)]
+  pub fn state_ref(&self) -> &StateManager {
+    &self.state
+  }
 }

+ 2 - 2
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,

+ 21 - 23
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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
 
 /// The plugin interface.
-pub trait Plugin<M: Params>: Send {
+pub trait Plugin<P: Params>: 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<M>, config: JsonValue) -> Result<()> {
+  fn initialize(&mut self, app: &App<P>, config: JsonValue) -> Result<()> {
     Ok(())
   }
 
@@ -37,23 +33,23 @@ pub trait Plugin<M: Params>: Send {
 
   /// Callback invoked when the webview is created.
   #[allow(unused_variables)]
-  fn created(&mut self, window: Window<M>) {}
+  fn created(&mut self, window: Window<P>) {}
 
   /// Callback invoked when the webview performs a navigation.
   #[allow(unused_variables)]
-  fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {}
+  fn on_page_load(&mut self, window: Window<P>, payload: PageLoadPayload) {}
 
   /// Add invoke_handler API extension commands.
   #[allow(unused_variables)]
-  fn extend_api(&mut self, message: InvokeMessage<M>, resolver: InvokeResolver<M>) {}
+  fn extend_api(&mut self, invoke: Invoke<P>) {}
 }
 
 /// Plugin collection type.
-pub(crate) struct PluginStore<M: Params> {
-  store: HashMap<&'static str, Box<dyn Plugin<M>>>,
+pub(crate) struct PluginStore<P: Params> {
+  store: HashMap<&'static str, Box<dyn Plugin<P>>>,
 }
 
-impl<M: Params> Default for PluginStore<M> {
+impl<P: Params> Default for PluginStore<P> {
   fn default() -> Self {
     Self {
       store: HashMap::new(),
@@ -61,16 +57,16 @@ impl<M: Params> Default for PluginStore<M> {
   }
 }
 
-impl<M: Params> PluginStore<M> {
+impl<P: Params> PluginStore<P> {
   /// Adds a plugin to the store.
   ///
   /// Returns `true` if a plugin with the same name is already in the store.
-  pub fn register<P: Plugin<M> + 'static>(&mut self, plugin: P) -> bool {
+  pub fn register<Plug: Plugin<P> + '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<M>, config: &PluginConfig) -> crate::Result<()> {
+  pub(crate) fn initialize(&mut self, app: &App<P>, config: &PluginConfig) -> crate::Result<()> {
     self.store.values_mut().try_for_each(|plugin| {
       plugin
         .initialize(
@@ -93,7 +89,7 @@ impl<M: Params> PluginStore<M> {
   }
 
   /// Runs the created hook for all plugins in the store.
-  pub(crate) fn created(&mut self, window: Window<M>) {
+  pub(crate) fn created(&mut self, window: Window<P>) {
     self
       .store
       .values_mut()
@@ -101,27 +97,29 @@ impl<M: Params> PluginStore<M> {
   }
 
   /// Runs the on_page_load hook for all plugins in the store.
-  pub(crate) fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {
+  pub(crate) fn on_page_load(&mut self, window: Window<P>, 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<M>, resolver: InvokeResolver<M>) {
-    let command = message.command.replace("plugin:", "");
+  pub(crate) fn extend_api(&mut self, mut invoke: Invoke<P>) {
+    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));
     }
   }
 }

+ 4 - 5
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<F>(mut self, invoke_handler: F) -> Self
   where
-    F:
-      Fn(InvokeMessage<Args<E, L, A, R>>, InvokeResolver<Args<E, L, A, R>>) + Send + Sync + 'static,
+    F: Fn(Invoke<Args<E, L, A, R>>) + Send + Sync + 'static,
   {
     self.invoke_handler = Box::new(invoke_handler);
     self

+ 7 - 7
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<Args<String, String, _, Wry>> = WindowManager::with_handlers(
       context,
       PluginStore::default(),
-      Box::new(|_, _| ()),
+      Box::new(|_| ()),
       Box::new(|_, _| ()),
       Default::default(),
       StateManager::new(),
@@ -416,8 +416,8 @@ mod test {
 }
 
 impl<P: Params> WindowManager<P> {
-  pub fn run_invoke_handler(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
-    (self.inner.invoke_handler)(message, resolver);
+  pub fn run_invoke_handler(&self, invoke: Invoke<P>) {
+    (self.inner.invoke_handler)(invoke);
   }
 
   pub fn run_on_page_load(&self, window: Window<P>, payload: PageLoadPayload) {
@@ -430,13 +430,13 @@ impl<P: Params> WindowManager<P> {
       .on_page_load(window, payload);
   }
 
-  pub fn extend_api(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
+  pub fn extend_api(&self, invoke: Invoke<P>) {
     self
       .inner
       .plugins
       .lock()
       .expect("poisoned plugin store")
-      .extend_api(message, resolver);
+      .extend_api(invoke);
   }
 
   pub fn initialize_plugins(&self, app: &App<P>) -> crate::Result<()> {

+ 10 - 17
core/tauri/src/runtime/window.rs

@@ -114,8 +114,9 @@ impl<M: Params> PartialEq for DetachedWindow<M> {
 /// 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<P> {
-    fn from_command(
-      _: &'de str,
-      _: &'de str,
-      message: &'de InvokeMessage<P>,
-    ) -> Result<Self, serde_json::Error> {
-      Ok(message.window())
+  impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
+    /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.
+    fn from_command(command: CommandItem<'de, P>) -> Result<Self, InvokeError> {
+      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);
           }
         }
       }

+ 6 - 9
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<T: Send + Sync + 'static> 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<P>,
-  ) -> Result<Self, serde_json::Error> {
-    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<Self, InvokeError> {
+    Ok(command.message.state_ref().get())
   }
 }
 

+ 2 - 2
examples/commands/src-tauri/src/main.rs

@@ -42,7 +42,7 @@ type Result<T> = std::result::Result<T, ()>;
 #[tauri::command]
 fn simple_command_with_result(argument: String) -> Result<String> {
   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<String> {
   println!("{:?} {:?}", argument, state.inner());
-  Ok(argument.unwrap_or_else(|| "".to_string()))
+  argument.ok_or(())
 }
 
 // Async commands