Просмотр исходного кода

refactor: use macros to match core commands with allowlist conditionals [TRI-028] (#20)

Lucas Nogueira 3 лет назад
Родитель
Сommit
5687b7fdcd

+ 1 - 0
core/tauri-macros/Cargo.toml

@@ -19,6 +19,7 @@ proc-macro = true
 proc-macro2 = "1"
 quote = "1"
 syn = { version = "1", features = [ "full" ] }
+heck = "0.3"
 tauri-codegen = { version = "1.0.0-beta.4", default-features = false, path = "../tauri-codegen" }
 
 [features]

+ 142 - 0
core/tauri-macros/src/command_module.rs

@@ -0,0 +1,142 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use heck::SnakeCase;
+use proc_macro::TokenStream;
+use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
+
+use quote::{format_ident, quote, quote_spanned};
+use syn::{
+  parse::{Parse, ParseStream},
+  spanned::Spanned,
+  Data, DeriveInput, Error, Fields, Ident, ItemFn, LitStr, Token,
+};
+
+pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
+  let name = &input.ident;
+  let data = &input.data;
+
+  let mut is_async = false;
+  let attrs = input.attrs;
+  for attr in attrs {
+    if attr.path.is_ident("cmd") {
+      let _ = attr.parse_args_with(|input: ParseStream| {
+        while let Some(token) = input.parse()? {
+          if let TokenTree::Ident(ident) = token {
+            is_async |= ident == "async";
+          }
+        }
+        Ok(())
+      });
+    }
+  }
+  let maybe_await = if is_async { quote!(.await) } else { quote!() };
+  let maybe_async = if is_async { quote!(async) } else { quote!() };
+
+  let mut matcher;
+
+  match data {
+    Data::Enum(data_enum) => {
+      matcher = TokenStream2::new();
+
+      for variant in &data_enum.variants {
+        let variant_name = &variant.ident;
+
+        let (fields_in_variant, variables) = match &variant.fields {
+          Fields::Unit => (quote_spanned! { variant.span() => }, quote!()),
+          Fields::Unnamed(fields) => {
+            let mut variables = TokenStream2::new();
+            for i in 0..fields.unnamed.len() {
+              let variable_name = format_ident!("value{}", i);
+              variables.extend(quote!(#variable_name,));
+            }
+            (quote_spanned! { variant.span() => (#variables) }, variables)
+          }
+          Fields::Named(fields) => {
+            let mut variables = TokenStream2::new();
+            for field in &fields.named {
+              let ident = field.ident.as_ref().unwrap();
+              variables.extend(quote!(#ident,));
+            }
+            (
+              quote_spanned! { variant.span() => { #variables } },
+              variables,
+            )
+          }
+        };
+
+        let mut variant_execute_function_name = format_ident!(
+          "{}",
+          variant_name.to_string().to_snake_case().to_lowercase()
+        );
+        variant_execute_function_name.set_span(variant_name.span());
+
+        matcher.extend(quote_spanned! {
+          variant.span() => #name::#variant_name #fields_in_variant => #name::#variant_execute_function_name(context, #variables)#maybe_await.map(Into::into),
+        });
+      }
+    }
+    _ => {
+      return Error::new(
+        Span::call_site(),
+        "CommandModule is only implemented for enums",
+      )
+      .to_compile_error()
+      .into()
+    }
+  };
+
+  let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+
+  let expanded = quote! {
+      impl #impl_generics #name #ty_generics #where_clause {
+        pub #maybe_async fn run<R: crate::Runtime>(self, context: crate::endpoints::InvokeContext<R>) -> crate::Result<crate::endpoints::InvokeResponse> {
+          match self {
+            #matcher
+          }
+        }
+      }
+  };
+
+  TokenStream::from(expanded)
+}
+
+/// Attributes for the module enum variant handler.
+pub struct HandlerAttributes {
+  allowlist: Ident,
+  error_message: String,
+}
+
+impl Parse for HandlerAttributes {
+  fn parse(input: ParseStream) -> syn::Result<Self> {
+    let allowlist = input.parse()?;
+    input.parse::<Token![,]>()?;
+    let raw: LitStr = input.parse()?;
+    let error_message = raw.value();
+    Ok(Self {
+      allowlist,
+      error_message,
+    })
+  }
+}
+
+pub fn command_handler(attributes: HandlerAttributes, function: ItemFn) -> TokenStream2 {
+  let allowlist = attributes.allowlist;
+  let error_message = attributes.error_message.as_str();
+  let signature = function.sig.clone();
+
+  quote!(
+    #[cfg(#allowlist)]
+    #function
+
+    #[cfg(not(#allowlist))]
+    #[allow(unused_variables)]
+    #[allow(unused_mut)]
+    #signature {
+      Err(crate::Error::ApiNotAllowlisted(
+        #error_message.to_string(),
+      ))
+    }
+  )
+}

+ 23 - 1
core/tauri-macros/src/lib.rs

@@ -4,9 +4,10 @@
 
 use crate::context::ContextItems;
 use proc_macro::TokenStream;
-use syn::{parse_macro_input, DeriveInput};
+use syn::{parse_macro_input, DeriveInput, ItemFn};
 
 mod command;
+mod command_module;
 mod runtime;
 
 #[macro_use]
@@ -73,3 +74,24 @@ pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStre
   let input = parse_macro_input!(input as DeriveInput);
   runtime::default_runtime(attributes, input).into()
 }
+
+/// Adds a `run` method to an enum (one of the tauri endpoint modules).
+/// The `run` method takes a `tauri::endpoints::InvokeContext`
+/// and returns a `tauri::Result<tauri::endpoints::InvokeResponse>`.
+/// It matches on each enum variant and call a method with name equal to the variant name, lowercased and snake_cased,
+/// passing the the context and the variant's fields as arguments.
+/// That function must also return the same `Result<InvokeResponse>`.
+#[doc(hidden)]
+#[proc_macro_derive(CommandModule, attributes(cmd))]
+pub fn derive_command_module(input: TokenStream) -> TokenStream {
+  let input = parse_macro_input!(input as DeriveInput);
+  command_module::generate_run_fn(input)
+}
+
+#[doc(hidden)]
+#[proc_macro_attribute]
+pub fn module_command_handler(attributes: TokenStream, input: TokenStream) -> TokenStream {
+  let attributes = parse_macro_input!(attributes as command_module::HandlerAttributes);
+  let input = parse_macro_input!(input as ItemFn);
+  command_module::command_handler(attributes, input).into()
+}

+ 22 - 16
core/tauri-utils/src/lib.rs

@@ -52,25 +52,31 @@ pub struct Env {
 #[allow(clippy::derivable_impls)]
 impl Default for Env {
   fn default() -> Self {
-    let env = Self {
-      #[cfg(target_os = "linux")]
-      appimage: std::env::var_os("APPIMAGE"),
-      #[cfg(target_os = "linux")]
-      appdir: std::env::var_os("APPDIR"),
-    };
     #[cfg(target_os = "linux")]
-    if env.appimage.is_some() || env.appdir.is_some() {
-      // validate that we're actually running on an AppImage
-      // an AppImage is mounted to `/tmp/.mount_${appPrefix}${hash}`
-      // see https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501
-      if !std::env::current_exe()
-        .map(|p| p.to_string_lossy().into_owned().starts_with("/tmp/.mount_"))
-        .unwrap_or(true)
-      {
-        panic!("`APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue.");
+    {
+      let env = Self {
+        #[cfg(target_os = "linux")]
+        appimage: std::env::var_os("APPIMAGE"),
+        #[cfg(target_os = "linux")]
+        appdir: std::env::var_os("APPDIR"),
+      };
+      if env.appimage.is_some() || env.appdir.is_some() {
+        // validate that we're actually running on an AppImage
+        // an AppImage is mounted to `/tmp/.mount_${appPrefix}${hash}`
+        // see https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501
+        if !std::env::current_exe()
+          .map(|p| p.to_string_lossy().into_owned().starts_with("/tmp/.mount_"))
+          .unwrap_or(true)
+        {
+          panic!("`APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue.");
+        }
       }
+      env
+    }
+    #[cfg(not(target_os = "linux"))]
+    {
+      Self {}
     }
-    env
   }
 }
 

+ 2 - 0
core/tauri/build.rs

@@ -33,6 +33,8 @@ fn main() {
     window_set_title: { any(window_all, feature = "window-set-title") },
     window_maximize: { any(window_all, feature = "window-maximize") },
     window_unmaximize: { any(window_all, feature = "window-unmaximize") },
+    window_minimize: { any(window_all, feature = "window-minimize") },
+    window_unminimize: { any(window_all, feature = "window-unminimize") },
     window_show: { any(window_all, feature = "window-show") },
     window_hide: { any(window_all, feature = "window-hide") },
     window_close: { any(window_all, feature = "window-close") },

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
core/tauri/scripts/bundle.js


+ 1 - 5
core/tauri/src/api/process/command.rs

@@ -161,11 +161,7 @@ fn relative_command_path(command: String) -> crate::Result<String> {
 #[cfg(windows)]
 fn relative_command_path(command: String) -> crate::Result<String> {
   match std::env::current_exe()?.parent() {
-    Some(exe_dir) => Ok(format!(
-      "{}/{}.exe",
-      exe_dir.to_string_lossy().to_string(),
-      command
-    )),
+    Some(exe_dir) => Ok(format!("{}/{}.exe", exe_dir.to_string_lossy(), command)),
     None => Err(crate::api::Error::Command("Could not evaluate executable dir".to_string()).into()),
   }
 }

+ 1 - 0
core/tauri/src/api/shell.rs

@@ -36,6 +36,7 @@ impl FromStr for Program {
   type Err = super::Error;
 
   fn from_str(s: &str) -> Result<Self, Self::Err> {
+    #[allow(clippy::match_str_case_mismatch)]
     let p = match s.to_lowercase().as_str() {
       "open" => Self::Open,
       "start" => Self::Start,

+ 36 - 24
core/tauri/src/endpoints.rs

@@ -28,6 +28,13 @@ mod process;
 mod shell;
 mod window;
 
+/// The context passed to the invoke handler.
+pub struct InvokeContext<R: Runtime> {
+  pub window: Window<R>,
+  pub config: Arc<Config>,
+  pub package_info: PackageInfo,
+}
+
 /// The response for a JS `invoke` call.
 pub struct InvokeResponse {
   json: crate::Result<JsonValue>,
@@ -68,90 +75,95 @@ impl Module {
     config: Arc<Config>,
     package_info: PackageInfo,
   ) {
+    let context = InvokeContext {
+      window,
+      config,
+      package_info,
+    };
     match self {
       Self::App(cmd) => resolver.respond_async(async move {
         cmd
-          .run(package_info)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Process(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Fs(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window, config, &package_info)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Path(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window, config, &package_info)
+          .run(context)
+          .and_then(|r| r.json)
+          .map_err(InvokeError::from)
+      }),
+      Self::Os(cmd) => resolver.respond_async(async move {
+        cmd
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
-      Self::Os(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)
+          .run(context)
           .await
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Shell(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Event(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Dialog(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window)
+          .run(context)
+          .and_then(|r| r.json)
+          .map_err(InvokeError::from)
+      }),
+      Self::Cli(cmd) => resolver.respond_async(async move {
+        cmd
+          .run(context)
           .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, &package_info)
-              .and_then(|r| r.json)
-              .map_err(InvokeError::from)
-          })
-        }
-      }
       Self::Notification(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window, config, &package_info)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Http(cmd) => resolver.respond_async(async move {
         cmd
-          .run()
+          .run(context)
           .await
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::GlobalShortcut(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),
       Self::Clipboard(cmd) => resolver.respond_async(async move {
         cmd
-          .run(window)
+          .run(context)
           .and_then(|r| r.json)
           .map_err(InvokeError::from)
       }),

+ 14 - 9
core/tauri/src/endpoints/app.rs

@@ -2,12 +2,13 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
-use crate::PackageInfo;
+use super::InvokeContext;
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::CommandModule;
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 #[allow(clippy::enum_variant_names)]
 pub enum Cmd {
@@ -20,11 +21,15 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  pub fn run(self, package_info: PackageInfo) -> crate::Result<InvokeResponse> {
-    match self {
-      Self::GetAppVersion => Ok(package_info.version.into()),
-      Self::GetAppName => Ok(package_info.name.into()),
-      Self::GetTauriVersion => Ok(env!("CARGO_PKG_VERSION").into()),
-    }
+  fn get_app_version<R: Runtime>(context: InvokeContext<R>) -> crate::Result<String> {
+    Ok(context.package_info.version)
+  }
+
+  fn get_app_name<R: Runtime>(context: InvokeContext<R>) -> crate::Result<String> {
+    Ok(context.package_info.name)
+  }
+
+  fn get_tauri_version<R: Runtime>(_context: InvokeContext<R>) -> crate::Result<&'static str> {
+    Ok(env!("CARGO_PKG_VERSION"))
   }
 }

+ 12 - 21
core/tauri/src/endpoints/cli.rs

@@ -2,12 +2,13 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
-use crate::{utils::config::CliConfig, PackageInfo};
+use super::{InvokeContext, InvokeResponse};
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The get CLI matches API.
@@ -15,24 +16,14 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[allow(unused_variables)]
-  pub fn run(
-    self,
-    cli_config: &CliConfig,
-    package_info: &PackageInfo,
-  ) -> crate::Result<InvokeResponse> {
-    match self {
-      #[allow(unused_variables)]
-      Self::CliMatches => {
-        #[cfg(cli)]
-        return crate::api::cli::get_matches(cli_config, package_info)
-          .map_err(Into::into)
-          .map(Into::into);
-        #[cfg(not(cli))]
-          Err(crate::Error::ApiNotEnabled(
-            "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)".to_string(),
-          ))
-      }
+  #[module_command_handler(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)")]
+  fn cli_matches<R: Runtime>(context: InvokeContext<R>) -> crate::Result<InvokeResponse> {
+    if let Some(cli) = &context.config.tauri.cli {
+      crate::api::cli::get_matches(cli, context.package_info)
+        .map(Into::into)
+        .map_err(Into::into)
+    } else {
+      Err(crate::Error::ApiNotAllowlisted("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)".into()))
     }
   }
 }

+ 17 - 25
core/tauri/src/endpoints/clipboard.rs

@@ -2,14 +2,15 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
+use super::InvokeContext;
 #[cfg(any(clipboard_write_text, clipboard_read_text))]
 use crate::runtime::ClipboardManager;
-use crate::{runtime::Runtime, window::Window};
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", content = "data", rename_all = "camelCase")]
 pub enum Cmd {
   /// Write a text string to the clipboard.
@@ -19,28 +20,19 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[allow(unused_variables)]
-  pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
-    match self {
-      #[cfg(clipboard_write_text)]
-      Self::WriteText(text) => Ok(
-        window
-          .app_handle
-          .clipboard_manager()
-          .write_text(text)?
-          .into(),
-      ),
-      #[cfg(not(clipboard_write_text))]
-      Self::WriteText(_) => Err(crate::Error::ApiNotAllowlisted(
-        "clipboard > readText".to_string(),
-      )),
+  #[module_command_handler(clipboard_write_text, "clipboard > writeText")]
+  fn write_text<R: Runtime>(context: InvokeContext<R>, text: String) -> crate::Result<()> {
+    Ok(
+      context
+        .window
+        .app_handle
+        .clipboard_manager()
+        .write_text(text)?,
+    )
+  }
 
-      #[cfg(clipboard_read_text)]
-      Self::ReadText => Ok(window.app_handle.clipboard_manager().read_text()?.into()),
-      #[cfg(not(clipboard_read_text))]
-      Self::ReadText => Err(crate::Error::ApiNotAllowlisted(
-        "clipboard > writeText".to_string(),
-      )),
-    }
+  #[module_command_handler(clipboard_read_text, "clipboard > readText")]
+  fn read_text<R: Runtime>(context: InvokeContext<R>) -> crate::Result<Option<String>> {
+    Ok(context.window.app_handle.clipboard_manager().read_text()?)
   }
 }

+ 93 - 112
core/tauri/src/endpoints/dialog.rs

@@ -2,11 +2,12 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
+use super::{InvokeContext, InvokeResponse};
 #[cfg(any(dialog_open, dialog_save))]
 use crate::api::dialog::FileDialogBuilder;
-use crate::{runtime::Runtime, Window};
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 use std::path::PathBuf;
 #[cfg(any(dialog_message, dialog_ask, dialog_confirm))]
@@ -49,7 +50,7 @@ pub struct SaveDialogOptions {
 }
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 #[allow(clippy::enum_variant_names)]
 pub enum Cmd {
@@ -75,64 +76,101 @@ pub enum Cmd {
 }
 
 impl Cmd {
+  #[module_command_handler(dialog_open, "dialog > open")]
   #[allow(unused_variables)]
-  pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
-    match self {
-      #[cfg(dialog_open)]
-      Self::OpenDialog { options } => open(&window, options),
-      #[cfg(not(dialog_open))]
-      Self::OpenDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > open".to_string())),
+  fn open_dialog<R: Runtime>(
+    context: InvokeContext<R>,
+    options: OpenDialogOptions,
+  ) -> crate::Result<InvokeResponse> {
+    let mut dialog_builder = FileDialogBuilder::new();
+    #[cfg(any(windows, target_os = "macos"))]
+    {
+      dialog_builder = dialog_builder.set_parent(&context.window);
+    }
+    if let Some(default_path) = options.default_path {
+      dialog_builder = set_default_path(dialog_builder, default_path);
+    }
+    for filter in options.filters {
+      let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
+      dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
+    }
 
-      #[cfg(dialog_save)]
-      Self::SaveDialog { options } => save(window, options),
-      #[cfg(not(dialog_save))]
-      Self::SaveDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > save".to_string())),
+    let (tx, rx) = channel();
 
-      #[cfg(dialog_message)]
-      Self::MessageDialog { message } => {
-        let exe = std::env::current_exe()?;
-        crate::api::dialog::message(
-          Some(&window),
-          &window.app_handle.package_info().name,
-          message,
-        );
-        Ok(().into())
-      }
-      #[cfg(not(dialog_message))]
-      Self::MessageDialog { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "dialog > message".to_string(),
-      )),
+    if options.directory {
+      dialog_builder.pick_folder(move |p| tx.send(p.into()).unwrap());
+    } else if options.multiple {
+      dialog_builder.pick_files(move |p| tx.send(p.into()).unwrap());
+    } else {
+      dialog_builder.pick_file(move |p| tx.send(p.into()).unwrap());
+    }
 
-      #[cfg(dialog_ask)]
-      Self::AskDialog { title, message } => {
-        let (tx, rx) = channel();
-        crate::api::dialog::ask(
-          Some(&window),
-          title.unwrap_or_else(|| window.app_handle.package_info().name.clone()),
-          message,
-          move |m| tx.send(m).unwrap(),
-        );
-        Ok(rx.recv().unwrap().into())
-      }
-      #[cfg(not(dialog_ask))]
-      Self::AskDialog { .. } => Err(crate::Error::ApiNotAllowlisted("dialog > ask".to_string())),
+    Ok(rx.recv().unwrap())
+  }
 
-      #[cfg(dialog_confirm)]
-      Self::ConfirmDialog { title, message } => {
-        let (tx, rx) = channel();
-        crate::api::dialog::confirm(
-          Some(&window),
-          title.unwrap_or_else(|| window.app_handle.package_info().name.clone()),
-          message,
-          move |m| tx.send(m).unwrap(),
-        );
-        Ok(rx.recv().unwrap().into())
-      }
-      #[cfg(not(dialog_confirm))]
-      Self::ConfirmDialog { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "dialog > confirm".to_string(),
-      )),
+  #[module_command_handler(dialog_save, "dialog > save")]
+  #[allow(unused_variables)]
+  fn save_dialog<R: Runtime>(
+    context: InvokeContext<R>,
+    options: SaveDialogOptions,
+  ) -> crate::Result<Option<PathBuf>> {
+    let mut dialog_builder = FileDialogBuilder::new();
+    #[cfg(any(windows, target_os = "macos"))]
+    {
+      dialog_builder = dialog_builder.set_parent(&context.window);
     }
+    if let Some(default_path) = options.default_path {
+      dialog_builder = set_default_path(dialog_builder, default_path);
+    }
+    for filter in options.filters {
+      let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
+      dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
+    }
+    let (tx, rx) = channel();
+    dialog_builder.save_file(move |p| tx.send(p).unwrap());
+    Ok(rx.recv().unwrap())
+  }
+
+  #[module_command_handler(dialog_message, "dialog > message")]
+  fn message_dialog<R: Runtime>(context: InvokeContext<R>, message: String) -> crate::Result<()> {
+    crate::api::dialog::message(
+      Some(&context.window),
+      &context.window.app_handle.package_info().name,
+      message,
+    );
+    Ok(())
+  }
+
+  #[module_command_handler(dialog_ask, "dialog > ask")]
+  fn ask_dialog<R: Runtime>(
+    context: InvokeContext<R>,
+    title: Option<String>,
+    message: String,
+  ) -> crate::Result<bool> {
+    let (tx, rx) = channel();
+    crate::api::dialog::ask(
+      Some(&context.window),
+      title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
+      message,
+      move |m| tx.send(m).unwrap(),
+    );
+    Ok(rx.recv().unwrap())
+  }
+
+  #[module_command_handler(dialog_confirm, "dialog > confirm")]
+  fn confirm_dialog<R: Runtime>(
+    context: InvokeContext<R>,
+    title: Option<String>,
+    message: String,
+  ) -> crate::Result<bool> {
+    let (tx, rx) = channel();
+    crate::api::dialog::confirm(
+      Some(&context.window),
+      title.unwrap_or_else(|| context.window.app_handle.package_info().name.clone()),
+      message,
+      move |m| tx.send(m).unwrap(),
+    );
+    Ok(rx.recv().unwrap())
   }
 }
 
@@ -153,60 +191,3 @@ fn set_default_path(
     dialog_builder.set_directory(default_path)
   }
 }
-
-/// Shows an open dialog.
-#[cfg(dialog_open)]
-#[allow(unused_variables)]
-pub fn open<R: Runtime>(
-  window: &Window<R>,
-  options: OpenDialogOptions,
-) -> crate::Result<InvokeResponse> {
-  let mut dialog_builder = FileDialogBuilder::new();
-  #[cfg(any(windows, target_os = "macos"))]
-  {
-    dialog_builder = dialog_builder.set_parent(window);
-  }
-  if let Some(default_path) = options.default_path {
-    dialog_builder = set_default_path(dialog_builder, default_path);
-  }
-  for filter in options.filters {
-    let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
-    dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
-  }
-
-  let (tx, rx) = channel();
-
-  if options.directory {
-    dialog_builder.pick_folder(move |p| tx.send(p.into()).unwrap());
-  } else if options.multiple {
-    dialog_builder.pick_files(move |p| tx.send(p.into()).unwrap());
-  } else {
-    dialog_builder.pick_file(move |p| tx.send(p.into()).unwrap());
-  }
-
-  Ok(rx.recv().unwrap())
-}
-
-/// Shows a save dialog.
-#[cfg(dialog_save)]
-#[allow(unused_variables)]
-pub fn save<R: Runtime>(
-  window: Window<R>,
-  options: SaveDialogOptions,
-) -> crate::Result<InvokeResponse> {
-  let mut dialog_builder = FileDialogBuilder::new();
-  #[cfg(any(windows, target_os = "macos"))]
-  {
-    dialog_builder = dialog_builder.set_parent(&window);
-  }
-  if let Some(default_path) = options.default_path {
-    dialog_builder = set_default_path(dialog_builder, default_path);
-  }
-  for filter in options.filters {
-    let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
-    dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
-  }
-  let (tx, rx) = channel();
-  dialog_builder.save_file(move |p| tx.send(p).unwrap());
-  Ok(rx.recv().unwrap().into())
-}

+ 38 - 29
core/tauri/src/endpoints/event.rs

@@ -2,11 +2,13 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::{endpoints::InvokeResponse, runtime::Runtime, sealed::ManagerBase, Manager, Window};
+use super::InvokeContext;
+use crate::{sealed::ManagerBase, Manager, Runtime, Window};
 use serde::Deserialize;
+use tauri_macros::CommandModule;
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Listen to an event.
@@ -25,35 +27,42 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
-    match self {
-      Self::Listen { event, handler } => {
-        let event_id = rand::random();
-        window.eval(&listen_js(&window, event.clone(), event_id, handler))?;
-        window.register_js_listener(event, event_id);
-        Ok(event_id.into())
-      }
-      Self::Unlisten { event_id } => {
-        window.eval(&unlisten_js(&window, event_id))?;
-        window.unregister_js_listener(event_id);
-        Ok(().into())
-      }
-      Self::Emit {
-        event,
-        window_label,
-        payload,
-      } => {
-        // dispatch the event to Rust listeners
-        window.trigger(&event, payload.clone());
+  fn listen<R: Runtime>(
+    context: InvokeContext<R>,
+    event: String,
+    handler: String,
+  ) -> crate::Result<u64> {
+    let event_id = rand::random();
+    context
+      .window
+      .eval(&listen_js(&context.window, event.clone(), event_id, handler))?;
+    context.window.register_js_listener(event, event_id);
+    Ok(event_id)
+  }
+
+  fn unlisten<R: Runtime>(context: InvokeContext<R>, event_id: u64) -> crate::Result<()> {
+    context
+      .window
+      .eval(&unlisten_js(&context.window, event_id))?;
+    context.window.unregister_js_listener(event_id);
+    Ok(())
+  }
+
+  fn emit<R: Runtime>(
+    context: InvokeContext<R>,
+    event: String,
+    window_label: Option<String>,
+    payload: Option<String>,
+  ) -> crate::Result<()> {
+    // dispatch the event to Rust listeners
+    context.window.trigger(&event, payload.clone());
 
-        if let Some(target) = window_label {
-          window.emit_to(&target, &event, payload)?;
-        } else {
-          window.emit_all(&event, payload)?;
-        }
-        Ok(().into())
-      }
+    if let Some(target) = window_label {
+      context.window.emit_to(&target, &event, payload)?;
+    } else {
+      context.window.emit_all(&event, payload)?;
     }
+    Ok(())
   }
 }
 

+ 219 - 376
core/tauri/src/endpoints/file_system.rs

@@ -2,14 +2,15 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
 use crate::{
   api::{dir, file, path::BaseDirectory},
   scope::Scopes,
   Config, Env, Manager, PackageInfo, Runtime, Window,
 };
 
+use super::InvokeContext;
 use serde::{Deserialize, Serialize};
+use tauri_macros::{module_command_handler, CommandModule};
 
 use std::{
   fs,
@@ -39,7 +40,7 @@ pub struct FileOperationOptions {
 }
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The read text file API.
@@ -100,110 +101,232 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[allow(unused_variables)]
-  pub fn run<R: Runtime>(
-    self,
-    window: Window<R>,
-    config: Arc<Config>,
-    package_info: &PackageInfo,
-  ) -> crate::Result<InvokeResponse> {
-    match self {
-      #[cfg(fs_read_text_file)]
-      Self::ReadTextFile { path, options } => {
-        read_text_file(&config, package_info, &window, path, options).map(Into::into)
-      }
-      #[cfg(not(fs_read_text_file))]
-      Self::ReadTextFile { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "fs > readTextFile".to_string(),
-      )),
+  #[module_command_handler(fs_read_text_file, "fs > readTextFile")]
+  fn read_text_file<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    options: Option<FileOperationOptions>,
+  ) -> crate::Result<String> {
+    file::read_string(resolve_path(
+      &context.config,
+      &context.package_info,
+      &context.window,
+      path,
+      options.and_then(|o| o.dir),
+    )?)
+    .map_err(crate::Error::FailedToExecuteApi)
+  }
 
-      #[cfg(fs_read_binary_file)]
-      Self::ReadBinaryFile { path, options } => {
-        read_binary_file(&config, package_info, &window, path, options).map(Into::into)
-      }
-      #[cfg(not(fs_read_binary_file))]
-      Self::ReadBinaryFile { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "readBinaryFile".to_string(),
-      )),
+  #[module_command_handler(fs_read_binary_file, "fs > readBinaryFile")]
+  fn read_binary_file<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    options: Option<FileOperationOptions>,
+  ) -> crate::Result<Vec<u8>> {
+    file::read_binary(resolve_path(
+      &context.config,
+      &context.package_info,
+      &context.window,
+      path,
+      options.and_then(|o| o.dir),
+    )?)
+    .map_err(crate::Error::FailedToExecuteApi)
+  }
 
-      #[cfg(fs_write_file)]
-      Self::WriteFile {
-        path,
-        contents,
-        options,
-      } => write_file(&config, package_info, &window, path, contents, options).map(Into::into),
-      #[cfg(not(fs_write_file))]
-      Self::WriteFile { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "fs > writeFile".to_string(),
-      )),
+  #[module_command_handler(fs_write_file, "fs > writeFile")]
+  fn write_file<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    contents: String,
+    options: Option<FileOperationOptions>,
+  ) -> crate::Result<()> {
+    File::create(resolve_path(
+      &context.config,
+      &context.package_info,
+      &context.window,
+      path,
+      options.and_then(|o| o.dir),
+    )?)
+    .map_err(crate::Error::Io)
+    .and_then(|mut f| f.write_all(contents.as_bytes()).map_err(|err| err.into()))?;
+    Ok(())
+  }
 
-      #[cfg(fs_write_binary_file)]
-      Self::WriteBinaryFile {
+  #[module_command_handler(fs_write_binary_file, "fs > writeBinaryFile")]
+  fn write_binary_file<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    contents: String,
+    options: Option<FileOperationOptions>,
+  ) -> crate::Result<()> {
+    base64::decode(contents)
+      .map_err(crate::Error::Base64Decode)
+      .and_then(|c| {
+        File::create(resolve_path(
+          &context.config,
+          &context.package_info,
+          &context.window,
+          path,
+          options.and_then(|o| o.dir),
+        )?)
+        .map_err(Into::into)
+        .and_then(|mut f| f.write_all(&c).map_err(|err| err.into()))
+      })?;
+    Ok(())
+  }
+
+  #[module_command_handler(fs_read_dir, "fs > readDir")]
+  fn read_dir<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    options: Option<DirOperationOptions>,
+  ) -> crate::Result<Vec<dir::DiskEntry>> {
+    let (recursive, dir) = if let Some(options_value) = options {
+      (options_value.recursive, options_value.dir)
+    } else {
+      (false, None)
+    };
+    dir::read_dir(
+      resolve_path(
+        &context.config,
+        &context.package_info,
+        &context.window,
         path,
-        contents,
-        options,
-      } => {
-        write_binary_file(&config, package_info, &window, path, contents, options).map(Into::into)
-      }
-      #[cfg(not(fs_write_binary_file))]
-      Self::WriteBinaryFile { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "writeBinaryFile".to_string(),
-      )),
+        dir,
+      )?,
+      recursive,
+    )
+    .map_err(crate::Error::FailedToExecuteApi)
+  }
 
-      #[cfg(fs_read_dir)]
-      Self::ReadDir { path, options } => {
-        read_dir(&config, package_info, &window, path, options).map(Into::into)
-      }
-      #[cfg(not(fs_read_dir))]
-      Self::ReadDir { .. } => Err(crate::Error::ApiNotAllowlisted("fs > readDir".to_string())),
+  #[module_command_handler(fs_copy_file, "fs > copyFile")]
+  fn copy_file<R: Runtime>(
+    context: InvokeContext<R>,
+    source: PathBuf,
+    destination: PathBuf,
+    options: Option<FileOperationOptions>,
+  ) -> crate::Result<()> {
+    let (src, dest) = match options.and_then(|o| o.dir) {
+      Some(dir) => (
+        resolve_path(
+          &context.config,
+          &context.package_info,
+          &context.window,
+          source,
+          Some(dir.clone()),
+        )?,
+        resolve_path(
+          &context.config,
+          &context.package_info,
+          &context.window,
+          destination,
+          Some(dir),
+        )?,
+      ),
+      None => (source, destination),
+    };
+    fs::copy(src, dest)?;
+    Ok(())
+  }
 
-      #[cfg(fs_copy_file)]
-      Self::CopyFile {
-        source,
-        destination,
-        options,
-      } => copy_file(&config, package_info, &window, source, destination, options).map(Into::into),
-      #[cfg(not(fs_copy_file))]
-      Self::CopyFile { .. } => Err(crate::Error::ApiNotAllowlisted("fs > copyFile".to_string())),
+  #[module_command_handler(fs_create_dir, "fs > createDir")]
+  fn create_dir<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    options: Option<DirOperationOptions>,
+  ) -> crate::Result<()> {
+    let (recursive, dir) = if let Some(options_value) = options {
+      (options_value.recursive, options_value.dir)
+    } else {
+      (false, None)
+    };
+    let resolved_path = resolve_path(
+      &context.config,
+      &context.package_info,
+      &context.window,
+      path,
+      dir,
+    )?;
+    if recursive {
+      fs::create_dir_all(resolved_path)?;
+    } else {
+      fs::create_dir(resolved_path)?;
+    }
 
-      #[cfg(fs_create_dir)]
-      Self::CreateDir { path, options } => {
-        create_dir(&config, package_info, &window, path, options).map(Into::into)
-      }
-      #[cfg(not(fs_create_dir))]
-      Self::CreateDir { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "fs > createDir".to_string(),
-      )),
+    Ok(())
+  }
 
-      #[cfg(fs_remove_dir)]
-      Self::RemoveDir { path, options } => {
-        remove_dir(&config, package_info, &window, path, options).map(Into::into)
-      }
-      #[cfg(not(fs_remove_dir))]
-      Self::RemoveDir { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "fs > removeDir".to_string(),
-      )),
+  #[module_command_handler(fs_remove_dir, "fs > removeDir")]
+  fn remove_dir<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    options: Option<DirOperationOptions>,
+  ) -> crate::Result<()> {
+    let (recursive, dir) = if let Some(options_value) = options {
+      (options_value.recursive, options_value.dir)
+    } else {
+      (false, None)
+    };
+    let resolved_path = resolve_path(
+      &context.config,
+      &context.package_info,
+      &context.window,
+      path,
+      dir,
+    )?;
+    if recursive {
+      fs::remove_dir_all(resolved_path)?;
+    } else {
+      fs::remove_dir(resolved_path)?;
+    }
 
-      #[cfg(fs_remove_file)]
-      Self::RemoveFile { path, options } => {
-        remove_file(&config, package_info, &window, path, options).map(Into::into)
-      }
-      #[cfg(not(fs_remove_file))]
-      Self::RemoveFile { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "fs > removeFile".to_string(),
-      )),
+    Ok(())
+  }
 
-      #[cfg(fs_rename_file)]
-      Self::RenameFile {
-        old_path,
-        new_path,
-        options,
-      } => rename_file(&config, package_info, &window, old_path, new_path, options).map(Into::into),
-      #[cfg(not(fs_rename_file))]
-      Self::RenameFile { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "fs > renameFile".to_string(),
-      )),
-    }
+  #[module_command_handler(fs_remove_file, "fs > removeFile")]
+  fn remove_file<R: Runtime>(
+    context: InvokeContext<R>,
+    path: PathBuf,
+    options: Option<FileOperationOptions>,
+  ) -> crate::Result<()> {
+    let resolved_path = resolve_path(
+      &context.config,
+      &context.package_info,
+      &context.window,
+      path,
+      options.and_then(|o| o.dir),
+    )?;
+    fs::remove_file(resolved_path)?;
+    Ok(())
+  }
+
+  #[module_command_handler(fs_rename_file, "fs > renameFile")]
+  fn rename_file<R: Runtime>(
+    context: InvokeContext<R>,
+    old_path: PathBuf,
+    new_path: PathBuf,
+    options: Option<FileOperationOptions>,
+  ) -> crate::Result<()> {
+    let (old, new) = match options.and_then(|o| o.dir) {
+      Some(dir) => (
+        resolve_path(
+          &context.config,
+          &context.package_info,
+          &context.window,
+          old_path,
+          Some(dir.clone()),
+        )?,
+        resolve_path(
+          &context.config,
+          &context.package_info,
+          &context.window,
+          new_path,
+          Some(dir),
+        )?,
+      ),
+      None => (old_path, new_path),
+    };
+    fs::rename(old, new).map_err(crate::Error::Io)
   }
 }
 
@@ -227,283 +350,3 @@ fn resolve_path<R: Runtime, P: AsRef<Path>>(
     Err(e) => Err(e.into()),
   }
 }
-
-/// Reads a directory.
-#[cfg(fs_read_dir)]
-pub fn read_dir<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  options: Option<DirOperationOptions>,
-) -> crate::Result<Vec<dir::DiskEntry>> {
-  let (recursive, dir) = if let Some(options_value) = options {
-    (options_value.recursive, options_value.dir)
-  } else {
-    (false, None)
-  };
-  dir::read_dir(
-    resolve_path(config, package_info, window, path, dir)?,
-    recursive,
-  )
-  .map_err(crate::Error::FailedToExecuteApi)
-}
-
-/// Copies a file.
-#[cfg(fs_copy_file)]
-pub fn copy_file<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  source: PathBuf,
-  destination: PathBuf,
-  options: Option<FileOperationOptions>,
-) -> crate::Result<()> {
-  let (src, dest) = match options.and_then(|o| o.dir) {
-    Some(dir) => (
-      resolve_path(config, package_info, window, source, Some(dir.clone()))?,
-      resolve_path(config, package_info, window, destination, Some(dir))?,
-    ),
-    None => (source, destination),
-  };
-  fs::copy(src, dest)?;
-  Ok(())
-}
-
-/// Creates a directory.
-#[cfg(fs_create_dir)]
-pub fn create_dir<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  options: Option<DirOperationOptions>,
-) -> crate::Result<()> {
-  let (recursive, dir) = if let Some(options_value) = options {
-    (options_value.recursive, options_value.dir)
-  } else {
-    (false, None)
-  };
-  let resolved_path = resolve_path(config, package_info, window, path, dir)?;
-  if recursive {
-    fs::create_dir_all(resolved_path)?;
-  } else {
-    fs::create_dir(resolved_path)?;
-  }
-
-  Ok(())
-}
-
-/// Removes a directory.
-#[cfg(fs_remove_dir)]
-pub fn remove_dir<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  options: Option<DirOperationOptions>,
-) -> crate::Result<()> {
-  let (recursive, dir) = if let Some(options_value) = options {
-    (options_value.recursive, options_value.dir)
-  } else {
-    (false, None)
-  };
-  let resolved_path = resolve_path(config, package_info, window, path, dir)?;
-  if recursive {
-    fs::remove_dir_all(resolved_path)?;
-  } else {
-    fs::remove_dir(resolved_path)?;
-  }
-
-  Ok(())
-}
-
-/// Removes a file
-#[cfg(fs_remove_file)]
-pub fn remove_file<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  options: Option<FileOperationOptions>,
-) -> crate::Result<()> {
-  let resolved_path = resolve_path(
-    config,
-    package_info,
-    window,
-    path,
-    options.and_then(|o| o.dir),
-  )?;
-  fs::remove_file(resolved_path)?;
-  Ok(())
-}
-
-/// Renames a file.
-#[cfg(fs_rename_file)]
-pub fn rename_file<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  old_path: PathBuf,
-  new_path: PathBuf,
-  options: Option<FileOperationOptions>,
-) -> crate::Result<()> {
-  let (old, new) = match options.and_then(|o| o.dir) {
-    Some(dir) => (
-      resolve_path(config, package_info, window, old_path, Some(dir.clone()))?,
-      resolve_path(config, package_info, window, new_path, Some(dir))?,
-    ),
-    None => (old_path, new_path),
-  };
-  fs::rename(old, new).map_err(crate::Error::Io)
-}
-
-/// Writes a text file.
-#[cfg(fs_write_file)]
-pub fn write_file<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  contents: String,
-  options: Option<FileOperationOptions>,
-) -> crate::Result<()> {
-  File::create(resolve_path(
-    config,
-    package_info,
-    window,
-    path,
-    options.and_then(|o| o.dir),
-  )?)
-  .map_err(crate::Error::Io)
-  .and_then(|mut f| f.write_all(contents.as_bytes()).map_err(|err| err.into()))?;
-  Ok(())
-}
-
-/// Writes a binary file.
-#[cfg(fs_write_binary_file)]
-pub fn write_binary_file<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  contents: String,
-  options: Option<FileOperationOptions>,
-) -> crate::Result<()> {
-  base64::decode(contents)
-    .map_err(crate::Error::Base64Decode)
-    .and_then(|c| {
-      File::create(resolve_path(
-        config,
-        package_info,
-        window,
-        path,
-        options.and_then(|o| o.dir),
-      )?)
-      .map_err(Into::into)
-      .and_then(|mut f| f.write_all(&c).map_err(|err| err.into()))
-    })?;
-  Ok(())
-}
-
-/// Reads a text file.
-#[cfg(fs_read_text_file)]
-pub fn read_text_file<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  options: Option<FileOperationOptions>,
-) -> crate::Result<String> {
-  file::read_string(resolve_path(
-    config,
-    package_info,
-    window,
-    path,
-    options.and_then(|o| o.dir),
-  )?)
-  .map_err(crate::Error::FailedToExecuteApi)
-}
-
-/// Reads a binary file.
-#[cfg(fs_read_binary_file)]
-pub fn read_binary_file<R: Runtime>(
-  config: &Config,
-  package_info: &PackageInfo,
-  window: &Window<R>,
-  path: PathBuf,
-  options: Option<FileOperationOptions>,
-) -> crate::Result<Vec<u8>> {
-  file::read_binary(resolve_path(
-    config,
-    package_info,
-    window,
-    path,
-    options.and_then(|o| o.dir),
-  )?)
-  .map_err(crate::Error::FailedToExecuteApi)
-}
-
-// test webview functionality.
-#[cfg(test)]
-mod test {
-  // use super::*;
-  // use web_view::*;
-
-  // create a makeshift webview
-  // fn create_test_webview() -> crate::Result<WebView<'static, ()>> {
-  //   // basic html set into webview
-  //   let content = r#"<html><head></head><body></body></html>"#;
-
-  //   Ok(
-  //     // use webview builder to create simple webview
-  //     WebViewBuilder::new()
-  //       .title("test")
-  //       .size(800, 800)
-  //       .resizable(true)
-  //       .debug(true)
-  //       .user_data(())
-  //       .invoke_handler(|_wv, _command, _arg| Ok(()))
-  //       .content(Content::Html(content))
-  //       .build()?,
-  //   )
-  // }
-
-  /* #[test]
-  #[cfg(not(any(target_os = "linux", target_os = "macos")))]
-  // test the file_write functionality
-  fn test_write_to_file() -> crate::Result<()> {
-    // import read_to_string and write to be able to manipulate the file.
-    use std::fs::{read_to_string, write};
-
-    // create the webview
-    let mut webview = create_test_webview()?;
-
-    // setup the contents and the path.
-    let contents = String::from(r#"Write to the Test file"#);
-    let path = String::from("test/fixture/test.txt".to_string()));
-
-    // clear the file by writing nothing to it.
-    write(&path, "")?;
-
-    //call write file with the path and contents.
-    write_file(
-      &webview_manager,
-      path.clone(),
-      contents.clone(),
-      String::from(""),
-      String::from(""),
-    );
-
-    // sleep the main thread to wait for the promise to execute.
-    std::thread::sleep(std::time::Duration::from_millis(200));
-
-    // read from the file.
-    let data = read_to_string(path)?;
-
-    // check that the file contents is equal to the expected contents.
-    assert_eq!(data, contents);
-
-    Ok(())
-  } */
-}

