snapshot of https://github.com/tauri-apps/tauri.git (dev, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
This commit is contained in:
524
crates/tauri-macros/src/command/wrapper.rs
Normal file
524
crates/tauri-macros/src/command/wrapper.rs
Normal file
@@ -0,0 +1,524 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::env::var;
|
||||
|
||||
use heck::{ToLowerCamelCase, ToSnakeCase};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{
|
||||
Expr, ExprLit, FnArg, ItemFn, Lit, Meta, Pat, Token, Visibility,
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
};
|
||||
use tauri_utils::acl::REMOVE_UNUSED_COMMANDS_ENV_VAR;
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum WrapperAttributeKind {
|
||||
Meta(Meta),
|
||||
Async,
|
||||
}
|
||||
|
||||
impl Parse for WrapperAttributeKind {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
match input.parse::<Meta>() {
|
||||
Ok(m) => Ok(Self::Meta(m)),
|
||||
Err(e) => match input.parse::<Token![async]>() {
|
||||
Ok(_) => Ok(Self::Async),
|
||||
Err(_) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WrapperAttributes {
|
||||
root: TokenStream2,
|
||||
execution_context: ExecutionContext,
|
||||
argument_case: ArgumentCase,
|
||||
rename: RenamePolicy,
|
||||
}
|
||||
|
||||
impl Parse for WrapperAttributes {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut wrapper_attributes = WrapperAttributes {
|
||||
root: quote!(::tauri),
|
||||
execution_context: ExecutionContext::Blocking,
|
||||
argument_case: ArgumentCase::Camel,
|
||||
rename: RenamePolicy::Keep,
|
||||
};
|
||||
|
||||
let attrs = Punctuated::<WrapperAttributeKind, Token![,]>::parse_terminated(input)?;
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
WrapperAttributeKind::Meta(Meta::List(_)) => {
|
||||
return Err(syn::Error::new(input.span(), "unexpected list input"));
|
||||
}
|
||||
WrapperAttributeKind::Meta(Meta::NameValue(v)) => {
|
||||
if v.path.is_ident("rename_all") {
|
||||
if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s),
|
||||
attrs: _,
|
||||
}) = v.value
|
||||
{
|
||||
wrapper_attributes.argument_case = match s.value().as_str() {
|
||||
"snake_case" => ArgumentCase::Snake,
|
||||
"camelCase" => ArgumentCase::Camel,
|
||||
_ => {
|
||||
return Err(syn::Error::new(
|
||||
s.span(),
|
||||
"expected \"camelCase\" or \"snake_case\"",
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if v.path.is_ident("rename") {
|
||||
if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s), ..
|
||||
}) = v.value
|
||||
{
|
||||
let lit = s.value();
|
||||
wrapper_attributes.rename = RenamePolicy::Rename(quote!(#lit));
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
v.span(),
|
||||
"expected string literal for rename",
|
||||
));
|
||||
}
|
||||
} else if v.path.is_ident("root") {
|
||||
if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s),
|
||||
attrs: _,
|
||||
}) = v.value
|
||||
{
|
||||
let lit = s.value();
|
||||
|
||||
wrapper_attributes.root = if lit == "crate" {
|
||||
quote!($crate)
|
||||
} else {
|
||||
let ident = Ident::new(&lit, Span::call_site());
|
||||
quote!(#ident)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
WrapperAttributeKind::Meta(Meta::Path(_)) => {
|
||||
return Err(syn::Error::new(
|
||||
input.span(),
|
||||
"unexpected input, expected one of `rename_all`, `rename`, `root`, `async`",
|
||||
));
|
||||
}
|
||||
WrapperAttributeKind::Async => {
|
||||
wrapper_attributes.execution_context = ExecutionContext::Async;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(wrapper_attributes)
|
||||
}
|
||||
}
|
||||
|
||||
/// The execution context of the command.
|
||||
enum ExecutionContext {
|
||||
Async,
|
||||
Blocking,
|
||||
}
|
||||
|
||||
/// The case of each argument name.
|
||||
#[derive(Copy, Clone)]
|
||||
enum ArgumentCase {
|
||||
Snake,
|
||||
Camel,
|
||||
}
|
||||
|
||||
/// The rename policy for the command.
|
||||
enum RenamePolicy {
|
||||
Keep,
|
||||
Rename(TokenStream2),
|
||||
}
|
||||
|
||||
/// The bindings we attach to `tauri::Invoke`.
|
||||
struct Invoke {
|
||||
message: Ident,
|
||||
resolver: Ident,
|
||||
acl: Ident,
|
||||
}
|
||||
|
||||
/// Create a new [`Wrapper`] from the function and the generated code parsed from the function.
|
||||
pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let mut attrs = parse_macro_input!(attributes as WrapperAttributes);
|
||||
let function = parse_macro_input!(item as ItemFn);
|
||||
let wrapper = super::format_command_wrapper(&function.sig.ident);
|
||||
let visibility = &function.vis;
|
||||
|
||||
if function.sig.asyncness.is_some() {
|
||||
attrs.execution_context = ExecutionContext::Async;
|
||||
}
|
||||
|
||||
// macros used with `pub use my_macro;` need to be exported with `#[macro_export]`.
|
||||
let maybe_macro_export = match &function.vis {
|
||||
Visibility::Public(_) | Visibility::Restricted(_) => {
|
||||
quote!(#[macro_export])
|
||||
}
|
||||
_ => TokenStream2::default(),
|
||||
};
|
||||
|
||||
let invoke = Invoke {
|
||||
message: format_ident!("__tauri_message__"),
|
||||
resolver: format_ident!("__tauri_resolver__"),
|
||||
acl: format_ident!("__tauri_acl__"),
|
||||
};
|
||||
|
||||
// Tauri currently doesn't support async commands that take a reference as input and don't return
|
||||
// a result. See: https://github.com/tauri-apps/tauri/issues/2533
|
||||
//
|
||||
// For now, we provide an informative error message to the user in that case. Once #2533 is
|
||||
// resolved, this check can be removed.
|
||||
let mut async_command_check = TokenStream2::new();
|
||||
if function.sig.asyncness.is_some() {
|
||||
// This check won't catch all possible problems but it should catch the most common ones.
|
||||
let mut ref_argument_span = None;
|
||||
|
||||
for arg in &function.sig.inputs {
|
||||
if let syn::FnArg::Typed(pat) = arg {
|
||||
match &*pat.ty {
|
||||
syn::Type::Reference(_) => {
|
||||
ref_argument_span = Some(pat.span());
|
||||
}
|
||||
syn::Type::Path(path) => {
|
||||
// Check if the type contains a lifetime argument
|
||||
let last = path.path.segments.last().unwrap();
|
||||
if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
|
||||
if args
|
||||
.args
|
||||
.iter()
|
||||
.any(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
|
||||
{
|
||||
ref_argument_span = Some(pat.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(span) = ref_argument_span {
|
||||
if let syn::ReturnType::Type(_, return_type) = &function.sig.output {
|
||||
// To check if the return type is `Result` we require it to check a trait that is
|
||||
// only implemented by `Result`. That way we don't exclude renamed result types
|
||||
// which we wouldn't otherwise be able to detect purely from the token stream.
|
||||
// The "error message" displayed to the user is simply the trait name.
|
||||
let diagnostic = quote!(#[diagnostic::on_unimplemented(message = "async commands that contain references as inputs must return a `Result`")]);
|
||||
|
||||
async_command_check = quote_spanned! {return_type.span() =>
|
||||
#[allow(unreachable_code, clippy::diverging_sub_expression, clippy::used_underscore_binding)]
|
||||
const _: () = if false {
|
||||
#diagnostic
|
||||
trait AsyncCommandMustReturnResult {}
|
||||
impl<A, B> AsyncCommandMustReturnResult for ::std::result::Result<A, B> {}
|
||||
let _check: #return_type = unreachable!();
|
||||
let _: &dyn AsyncCommandMustReturnResult = &_check;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
return quote_spanned! {
|
||||
span => compile_error!("async commands that contain references as inputs must return a `Result`");
|
||||
}.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let plugin_name = var("CARGO_PKG_NAME")
|
||||
.expect("missing `CARGO_PKG_NAME` environment variable")
|
||||
.strip_prefix("tauri-plugin-")
|
||||
.map(|name| quote!(::core::option::Option::Some(#name)))
|
||||
.unwrap_or_else(|| quote!(::core::option::Option::None));
|
||||
|
||||
let body = match attrs.execution_context {
|
||||
ExecutionContext::Async => body_async(&plugin_name, &function, &invoke, &attrs)
|
||||
.unwrap_or_else(syn::Error::into_compile_error),
|
||||
ExecutionContext::Blocking => body_blocking(&plugin_name, &function, &invoke, &attrs)
|
||||
.unwrap_or_else(syn::Error::into_compile_error),
|
||||
};
|
||||
|
||||
let Invoke {
|
||||
message,
|
||||
resolver,
|
||||
acl,
|
||||
} = invoke;
|
||||
|
||||
let root = attrs.root;
|
||||
|
||||
let kind = match attrs.execution_context {
|
||||
ExecutionContext::Async if function.sig.asyncness.is_none() => "sync_threadpool",
|
||||
ExecutionContext::Async => "async",
|
||||
ExecutionContext::Blocking => "sync",
|
||||
};
|
||||
|
||||
let loc = function.span().start();
|
||||
let line = loc.line;
|
||||
let col = loc.column;
|
||||
|
||||
let maybe_span = if cfg!(feature = "tracing") {
|
||||
quote!({
|
||||
let _span = tracing::debug_span!(
|
||||
"ipc::request::handler",
|
||||
cmd = #message.command(),
|
||||
kind = #kind,
|
||||
loc.line = #line,
|
||||
loc.col = #col,
|
||||
is_internal = false,
|
||||
)
|
||||
.entered();
|
||||
})
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
// Allow this to be unused when we're building with `build > removeUnusedCommands` for dead code elimination
|
||||
let maybe_allow_unused = if var(REMOVE_UNUSED_COMMANDS_ENV_VAR).is_ok() {
|
||||
quote!(#[allow(unused)])
|
||||
} else {
|
||||
TokenStream2::default()
|
||||
};
|
||||
|
||||
// Always define a hidden macro that returns the externally invoked command name.
|
||||
// This lets the handler match on the renamed string while the original function
|
||||
// identifier remains usable in `generate_handler![original_fn_name]`.
|
||||
let command_name_macro_ident = format_ident!("__tauri_command_name_{}", function.sig.ident);
|
||||
let command_name_value = if let RenamePolicy::Rename(ref rename) = attrs.rename {
|
||||
quote!(#rename)
|
||||
} else {
|
||||
let ident = &function.sig.ident;
|
||||
quote!(stringify!(#ident))
|
||||
};
|
||||
|
||||
// Rely on rust 2018 edition to allow importing a macro from a path.
|
||||
quote!(
|
||||
#async_command_check
|
||||
|
||||
#maybe_allow_unused
|
||||
#function
|
||||
|
||||
// Command name macro used by the handler for pattern matching.
|
||||
// This macro returns the command name string literal (renamed or original).
|
||||
#maybe_allow_unused
|
||||
#maybe_macro_export
|
||||
#[doc(hidden)]
|
||||
macro_rules! #command_name_macro_ident {
|
||||
() => {
|
||||
#command_name_value
|
||||
};
|
||||
}
|
||||
|
||||
#maybe_allow_unused
|
||||
#maybe_macro_export
|
||||
#[doc(hidden)]
|
||||
macro_rules! #wrapper {
|
||||
// double braces because the item is expected to be a block expression
|
||||
($path:path, $invoke:ident) => {
|
||||
// The IIFE here is for preventing stack overflow on Windows,
|
||||
// see https://github.com/tauri-apps/tauri/issues/12488
|
||||
{
|
||||
move || {
|
||||
#[allow(unused_imports)]
|
||||
use #root::ipc::private::*;
|
||||
// prevent warnings when the body is a `compile_error!` or if the command has no arguments
|
||||
#[allow(unused_variables)]
|
||||
let #root::ipc::Invoke { message: #message, resolver: #resolver, acl: #acl } = $invoke;
|
||||
|
||||
#maybe_span
|
||||
|
||||
#body
|
||||
}
|
||||
}()
|
||||
};
|
||||
}
|
||||
|
||||
// allow the macro to be resolved with the same path as the command function
|
||||
#[allow(unused_imports)]
|
||||
#visibility use {#wrapper, #command_name_macro_ident};
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Generates an asynchronous command response from the arguments and return value of a function.
|
||||
///
|
||||
/// See the [`tauri::command`] module for all the items and traits that make this possible.
|
||||
///
|
||||
/// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
|
||||
fn body_async(
|
||||
plugin_name: &TokenStream2,
|
||||
function: &ItemFn,
|
||||
invoke: &Invoke,
|
||||
attributes: &WrapperAttributes,
|
||||
) -> syn::Result<TokenStream2> {
|
||||
let Invoke {
|
||||
message,
|
||||
resolver,
|
||||
acl,
|
||||
} = invoke;
|
||||
parse_args(plugin_name, function, message, acl, attributes).map(|args| {
|
||||
#[cfg(feature = "tracing")]
|
||||
quote! {
|
||||
use tracing::Instrument;
|
||||
|
||||
let span = tracing::debug_span!("ipc::request::run");
|
||||
#resolver.respond_async_serialized(async move {
|
||||
let result = $path(#(#args?),*);
|
||||
let kind = (&result).async_kind();
|
||||
kind.future(result).await
|
||||
}
|
||||
.instrument(span));
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
quote! {
|
||||
#resolver.respond_async_serialized(async move {
|
||||
let result = $path(#(#args?),*);
|
||||
let kind = (&result).async_kind();
|
||||
kind.future(result).await
|
||||
});
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates a blocking command response from the arguments and return value of a function.
|
||||
///
|
||||
/// See the [`tauri::command`] module for all the items and traits that make this possible.
|
||||
///
|
||||
/// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
|
||||
fn body_blocking(
|
||||
plugin_name: &TokenStream2,
|
||||
function: &ItemFn,
|
||||
invoke: &Invoke,
|
||||
attributes: &WrapperAttributes,
|
||||
) -> syn::Result<TokenStream2> {
|
||||
let Invoke {
|
||||
message,
|
||||
resolver,
|
||||
acl,
|
||||
} = invoke;
|
||||
let args = parse_args(plugin_name, function, message, acl, attributes)?;
|
||||
|
||||
// the body of a `match` to early return any argument that wasn't successful in parsing.
|
||||
let match_body = quote!({
|
||||
Ok(arg) => arg,
|
||||
Err(err) => { #resolver.invoke_error(err); return true },
|
||||
});
|
||||
|
||||
let maybe_span = if cfg!(feature = "tracing") {
|
||||
quote!(let _span = tracing::debug_span!("ipc::request::run").entered();)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#maybe_span
|
||||
let result = $path(#(match #args #match_body),*);
|
||||
let kind = (&result).blocking_kind();
|
||||
kind.block(result, #resolver);
|
||||
return true;
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse all arguments for the command wrapper to use from the signature of the command function.
|
||||
fn parse_args(
|
||||
plugin_name: &TokenStream2,
|
||||
function: &ItemFn,
|
||||
message: &Ident,
|
||||
acl: &Ident,
|
||||
attributes: &WrapperAttributes,
|
||||
) -> syn::Result<Vec<TokenStream2>> {
|
||||
function
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
parse_arg(
|
||||
plugin_name,
|
||||
&function.sig.ident,
|
||||
arg,
|
||||
message,
|
||||
acl,
|
||||
attributes,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Transform a [`FnArg`] into a command argument.
|
||||
fn parse_arg(
|
||||
plugin_name: &TokenStream2,
|
||||
command: &Ident,
|
||||
arg: &FnArg,
|
||||
message: &Ident,
|
||||
acl: &Ident,
|
||||
attributes: &WrapperAttributes,
|
||||
) -> syn::Result<TokenStream2> {
|
||||
// we have no use for self arguments
|
||||
let mut arg = match arg {
|
||||
FnArg::Typed(arg) => arg.pat.as_ref().clone(),
|
||||
FnArg::Receiver(arg) => {
|
||||
return Err(syn::Error::new(
|
||||
arg.span(),
|
||||
"unable to use self as a command function parameter",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// we only support patterns that allow us to extract some sort of keyed identifier
|
||||
let mut key = match &mut arg {
|
||||
Pat::Ident(arg) => arg.ident.unraw().to_string(),
|
||||
Pat::Wild(_) => "".into(), // we always convert to camelCase, so "_" will end up empty anyways
|
||||
Pat::Struct(s) => super::path_to_command(&mut s.path).ident.to_string(),
|
||||
Pat::TupleStruct(s) => super::path_to_command(&mut s.path).ident.to_string(),
|
||||
err => {
|
||||
return Err(syn::Error::new(
|
||||
err.span(),
|
||||
"only named, wildcard, struct, and tuple struct arguments allowed",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// also catch self arguments that use FnArg::Typed syntax
|
||||
if key == "self" {
|
||||
return Err(syn::Error::new(
|
||||
key.span(),
|
||||
"unable to use self as a command function parameter",
|
||||
));
|
||||
}
|
||||
|
||||
match attributes.argument_case {
|
||||
ArgumentCase::Camel => {
|
||||
key = key.to_lower_camel_case();
|
||||
}
|
||||
ArgumentCase::Snake => {
|
||||
key = key.to_snake_case();
|
||||
}
|
||||
}
|
||||
|
||||
let root = &attributes.root;
|
||||
let command_name = if let RenamePolicy::Rename(r) = &attributes.rename {
|
||||
quote!(stringify!(#r))
|
||||
} else {
|
||||
quote!(stringify!(#command))
|
||||
};
|
||||
|
||||
Ok(quote!(#root::ipc::CommandArg::from_command(
|
||||
#root::ipc::CommandItem {
|
||||
plugin: #plugin_name,
|
||||
name: #command_name,
|
||||
key: #key,
|
||||
message: &#message,
|
||||
acl: &#acl,
|
||||
}
|
||||
)))
|
||||
}
|
||||
Reference in New Issue
Block a user