Skip to content

Commit 8b6f3de

Browse files
feat(core): add state management, closes #1655 (#1665)
* 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>
1 parent d92fde7 commit 8b6f3de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1070
-222
lines changed

.changes/app-state.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Adds `manage` API to the `Builder` struct, which manages app state.

.changes/command-state.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-macros": patch
3+
---
4+
5+
Adds support to command state, triggered when a command argument is `arg: State<'_, StateType>`.

.changes/plugin-refactor.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Refactored the `Plugin` trait `initialize` and `extend_api` signatures.
6+
`initialize` now takes the `App` as first argument, and `extend_api` takes a `InvokeResolver` as last argument.
7+
This adds support to managed state on plugins.

.changes/remove-with-window.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `FromCommand` trait.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"examples/api/src-tauri",
1212
"examples/helloworld/src-tauri",
1313
"examples/multiwindow/src-tauri",
14+
"examples/commands/src-tauri",
1415
# used to build updater artifacts
1516
"examples/updater/src-tauri",
1617
]

core/tauri-macros/src/command.rs

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,35 @@
33
// SPDX-License-Identifier: MIT
44

55
use proc_macro2::TokenStream;
6-
use quote::{format_ident, quote};
6+
use quote::{format_ident, quote, TokenStreamExt};
77
use syn::{
8-
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Path,
9-
ReturnType, Token, Type,
8+
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Pat, Path, ReturnType, Token, Type,
9+
Visibility,
1010
};
1111