+ 67 - 54
core/tauri/src/endpoints/global_shortcut.rs

@@ -2,15 +2,16 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
-use crate::{Runtime, Window};
+use super::InvokeContext;
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 #[cfg(global_shortcut_all)]
 use crate::runtime::GlobalShortcutManager;
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Register a global shortcut.
@@ -28,9 +29,71 @@ pub enum Cmd {
   IsRegistered { shortcut: String },
 }
 
+impl Cmd {
+  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  fn register<R: Runtime>(
+    context: InvokeContext<R>,
+    shortcut: String,
+    handler: String,
+  ) -> crate::Result<()> {
+    let mut manager = context.window.app_handle.global_shortcut_manager();
+    register_shortcut(context.window, &mut manager, shortcut, handler)?;
+    Ok(())
+  }
+
+  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  fn register_all<R: Runtime>(
+    context: InvokeContext<R>,
+    shortcuts: Vec<String>,
+    handler: String,
+  ) -> crate::Result<()> {
+    let mut manager = context.window.app_handle.global_shortcut_manager();
+    for shortcut in shortcuts {
+      register_shortcut(
+        context.window.clone(),
+        &mut manager,
+        shortcut,
+        handler.clone(),
+      )?;
+    }
+    Ok(())
+  }
+
+  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  fn unregister<R: Runtime>(context: InvokeContext<R>, shortcut: String) -> crate::Result<()> {
+    context
+      .window
+      .app_handle
+      .global_shortcut_manager()
+      .unregister(&shortcut)?;
+    Ok(())
+  }
+
+  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  fn unregister_all<R: Runtime>(context: InvokeContext<R>) -> crate::Result<()> {
+    context
+      .window
+      .app_handle
+      .global_shortcut_manager()
+      .unregister_all()?;
+    Ok(())
+  }
+
+  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  fn is_registered<R: Runtime>(context: InvokeContext<R>, shortcut: String) -> crate::Result<bool> {
+    Ok(
+      context
+        .window
+        .app_handle
+        .global_shortcut_manager()
+        .is_registered(&shortcut)?,
+    )
+  }
+}
+
 #[cfg(global_shortcut_all)]
 fn register_shortcut<R: Runtime>(
-  window: Window<R>,
+  window: crate::Window<R>,
   manager: &mut R::GlobalShortcutManager,
   shortcut: String,
   handler: String,
@@ -43,53 +106,3 @@ fn register_shortcut<R: Runtime>(
   })?;
   Ok(())
 }
