浏览代码

perf(core): improve binary size with api enum serde refactor (#3952)

Lucas Fernandes Nogueira 3 年之前
父节点
当前提交
c23f139ba8

+ 5 - 0
.changes/binary-size-perf.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Reduce the amount of generated code for the API endpoints.

+ 1 - 0
Cargo.toml

@@ -24,6 +24,7 @@ exclude = [
 
 # default to small, optimized workspace release binaries
 [profile.release]
+strip = true
 panic = "abort"
 codegen-units = 1
 lto = true

+ 150 - 53
core/tauri-macros/src/command_module.rs

@@ -2,35 +2,115 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use heck::ToSnakeCase;
+use heck::{ToLowerCamelCase, ToSnakeCase};
 use proc_macro::TokenStream;
-use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
+use proc_macro2::{Span, TokenStream as TokenStream2};
 
 use quote::{format_ident, quote, quote_spanned};
 use syn::{
   parse::{Parse, ParseStream},
+  parse_quote,
   spanned::Spanned,
-  Data, DeriveInput, Error, Fields, FnArg, Ident, ItemFn, LitStr, Pat, Token,
+  Data, DeriveInput, Error, Fields, Ident, ItemFn, LitStr, Token,
 };
 
-pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
+pub(crate) fn generate_command_enum(mut input: DeriveInput) -> TokenStream {
+  let mut deserialize_functions = TokenStream2::new();
+  let mut errors = TokenStream2::new();
+
+  input.attrs.push(parse_quote!(#[allow(dead_code)]));
+
+  match &mut input.data {
+    Data::Enum(data_enum) => {
+      for variant in &mut data_enum.variants {
+        let mut feature: Option<Ident> = None;
+        let mut error_message: Option<String> = None;
+
+        for attr in &variant.attrs {
+          if attr.path.is_ident("cmd") {
+            let r = attr
+              .parse_args_with(|input: ParseStream| {
+                if let Ok(f) = input.parse::<Ident>() {
+                  feature.replace(f);
+                  input.parse::<Token![,]>()?;
+                  let error_message_raw: LitStr = input.parse()?;
+                  error_message.replace(error_message_raw.value());
+                }
+                Ok(quote!())
+              })
+              .unwrap_or_else(syn::Error::into_compile_error);
+            errors.extend(r);
+          }
+        }
+
+        if let Some(f) = feature {
+          let error_message = if let Some(e) = error_message {
+            let e = e.to_string();
+            quote!(#e)
+          } else {
+            quote!("This API is not enabled in the allowlist.")
+          };
+
+          let deserialize_function_name = quote::format_ident!("__{}_deserializer", variant.ident);
+          deserialize_functions.extend(quote! {
+            #[cfg(not(#f))]
+            #[allow(non_snake_case)]
+            fn #deserialize_function_name<'de, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error>
+            where
+              D: ::serde::de::Deserializer<'de>,
+            {
+              ::std::result::Result::Err(::serde::de::Error::custom(crate::Error::ApiNotAllowlisted(#error_message.into()).to_string()))
+            }
+          });
+
+          let deserialize_function_name = deserialize_function_name.to_string();
+
+          variant
+          .attrs
+          .push(parse_quote!(#[cfg_attr(not(#f), serde(deserialize_with = #deserialize_function_name))]));
+        }
+      }
+    }
+    _ => {
+      return Error::new(
+        Span::call_site(),
+        "`command_enum` is only implemented for enums",
+      )
+      .to_compile_error()
+      .into()
+    }
+  };
+
+  TokenStream::from(quote! {
+    #errors
+    #input
+    #deserialize_functions
+  })
+}
+
+pub(crate) fn generate_run_fn(input: DeriveInput) -> TokenStream {
   let name = &input.ident;
   let data = &input.data;
 
+  let mut errors = TokenStream2::new();
+
   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";
+      let r = attr
+        .parse_args_with(|input: ParseStream| {
+          if let Ok(token) = input.parse::<Ident>() {
+            is_async = token == "async";
           }
-        }
-        Ok(())
-      });
+          Ok(quote!())
+        })
+        .unwrap_or_else(syn::Error::into_compile_error);
+      errors.extend(r);
     }
   }
+
   let maybe_await = if is_async { quote!(.await) } else { quote!() };
   let maybe_async = if is_async { quote!(async) } else { quote!() };
 
@@ -43,6 +123,30 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
       for variant in &data_enum.variants {
         let variant_name = &variant.ident;
 
+        let mut feature = None;
+
+        for attr in &variant.attrs {
+          if attr.path.is_ident("cmd") {
+            let r = attr
+              .parse_args_with(|input: ParseStream| {
+                if let Ok(f) = input.parse::<Ident>() {
+                  feature.replace(f);
+                  input.parse::<Token![,]>()?;
+                  let _: LitStr = input.parse()?;
+                }
+                Ok(quote!())
+              })
+              .unwrap_or_else(syn::Error::into_compile_error);
+            errors.extend(r);
+          }
+        }
+
+        let maybe_feature_check = if let Some(f) = feature {
+          quote!(#[cfg(#f)])
+        } else {
+          quote!()
+        };
+
         let (fields_in_variant, variables) = match &variant.fields {
           Fields::Unit => (quote_spanned! { variant.span() => }, quote!()),
           Fields::Unnamed(fields) => {
@@ -73,9 +177,13 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
         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),
+          variant.span() => #maybe_feature_check #name::#variant_name #fields_in_variant => #name::#variant_execute_function_name(context, #variables)#maybe_await.map(Into::into),
         });
       }
+
+      matcher.extend(quote! {
+        _ => Err(crate::error::into_anyhow("API not in the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")),
+      });
     }
     _ => {
       return Error::new(
@@ -90,7 +198,8 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
   let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
 
   let expanded = quote! {
-      impl #impl_generics #name #ty_generics #where_clause {
+    #errors
+    impl #impl_generics #name #ty_generics #where_clause {
         pub #maybe_async fn run<R: crate::Runtime>(self, context: crate::endpoints::InvokeContext<R>) -> super::Result<crate::endpoints::InvokeResponse> {
           match self {
             #matcher
@@ -105,26 +214,25 @@ pub fn generate_run_fn(input: DeriveInput) -> TokenStream {
 /// 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,
+      allowlist: input.parse()?,
     })
   }
 }
 
+pub enum AllowlistCheckKind {
+  Runtime,
+  Serde,
+}
+
 pub struct HandlerTestAttributes {
   allowlist: Ident,
   error_message: String,
-  is_async: bool,
+  allowlist_check_kind: AllowlistCheckKind,
 }
 
 impl Parse for HandlerTestAttributes {
@@ -133,60 +241,48 @@ impl Parse for HandlerTestAttributes {
     input.parse::<Token![,]>()?;
     let error_message_raw: LitStr = input.parse()?;
     let error_message = error_message_raw.value();
-    let _ = input.parse::<Token![,]>();
-    let is_async = input
-      .parse::<Ident>()
-      .map(|i| i == "async")
-      .unwrap_or_default();
+    let allowlist_check_kind =
+      if let (Ok(_), Ok(i)) = (input.parse::<Token![,]>(), input.parse::<Ident>()) {
+        if i == "runtime" {
+          AllowlistCheckKind::Runtime
+        } else {
+          AllowlistCheckKind::Serde
+        }
+      } else {
+        AllowlistCheckKind::Serde
+      };
 
     Ok(Self {
       allowlist,
       error_message,
-      is_async,
+      allowlist_check_kind,
     })
   }
 }
 
 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(anyhow::anyhow!(crate::Error::ApiNotAllowlisted(#error_message.to_string()).to_string()))
-    }
   )
 }
 
 pub fn command_test(attributes: HandlerTestAttributes, function: ItemFn) -> TokenStream2 {
   let allowlist = attributes.allowlist;
-  let is_async = attributes.is_async;
   let error_message = attributes.error_message.as_str();
   let signature = function.sig.clone();
-  let test_name = function.sig.ident.clone();
-  let mut args = quote!();
-  for arg in &function.sig.inputs {
-    if let FnArg::Typed(t) = arg {
-      if let Pat::Ident(i) = &*t.pat {
-        let ident = &i.ident;
-        args.extend(quote!(#ident,))
-      }
-    }
-  }
 
-  let response = if is_async {
-    quote!(crate::async_runtime::block_on(
-      super::Cmd::#test_name(crate::test::mock_invoke_context(), #args)
-    ))
-  } else {
-    quote!(super::Cmd::#test_name(crate::test::mock_invoke_context(), #args))
+  let enum_variant_name = function.sig.ident.to_string().to_lower_camel_case();
+  let response = match attributes.allowlist_check_kind {
+    AllowlistCheckKind::Runtime => {
+      let test_name = function.sig.ident.clone();
+      quote!(super::Cmd::#test_name(crate::test::mock_invoke_context()))
+    }
+    AllowlistCheckKind::Serde => quote! {
+      serde_json::from_str::<super::Cmd>(&format!(r#"{{ "cmd": "{}", "data": null }}"#, #enum_variant_name))
+    },
   };
 
   quote!(
@@ -194,6 +290,7 @@ pub fn command_test(attributes: HandlerTestAttributes, function: ItemFn) -> Toke
     #function
 
     #[cfg(not(#allowlist))]
+    #[allow(unused_variables)]
     #[quickcheck_macros::quickcheck]
     #signature {
       if let Err(e) = #response {

+ 11 - 3
core/tauri-macros/src/lib.rs

@@ -76,6 +76,14 @@ pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStre
   runtime::default_runtime(attributes, input).into()
 }
 
+/// Prepares the command module enum.
+#[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)
+}
+
 /// 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>`.
@@ -83,10 +91,10 @@ pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStre
 /// 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 {
+#[proc_macro_attribute]
+pub fn command_enum(_: TokenStream, input: TokenStream) -> TokenStream {
   let input = parse_macro_input!(input as DeriveInput);
-  command_module::generate_run_fn(input)
+  command_module::generate_command_enum(input)
 }
 
 #[doc(hidden)]

+ 2 - 0
core/tauri/build.rs

@@ -60,6 +60,8 @@ fn main() {
     shell_execute: { any(shell_all, feature = "shell-execute") },
     shell_sidecar: { any(shell_all, feature = "shell-sidecar") },
     shell_open: { any(shell_all, feature = "shell-open") },
+    // helper for the command module macro
+    shell_script: { any(shell_execute, shell_sidecar) },
     // helper for the shell scope functionality
     shell_scope: { any(shell_execute, shell_sidecar, feature = "shell-open-api") },
 

文件差异内容过多而无法显示
+ 0 - 0
core/tauri/scripts/bundle.js


+ 0 - 1
core/tauri/src/endpoints.rs

@@ -17,7 +17,6 @@ mod cli;
 mod clipboard;
 mod dialog;
 mod event;
-#[allow(unused_imports)]
 mod file_system;
 mod global_shortcut;
 mod http;

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

@@ -5,9 +5,10 @@
 use super::InvokeContext;
 use crate::Runtime;
 use serde::Deserialize;
-use tauri_macros::CommandModule;
+use tauri_macros::{command_enum, CommandModule};
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 #[allow(clippy::enum_variant_names)]

+ 13 - 5
core/tauri/src/endpoints/cli.rs

@@ -2,13 +2,16 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::{InvokeContext, InvokeResponse};
 use crate::Runtime;
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 /// The API descriptor.
-#[derive(Deserialize, CommandModule)]
+#[command_enum]
+#[derive(CommandModule, Deserialize)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The get CLI matches API.
@@ -16,21 +19,26 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[module_command_handler(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)")]
+  #[module_command_handler(cli)]
   fn cli_matches<R: Runtime>(context: InvokeContext<R>) -> super::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()).into_anyhow())
+      Err(crate::error::into_anyhow("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)"))
     }
   }
+
+  #[cfg(not(cli))]
+  fn cli_matches<R: Runtime>(_: InvokeContext<R>) -> super::Result<InvokeResponse> {
+    Err(crate::error::into_anyhow("CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)"))
+  }
 }
 
 #[cfg(test)]
 mod tests {
-  #[tauri_macros::module_command_test(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)")]
+  #[tauri_macros::module_command_test(cli, "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)", runtime)]
   #[quickcheck_macros::quickcheck]
   fn cli_matches() {
     let res = super::Cmd::cli_matches(crate::test::mock_invoke_context());

+ 20 - 7
core/tauri/src/endpoints/clipboard.rs

@@ -2,25 +2,29 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 #[cfg(any(clipboard_write_text, clipboard_read_text))]
 use crate::runtime::ClipboardManager;
 use crate::Runtime;
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", content = "data", rename_all = "camelCase")]
 pub enum Cmd {
   /// Write a text string to the clipboard.
+  #[cmd(clipboard_write_text, "clipboard > writeText")]
   WriteText(String),
   /// Read clipboard content as text.
   ReadText,
 }
 
 impl Cmd {
-  #[module_command_handler(clipboard_write_text, "clipboard > writeText")]
+  #[module_command_handler(clipboard_write_text)]
   fn write_text<R: Runtime>(context: InvokeContext<R>, text: String) -> super::Result<()> {
     context
       .window
@@ -30,7 +34,7 @@ impl Cmd {
       .map_err(crate::error::into_anyhow)
   }
 
-  #[module_command_handler(clipboard_read_text, "clipboard > readText")]
+  #[module_command_handler(clipboard_read_text)]
   fn read_text<R: Runtime>(context: InvokeContext<R>) -> super::Result<Option<String>> {
     context
       .window
@@ -39,6 +43,11 @@ impl Cmd {
       .read_text()
       .map_err(crate::error::into_anyhow)
   }
+
+  #[cfg(not(clipboard_read_text))]
+  fn read_text<R: Runtime>(_: InvokeContext<R>) -> super::Result<()> {
+    Err(crate::Error::ApiNotAllowlisted("clipboard > readText".into()).into_anyhow())
+  }
 }
 
 #[cfg(test)]
@@ -48,16 +57,20 @@ mod tests {
   fn write_text(text: String) {
     let ctx = crate::test::mock_invoke_context();
     super::Cmd::write_text(ctx.clone(), text.clone()).unwrap();
+    #[cfg(clipboard_read_text)]
     assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text));
   }
 
-  #[tauri_macros::module_command_test(clipboard_read_text, "clipboard > readText")]
+  #[tauri_macros::module_command_test(clipboard_read_text, "clipboard > readText", runtime)]
   #[quickcheck_macros::quickcheck]
   fn read_text() {
     let ctx = crate::test::mock_invoke_context();
     assert_eq!(super::Cmd::read_text(ctx.clone()).unwrap(), None);
-    let text = "Tauri!".to_string();
-    super::Cmd::write_text(ctx.clone(), text.clone()).unwrap();
-    assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text));
+    #[cfg(clipboard_write_text)]
+    {
+      let text = "Tauri!".to_string();
+      super::Cmd::write_text(ctx.clone(), text.clone()).unwrap();
+      assert_eq!(super::Cmd::read_text(ctx).unwrap(), Some(text));
+    }
   }
 }

+ 17 - 15
core/tauri/src/endpoints/dialog.rs

@@ -2,12 +2,14 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::{InvokeContext, InvokeResponse};
 use crate::Runtime;
 #[cfg(any(dialog_open, dialog_save))]
 use crate::{api::dialog::blocking::FileDialogBuilder, Manager, Scopes};
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 use std::path::PathBuf;
 
@@ -56,25 +58,25 @@ pub struct SaveDialogOptions {
 }
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 #[allow(clippy::enum_variant_names)]
 pub enum Cmd {
   /// The open dialog API.
-  OpenDialog {
-    options: OpenDialogOptions,
-  },
+  #[cmd(dialog_open, "dialog > open")]
+  OpenDialog { options: OpenDialogOptions },
   /// The save dialog API.
-  SaveDialog {
-    options: SaveDialogOptions,
-  },
-  MessageDialog {
-    message: String,
-  },
+  #[cmd(dialog_save, "dialog > save")]
+  SaveDialog { options: SaveDialogOptions },
+  #[cmd(dialog_message, "dialog > message")]
+  MessageDialog { message: String },
+  #[cmd(dialog_ask, "dialog > ask")]
   AskDialog {
     title: Option<String>,
     message: String,
   },
+  #[cmd(dialog_confirm, "dialog > confirm")]
   ConfirmDialog {
     title: Option<String>,
     message: String,
@@ -82,7 +84,7 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[module_command_handler(dialog_open, "dialog > open")]
+  #[module_command_handler(dialog_open)]
   #[allow(unused_variables)]
   fn open_dialog<R: Runtime>(
     context: InvokeContext<R>,
@@ -130,7 +132,7 @@ impl Cmd {
     Ok(res)
   }
 
-  #[module_command_handler(dialog_save, "dialog > save")]
+  #[module_command_handler(dialog_save)]
   #[allow(unused_variables)]
   fn save_dialog<R: Runtime>(
     context: InvokeContext<R>,
@@ -159,7 +161,7 @@ impl Cmd {
     Ok(path)
   }
 
-  #[module_command_handler(dialog_message, "dialog > message")]
+  #[module_command_handler(dialog_message)]
   fn message_dialog<R: Runtime>(context: InvokeContext<R>, message: String) -> super::Result<()> {
     crate::api::dialog::blocking::message(
       Some(&context.window),
@@ -169,7 +171,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(dialog_ask, "dialog > ask")]
+  #[module_command_handler(dialog_ask)]
   fn ask_dialog<R: Runtime>(
     context: InvokeContext<R>,
     title: Option<String>,
@@ -182,7 +184,7 @@ impl Cmd {
     ))
   }
 
-  #[module_command_handler(dialog_confirm, "dialog > confirm")]
+  #[module_command_handler(dialog_confirm)]
   fn confirm_dialog<R: Runtime>(
     context: InvokeContext<R>,
     title: Option<String>,

+ 4 - 1
core/tauri/src/endpoints/event.rs

@@ -2,6 +2,8 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 use crate::{
   api::ipc::CallbackFn,
@@ -12,7 +14,7 @@ use crate::{
   Manager, Runtime,
 };
 use serde::{de::Deserializer, Deserialize};
-use tauri_macros::CommandModule;
+use tauri_macros::{command_enum, CommandModule};
 
 pub struct EventId(String);
 
@@ -51,6 +53,7 @@ impl<'de> Deserialize<'de> for WindowLabel {
 }
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {

+ 22 - 10
core/tauri/src/endpoints/file_system.rs

@@ -2,6 +2,8 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use crate::{
   api::{
     dir,
@@ -19,7 +21,7 @@ use serde::{
   de::{Deserializer, Error as DeError},
   Deserialize, Serialize,
 };
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 use std::fmt::{Debug, Formatter};
 use std::{
@@ -50,52 +52,62 @@ pub struct FileOperationOptions {
 }
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub(crate) enum Cmd {
   /// The read binary file API.
+  #[cmd(fs_read_file, "fs > readFile")]
   ReadFile {
     path: SafePathBuf,
     options: Option<FileOperationOptions>,
   },
   /// The read binary file API.
+  #[cmd(fs_read_file, "fs > readFile")]
   ReadTextFile {
     path: SafePathBuf,
     options: Option<FileOperationOptions>,
   },
   /// The write file API.
+  #[cmd(fs_write_file, "fs > writeFile")]
   WriteFile {
     path: SafePathBuf,
     contents: Vec<u8>,
     options: Option<FileOperationOptions>,
   },
   /// The read dir API.
+  #[cmd(fs_read_dir, "fs > readDir")]
   ReadDir {
     path: SafePathBuf,
     options: Option<DirOperationOptions>,
   },
   /// The copy file API.
+  #[cmd(fs_copy_file, "fs > copyFile")]
   CopyFile {
     source: SafePathBuf,
     destination: SafePathBuf,
     options: Option<FileOperationOptions>,
   },
   /// The create dir API.
+  #[cmd(fs_create_dir, "fs > createDir")]
   CreateDir {
     path: SafePathBuf,
     options: Option<DirOperationOptions>,
   },
   /// The remove dir API.
+  #[cmd(fs_remove_dir, "fs > removeDir")]
   RemoveDir {
     path: SafePathBuf,
     options: Option<DirOperationOptions>,
   },
   /// The remove file API.
+  #[cmd(fs_remove_file, "fs > removeFile")]
   RemoveFile {
     path: SafePathBuf,
     options: Option<FileOperationOptions>,
   },
   /// The rename file API.
+  #[cmd(fs_rename_file, "fs > renameFile")]
   #[serde(rename_all = "camelCase")]
   RenameFile {
     old_path: SafePathBuf,
@@ -105,7 +117,7 @@ pub(crate) enum Cmd {
 }
 
 impl Cmd {
-  #[module_command_handler(fs_read_file, "fs > readFile")]
+  #[module_command_handler(fs_read_file)]
   fn read_file<R: Runtime>(
     context: InvokeContext<R>,
     path: SafePathBuf,
@@ -123,7 +135,7 @@ impl Cmd {
       .map_err(Into::into)
   }
 
-  #[module_command_handler(fs_read_file, "fs > readFile")]
+  #[module_command_handler(fs_read_file)]
   fn read_text_file<R: Runtime>(
     context: InvokeContext<R>,
     path: SafePathBuf,
@@ -141,7 +153,7 @@ impl Cmd {
       .map_err(Into::into)
   }
 
-  #[module_command_handler(fs_write_file, "fs > writeFile")]
+  #[module_command_handler(fs_write_file)]
   fn write_file<R: Runtime>(
     context: InvokeContext<R>,
     path: SafePathBuf,
@@ -161,7 +173,7 @@ impl Cmd {
       .and_then(|mut f| f.write_all(&contents).map_err(|err| err.into()))
   }
 
-  #[module_command_handler(fs_read_dir, "fs > readDir")]
+  #[module_command_handler(fs_read_dir)]
   fn read_dir<R: Runtime>(
     context: InvokeContext<R>,
     path: SafePathBuf,
@@ -184,7 +196,7 @@ impl Cmd {
       .map_err(Into::into)
   }
 
-  #[module_command_handler(fs_copy_file, "fs > copyFile")]
+  #[module_command_handler(fs_copy_file)]
   fn copy_file<R: Runtime>(
     context: InvokeContext<R>,
     source: SafePathBuf,
@@ -215,7 +227,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(fs_create_dir, "fs > createDir")]
+  #[module_command_handler(fs_create_dir)]
   fn create_dir<R: Runtime>(
     context: InvokeContext<R>,
     path: SafePathBuf,
@@ -244,7 +256,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(fs_remove_dir, "fs > removeDir")]
+  #[module_command_handler(fs_remove_dir)]
   fn remove_dir<R: Runtime>(
     context: InvokeContext<R>,
     path: SafePathBuf,
@@ -273,7 +285,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(fs_remove_file, "fs > removeFile")]
+  #[module_command_handler(fs_remove_file)]
   fn remove_file<R: Runtime>(
     context: InvokeContext<R>,
     path: SafePathBuf,
@@ -291,7 +303,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(fs_rename_file, "fs > renameFile")]
+  #[module_command_handler(fs_rename_file)]
   fn rename_file<R: Runtime>(
     context: InvokeContext<R>,
     old_path: SafePathBuf,

+ 19 - 7
core/tauri/src/endpoints/global_shortcut.rs

@@ -2,38 +2,45 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 use crate::{api::ipc::CallbackFn, Runtime};
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 #[cfg(global_shortcut_all)]
 use crate::runtime::GlobalShortcutManager;
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Register a global shortcut.
+  #[cmd(global_shortcut_all, "globalShortcut > all")]
   Register {
     shortcut: String,
     handler: CallbackFn,
   },
   /// Register a list of global shortcuts.
+  #[cmd(global_shortcut_all, "globalShortcut > all")]
   RegisterAll {
     shortcuts: Vec<String>,
     handler: CallbackFn,
   },
   /// Unregister a global shortcut.
+  #[cmd(global_shortcut_all, "globalShortcut > all")]
   Unregister { shortcut: String },
   /// Unregisters all registered shortcuts.
   UnregisterAll,
   /// Determines whether the given hotkey is registered or not.
+  #[cmd(global_shortcut_all, "globalShortcut > all")]
   IsRegistered { shortcut: String },
 }
 
 impl Cmd {
-  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  #[module_command_handler(global_shortcut_all)]
   fn register<R: Runtime>(
     context: InvokeContext<R>,
     shortcut: String,
@@ -44,7 +51,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  #[module_command_handler(global_shortcut_all)]
   fn register_all<R: Runtime>(
     context: InvokeContext<R>,
     shortcuts: Vec<String>,
@@ -57,7 +64,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  #[module_command_handler(global_shortcut_all)]
   fn unregister<R: Runtime>(context: InvokeContext<R>, shortcut: String) -> super::Result<()> {
     context
       .window
@@ -68,7 +75,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  #[module_command_handler(global_shortcut_all)]
   fn unregister_all<R: Runtime>(context: InvokeContext<R>) -> super::Result<()> {
     context
       .window
@@ -79,7 +86,12 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(global_shortcut_all, "globalShortcut > all")]
+  #[cfg(not(global_shortcut_all))]
+  fn unregister_all<R: Runtime>(_: InvokeContext<R>) -> super::Result<()> {
+    Err(crate::Error::ApiNotAllowlisted("globalShortcut > all".into()).into_anyhow())
+  }
+
+  #[module_command_handler(global_shortcut_all)]
   fn is_registered<R: Runtime>(context: InvokeContext<R>, shortcut: String) -> super::Result<bool> {
     context
       .window
@@ -139,7 +151,7 @@ mod tests {
     assert!(!super::Cmd::is_registered(ctx, shortcut).unwrap());
   }
 
-  #[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all")]
+  #[tauri_macros::module_command_test(global_shortcut_all, "globalShortcut > all", runtime)]
   #[quickcheck_macros::quickcheck]
   fn unregister_all() {
     let shortcuts = vec!["CTRL+X".to_string(), "SUPER+C".to_string(), "D".to_string()];

+ 13 - 6
core/tauri/src/endpoints/http.rs

@@ -2,10 +2,12 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 use crate::Runtime;
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 #[cfg(http_request)]
 use std::{
@@ -20,6 +22,7 @@ type ClientBuilder = ();
 #[cfg(not(http_request))]
 type HttpRequestBuilder = ();
 #[cfg(not(http_request))]
+#[allow(dead_code)]
 type ResponseData = ();
 
 type ClientId = u32;
@@ -34,15 +37,19 @@ fn clients() -> &'static ClientStore {
 }
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[cmd(async)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Create a new HTTP client.
+  #[cmd(http_request, "http > request")]
   CreateClient { options: Option<ClientBuilder> },
   /// Drop a HTTP client.
+  #[cmd(http_request, "http > request")]
   DropClient { client: ClientId },
   /// The HTTP request API.
+  #[cmd(http_request, "http > request")]
   HttpRequest {
     client: ClientId,
     options: Box<HttpRequestBuilder>,
@@ -50,7 +57,7 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[module_command_handler(http_request, "http > request")]
+  #[module_command_handler(http_request)]
   async fn create_client<R: Runtime>(
     _context: InvokeContext<R>,
     options: Option<ClientBuilder>,
@@ -62,7 +69,7 @@ impl Cmd {
     Ok(id)
   }
 
-  #[module_command_handler(http_request, "http > request")]
+  #[module_command_handler(http_request)]
   async fn drop_client<R: Runtime>(
     _context: InvokeContext<R>,
     client: ClientId,
@@ -72,7 +79,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[module_command_handler(http_request, "http > request")]
+  #[module_command_handler(http_request)]
   async fn http_request<R: Runtime>(
     context: InvokeContext<R>,
     client_id: ClientId,
@@ -115,7 +122,7 @@ impl Cmd {
 mod tests {
   use super::{ClientBuilder, ClientId};
 
-  #[tauri_macros::module_command_test(http_request, "http > request", async)]
+  #[tauri_macros::module_command_test(http_request, "http > request")]
   #[quickcheck_macros::quickcheck]
   fn create_client(options: Option<ClientBuilder>) {
     assert!(crate::async_runtime::block_on(super::Cmd::create_client(
@@ -125,7 +132,7 @@ mod tests {
     .is_ok());
   }
 
-  #[tauri_macros::module_command_test(http_request, "http > request", async)]
+  #[tauri_macros::module_command_test(http_request, "http > request")]
   #[quickcheck_macros::quickcheck]
   fn drop_client(client_id: ClientId) {
     crate::async_runtime::block_on(async move {

+ 6 - 2
core/tauri/src/endpoints/notification.rs

@@ -2,10 +2,12 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 use crate::Runtime;
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 #[cfg(notification_all)]
 use crate::{api::notification::Notification, Env, Manager};
@@ -28,10 +30,12 @@ pub struct NotificationOptions {
 }
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The show notification API.
+  #[cmd(notification_all, "notification > all")]
   Notification { options: NotificationOptions },
   /// The request notification permission API.
   RequestNotificationPermission,
@@ -40,7 +44,7 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[module_command_handler(notification_all, "notification > all")]
+  #[module_command_handler(notification_all)]
   fn notification<R: Runtime>(
     context: InvokeContext<R>,
     options: NotificationOptions,

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

@@ -2,13 +2,16 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 use crate::Runtime;
 use serde::Deserialize;
 use std::path::PathBuf;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
@@ -19,33 +22,52 @@ pub enum Cmd {
   Tempdir,
 }
 
+#[cfg(os_all)]
 impl Cmd {
-  #[module_command_handler(os_all, "os > all")]
   fn platform<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
     Ok(os_platform())
   }
 
-  #[module_command_handler(os_all, "os > all")]
   fn version<R: Runtime>(_context: InvokeContext<R>) -> super::Result<String> {
     Ok(os_info::get().version().to_string())
   }
 
-  #[module_command_handler(os_all, "os > all")]
   fn os_type<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
     Ok(os_type())
   }
 
-  #[module_command_handler(os_all, "os > all")]
   fn arch<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
     Ok(std::env::consts::ARCH)
   }
 
-  #[module_command_handler(os_all, "os > all")]
   fn tempdir<R: Runtime>(_context: InvokeContext<R>) -> super::Result<PathBuf> {
     Ok(std::env::temp_dir())
   }
 }
 
+#[cfg(not(os_all))]
+impl Cmd {
+  fn platform<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
+    Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
+  }
+
+  fn version<R: Runtime>(_context: InvokeContext<R>) -> super::Result<String> {
+    Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
+  }
+
+  fn os_type<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
+    Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
+  }
+
+  fn arch<R: Runtime>(_context: InvokeContext<R>) -> super::Result<&'static str> {
+    Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
+  }
+
+  fn tempdir<R: Runtime>(_context: InvokeContext<R>) -> super::Result<PathBuf> {
+    Err(crate::Error::ApiNotAllowlisted("os > all".into()).into_anyhow())
+  }
+}
+
 #[cfg(os_all)]
 fn os_type() -> &'static str {
   #[cfg(target_os = "linux")]
@@ -55,6 +77,7 @@ fn os_type() -> &'static str {
   #[cfg(target_os = "macos")]
   return "Darwin";
 }
+
 #[cfg(os_all)]
 fn os_platform() -> &'static str {
   match std::env::consts::OS {
@@ -66,23 +89,23 @@ fn os_platform() -> &'static str {
 
 #[cfg(test)]
 mod tests {
-  #[tauri_macros::module_command_test(os_all, "os > all")]
+  #[tauri_macros::module_command_test(os_all, "os > all", runtime)]
   #[quickcheck_macros::quickcheck]
   fn platform() {}
 
-  #[tauri_macros::module_command_test(os_all, "os > all")]
+  #[tauri_macros::module_command_test(os_all, "os > all", runtime)]
   #[quickcheck_macros::quickcheck]
   fn version() {}
 
-  #[tauri_macros::module_command_test(os_all, "os > all")]
+  #[tauri_macros::module_command_test(os_all, "os > all", runtime)]
   #[quickcheck_macros::quickcheck]
   fn os_type() {}
 
-  #[tauri_macros::module_command_test(os_all, "os > all")]
+  #[tauri_macros::module_command_test(os_all, "os > all", runtime)]
   #[quickcheck_macros::quickcheck]
   fn arch() {}
 
-  #[tauri_macros::module_command_test(os_all, "os > all")]
+  #[tauri_macros::module_command_test(os_all, "os > all", runtime)]
   #[quickcheck_macros::quickcheck]
   fn tempdir() {}
 }

+ 27 - 31
core/tauri/src/endpoints/path.rs

@@ -2,6 +2,8 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use crate::{api::path::BaseDirectory, Runtime};
 #[cfg(path_all)]
 use crate::{Env, Manager};
@@ -11,42 +13,36 @@ use std::path::{Component, Path, MAIN_SEPARATOR};
 
 use super::InvokeContext;
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
+  #[cmd(path_all, "path > all")]
   ResolvePath {
     path: String,
     directory: Option<BaseDirectory>,
   },
-  Resolve {
-    paths: Vec<String>,
-  },
-  Normalize {
-    path: String,
-  },
-  Join {
-    paths: Vec<String>,
-  },
-  Dirname {
-    path: String,
-  },
-  Extname {
-    path: String,
-  },
-  Basename {
-    path: String,
-    ext: Option<String>,
-  },
-  IsAbsolute {
-    path: String,
-  },
+  #[cmd(path_all, "path > all")]
+  Resolve { paths: Vec<String> },
+  #[cmd(path_all, "path > all")]
+  Normalize { path: String },
+  #[cmd(path_all, "path > all")]
+  Join { paths: Vec<String> },
+  #[cmd(path_all, "path > all")]
+  Dirname { path: String },
+  #[cmd(path_all, "path > all")]
+  Extname { path: String },
+  #[cmd(path_all, "path > all")]
+  Basename { path: String, ext: Option<String> },
+  #[cmd(path_all, "path > all")]
+  IsAbsolute { path: String },
 }
 
 impl Cmd {
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn resolve_path<R: Runtime>(
     context: InvokeContext<R>,
     path: String,
@@ -62,7 +58,7 @@ impl Cmd {
     .map_err(Into::into)
   }
 
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn resolve<R: Runtime>(_context: InvokeContext<R>, paths: Vec<String>) -> super::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.
@@ -77,7 +73,7 @@ impl Cmd {
     Ok(normalize_path(&path))
   }
 
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn normalize<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<String> {
     let mut p = normalize_path_no_absolute(Path::new(&path))
       .to_string_lossy()
@@ -101,7 +97,7 @@ impl Cmd {
     )
   }
 
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn join<R: Runtime>(_context: InvokeContext<R>, mut paths: Vec<String>) -> super::Result<String> {
     let path = PathBuf::from(
       paths
@@ -125,7 +121,7 @@ impl Cmd {
     Ok(if p.is_empty() { ".".into() } else { p })
   }
 
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn dirname<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<PathBuf> {
     match Path::new(&path).parent() {
       Some(p) => Ok(p.to_path_buf()),
@@ -135,7 +131,7 @@ impl Cmd {
     }
   }
 
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn extname<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<String> {
     match Path::new(&path)
       .extension()
@@ -148,7 +144,7 @@ impl Cmd {
     }
   }
 
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn basename<R: Runtime>(
     _context: InvokeContext<R>,
     path: String,
@@ -169,7 +165,7 @@ impl Cmd {
     }
   }
 
-  #[module_command_handler(path_all, "path > all")]
+  #[module_command_handler(path_all)]
   fn is_absolute<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<bool> {
     Ok(Path::new(&path).is_absolute())
   }

+ 13 - 4
core/tauri/src/endpoints/process.rs

@@ -2,32 +2,41 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 #[cfg(process_relaunch)]
 use crate::Manager;
 use crate::Runtime;
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// Relaunch application
   Relaunch,
   /// Close application with provided exit_code
+  #[cmd(process_exit, "process > exit")]
   #[serde(rename_all = "camelCase")]
   Exit { exit_code: i32 },
 }
 
 impl Cmd {
-  #[module_command_handler(process_relaunch, "process > relaunch")]
+  #[module_command_handler(process_relaunch)]
   fn relaunch<R: Runtime>(context: InvokeContext<R>) -> super::Result<()> {
     context.window.app_handle().restart();
     Ok(())
   }
 
-  #[module_command_handler(process_exit, "process > exit")]
+  #[cfg(not(process_relaunch))]
+  fn relaunch<R: Runtime>(_: InvokeContext<R>) -> super::Result<()> {
+    Err(crate::Error::ApiNotAllowlisted("process > relaunch".into()).into_anyhow())
+  }
+
+  #[module_command_handler(process_exit)]
   fn exit<R: Runtime>(_context: InvokeContext<R>, exit_code: i32) -> super::Result<()> {
     // would be great if we can have a handler inside tauri
     // who close all window and emit an event that user can catch
@@ -38,7 +47,7 @@ impl Cmd {
 
 #[cfg(test)]
 mod tests {
-  #[tauri_macros::module_command_test(process_relaunch, "process > relaunch")]
+  #[tauri_macros::module_command_test(process_relaunch, "process > relaunch", runtime)]
   #[quickcheck_macros::quickcheck]
   fn relaunch() {}
 

+ 15 - 29
core/tauri/src/endpoints/shell.rs

@@ -2,12 +2,14 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::InvokeContext;
 use crate::{api::ipc::CallbackFn, Runtime};
 #[cfg(shell_scope)]
 use crate::{Manager, Scopes};
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 #[cfg(shell_scope)]
 use crate::ExecuteArgs;
@@ -55,10 +57,12 @@ pub struct CommandOptions {
 }
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
   /// The execute script API.
+  #[cmd(shell_script, "shell > execute or shell > sidecar")]
   #[serde(rename_all = "camelCase")]
   Execute {
     program: String,
@@ -67,20 +71,16 @@ pub enum Cmd {
     #[serde(default)]
     options: CommandOptions,
   },
-  StdinWrite {
-    pid: ChildId,
-    buffer: Buffer,
-  },
-  KillChild {
-    pid: ChildId,
-  },
-  Open {
-    path: String,
-    with: Option<String>,
-  },
+  #[cmd(shell_script, "shell > execute or shell > sidecar")]
+  StdinWrite { pid: ChildId, buffer: Buffer },
+  #[cmd(shell_script, "shell > execute or shell > sidecar")]
+  KillChild { pid: ChildId },
+  #[cmd(shell_open, "shell > open")]
+  Open { path: String, with: Option<String> },
 }
 
 impl Cmd {
+  #[module_command_handler(shell_script)]
   #[allow(unused_variables)]
   fn execute<R: Runtime>(
     context: InvokeContext<R>,
@@ -169,7 +169,7 @@ impl Cmd {
     }
   }
 
-  #[cfg(any(shell_execute, shell_sidecar))]
+  #[module_command_handler(shell_script)]
   fn stdin_write<R: Runtime>(
     _context: InvokeContext<R>,
     pid: ChildId,
@@ -184,16 +184,7 @@ impl Cmd {
     Ok(())
   }
 
-  #[cfg(not(any(shell_execute, shell_sidecar)))]
-  fn stdin_write<R: Runtime>(
-    _context: InvokeContext<R>,
-    _pid: ChildId,
-    _buffer: Buffer,
-  ) -> super::Result<()> {
-    Err(crate::Error::ApiNotAllowlisted("shell > execute or shell > sidecar".into()).into_anyhow())
-  }
-
-  #[cfg(any(shell_execute, shell_sidecar))]
+  #[module_command_handler(shell_script)]
   fn kill_child<R: Runtime>(_context: InvokeContext<R>, pid: ChildId) -> super::Result<()> {
     if let Some(child) = command_childs().lock().unwrap().remove(&pid) {
       child.kill()?;
@@ -201,15 +192,10 @@ impl Cmd {
     Ok(())
   }
 
-  #[cfg(not(any(shell_execute, shell_sidecar)))]
-  fn kill_child<R: Runtime>(_context: InvokeContext<R>, _pid: ChildId) -> super::Result<()> {
-    Err(crate::Error::ApiNotAllowlisted("shell > execute or shell > sidecar".into()).into_anyhow())
-  }
-
   /// Open a (url) path with a default or specific browser opening program.
   ///
   /// See [`crate::api::shell::open`] for how it handles security-related measures.
-  #[module_command_handler(shell_open, "shell > open")]
+  #[module_command_handler(shell_open)]
   fn open<R: Runtime>(
     context: InvokeContext<R>,
     path: String,

+ 7 - 5
core/tauri/src/endpoints/window.rs

@@ -2,6 +2,8 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+#![allow(unused_imports)]
+
 use super::{InvokeContext, InvokeResponse};
 #[cfg(window_create)]
 use crate::runtime::{webview::WindowBuilder, Dispatch};
@@ -14,7 +16,7 @@ use crate::{
   CursorIcon, Icon, Manager, Runtime,
 };
 use serde::Deserialize;
-use tauri_macros::{module_command_handler, CommandModule};
+use tauri_macros::{command_enum, module_command_handler, CommandModule};
 
 #[derive(Deserialize)]
 #[serde(untagged)]
@@ -168,13 +170,13 @@ impl WindowManagerCmd {
 }
 
 /// The API descriptor.
+#[command_enum]
 #[derive(Deserialize, CommandModule)]
 #[cmd(async)]
 #[serde(tag = "cmd", content = "data", rename_all = "camelCase")]
 pub enum Cmd {
-  CreateWebview {
-    options: Box<WindowConfig>,
-  },
+  #[cmd(window_create, "window > create")]
+  CreateWebview { options: Box<WindowConfig> },
   Manage {
     label: Option<String>,
     cmd: WindowManagerCmd,
@@ -182,7 +184,7 @@ pub enum Cmd {
 }
 
 impl Cmd {
-  #[module_command_handler(window_create, "window > create")]
+  #[module_command_handler(window_create)]
   async fn create_webview<R: Runtime>(
     context: InvokeContext<R>,
     options: Box<WindowConfig>,

+ 2 - 4
core/tauri/src/error.rs

@@ -71,11 +71,8 @@ pub enum Error {
   /// Client with specified ID not found.
   #[error("http client dropped or not initialized")]
   HttpClientNotInitialized,
-  /// API not enabled by Tauri.
-  #[error("{0}")]
-  ApiNotEnabled(String),
   /// API not whitelisted on tauri.conf.json
-  #[error("'{0}' not on the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")]
+  #[error("'{0}' not in the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)")]
   ApiNotAllowlisted(String),
   /// Invalid args when running a command.
   #[error("invalid args `{1}` for command `{0}`: {2}")]
@@ -138,6 +135,7 @@ pub(crate) fn into_anyhow<T: std::fmt::Display>(err: T) -> anyhow::Error {
 }
 
 impl Error {
+  #[allow(dead_code)]
   pub(crate) fn into_anyhow(self) -> anyhow::Error {
     anyhow::anyhow!(self.to_string())
   }

+ 4 - 1
tooling/api/src/clipboard.ts

@@ -35,7 +35,10 @@ async function readText(): Promise<string | null> {
   return invokeTauriCommand({
     __tauriModule: 'Clipboard',
     message: {
-      cmd: 'readText'
+      cmd: 'readText',
+      // if data is not set, `serde` will ignore the custom deserializer
+      // that is set when the API is not allowlisted
+      data: null
     }
   })
 }

部分文件因为文件数量过多而无法显示