|
| 1 | +use proc_macro2::TokenStream; |
| 2 | +use quote::{format_ident, quote}; |
| 3 | +use syn::{ |
| 4 | + parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Path, |
| 5 | + ReturnType, Token, Type, |
| 6 | +}; |
| 7 | + |
| 8 | +pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream { |
| 9 | + // Check if "with_manager" attr was passed to macro |
| 10 | + let uses_manager = attrs.iter().any(|a| { |
| 11 | + if let NestedMeta::Meta(Meta::Path(path)) = a { |
| 12 | + path |
| 13 | + .get_ident() |
| 14 | + .map(|i| *i == "with_manager") |
| 15 | + .unwrap_or(false) |
| 16 | + } else { |
| 17 | + false |
| 18 | + } |
| 19 | + }); |
| 20 | + |
| 21 | + let fn_name = function.sig.ident.clone(); |
| 22 | + let fn_name_str = fn_name.to_string(); |
| 23 | + let fn_wrapper = format_ident!("{}_wrapper", fn_name); |
| 24 | + let returns_result = match function.sig.output { |
| 25 | + ReturnType::Type(_, ref ty) => match &**ty { |
| 26 | + Type::Path(type_path) => { |
| 27 | + type_path |
| 28 | + .path |
| 29 | + .segments |
| 30 | + .first() |
| 31 | + .map(|seg| seg.ident.to_string()) |
| 32 | + == Some("Result".to_string()) |
| 33 | + } |
| 34 | + _ => false, |
| 35 | + }, |
| 36 | + ReturnType::Default => false, |
| 37 | + }; |
| 38 | + |
| 39 | + // Split function args into names and types |
| 40 | + let (mut names, mut types): (Vec<Ident>, Vec<Path>) = function |
| 41 | + .sig |
| 42 | + .inputs |
| 43 | + .iter() |
| 44 | + .map(|param| { |
| 45 | + let mut arg_name = None; |
| 46 | + let mut arg_type = None; |
| 47 | + if let FnArg::Typed(arg) = param { |
| 48 | + if let Pat::Ident(ident) = arg.pat.as_ref() { |
| 49 | + arg_name = Some(ident.ident.clone()); |
| 50 | + } |
| 51 | + if let Type::Path(path) = arg.ty.as_ref() { |
| 52 | + arg_type = Some(path.path.clone()); |
| 53 | + } |
| 54 | + } |
| 55 | + ( |
| 56 | + arg_name.clone().unwrap(), |
| 57 | + arg_type.unwrap_or_else(|| panic!("Invalid type for arg \"{}\"", arg_name.unwrap())), |
| 58 | + ) |
| 59 | + }) |
| 60 | + .unzip(); |
| 61 | + |
| 62 | + // If function doesn't take the webview manager, wrapper just takes webview manager generically and ignores it |
| 63 | + // Otherwise the wrapper uses the specific type from the original function declaration |
| 64 | + let mut manager_arg_type = quote!(::tauri::WebviewManager<A>); |
| 65 | + let mut application_ext_generic = quote!(<A: ::tauri::ApplicationExt>); |
| 66 | + let manager_arg_maybe = match types.first() { |
| 67 | + Some(first_type) if uses_manager => { |
| 68 | + // Give wrapper specific type |
| 69 | + manager_arg_type = quote!(#first_type); |
| 70 | + // Generic is no longer needed |
| 71 | + application_ext_generic = quote!(); |
| 72 | + // Remove webview manager arg from list so it isn't expected as arg from JS |
| 73 | + types.drain(0..1); |
| 74 | + names.drain(0..1); |
| 75 | + // Tell wrapper to pass webview manager to original function |
| 76 | + quote!(_manager,) |
| 77 | + } |
| 78 | + // Tell wrapper not to pass webview manager to original function |
| 79 | + _ => quote!(), |
| 80 | + }; |
| 81 | + let await_maybe = if function.sig.asyncness.is_some() { |
| 82 | + quote!(.await) |
| 83 | + } else { |
| 84 | + quote!() |
| 85 | + }; |
| 86 | + |
| 87 | + // if the command handler returns a Result, |
| 88 | + // we just map the values to the ones expected by Tauri |
| 89 | + // otherwise we wrap it with an `Ok()`, converting the return value to tauri::InvokeResponse |
| 90 | + // note that all types must implement `serde::Serialize`. |
| 91 | + let return_value = if returns_result { |
| 92 | + quote! { |
| 93 | + match #fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe { |
| 94 | + Ok(value) => ::core::result::Result::Ok(value.into()), |
| 95 | + Err(e) => ::core::result::Result::Err(tauri::Error::Command(::serde_json::to_value(e)?)), |
| 96 | + } |
| 97 | + } |
| 98 | + } else { |
| 99 | + quote! { ::core::result::Result::Ok(#fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe.into()) } |
| 100 | + }; |
| 101 | + |
| 102 | + quote! { |
| 103 | + #function |
| 104 | + pub async fn #fn_wrapper #application_ext_generic(_manager: #manager_arg_type, arg: ::serde_json::Value) -> ::tauri::Result<::tauri::InvokeResponse> { |
| 105 | + #[derive(::serde::Deserialize)] |
| 106 | + #[serde(rename_all = "camelCase")] |
| 107 | + struct ParsedArgs { |
| 108 | + #(#names: #types),* |
| 109 | + } |
| 110 | + let parsed_args: ParsedArgs = ::serde_json::from_value(arg).map_err(|e| ::tauri::Error::InvalidArgs(#fn_name_str, e))?; |
| 111 | + #return_value |
| 112 | + } |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream { |
| 117 | + // Get paths of functions passed to macro |
| 118 | + let paths = <Punctuated<Path, Token![,]>>::parse_terminated |
| 119 | + .parse(item) |
| 120 | + .expect("generate_handler!: Failed to parse list of command functions"); |
| 121 | + |
| 122 | + // Get names of functions, used for match statement |
| 123 | + let fn_names = paths |
| 124 | + .iter() |
| 125 | + .map(|p| p.segments.last().unwrap().ident.clone()); |
| 126 | + |
| 127 | + // Get paths to wrapper functions |
| 128 | + let fn_wrappers = paths.iter().map(|func| { |
| 129 | + let mut func = func.clone(); |
| 130 | + let mut last_segment = func.segments.last_mut().unwrap(); |
| 131 | + last_segment.ident = format_ident!("{}_wrapper", last_segment.ident); |
| 132 | + func |
| 133 | + }); |
| 134 | + |
| 135 | + quote! { |
| 136 | + |webview_manager, arg| async move { |
| 137 | + let dispatch: ::std::result::Result<::tauri::DispatchInstructions, ::serde_json::Error> = |
| 138 | + ::serde_json::from_str(&arg); |
| 139 | + match dispatch { |
| 140 | + Err(e) => Err(e.into()), |
| 141 | + Ok(dispatch) => { |
| 142 | + match dispatch.cmd.as_str() { |
| 143 | + #(stringify!(#fn_names) => #fn_wrappers(webview_manager, dispatch.args).await,)* |
| 144 | + _ => Err(tauri::Error::UnknownApi(None)), |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + } |
| 150 | +} |
0 commit comments