-
-#[cfg(not(global_shortcut_all))]
-impl Cmd {
-  pub fn run<R: Runtime>(self, _window: Window<R>) -> crate::Result<InvokeResponse> {
-    Err(crate::Error::ApiNotAllowlisted(
-      "globalShortcut > all".to_string(),
-    ))
-  }
-}
-
-#[cfg(global_shortcut_all)]
-impl Cmd {
-  pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
-    match self {
-      Self::Register { shortcut, handler } => {
-        let mut manager = window.app_handle.global_shortcut_manager();
-        register_shortcut(window, &mut manager, shortcut, handler)?;
-        Ok(().into())
-      }
-      Self::RegisterAll { shortcuts, handler } => {
-        let mut manager = window.app_handle.global_shortcut_manager();
-        for shortcut in shortcuts {
-          register_shortcut(window.clone(), &mut manager, shortcut, handler.clone())?;
-        }
-        Ok(().into())
-      }
-      Self::Unregister { shortcut } => {
-        window
-          .app_handle
-          .global_shortcut_manager()
-          .unregister(&shortcut)?;
-        Ok(().into())
-      }
-      Self::UnregisterAll => {
-        window
-          .app_handle
-          .global_shortcut_manager()
-          .unregister_all()?;
-        Ok(().into())
-      }
-      Self::IsRegistered { shortcut } => Ok(
-        window
-          .app_handle
-          .global_shortcut_manager()
-          .is_registered(&shortcut)?
-          .into(),
-      ),
-    }
-  }
-}

