Skip to content

Commit

Permalink
feat(core): add state management, closes #1655 (#1665)
Browse files Browse the repository at this point in the history
* feat(core): add state management, closes #1655

* fix(tests): ignore doc example

* use a trait to manage #[command] parameters

* add docs [skip ci]

* finish command before moving into respond_async

* Revert "finish command before moving into respond_async"

This reverts commit 4651bed.

* refactor: split InvokeMessage into InvokeResolver, add InvokeResponse

* feat: add managed state to the plugin interface

* feat: add commands example

* add change file [skip ci]

* cleanup clones

Co-authored-by: chip reed <chip@chip.sh>
  • Loading branch information
lucasfernog and chippers committed May 2, 2021
1 parent d92fde7 commit 8b6f3de
Show file tree
Hide file tree
Showing 43 changed files with 1,070 additions and 222 deletions.
5 changes: 5 additions & 0 deletions .changes/app-state.md
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Adds `manage` API to the `Builder` struct, which manages app state.
5 changes: 5 additions & 0 deletions .changes/command-state.md
@@ -0,0 +1,5 @@
---
"tauri-macros": patch
---

Adds support to command state, triggered when a command argument is `arg: State<'_, StateType>`.
7 changes: 7 additions & 0 deletions .changes/plugin-refactor.md
@@ -0,0 +1,7 @@
---
"tauri": patch
---

Refactored the `Plugin` trait `initialize` and `extend_api` signatures.
`initialize` now takes the `App` as first argument, and `extend_api` takes a `InvokeResolver` as last argument.
This adds support to managed state on plugins.
5 changes: 5 additions & 0 deletions .changes/remove-with-window.md
@@ -0,0 +1,5 @@
---
"tauri": patch
---

Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `FromCommand` trait.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -11,6 +11,7 @@ members = [
"examples/api/src-tauri",
"examples/helloworld/src-tauri",
"examples/multiwindow/src-tauri",
"examples/commands/src-tauri",
# used to build updater artifacts
"examples/updater/src-tauri",
]
Expand Down
139 changes: 72 additions & 67 deletions core/tauri-macros/src/command.rs
Expand Up @@ -3,28 +3,35 @@
// SPDX-License-Identifier: MIT

use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use quote::{format_ident, quote, TokenStreamExt};
use syn::{
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Path,
ReturnType, Token, Type,
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Pat, Path, ReturnType, Token, Type,
Visibility,
};

pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream {
// Check if "with_window" attr was passed to macro
let with_window = attrs.iter().any(|a| {
if let NestedMeta::Meta(Meta::Path(path)) = a {
path
.get_ident()
.map(|i| *i == "with_window")
.unwrap_or(false)
} else {
false
fn fn_wrapper(function: &ItemFn) -> (&Visibility, Ident) {
(
&function.vis,
format_ident!("{}_wrapper", function.sig.ident),
)
}

fn err(function: ItemFn, error_message: &str) -> TokenStream {
let (vis, wrap) = fn_wrapper(&function);
quote! {
#function

#vis fn #wrap<P: ::tauri::Params>(_message: ::tauri::InvokeMessage<P>) {
compile_error!(#error_message);
unimplemented!()
}
});
}
}

pub fn generate_command(function: ItemFn) -> TokenStream {
let fn_name = function.sig.ident.clone();
let fn_name_str = fn_name.to_string();
let fn_wrapper = format_ident!("{}_wrapper", fn_name);
let (vis, fn_wrapper) = fn_wrapper(&function);
let returns_result = match function.sig.output {
ReturnType::Type(_, ref ty) => match &**ty {
Type::Path(type_path) => {
Expand All @@ -40,40 +47,45 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
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());
}
let mut invoke_arg_names: Vec<Ident> = Default::default();
let mut invoke_arg_types: Vec<Path> = Default::default();
let mut invoke_args: TokenStream = Default::default();

for param in &function.sig.inputs {
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();

let window_arg_maybe = match types.first() {
Some(_) if with_window => {
// Remove window arg from list so it isn't expected as arg from JS
types.drain(0..1);
names.drain(0..1);
// Tell wrapper to pass `window` to original function
quote!(_window,)
}
// Tell wrapper not to pass `window` to original function
_ => quote!(),
};

let arg_name_ = arg_name.clone().unwrap();
let arg_name_s = arg_name_.to_string();

let arg_type = match arg_type {
Some(arg_type) => arg_type,
None => {
return err(
function.clone(),
&format!("invalid type for arg: {}", arg_name_),
)
}
};

invoke_args.append_all(quote! {
let #arg_name_ = match <#arg_type>::from_command(#fn_name_str, #arg_name_s, &message) {
Ok(value) => value,
Err(e) => return tauri::InvokeResponse::error(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string())
};
});
invoke_arg_names.push(arg_name_.clone());
invoke_arg_types.push(arg_type);
}

let await_maybe = if function.sig.asyncness.is_some() {
quote!(.await)
} else {
Expand All @@ -86,30 +98,23 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
// note that all types must implement `serde::Serialize`.
let return_value = if returns_result {
quote! {
match #fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe {
Ok(value) => ::core::result::Result::Ok(value),
Err(e) => ::core::result::Result::Err(e),
match #fn_name(#(#invoke_arg_names),*)#await_maybe {
Ok(value) => ::core::result::Result::<_, ()>::Ok(value).into(),
Err(e) => ::core::result::Result::<(), _>::Err(e).into(),
}
}
} else {
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe) }
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe).into() }
};

quote! {
#function
pub fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>) {
#[derive(::serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct ParsedArgs {
#(#names: #types),*
}
let _window = message.window();
match ::serde_json::from_value::<ParsedArgs>(message.payload()) {
Ok(parsed_args) => message.respond_async(async move {
#return_value
}),
Err(e) => message.reject(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string()),
}
#vis fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>, resolver: ::tauri::InvokeResolver<P>) {
use ::tauri::command::FromCommand;
resolver.respond_async(async move {
#invoke_args
#return_value
})
}
}
}
Expand All @@ -134,12 +139,12 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
});

quote! {
move |message| {
move |message, resolver| {
let cmd = message.command().to_string();
match cmd.as_str() {
#(stringify!(#fn_names) => #fn_wrappers(message),)*
#(stringify!(#fn_names) => #fn_wrappers(message, resolver),)*
_ => {
message.reject(format!("command {} not found", cmd))
resolver.reject(format!("command {} not found", cmd))
},
}
}
Expand Down
7 changes: 3 additions & 4 deletions core/tauri-macros/src/lib.rs
Expand Up @@ -5,18 +5,17 @@
extern crate proc_macro;
use crate::context::ContextItems;
use proc_macro::TokenStream;
use syn::{parse_macro_input, AttributeArgs, ItemFn};
use syn::{parse_macro_input, ItemFn};

mod command;

#[macro_use]
mod context;

#[proc_macro_attribute]
pub fn command(attrs: TokenStream, item: TokenStream) -> TokenStream {
pub fn command(_attrs: TokenStream, item: TokenStream) -> TokenStream {
let function = parse_macro_input!(item as ItemFn);
let attrs = parse_macro_input!(attrs as AttributeArgs);
let gen = command::generate_command(attrs, function);
let gen = command::generate_command(function);
gen.into()
}

Expand Down
1 change: 1 addition & 0 deletions core/tauri/Cargo.toml
Expand Up @@ -48,6 +48,7 @@ shared_child = "0.3"
os_pipe = "0.9"
minisign-verify = "0.1.8"
image = "0.23"
state = "0.4"

[build-dependencies]
cfg_aliases = "0.1.1"
Expand Down

0 comments on commit 8b6f3de

Please sign in to comment.