12-
pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream {
13-
// Check if "with_window" attr was passed to macro
14-
let with_window = attrs.iter().any(|a| {
15-
if let NestedMeta::Meta(Meta::Path(path)) = a {
16-
path
17-
.get_ident()
18-
.map(|i| *i == "with_window")
19-
.unwrap_or(false)
20-
} else {
21-
false
12+
fn fn_wrapper(function: &ItemFn) -> (&Visibility, Ident) {
13+
(
14+
&function.vis,
15+
format_ident!("{}_wrapper", function.sig.ident),
16+
)
17+
}
18+
19+
fn err(function: ItemFn, error_message: &str) -> TokenStream {
20+
let (vis, wrap) = fn_wrapper(&function);
21+
quote! {
22+
#function
23+
24+
#vis fn #wrap<P: ::tauri::Params>(_message: ::tauri::InvokeMessage<P>) {
25+
compile_error!(#error_message);
26+
unimplemented!()
2227
}
23-
});
28+
}
29+
}
2430

31+
pub fn generate_command(function: ItemFn) -> TokenStream {
2532
let fn_name = function.sig.ident.clone();
2633
let fn_name_str = fn_name.to_string();
27-
let fn_wrapper = format_ident!("{}_wrapper", fn_name);
34+
let (vis, fn_wrapper) = fn_wrapper(&function);
2835
let returns_result = match function.sig.output {
2936
ReturnType::Type(_, ref ty) => match &**ty {
3037
Type::Path(type_path) => {
@@ -40,40 +47,45 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
4047
ReturnType::Default => false,
4148
};
4249

43-
// Split function args into names and types
44-
let (mut names, mut types): (Vec<Ident>, Vec<Path>) = function
45-
.sig
46-
.inputs
47-
.iter()
48-
.map(|param| {
49-
let mut arg_name = None;
50-
let mut arg_type = None;
51-
if let FnArg::Typed(arg) = param {
52-
if let Pat::Ident(ident) = arg.pat.as_ref() {
53-
arg_name = Some(ident.ident.clone());
54-
}
55-
if let Type::Path(path) = arg.ty.as_ref() {
56-
arg_type = Some(path.path.clone());
57-
}
50+
let mut invoke_arg_names: Vec<Ident> = Default::default();
51+
let mut invoke_arg_types: Vec<Path> = Default::default();
52+
let mut invoke_args: TokenStream = Default::default();
53+
54+
for param in &function.sig.inputs {
55+
let mut arg_name = None;
56+
let mut arg_type = None;
57+
if let FnArg::Typed(arg) = param {
58+
if let Pat::Ident(ident) = arg.pat.as_ref() {
59+
arg_name = Some(ident.ident.clone());
60+
}
61+
if let Type::Path(path) = arg.ty.as_ref() {
62+
arg_type = Some(path.path.clone());
5863
}
59-
(
60-
arg_name.clone().unwrap(),
61-
arg_type.unwrap_or_else(|| panic!("Invalid type for arg \"{}\"", arg_name.unwrap())),
62-
)
63-
})
64-
.unzip();
65-
66-
let window_arg_maybe = match types.first() {
67-
Some(_) if with_window => {
68-
// Remove window arg from list so it isn't expected as arg from JS
69-
types.drain(0..1);
70-
names.drain(0..1);
71-
// Tell wrapper to pass `window` to original function
72-
quote!(_window,)
7364
}
74-
// Tell wrapper not to pass `window` to original function
75-
_ => quote!(),
76-
};
65+
66+
let arg_name_ = arg_name.clone().unwrap();
67+
let arg_name_s = arg_name_.to_string();
68+
69+
let arg_type = match arg_type {
70+
Some(arg_type) => arg_type,
71+
None => {
72+
return err(
73+
function.clone(),
74+
&format!("invalid type for arg: {}", arg_name_),
75+
)
76+
}
77+
};
78+
79+
invoke_args.append_all(quote! {
80+
let #arg_name_ = match <#arg_type>::from_command(#fn_name_str, #arg_name_s, &message) {
81+
Ok(value) => value,
82+
Err(e) => return tauri::InvokeResponse::error(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string())
83+
};
84+
});
85+
invoke_arg_names.push(arg_name_.clone());
86+
invoke_arg_types.push(arg_type);
87+
}
88+
7789
let await_maybe = if function.sig.asyncness.is_some() {
7890
quote!(.await)
7991
} else {
@@ -86,30 +98,23 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
8698
// note that all types must implement `serde::Serialize`.
8799
let return_value = if returns_result {
88100
quote! {
89-
match #fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe {
90-
Ok(value) => ::core::result::Result::Ok(value),
91-
Err(e) => ::core::result::Result::Err(e),
101+
match #fn_name(#(#invoke_arg_names),*)#await_maybe {
102+
Ok(value) => ::core::result::Result::<_, ()>::Ok(value).into(),
103+
Err(e) => ::core::result::Result::<(), _>::Err(e).into(),
92104
}
93105
}
94106
} else {
95-
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#window_arg_maybe #(parsed_args.#names),*)#await_maybe) }
107+
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe).into() }
96108
};
97109

98110
quote! {
99111
#function
100-
pub fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>) {
101-
#[derive(::serde::Deserialize)]
102-
#[serde(rename_all = "camelCase")]
103-
struct ParsedArgs {
104-
#(#names: #types),*
105-
}
106-
let _window = message.window();
107-
match ::serde_json::from_value::<ParsedArgs>(message.payload()) {
108-
Ok(parsed_args) => message.respond_async(async move {
109-
#return_value
110-
}),
111-
Err(e) => message.reject(::tauri::Error::InvalidArgs(#fn_name_str, e).to_string()),
112-
}
112+
#vis fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>, resolver: ::tauri::InvokeResolver<P>) {
113+
use ::tauri::command::FromCommand;
114+
resolver.respond_async(async move {
115+
#invoke_args
116+
#return_value
117+
})
113118
}
114119
}
115120
}
@@ -134,12 +139,12 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
134139
});
135140

136141
quote! {
137-
move |message| {
142+
move |message, resolver| {
138143
let cmd = message.command().to_string();
139144
match cmd.as_str() {
140-
#(stringify!(#fn_names) => #fn_wrappers(message),)*
145+
#(stringify!(#fn_names) => #fn_wrappers(message, resolver),)*
141146
_ => {
142-
message.reject(format!("command {} not found", cmd))
147+
resolver.reject(format!("command {} not found", cmd))
143148
},
144149
}
145150
}

core/tauri-macros/src/lib.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@
55
extern crate proc_macro;
66
use crate::context::ContextItems;
77
use proc_macro::TokenStream;
8-
use syn::{parse_macro_input, AttributeArgs, ItemFn};
8+
use syn::{parse_macro_input, ItemFn};
99

1010
mod command;
1111

1212
#[macro_use]
1313
mod context;
1414

1515
#[proc_macro_attribute]
16-
pub fn command(attrs: TokenStream, item: TokenStream) -> TokenStream {
16+
pub fn command(_attrs: TokenStream, item: TokenStream) -> TokenStream {
1717
let function = parse_macro_input!(item as ItemFn);
18-
let attrs = parse_macro_input!(attrs as AttributeArgs);
19-
let gen = command::generate_command(attrs, function);
18+
let gen = command::generate_command(function);
2019
gen.into()
2120
}
2221

core/tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ shared_child = "0.3"
4848
os_pipe = "0.9"
4949
minisign-verify = "0.1.8"
5050
image = "0.23"
51+
state = "0.4"
5152

5253
[build-dependencies]
5354
cfg_aliases = "0.1.1"

0 commit comments

Comments
 (0)