+ 40 - 53
core/tauri/src/endpoints/http.rs

@@ -2,10 +2,11 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
-
-use crate::api::http::{ClientBuilder, HttpRequestBuilder};
+use super::InvokeContext;
+use crate::api::http::{ClientBuilder, HttpRequestBuilder, ResponseData};
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 #[cfg(http_request)]
 use std::{
@@ -25,7 +26,8 @@ fn clients() -> &'static ClientStore {
 }
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
+#[cmd(async)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Create a new HTTP client.
@@ -40,56 +42,41 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  pub async fn run(self) -> crate::Result<InvokeResponse> {
-    match self {
-      #[cfg(http_request)]
-      Self::CreateClient { options } => {
-        let client = options.unwrap_or_default().build()?;
-        let mut store = clients().lock().unwrap();
-        let id = rand::random::<ClientId>();
-        store.insert(id, client);
-        Ok(InvokeResponse::from(id))
-      }
-      #[cfg(not(http_request))]
-      Self::CreateClient { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "http > request".to_string(),
-      )),
-
-      #[cfg(http_request)]
-      Self::DropClient { client } => {
-        let mut store = clients().lock().unwrap();
-        store.remove(&client);
-        Ok(().into())
-      }
-      #[cfg(not(http_request))]
-      Self::DropClient { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "http > request".to_string(),
-      )),
+  #[module_command_handler(http_request, "http > request")]
+  async fn create_client<R: Runtime>(
+    _context: InvokeContext<R>,
+    options: Option<ClientBuilder>,
+  ) -> crate::Result<ClientId> {
+    let client = options.unwrap_or_default().build()?;
+    let mut store = clients().lock().unwrap();
+    let id = rand::random::<ClientId>();
+    store.insert(id, client);
+    Ok(id)
+  }
 
