123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- use proc_macro2::TokenStream;
- use quote::{format_ident, quote};
- use syn::{
- parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Path,
- ReturnType, Token, Type,
- };
- pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream {
- // Check if "with_manager" attr was passed to macro
- let uses_manager = attrs.iter().any(|a| {
- if let NestedMeta::Meta(Meta::Path(path)) = a {
- path
- .get_ident()
- .map(|i| *i == "with_manager")
- .unwrap_or(false)
- } else {
- false
- }
- });
- let fn_name = function.sig.ident.clone();
- let fn_name_str = fn_name.to_string();
- let fn_wrapper = format_ident!("{}_wrapper", fn_name);
- let returns_result = match function.sig.output {
- ReturnType::Type(_, ref ty) => match &**ty {
- Type::Path(type_path) => {
- type_path
- .path
- .segments
- .first()
- .map(|seg| seg.ident.to_string())
- == Some("Result".to_string())
- }
- _ => false,
- },
- ReturnType::Default => false,
- };
- // Split function args into names and types
- let (mut names, mut types): (Vec<Ident>, Vec<Path>) = function
- .sig
- .inputs
- .iter()
- .map(|param| {
- let mut arg_name = None;
- let mut arg_type = None;
- if let FnArg::Typed(arg) = param {
- if let Pat::Ident(ident) = arg.pat.as_ref() {
- arg_name = Some(ident.ident.clone());
- }
- if let Type::Path(path) = arg.ty.as_ref() {
- arg_type = Some(path.path.clone());
- }
- }
- (
- arg_name.clone().unwrap(),
- arg_type.unwrap_or_else(|| panic!("Invalid type for arg \"{}\"", arg_name.unwrap())),
- )
- })
- .unzip();
- // If function doesn't take the webview manager, wrapper just takes webview manager generically and ignores it
- // Otherwise the wrapper uses the specific type from the original function declaration
- let mut manager_arg_type = quote!(::tauri::WebviewManager<A>);
- let mut application_ext_generic = quote!(<A: ::tauri::ApplicationExt>);
- let manager_arg_maybe = match types.first() {
- Some(first_type) if uses_manager => {
- // Give wrapper specific type
- manager_arg_type = quote!(#first_type);
- // Generic is no longer needed
- application_ext_generic = quote!();
- // Remove webview manager arg from list so it isn't expected as arg from JS
- types.drain(0..1);
- names.drain(0..1);
- // Tell wrapper to pass webview manager to original function
- quote!(_manager,)
- }
- // Tell wrapper not to pass webview manager to original function
- _ => quote!(),
- };
- let await_maybe = if function.sig.asyncness.is_some() {
- quote!(.await)
- } else {
- quote!()
- };
- // if the command handler returns a Result,
- // we just map the values to the ones expected by Tauri
- // otherwise we wrap it with an `Ok()`, converting the return value to tauri::InvokeResponse
- // note that all types must implement `serde::Serialize`.
- let return_value = if returns_result {
- quote! {
- match #fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe {
- Ok(value) => ::core::result::Result::Ok(value.into()),
- Err(e) => ::core::result::Result::Err(tauri::Error::Command(::serde_json::to_value(e)?)),
- }
- }
- } else {
- quote! { ::core::result::Result::Ok(#fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe.into()) }
- };
- quote! {
- #function
- pub async fn #fn_wrapper #application_ext_generic(_manager: #manager_arg_type, arg: ::serde_json::Value) -> ::tauri::Result<::tauri::InvokeResponse> {
- #[derive(::serde::Deserialize)]
- #[serde(rename_all = "camelCase")]
- struct ParsedArgs {
- #(#names: #types),*
- }
- let parsed_args: ParsedArgs = ::serde_json::from_value(arg).map_err(|e| ::tauri::Error::InvalidArgs(#fn_name_str, e))?;
- #return_value
- }
- }
- }
- pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
- // Get paths of functions passed to macro
- let paths = <Punctuated<Path, Token![,]>>::parse_terminated
- .parse(item)
- .expect("generate_handler!: Failed to parse list of command functions");
- // Get names of functions, used for match statement
- let fn_names = paths
- .iter()
- .map(|p| p.segments.last().unwrap().ident.clone());
- // Get paths to wrapper functions
- let fn_wrappers = paths.iter().map(|func| {
- let mut func = func.clone();
- let mut last_segment = func.segments.last_mut().unwrap();
- last_segment.ident = format_ident!("{}_wrapper", last_segment.ident);
- func
- });
- quote! {
- |webview_manager, arg| async move {
- let dispatch: ::std::result::Result<::tauri::DispatchInstructions, ::serde_json::Error> =
- ::serde_json::from_str(&arg);
- match dispatch {
- Err(e) => Err(e.into()),
- Ok(dispatch) => {
- match dispatch.cmd.as_str() {
- #(stringify!(#fn_names) => #fn_wrappers(webview_manager, dispatch.args).await,)*
- _ => Err(tauri::Error::UnknownApi(None)),
- }
- }
- }
- }
- }
- }
|