-      #[cfg(http_request)]
-      Self::HttpRequest { client, options } => {
-        return make_request(client, *options).await.map(Into::into);
-      }
-      #[cfg(not(http_request))]
-      Self::HttpRequest { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "http > request".to_string(),
-      )),
-    }
+  #[module_command_handler(http_request, "http > request")]
+  async fn drop_client<R: Runtime>(
+    _context: InvokeContext<R>,
+    client: ClientId,
+  ) -> crate::Result<()> {
+    let mut store = clients().lock().unwrap();
+    store.remove(&client);
+    Ok(())
   }
-}
 
-/// Makes an HTTP request and resolves the response to the webview
-#[cfg(http_request)]
-pub async fn make_request(
-  client_id: ClientId,
-  options: HttpRequestBuilder,
-) -> crate::Result<crate::api::http::ResponseData> {
-  let client = clients()
-    .lock()
-    .unwrap()
-    .get(&client_id)
-    .ok_or(crate::Error::HttpClientNotInitialized)?
-    .clone();
-  let response = client.send(options).await?;
-  Ok(response.read().await?)
+  #[module_command_handler(http_request, "http > request")]
+  async fn http_request<R: Runtime>(
+    _context: InvokeContext<R>,
+    client_id: ClientId,
+    options: Box<HttpRequestBuilder>,
+  ) -> crate::Result<ResponseData> {
+    let client = clients()
+      .lock()
+      .unwrap()
+      .get(&client_id)
+      .ok_or(crate::Error::HttpClientNotInitialized)?
+      .clone();
+    let response = client.send(*options).await?;
+    Ok(response.read().await?)
+  }
 }

+ 82 - 91
core/tauri/src/endpoints/notification.rs

@@ -2,14 +2,13 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
+use super::InvokeContext;
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 #[cfg(notification_all)]
 use crate::{api::notification::Notification, Env, Manager};
-use crate::{Config, PackageInfo, Runtime, Window};
-
-use std::sync::Arc;
 
 // `Granted` response from `request_permission`. Matches the Web API return value.
 #[cfg(notification_all)]
@@ -29,7 +28,7 @@ pub struct NotificationOptions {
 }
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The show notification API.
@@ -41,101 +40,93 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[allow(unused_variables)]
-  pub fn run<R: Runtime>(
-    self,
-    window: Window<R>,
-    config: Arc<Config>,
-    package_info: &PackageInfo,
-  ) -> crate::Result<InvokeResponse> {
-    match self {
-      #[cfg(notification_all)]
-      Self::Notification { options } => send(options, &config).map(Into::into),
-      #[cfg(not(notification_all))]
-      Self::Notification { .. } => Err(crate::Error::ApiNotAllowlisted("notification".to_string())),
-      Self::IsNotificationPermissionGranted => {
-        #[cfg(notification_all)]
-        return is_permission_granted(&window, &config, package_info).map(Into::into);
-        #[cfg(not(notification_all))]
-        Ok(false.into())
-      }
-      Self::RequestNotificationPermission => {
-        #[cfg(notification_all)]
-        return request_permission(&window, &config, package_info).map(Into::into);
-        #[cfg(not(notification_all))]
-        Ok(PERMISSION_DENIED.into())
-      }
+  #[module_command_handler(notification_all, "notification > all")]
+  fn notification<R: Runtime>(
+    context: InvokeContext<R>,
+    options: NotificationOptions,
+  ) -> crate::Result<()> {
+    let mut notification =
+      Notification::new(context.config.tauri.bundle.identifier.clone()).title(options.title);
+    if let Some(body) = options.body {
+      notification = notification.body(body);
+    }
+    if let Some(icon) = options.icon {
+      notification = notification.icon(icon);
     }
+    notification.show()?;
+    Ok(())
   }
-}
 
-#[cfg(notification_all)]
-pub fn send(options: NotificationOptions, config: &Config) -> crate::Result<InvokeResponse> {
-  let mut notification =
-    Notification::new(config.tauri.bundle.identifier.clone()).title(options.title);
-  if let Some(body) = options.body {
-    notification = notification.body(body);
-  }
-  if let Some(icon) = options.icon {
-    notification = notification.icon(icon);
-  }
-  notification.show()?;
-  Ok(().into())
-}
+  #[cfg(notification_all)]
+  fn request_notification_permission<R: Runtime>(
+    context: InvokeContext<R>,
+  ) -> crate::Result<&'static str> {
+    let mut settings = crate::settings::read_settings(
+      &context.config,
+      &context.package_info,
+      context.window.state::<Env>().inner(),
+    );
+    if let Some(allow_notification) = settings.allow_notification {
+      return Ok(if allow_notification {
+        PERMISSION_GRANTED
+      } else {
+        PERMISSION_DENIED
+      });
+    }
+    let (tx, rx) = std::sync::mpsc::channel();
+    crate::api::dialog::ask(
+      Some(&context.window),
+      "Permissions",
+      "This app wants to show notifications. Do you allow?",
+      move |answer| {
+        tx.send(answer).unwrap();
+      },
+    );
 
-#[cfg(notification_all)]
-pub fn is_permission_granted<R: Runtime>(
-  window: &Window<R>,
-  config: &Config,
-  package_info: &PackageInfo,
-) -> crate::Result<InvokeResponse> {
-  let settings =
-    crate::settings::read_settings(config, package_info, window.state::<Env>().inner());
-  if let Some(allow_notification) = settings.allow_notification {
-    Ok(allow_notification.into())
-  } else {
-    Ok(().into())
-  }
-}
+    let answer = rx.recv().unwrap();
 
-#[cfg(notification_all)]
-pub fn request_permission<R: Runtime>(
-  window: &Window<R>,
-  config: &Config,
-  package_info: &PackageInfo,
-) -> crate::Result<String> {
-  let mut settings =
-    crate::settings::read_settings(config, package_info, window.state::<Env>().inner());
-  if let Some(allow_notification) = settings.allow_notification {
-    return Ok(if allow_notification {
-      PERMISSION_GRANTED.to_string()
+    settings.allow_notification = Some(answer);
+    crate::settings::write_settings(
+      &context.config,
+      &context.package_info,
+      context.window.state::<Env>().inner(),
+      settings,
+    )?;
+
+    if answer {
+      Ok(PERMISSION_GRANTED)
     } else {
-      PERMISSION_DENIED.to_string()
-    });
+      Ok(PERMISSION_DENIED)
+    }
   }
-  let (tx, rx) = std::sync::mpsc::channel();
-  crate::api::dialog::ask(
-    Some(window),
-    "Permissions",
-    "This app wants to show notifications. Do you allow?",
-    move |answer| {
-      tx.send(answer).unwrap();
-    },
-  );
 
-  let answer = rx.recv().unwrap();
+  #[cfg(not(notification_all))]
+  fn request_notification_permission<R: Runtime>(
+    _context: InvokeContext<R>,
+  ) -> crate::Result<&'static str> {
+    Ok(PERMISSION_DENIED)
+  }
 
-  settings.allow_notification = Some(answer);
-  crate::settings::write_settings(
-    config,
-    package_info,
-    window.state::<Env>().inner(),
-    settings,
-  )?;
+  #[cfg(notification_all)]
+  fn is_notification_permission_granted<R: Runtime>(
+    context: InvokeContext<R>,
+  ) -> crate::Result<Option<bool>> {
+    let settings = crate::settings::read_settings(
+      &context.config,
+      &context.package_info,
+      context.window.state::<Env>().inner(),
+    );
+    if let Some(allow_notification) = settings.allow_notification {
+      Ok(Some(allow_notification))
+    } else {
+      Ok(None)
+    }
+  }
 
-  if answer {
-    Ok(PERMISSION_GRANTED.to_string())
-  } else {
-    Ok(PERMISSION_DENIED.to_string())
+  #[cfg(not(notification_all))]
+  fn is_notification_permission_granted<R: Runtime>(
+    _context: InvokeContext<R>,
+  ) -> crate::Result<Option<bool>> {
+    Ok(Some(false))
   }
 }

+ 34 - 21
core/tauri/src/endpoints/operating_system.rs

@@ -2,51 +2,64 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
+use super::InvokeContext;
+use crate::Runtime;
 use serde::Deserialize;
+use std::path::PathBuf;
+use tauri_macros::{module_command_handler, CommandModule};
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   Platform,
   Version,
-  Type,
+  OsType,
   Arch,
   Tempdir,
 }
 
 impl Cmd {
-  #[allow(unused_variables)]
-  pub fn run(self) -> crate::Result<InvokeResponse> {
-    #[cfg(os_all)]
-    return match self {
-      Self::Platform => Ok(os_platform().into()),
-      Self::Version => Ok(os_info::get().version().to_string().into()),
-      Self::Type => Ok(os_type().into()),
-      Self::Arch => Ok(std::env::consts::ARCH.into()),
-      Self::Tempdir => Ok(std::env::temp_dir().into()),
-    };
-    #[cfg(not(os_all))]
-    Err(crate::Error::ApiNotAllowlisted("os".into()))
+  #[module_command_handler(os_all, "os > all")]
+  fn platform<R: Runtime>(_context: InvokeContext<R>) -> crate::Result<&'static str> {
+    Ok(os_platform())
+  }
+
+  #[module_command_handler(os_all, "os > all")]
+  fn version<R: Runtime>(_context: InvokeContext<R>) -> crate::Result<String> {
+    Ok(os_info::get().version().to_string())
+  }
+
+  #[module_command_handler(os_all, "os > all")]
+  fn os_type<R: Runtime>(_context: InvokeContext<R>) -> crate::Result<&'static str> {
+    Ok(os_type())
+  }
+
+  #[module_command_handler(os_all, "os > all")]
+  fn arch<R: Runtime>(_context: InvokeContext<R>) -> crate::Result<&'static str> {
+    Ok(std::env::consts::ARCH)
+  }
+
+  #[module_command_handler(os_all, "os > all")]
+  fn tempdir<R: Runtime>(_context: InvokeContext<R>) -> crate::Result<PathBuf> {
+    Ok(std::env::temp_dir())
   }
 }
 
 #[cfg(os_all)]
-fn os_type() -> String {
+fn os_type() -> &'static str {
   #[cfg(target_os = "linux")]
-  return "Linux".into();
+  return "Linux";
   #[cfg(target_os = "windows")]
-  return "Windows_NT".into();
+  return "Windows_NT";
   #[cfg(target_os = "macos")]
-  return "Darwin".into();
+  return "Darwin";
 }
 #[cfg(os_all)]
-fn os_platform() -> String {
+fn os_platform() -> &'static str {
   match std::env::consts::OS {
     "windows" => "win32",
     "macos" => "darwin",
     _ => std::env::consts::OS,
   }
-  .into()
 }

+ 122 - 134
core/tauri/src/endpoints/path.rs

@@ -2,16 +2,19 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
-use crate::{api::path::BaseDirectory, Config, PackageInfo, Runtime, Window};
+use crate::{api::path::BaseDirectory, Runtime};
 #[cfg(path_all)]
 use crate::{Env, Manager};
-use serde::Deserialize;
+use std::path::PathBuf;
 #[cfg(path_all)]
-use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR};
-use std::sync::Arc;
+use std::path::{Component, Path, MAIN_SEPARATOR};
+
+use super::InvokeContext;
+use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
+
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   ResolvePath {
@@ -43,151 +46,136 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[allow(unused_variables)]
-  pub fn run<R: Runtime>(
-    self,
-    window: Window<R>,
-    config: Arc<Config>,
-    package_info: &PackageInfo,
-  ) -> crate::Result<InvokeResponse> {
-    #[cfg(path_all)]
-    return match self {
-      Cmd::ResolvePath { directory, path } => resolve_path_handler(
-        &config,
-        package_info,
-        window.state::<Env>().inner(),
-        path,
-        directory,
-      )
-      .map(Into::into),
-      Cmd::Resolve { paths } => resolve(paths).map(Into::into),
-      Cmd::Normalize { path } => normalize(path).map(Into::into),
-      Cmd::Join { paths } => join(paths).map(Into::into),
-      Cmd::Dirname { path } => dirname(path).map(Into::into),
-      Cmd::Extname { path } => extname(path).map(Into::into),
-      Cmd::Basename { path, ext } => basename(path, ext).map(Into::into),
-      Cmd::IsAbsolute { path } => Ok(Path::new(&path).is_absolute()).map(Into::into),
-    };
-    #[cfg(not(path_all))]
-    Err(crate::Error::ApiNotAllowlisted("path".into()))
+  #[module_command_handler(path_all, "path > all")]
+  fn resolve_path<R: Runtime>(
+    context: InvokeContext<R>,
+    path: String,
+    directory: Option<BaseDirectory>,
+  ) -> crate::Result<PathBuf> {
+    crate::api::path::resolve_path(
+      &context.config,
+      &context.package_info,
+      context.window.state::<Env>().inner(),
+      path,
+      directory,
+    )
+    .map_err(Into::into)
   }
-}
 
-#[cfg(path_all)]
-pub fn resolve_path_handler(
-  config: &Config,
-  package_info: &PackageInfo,
-  env: &Env,
-  path: String,
-  directory: Option<BaseDirectory>,
-) -> crate::Result<PathBuf> {
-  crate::api::path::resolve_path(config, package_info, env, path, directory).map_err(Into::into)
-}
-
-#[cfg(path_all)]
-fn resolve(paths: Vec<String>) -> crate::Result<String> {
-  // Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which
-  // will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path.
-  //
-  // examples:
-  // 1. `vec!["."]` or `vec![]` will be equal to `std::env::current_dir()`
-  // 2. `vec!["/foo/bar", "/tmp/file", "baz"]` will be equal to `PathBuf::from("/tmp/file/baz")`
-  let mut path = std::env::current_dir()?;
-  for p in paths {
-    path.push(p);
+  #[module_command_handler(path_all, "path > all")]
+  fn resolve<R: Runtime>(_context: InvokeContext<R>, paths: Vec<String>) -> crate::Result<PathBuf> {
+    // Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which
+    // will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path.
+    //
+    // examples:
+    // 1. `vec!["."]` or `vec![]` will be equal to `std::env::current_dir()`
+    // 2. `vec!["/foo/bar", "/tmp/file", "baz"]` will be equal to `PathBuf::from("/tmp/file/baz")`
+    let mut path = std::env::current_dir()?;
+    for p in paths {
+      path.push(p);
+    }
+    Ok(normalize_path(&path))
   }
-  Ok(normalize_path(&path).to_string_lossy().to_string())
-}
 
-#[cfg(path_all)]
-fn join(mut paths: Vec<String>) -> crate::Result<String> {
-  let path = PathBuf::from(
-    paths
-      .iter_mut()
-      .map(|p| {
-        // Add a `MAIN_SEPARATOR` if it doesn't already have one.
-        // Doing this to ensure that the vector elements are separated in
-        // the resulting string so path.components() can work correctly when called
-        // in `normalize_path_no_absolute()` later on.
-        if !p.ends_with('/') && !p.ends_with('\\') {
+  #[module_command_handler(path_all, "path > all")]
+  fn normalize<R: Runtime>(_context: InvokeContext<R>, path: String) -> crate::Result<String> {
+    let mut p = normalize_path_no_absolute(Path::new(&path))
+      .to_string_lossy()
+      .to_string();
+    Ok(
+      // Node.js behavior is to return `".."` for `normalize("..")`
+      // and `"."` for `normalize("")` or `normalize(".")`
+      if p.is_empty() && path == ".." {
+        "..".into()
+      } else if p.is_empty() && path == "." {
+        ".".into()
+      } else {
+        // Add a trailing separator if the path passed to this functions had a trailing separator. That's how Node.js behaves.
+        if (path.ends_with('/') || path.ends_with('\\'))
+          && (!p.ends_with('/') || !p.ends_with('\\'))
+        {
           p.push(MAIN_SEPARATOR);
         }
-        p.to_string()
-      })
-      .collect::<String>(),
-  );
+        p
+      },
+    )
+  }
 
-  let p = normalize_path_no_absolute(&path)
-    .to_string_lossy()
-    .to_string();
-  Ok(if p.is_empty() { ".".into() } else { p })
-}
+  #[module_command_handler(path_all, "path > all")]
+  fn join<R: Runtime>(_context: InvokeContext<R>, mut paths: Vec<String>) -> crate::Result<String> {
+    let path = PathBuf::from(
+      paths
+        .iter_mut()
+        .map(|p| {
+          // Add a `MAIN_SEPARATOR` if it doesn't already have one.
+          // Doing this to ensure that the vector elements are separated in
+          // the resulting string so path.components() can work correctly when called
+          // in `normalize_path_no_absolute()` later on.
+          if !p.ends_with('/') && !p.ends_with('\\') {
+            p.push(MAIN_SEPARATOR);
+          }
+          p.to_string()
+        })
+        .collect::<String>(),
+    );
 
-#[cfg(path_all)]
-fn normalize(path: String) -> crate::Result<String> {
-  let mut p = normalize_path_no_absolute(Path::new(&path))
-    .to_string_lossy()
-    .to_string();
+    let p = normalize_path_no_absolute(&path)
+      .to_string_lossy()
+      .to_string();
+    Ok(if p.is_empty() { ".".into() } else { p })
+  }
 
-  Ok(
-    // Node.js behavior is to return `".."` for `normalize("..")`
-    // and `"."` for `normalize("")` or `normalize(".")`
-    if p.is_empty() && path == ".." {
-      "..".into()
-    } else if p.is_empty() && path == "." {
-      ".".into()
-    } else {
-      // Add a trailing separator if the path passed to this functions had a trailing separator. That's how Node.js behaves.
-      if (path.ends_with('/') || path.ends_with('\\')) && (!p.ends_with('/') || !p.ends_with('\\'))
-      {
-        p.push(MAIN_SEPARATOR);
-      }
-      p
-    },
-  )
-}
+  #[module_command_handler(path_all, "path > all")]
+  fn dirname<R: Runtime>(_context: InvokeContext<R>, path: String) -> crate::Result<PathBuf> {
+    match Path::new(&path).parent() {
+      Some(p) => Ok(p.to_path_buf()),
+      None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path(
+        "Couldn't get the parent directory".into(),
+      ))),
+    }
+  }
 
-#[cfg(path_all)]
-fn dirname(path: String) -> crate::Result<String> {
-  match Path::new(&path).parent() {
-    Some(p) => Ok(p.to_string_lossy().to_string()),
-    None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path(
-      "Couldn't get the parent directory".into(),
-    ))),
+  #[module_command_handler(path_all, "path > all")]
+  fn extname<R: Runtime>(_context: InvokeContext<R>, path: String) -> crate::Result<String> {
+    match Path::new(&path)
+      .extension()
+      .and_then(std::ffi::OsStr::to_str)
+    {
+      Some(p) => Ok(p.to_string()),
+      None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path(
+        "Couldn't get the extension of the file".into(),
+      ))),
+    }
   }
-}
 
-#[cfg(path_all)]
-fn extname(path: String) -> crate::Result<String> {
-  match Path::new(&path)
-    .extension()
-    .and_then(std::ffi::OsStr::to_str)
-  {
-    Some(p) => Ok(p.to_string()),
-    None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path(
-      "Couldn't get the extension of the file".into(),
-    ))),
+  #[module_command_handler(path_all, "path > all")]
+  fn basename<R: Runtime>(
+    _context: InvokeContext<R>,
+    path: String,
+    ext: Option<String>,
+  ) -> crate::Result<String> {
+    match Path::new(&path)
+      .file_name()
+      .and_then(std::ffi::OsStr::to_str)
+    {
+      Some(p) => Ok(if let Some(ext) = ext {
+        p.replace(ext.as_str(), "")
+      } else {
+        p.to_string()
+      }),
+      None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path(
+        "Couldn't get the basename".into(),
+      ))),
+    }
   }
-}
 
-#[cfg(path_all)]
-fn basename(path: String, ext: Option<String>) -> crate::Result<String> {
-  match Path::new(&path)
-    .file_name()
-    .and_then(std::ffi::OsStr::to_str)
-  {
-    Some(p) => Ok(if let Some(ext) = ext {
-      p.replace(ext.as_str(), "")
-    } else {
-      p.to_string()
-    }),
-    None => Err(crate::Error::FailedToExecuteApi(crate::api::Error::Path(
-      "Couldn't get the basename".into(),
-    ))),
+  #[module_command_handler(path_all, "path > all")]
+  fn is_absolute<R: Runtime>(_context: InvokeContext<R>, path: String) -> crate::Result<bool> {
+    Ok(Path::new(&path).is_absolute())
   }
 }
 
-/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util
+/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util.
 /// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106
 #[cfg(path_all)]
 fn normalize_path(path: &Path) -> PathBuf {

+ 15 - 27
core/tauri/src/endpoints/process.rs

@@ -2,14 +2,15 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use super::InvokeResponse;
+use super::InvokeContext;
 #[cfg(process_relaunch)]
 use crate::Manager;
-use crate::{Runtime, Window};
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Relaunch application
@@ -20,30 +21,17 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[allow(unused_variables)]
-  pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
-    match self {
-      #[cfg(process_relaunch)]
-      Self::Relaunch => Ok({
-        crate::api::process::restart(&window.state());
-        ().into()
-      }),
-      #[cfg(not(process_relaunch))]
-      Self::Relaunch => Err(crate::Error::ApiNotAllowlisted(
-        "process > relaunch".to_string(),
-      )),
+  #[module_command_handler(process_relaunch, "process > relaunch")]
+  fn relaunch<R: Runtime>(context: InvokeContext<R>) -> crate::Result<()> {
+    crate::api::process::restart(&context.window.state());
+    Ok(())
+  }
 
-      #[cfg(process_exit)]
-      Self::Exit { exit_code } => {
-        // would be great if we can have a handler inside tauri
-        // who close all window and emit an event that user can catch
-        // if they want to process something before closing the app
-        std::process::exit(exit_code);
-      }
-      #[cfg(not(process_exit))]
-      Self::Exit { .. } => Err(crate::Error::ApiNotAllowlisted(
-        "process > exit".to_string(),
-      )),
-    }
+  #[module_command_handler(process_exit, "process > exit")]
+  fn exit<R: Runtime>(_context: InvokeContext<R>, exit_code: i32) -> crate::Result<()> {
+    // would be great if we can have a handler inside tauri
+    // who close all window and emit an event that user can catch
+    // if they want to process something before closing the app
+    std::process::exit(exit_code);
   }
 }

+ 92 - 97
core/tauri/src/endpoints/shell.rs

@@ -2,8 +2,10 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use crate::{endpoints::InvokeResponse, runtime::Runtime, Window};
+use super::InvokeContext;
+use crate::Runtime;
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 #[cfg(shell_execute)]
 use std::sync::{Arc, Mutex};
@@ -46,7 +48,7 @@ pub struct CommandOptions {
 }
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The execute script API.
@@ -73,107 +75,100 @@ pub enum Cmd {
 
 impl Cmd {
   #[allow(unused_variables)]
-  pub fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
-    match self {
-      Self::Execute {
-        program,
-        args,
-        on_event_fn,
-        options,
-      } => {
-        let mut command = if options.sidecar {
-          #[cfg(not(shell_sidecar))]
-          return Err(crate::Error::ApiNotAllowlisted(
-            "shell > sidecar".to_string(),
-          ));
-          #[cfg(shell_sidecar)]
-          crate::api::process::Command::new_sidecar(program)?
-        } else {
-          #[cfg(not(shell_execute))]
-          return Err(crate::Error::ApiNotAllowlisted(
-            "shell > execute".to_string(),
-          ));
-          #[cfg(shell_execute)]
-          crate::api::process::Command::new(program)
-        };
-        #[cfg(any(shell_execute, shell_sidecar))]
-        {
-          command = command.args(args);
-          if let Some(cwd) = options.cwd {
-            command = command.current_dir(cwd);
-          }
-          if let Some(env) = options.env {
-            command = command.envs(env);
-          } else {
-            command = command.env_clear();
-          }
-          let (mut rx, child) = command.spawn()?;
-
-          let pid = child.pid();
-          command_childs().lock().unwrap().insert(pid, child);
-
-          crate::async_runtime::spawn(async move {
-            while let Some(event) = rx.recv().await {
-              if matches!(event, crate::api::process::CommandEvent::Terminated(_)) {
-                command_childs().lock().unwrap().remove(&pid);
-              }
-              let js = crate::api::ipc::format_callback(on_event_fn.clone(), &event)
-                .expect("unable to serialize CommandEvent");
-
-              let _ = window.eval(js.as_str());
-            }
-          });
-
-          Ok(pid.into())
-        }
+  fn execute<R: Runtime>(
+    context: InvokeContext<R>,
+    program: String,
+    args: Vec<String>,
+    on_event_fn: String,
+    options: CommandOptions,
+  ) -> crate::Result<ChildId> {
+    let mut command = if options.sidecar {
+      #[cfg(not(shell_sidecar))]
+      return Err(crate::Error::ApiNotAllowlisted(
+        "shell > sidecar".to_string(),
+      ));
+      #[cfg(shell_sidecar)]
+      crate::api::process::Command::new_sidecar(program)?
+    } else {
+      #[cfg(not(shell_execute))]
+      return Err(crate::Error::ApiNotAllowlisted(
+        "shell > execute".to_string(),
+      ));
+      #[cfg(shell_execute)]
+      crate::api::process::Command::new(program)
+    };
+    #[cfg(any(shell_execute, shell_sidecar))]
+    {
+      command = command.args(args);
+      if let Some(cwd) = options.cwd {
+        command = command.current_dir(cwd);
       }
-      Self::KillChild { pid } => {
-        #[cfg(shell_execute)]
-        {
-          if let Some(child) = command_childs().lock().unwrap().remove(&pid) {
-            child.kill()?;
-          }
-          Ok(().into())
-        }
-        #[cfg(not(shell_execute))]
-        Err(crate::Error::ApiNotAllowlisted(
-          "shell > execute".to_string(),
-        ))
+      if let Some(env) = options.env {
+        command = command.envs(env);
+      } else {
+        command = command.env_clear();
       }
-      Self::StdinWrite { pid, buffer } => {
-        #[cfg(shell_execute)]
-        {
-          if let Some(child) = command_childs().lock().unwrap().get_mut(&pid) {
-            match buffer {
-              Buffer::Text(t) => child.write(t.as_bytes())?,
-              Buffer::Raw(r) => child.write(&r)?,
-            }
+      let (mut rx, child) = command.spawn()?;
+
+      let pid = child.pid();
+      command_childs().lock().unwrap().insert(pid, child);
+
+      crate::async_runtime::spawn(async move {
+        while let Some(event) = rx.recv().await {
+          if matches!(event, crate::api::process::CommandEvent::Terminated(_)) {
+            command_childs().lock().unwrap().remove(&pid);
           }
-          Ok(().into())
-        }
-        #[cfg(not(shell_execute))]
-        Err(crate::Error::ApiNotAllowlisted(
-          "shell > execute".to_string(),
-        ))
-      }
-      Self::Open { path, with } => {
-        #[cfg(shell_open)]
-        match crate::api::shell::open(
-          path,
-          if let Some(w) = with {
-            use std::str::FromStr;
-            Some(crate::api::shell::Program::from_str(&w)?)
-          } else {
-            None
-          },
-        ) {
-          Ok(_) => Ok(().into()),
-          Err(err) => Err(crate::Error::FailedToExecuteApi(err)),
+          let js = crate::api::ipc::format_callback(on_event_fn.clone(), &event)
+            .expect("unable to serialize CommandEvent");
+
+          let _ = context.window.eval(js.as_str());
         }
+      });
+
+      Ok(pid)
+    }
+  }
 
-        #[cfg(not(shell_open))]
-        Err(crate::Error::ApiNotAllowlisted("shell > open".to_string()))
+  #[module_command_handler(shell_execute, "shell > execute")]
+  fn stdin_write<R: Runtime>(
+    _context: InvokeContext<R>,
+    pid: ChildId,
+    buffer: Buffer,
+  ) -> crate::Result<()> {
+    if let Some(child) = command_childs().lock().unwrap().get_mut(&pid) {
+      match buffer {
+        Buffer::Text(t) => child.write(t.as_bytes())?,
+        Buffer::Raw(r) => child.write(&r)?,
       }
     }
+    Ok(())
+  }
+
+  #[module_command_handler(shell_execute, "shell > execute")]
+  fn kill_child<R: Runtime>(_context: InvokeContext<R>, pid: ChildId) -> crate::Result<()> {
+    if let Some(child) = command_childs().lock().unwrap().remove(&pid) {
+      child.kill()?;
+    }
+    Ok(())
+  }
+
+  #[module_command_handler(shell_open, "shell > open")]
+  fn open<R: Runtime>(
+    _context: InvokeContext<R>,
+    path: String,
+    with: Option<String>,
+  ) -> crate::Result<()> {
+    match crate::api::shell::open(
+      path,
+      if let Some(w) = with {
+        use std::str::FromStr;
+        Some(crate::api::shell::Program::from_str(&w)?)
+      } else {
+        None
+      },
+    ) {
+      Ok(_) => Ok(()),
+      Err(err) => Err(crate::Error::FailedToExecuteApi(err)),
+    }
   }
 }

+ 112 - 110
core/tauri/src/endpoints/window.rs

@@ -2,18 +2,19 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+use super::{InvokeContext, InvokeResponse};
 #[cfg(window_create)]
 use crate::runtime::{webview::WindowBuilder, Dispatch};
 use crate::{
-  endpoints::InvokeResponse,
   runtime::{
     window::dpi::{Position, Size},
     Runtime, UserAttentionType,
   },
   utils::config::WindowConfig,
-  Manager, Window,
+  Manager,
 };
 use serde::Deserialize;
+use tauri_macros::{module_command_handler, CommandModule};
 
 use crate::runtime::Icon;
 use std::path::PathBuf;
@@ -132,7 +133,8 @@ impl WindowManagerCmd {
 }
 
 /// The API descriptor.
-#[derive(Deserialize)]
+#[derive(Deserialize, CommandModule)]
+#[cmd(async)]
 #[serde(tag = "cmd", content = "data", rename_all = "camelCase")]
 pub enum Cmd {
   CreateWebview {
@@ -151,121 +153,121 @@ struct WindowCreatedEvent {
 }
 
 impl Cmd {
-  #[allow(dead_code)]
-  pub async fn run<R: Runtime>(self, window: Window<R>) -> crate::Result<InvokeResponse> {
-    match self {
-      #[cfg(not(window_create))]
-      Self::CreateWebview { .. } => {
-        return Err(crate::Error::ApiNotAllowlisted(
-          "window > create".to_string(),
-        ));
-      }
-      #[cfg(window_create)]
-      Self::CreateWebview { options } => {
-        let mut window = window;
-        let label = options.label.clone();
-        let url = options.url.clone();
+  #[module_command_handler(window_create, "window > create")]
+  async fn create_webview<R: Runtime>(
+    context: InvokeContext<R>,
+    options: Box<WindowConfig>,
+  ) -> crate::Result<()> {
+    let mut window = context.window;
+    let label = options.label.clone();
+    let url = options.url.clone();
+
+    window
+      .create_window(label.clone(), url, |_, webview_attributes| {
+        (
+          <<R::Dispatcher as Dispatch>::WindowBuilder>::with_config(*options),
+          webview_attributes,
+        )
+      })?
+      .emit_others("tauri://window-created", Some(WindowCreatedEvent { label }))?;
+
+    Ok(())
+  }
 
-        window
-          .create_window(label.clone(), url, |_, webview_attributes| {
-            (
-              <<R::Dispatcher as Dispatch>::WindowBuilder>::with_config(*options),
-              webview_attributes,
-            )
-          })?
-          .emit_others("tauri://window-created", Some(WindowCreatedEvent { label }))?;
+  async fn manage<R: Runtime>(
+    context: InvokeContext<R>,
+    label: Option<String>,
+    cmd: WindowManagerCmd,
+  ) -> crate::Result<InvokeResponse> {
+    let window = match label {
+      Some(l) if !l.is_empty() => context
+        .window
+        .get_window(&l)
+        .ok_or(crate::Error::WebviewNotFound)?,
+      _ => context.window,
+    };
+    match cmd {
+      // Getters
+      WindowManagerCmd::ScaleFactor => return Ok(window.scale_factor()?.into()),
+      WindowManagerCmd::InnerPosition => return Ok(window.inner_position()?.into()),
+      WindowManagerCmd::OuterPosition => return Ok(window.outer_position()?.into()),
+      WindowManagerCmd::InnerSize => return Ok(window.inner_size()?.into()),
+      WindowManagerCmd::OuterSize => return Ok(window.outer_size()?.into()),
+      WindowManagerCmd::IsFullscreen => return Ok(window.is_fullscreen()?.into()),
+      WindowManagerCmd::IsMaximized => return Ok(window.is_maximized()?.into()),
+      WindowManagerCmd::IsDecorated => return Ok(window.is_decorated()?.into()),
+      WindowManagerCmd::IsResizable => return Ok(window.is_resizable()?.into()),
+      WindowManagerCmd::IsVisible => return Ok(window.is_visible()?.into()),
+      WindowManagerCmd::CurrentMonitor => return Ok(window.current_monitor()?.into()),
+      WindowManagerCmd::PrimaryMonitor => return Ok(window.primary_monitor()?.into()),
+      WindowManagerCmd::AvailableMonitors => return Ok(window.available_monitors()?.into()),
+      // Setters
+      #[cfg(window_center)]
+      WindowManagerCmd::Center => window.center()?,
+      #[cfg(window_request_user_attention)]
+      WindowManagerCmd::RequestUserAttention(request_type) => {
+        window.request_user_attention(request_type)?
       }
-      Self::Manage { label, cmd } => {
-        let window = match label {
-          Some(l) if !l.is_empty() => window.get_window(&l).ok_or(crate::Error::WebviewNotFound)?,
-          _ => window,
-        };
-        match cmd {
-          // Getters
-          WindowManagerCmd::ScaleFactor => return Ok(window.scale_factor()?.into()),
-          WindowManagerCmd::InnerPosition => return Ok(window.inner_position()?.into()),
-          WindowManagerCmd::OuterPosition => return Ok(window.outer_position()?.into()),
-          WindowManagerCmd::InnerSize => return Ok(window.inner_size()?.into()),
-          WindowManagerCmd::OuterSize => return Ok(window.outer_size()?.into()),
-          WindowManagerCmd::IsFullscreen => return Ok(window.is_fullscreen()?.into()),
-          WindowManagerCmd::IsMaximized => return Ok(window.is_maximized()?.into()),
-          WindowManagerCmd::IsDecorated => return Ok(window.is_decorated()?.into()),
-          WindowManagerCmd::IsResizable => return Ok(window.is_resizable()?.into()),
-          WindowManagerCmd::IsVisible => return Ok(window.is_visible()?.into()),
-          WindowManagerCmd::CurrentMonitor => return Ok(window.current_monitor()?.into()),
-          WindowManagerCmd::PrimaryMonitor => return Ok(window.primary_monitor()?.into()),
-          WindowManagerCmd::AvailableMonitors => return Ok(window.available_monitors()?.into()),
-          // Setters
-          #[cfg(window_center)]
-          WindowManagerCmd::Center => window.center()?,
-          #[cfg(window_request_user_attention)]
-          WindowManagerCmd::RequestUserAttention(request_type) => {
-            window.request_user_attention(request_type)?
-          }
-          #[cfg(window_set_resizable)]
-          WindowManagerCmd::SetResizable(resizable) => window.set_resizable(resizable)?,
-          #[cfg(window_set_title)]
-          WindowManagerCmd::SetTitle(title) => window.set_title(&title)?,
-          #[cfg(window_maximize)]
-          WindowManagerCmd::Maximize => window.maximize()?,
-          #[cfg(window_unmaximize)]
-          WindowManagerCmd::Unmaximize => window.unmaximize()?,
-          #[cfg(all(window_maximize, window_unmaximize))]
-          WindowManagerCmd::ToggleMaximize => match window.is_maximized()? {
+      #[cfg(window_set_resizable)]
+      WindowManagerCmd::SetResizable(resizable) => window.set_resizable(resizable)?,
+      #[cfg(window_set_title)]
+      WindowManagerCmd::SetTitle(title) => window.set_title(&title)?,
+      #[cfg(window_maximize)]
+      WindowManagerCmd::Maximize => window.maximize()?,
+      #[cfg(window_unmaximize)]
+      WindowManagerCmd::Unmaximize => window.unmaximize()?,
+      #[cfg(all(window_maximize, window_unmaximize))]
+      WindowManagerCmd::ToggleMaximize => match window.is_maximized()? {
+        true => window.unmaximize()?,
+        false => window.maximize()?,
+      },
+      #[cfg(window_minimize)]
+      WindowManagerCmd::Minimize => window.minimize()?,
+      #[cfg(window_unminimize)]
+      WindowManagerCmd::Unminimize => window.unminimize()?,
+      #[cfg(window_show)]
+      WindowManagerCmd::Show => window.show()?,
+      #[cfg(window_hide)]
+      WindowManagerCmd::Hide => window.hide()?,
+      #[cfg(window_close)]
+      WindowManagerCmd::Close => window.close()?,
+      #[cfg(window_set_decorations)]
+      WindowManagerCmd::SetDecorations(decorations) => window.set_decorations(decorations)?,
+      #[cfg(window_set_always_on_top)]
+      WindowManagerCmd::SetAlwaysOnTop(always_on_top) => window.set_always_on_top(always_on_top)?,
+      #[cfg(window_set_size)]
+      WindowManagerCmd::SetSize(size) => window.set_size(size)?,
+      #[cfg(window_set_min_size)]
+      WindowManagerCmd::SetMinSize(size) => window.set_min_size(size)?,
+      #[cfg(window_set_max_size)]
+      WindowManagerCmd::SetMaxSize(size) => window.set_max_size(size)?,
+      #[cfg(window_set_position)]
+      WindowManagerCmd::SetPosition(position) => window.set_position(position)?,
+      #[cfg(window_set_fullscreen)]
+      WindowManagerCmd::SetFullscreen(fullscreen) => window.set_fullscreen(fullscreen)?,
+      #[cfg(window_set_focus)]
+      WindowManagerCmd::SetFocus => window.set_focus()?,
+      #[cfg(window_set_icon)]
+      WindowManagerCmd::SetIcon { icon } => window.set_icon(icon.into())?,
+      #[cfg(window_set_skip_taskbar)]
+      WindowManagerCmd::SetSkipTaskbar(skip) => window.set_skip_taskbar(skip)?,
+      #[cfg(window_start_dragging)]
+      WindowManagerCmd::StartDragging => window.start_dragging()?,
+      #[cfg(window_print)]
+      WindowManagerCmd::Print => window.print()?,
+      // internals
+      #[cfg(all(window_maximize, window_unmaximize))]
+      WindowManagerCmd::InternalToggleMaximize => {
+        if window.is_resizable()? {
+          match window.is_maximized()? {
             true => window.unmaximize()?,
             false => window.maximize()?,
-          },
-          #[cfg(window_minimize)]
-          WindowManagerCmd::Minimize => window.minimize()?,
-          #[cfg(window_unminimize)]
-          WindowManagerCmd::Unminimize => window.unminimize()?,
-          #[cfg(window_show)]
-          WindowManagerCmd::Show => window.show()?,
-          #[cfg(window_hide)]
-          WindowManagerCmd::Hide => window.hide()?,
-          #[cfg(window_close)]
-          WindowManagerCmd::Close => window.close()?,
-          #[cfg(window_set_decorations)]
-          WindowManagerCmd::SetDecorations(decorations) => window.set_decorations(decorations)?,
-          #[cfg(window_set_always_on_top)]
-          WindowManagerCmd::SetAlwaysOnTop(always_on_top) => {
-            window.set_always_on_top(always_on_top)?
-          }
-          #[cfg(window_set_size)]
-          WindowManagerCmd::SetSize(size) => window.set_size(size)?,
-          #[cfg(window_set_min_size)]
-          WindowManagerCmd::SetMinSize(size) => window.set_min_size(size)?,
-          #[cfg(window_set_max_size)]
-          WindowManagerCmd::SetMaxSize(size) => window.set_max_size(size)?,
-          #[cfg(window_set_position)]
-          WindowManagerCmd::SetPosition(position) => window.set_position(position)?,
-          #[cfg(window_set_fullscreen)]
-          WindowManagerCmd::SetFullscreen(fullscreen) => window.set_fullscreen(fullscreen)?,
-          #[cfg(window_set_focus)]
-          WindowManagerCmd::SetFocus => window.set_focus()?,
-          #[cfg(window_set_icon)]
-          WindowManagerCmd::SetIcon { icon } => window.set_icon(icon.into())?,
-          #[cfg(window_set_skip_taskbar)]
-          WindowManagerCmd::SetSkipTaskbar(skip) => window.set_skip_taskbar(skip)?,
-          #[cfg(window_start_dragging)]
-          WindowManagerCmd::StartDragging => window.start_dragging()?,
-          #[cfg(window_print)]
-          WindowManagerCmd::Print => window.print()?,
-          // internals
-          #[cfg(all(window_maximize, window_unmaximize))]
-          WindowManagerCmd::InternalToggleMaximize => {
-            if window.is_resizable()? {
-              match window.is_maximized()? {
-                true => window.unmaximize()?,
-                false => window.maximize()?,
-              }
-            }
           }
-          _ => return Err(cmd.into_allowlist_error()),
         }
       }
+      #[allow(unreachable_patterns)]
+      _ => return Err(cmd.into_allowlist_error()),
     }
-
     #[allow(unreachable_code)]
     Ok(().into())
   }

+ 0 - 3
core/tauri/src/error.rs

@@ -71,9 +71,6 @@ pub enum Error {
   /// Error initializing plugin.
   #[error("failed to initialize plugin `{0}`: {1}")]
   PluginInitialization(String, String),
-  /// `default_path` provided to dialog API doesn't exist.
-  #[error("failed to setup dialog: provided default path `{0}` doesn't exist")]
-  DialogDefaultPathNotExists(PathBuf),
   /// Encountered an error creating the app system tray,
   #[error("error encountered during tray setup: {0}")]
   SystemTray(Box<dyn std::error::Error + Send>),

+ 1 - 0
examples/api/src-tauri/Cargo.lock

@@ -3129,6 +3129,7 @@ dependencies = [
 name = "tauri-macros"
 version = "1.0.0-beta.5"
 dependencies = [
+ "heck",
  "proc-macro2",
  "quote",
  "syn",

+ 4 - 1
examples/api/src/components/Http.svelte

@@ -7,7 +7,10 @@
   export let onMessage;
 
   async function makeHttpRequest() {
-    const client = await getClient();
+    const client = await getClient().catch(e => {
+      onMessage(e)
+      throw e
+    });
     let method = httpMethod || "GET";
     let url = httpUrl || "";
 

+ 5 - 1
examples/api/src/components/Window.svelte

@@ -57,7 +57,11 @@
   function getIcon() {
     openDialog({
       multiple: false,
-    }).then(windowMap[selectedWindow].setIcon);
+    }).then(path => {
+      if (path) {
+        windowMap[selectedWindow].setIcon(path)
+      }
+    });
   }
 
   function createWindow() {

+ 1 - 1
tooling/api/src/os.ts

@@ -82,7 +82,7 @@ async function type(): Promise<
   return invokeTauriCommand<string>({
     __tauriModule: 'Os',
     message: {
-      cmd: 'type'
+      cmd: 'osType'
     }
   })
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов