refactor(core): merge invoke items into single struct, allow ? (#1683)

This commit is contained in:
chip 2021-05-02 21:17:47 -07:00 committed by GitHub
parent 152c755c47
commit 1d6f418129
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 314 additions and 455 deletions

View File

@ -3,5 +3,5 @@
---
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.
`initialize` now takes the `App` as first argument, and `extend_api` takes an `Invoke` instead of `InvokeMessage`.
This adds support to managed state on plugins.

View File

@ -2,4 +2,4 @@
"tauri": patch
---
Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `FromCommand` trait.
Removes the `with_window` attribute on the `command` macro. Tauri now infers it using the `CommandArg` trait.

View File

@ -63,7 +63,7 @@ pub fn generate_command(function: ItemFn) -> TokenStream {
}
}
let arg_name_ = arg_name.clone().unwrap();
let arg_name_ = arg_name.unwrap();
let arg_name_s = arg_name_.to_string();
let arg_type = match arg_type {
@ -76,13 +76,14 @@ pub fn generate_command(function: ItemFn) -> TokenStream {
}
};
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())
};
let item = quote!(::tauri::command::CommandItem {
name: #fn_name_str,
key: #arg_name_s,
message: &__message,
});
invoke_arg_names.push(arg_name_.clone());
invoke_args.append_all(quote!(let #arg_name_ = <#arg_type>::from_command(#item)?;));
invoke_arg_names.push(arg_name_);
invoke_arg_types.push(arg_type);
}
@ -97,21 +98,19 @@ pub fn generate_command(function: ItemFn) -> TokenStream {
// 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(#(#invoke_arg_names),*)#await_maybe {
Ok(value) => ::core::result::Result::<_, ()>::Ok(value).into(),
Err(e) => ::core::result::Result::<(), _>::Err(e).into(),
}
}
quote!(::core::result::Result::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe?))
} else {
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe).into() }
quote! { ::core::result::Result::<_, ::tauri::InvokeError>::Ok(#fn_name(#(#invoke_arg_names),*)#await_maybe) }
};
// double underscore prefix temporary until underlying scoping issue is fixed (planned)
quote! {
#function
#vis fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>, resolver: ::tauri::InvokeResolver<P>) {
use ::tauri::command::FromCommand;
resolver.respond_async(async move {
#vis fn #fn_wrapper<P: ::tauri::Params>(invoke: ::tauri::Invoke<P>) {
use ::tauri::command::CommandArg;
let ::tauri::Invoke { message: __message, resolver: __resolver } = invoke;
__resolver.respond_async(async move {
#invoke_args
#return_value
})
@ -139,12 +138,12 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
});
quote! {
move |message, resolver| {
let cmd = message.command().to_string();
match cmd.as_str() {
#(stringify!(#fn_names) => #fn_wrappers(message, resolver),)*
move |invoke| {
let cmd = invoke.message.command();
match cmd {
#(stringify!(#fn_names) => #fn_wrappers(invoke),)*
_ => {
resolver.reject(format!("command {} not found", cmd))
invoke.resolver.reject(format!("command {} not found", cmd))
},
}
}

View File

@ -4,297 +4,136 @@
//! Useful items for custom commands.
use crate::hooks::InvokeError;
use crate::{InvokeMessage, Params};
use serde::de::Visitor;
use serde::Deserializer;
/// A [`Deserializer`] wrapper around [`Value::get`].
///
/// If the key doesn't exist, an error will be returned if the deserialized type is not expecting
/// an optional item. If the key does exist, the value will be called with [`Value`]'s
/// [`Deserializer`] implementation.
struct KeyedValue<'de> {
command: &'de str,
key: &'de str,
value: &'de serde_json::Value,
/// Represents a custom command.
pub struct CommandItem<'a, P: Params> {
/// The name of the command, e.g. `handler` on `#[command] fn handler(value: u64)`
pub name: &'static str,
/// The key of the command item, e.g. `value` on `#[command] fn handler(value: u64)`
pub key: &'static str,
/// The [`InvokeMessage`] that was passed to this command.
pub message: &'a InvokeMessage<P>,
}
macro_rules! kv_value {
($s:ident) => {{
/// Trait implemented by command arguments to derive a value from a [`CommandItem`].
///
/// # Command Arguments
///
/// A command argument is any type that represents an item parsable from a [`CommandItem`]. Most
/// implementations will use the data stored in [`InvokeMessage`] since [`CommandItem`] is mostly a
/// wrapper around it.
///
/// # Provided Implementations
///
/// Tauri implements [`CommandArg`] automatically for a number of types.
/// * [`tauri::Window`]
/// * [`tauri::State`]
/// * `T where T: serde::Deserialize`
/// * Any type that implements `Deserialize` can automatically be used as a [`CommandArg`].
pub trait CommandArg<'de, P: Params>: Sized {
/// Derives an instance of `Self` from the [`CommandItem`].
///
/// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`].
fn from_command(command: CommandItem<'de, P>) -> Result<Self, InvokeError>;
}
/// Automatically implement [`CommandArg`] for any type that can be deserialized.
impl<'de, D: serde::Deserialize<'de>, P: Params> CommandArg<'de, P> for D {
fn from_command(command: CommandItem<'de, P>) -> Result<Self, InvokeError> {
let arg = command.key;
Self::deserialize(command).map_err(|e| crate::Error::InvalidArgs(arg, e).into())
}
}
/// Pass the result of [`serde_json::Value::get`] into [`serde_json::Value`]'s deserializer.
///
/// Returns an error if the [`CommandItem`]'s key does not exist in the value.
macro_rules! pass {
($fn:ident, $($arg:ident: $argt:ty),+) => {
fn $fn<V: Visitor<'de>>(self, $($arg: $argt),*) -> Result<V::Value, Self::Error> {
use serde::de::Error;
match $s.value.get($s.key) {
Some(value) => value,
match self.message.payload.get(self.key) {
Some(value) => value.$fn($($arg),*),
None => {
return Err(serde_json::Error::custom(format!(
"command {} missing required key `{}`",
$s.command, $s.key
Err(serde_json::Error::custom(format!(
"command {} missing required key {}",
self.name, self.key
)))
}
}
}};
}
}
}
impl<'de> Deserializer<'de> for KeyedValue<'de> {
/// A [`Deserializer`] wrapper around [`CommandItem`].
///
/// If the key doesn't exist, an error will be returned if the deserialized type is not expecting
/// an optional item. If the key does exist, the value will be called with
/// [`Value`](serde_json::Value)'s [`Deserializer`] implementation.
impl<'de, P: Params> Deserializer<'de> for CommandItem<'de, P> {
type Error = serde_json::Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_any(visitor)
}
pass!(deserialize_any, visitor: V);
pass!(deserialize_bool, visitor: V);
pass!(deserialize_i8, visitor: V);
pass!(deserialize_i16, visitor: V);
pass!(deserialize_i32, visitor: V);
pass!(deserialize_i64, visitor: V);
pass!(deserialize_u8, visitor: V);
pass!(deserialize_u16, visitor: V);
pass!(deserialize_u32, visitor: V);
pass!(deserialize_u64, visitor: V);
pass!(deserialize_f32, visitor: V);
pass!(deserialize_f64, visitor: V);
pass!(deserialize_char, visitor: V);
pass!(deserialize_str, visitor: V);
pass!(deserialize_string, visitor: V);
pass!(deserialize_bytes, visitor: V);
pass!(deserialize_byte_buf, visitor: V);
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_bool(visitor)
}
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i8(visitor)
}
fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i16(visitor)
}
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i32(visitor)
}
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_i64(visitor)
}
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u8(visitor)
}
fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u16(visitor)
}
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u32(visitor)
}
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_u64(visitor)
}
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_f32(visitor)
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_f64(visitor)
}
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_char(visitor)
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_str(visitor)
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_string(visitor)
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_bytes(visitor)
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_byte_buf(visitor)
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
match self.value.get(self.key) {
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
match self.message.payload.get(self.key) {
Some(value) => value.deserialize_option(visitor),
None => visitor.visit_none(),
}
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_unit(visitor)
}
pass!(deserialize_unit, visitor: V);
pass!(deserialize_unit_struct, name: &'static str, visitor: V);
pass!(deserialize_newtype_struct, name: &'static str, visitor: V);
pass!(deserialize_seq, visitor: V);
pass!(deserialize_tuple, len: usize, visitor: V);
fn deserialize_unit_struct<V>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_unit_struct(name, visitor)
}
fn deserialize_newtype_struct<V>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_newtype_struct(name, visitor)
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_seq(visitor)
}
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_tuple(len, visitor)
}
fn deserialize_tuple_struct<V>(
self,
pass!(
deserialize_tuple_struct,
name: &'static str,
len: usize,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_tuple_struct(name, len, visitor)
}
visitor: V
);
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_map(visitor)
}
pass!(deserialize_map, visitor: V);
fn deserialize_struct<V>(
self,
pass!(
deserialize_struct,
name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_struct(name, fields, visitor)
}
visitor: V
);
fn deserialize_enum<V>(
self,
pass!(
deserialize_enum,
name: &'static str,
variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_enum(name, variants, visitor)
}
fields: &'static [&'static str],
visitor: V
);
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_identifier(visitor)
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
kv_value!(self).deserialize_ignored_any(visitor)
}
}
/// Trait implemented by command arguments to derive a value from a [`InvokeMessage`].
/// [`tauri::Window`], [`tauri::State`] and types that implements [`Deserialize`] automatically implements this trait.
pub trait FromCommand<'de, P: Params>: Sized {
/// Derives an instance of `Self` from the [`InvokeMessage`].
/// If the derivation fails, the corresponding message will be rejected using [`InvokeMessage#reject`].
///
/// # Arguments
/// - `command`: the command value passed to invoke, e.g. `initialize` on `invoke('initialize', {})`.
/// - `key`: The name of the variable in the command handler, e.g. `value` on `#[command] fn handler(value: u64)`
/// - `message`: The [`InvokeMessage`] instance.
fn from_command(
command: &'de str,
key: &'de str,
message: &'de InvokeMessage<P>,
) -> ::core::result::Result<Self, serde_json::Error>;
}
impl<'de, D: serde::Deserialize<'de>, P: Params> FromCommand<'de, P> for D {
fn from_command(
command: &'de str,
key: &'de str,
message: &'de InvokeMessage<P>,
) -> ::core::result::Result<Self, serde_json::Error> {
D::deserialize(KeyedValue {
command,
key,
value: &message.payload,
})
}
pass!(deserialize_identifier, visitor: V);
pass!(deserialize_ignored_any, visitor: V);
}

View File

@ -4,8 +4,8 @@
use crate::{
api::{config::Config, PackageInfo},
hooks::{InvokeMessage, InvokeResolver},
Params, Window,
hooks::{InvokeError, InvokeMessage, InvokeResolver},
Invoke, Params, Window,
};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
@ -66,67 +66,46 @@ impl Module {
cmd
.run(package_info)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Process(cmd) => resolver.respond_async(async move {
cmd
.run()
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Fs(cmd) => resolver.respond_async(async move {
cmd
.run()
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
}),
Self::Process(cmd) => resolver
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }),
Self::Fs(cmd) => resolver
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }),
Self::Window(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.await
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
}),
Self::Shell(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
}),
Self::Event(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
}),
Self::Internal(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
}),
Self::Dialog(cmd) => resolver.respond_async(async move {
cmd
.run()
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
}),
Self::Dialog(cmd) => resolver
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(InvokeError::from) }),
Self::Cli(cmd) => {
if let Some(cli_config) = config.tauri.cli.clone() {
resolver.respond_async(async move {
cmd
.run(&cli_config)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
})
}
}
@ -136,8 +115,7 @@ impl Module {
cmd
.run(identifier)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
})
}
Self::Http(cmd) => resolver.respond_async(async move {
@ -145,32 +123,35 @@ impl Module {
.run()
.await
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
}),
Self::GlobalShortcut(cmd) => resolver.respond_async(async move {
cmd
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
.into()
.map_err(InvokeError::from)
}),
}
}
}
pub(crate) fn handle<M: Params>(
pub(crate) fn handle<P: Params>(
module: String,
message: InvokeMessage<M>,
resolver: InvokeResolver<M>,
invoke: Invoke<P>,
config: &Config,
package_info: &PackageInfo,
) {
let mut payload = message.payload;
let Invoke { message, resolver } = invoke;
let InvokeMessage {
mut payload,
window,
..
} = message;
if let JsonValue::Object(ref mut obj) = payload {
obj.insert("module".to_string(), JsonValue::String(module));
}
let window = message.window;
match serde_json::from_value::<Module>(payload) {
Ok(module) => module.run(window, resolver, config, package_info.clone()),
Err(e) => resolver.reject(e.to_string()),

View File

@ -27,8 +27,9 @@ pub enum Buffer {
Raw(Vec<u8>),
}
#[allow(clippy::unnecessary_wraps)]
fn default_env() -> Option<HashMap<String, String>> {
Some(Default::default())
Some(HashMap::default())
}
#[allow(dead_code)]

View File

@ -12,13 +12,13 @@ use serde_json::Value as JsonValue;
use std::{future::Future, sync::Arc};
/// A closure that is run when the Tauri application is setting up.
pub type SetupHook<M> = Box<dyn Fn(&mut App<M>) -> Result<(), Box<dyn std::error::Error>> + Send>;
pub type SetupHook<P> = Box<dyn Fn(&mut App<P>) -> Result<(), Box<dyn std::error::Error>> + Send>;
/// A closure that is run everytime Tauri receives a message it doesn't explicitly handle.
pub type InvokeHandler<M> = dyn Fn(InvokeMessage<M>, InvokeResolver<M>) + Send + Sync + 'static;
pub type InvokeHandler<P> = dyn Fn(Invoke<P>) + Send + Sync + 'static;
/// A closure that is run once every time a window is created and loaded.
pub type OnPageLoad<M> = dyn Fn(Window<M>, PageLoadPayload) + Send + Sync + 'static;
pub type OnPageLoad<P> = dyn Fn(Window<P>, PageLoadPayload) + Send + Sync + 'static;
/// The payload for the [`OnPageLoad`] hook.
#[derive(Debug, Clone, Deserialize)]
@ -33,33 +33,67 @@ impl PageLoadPayload {
}
}
/// The message and resolver given to a custom command.
pub struct Invoke<P: Params> {
/// The message passed.
pub message: InvokeMessage<P>,
/// The resolver of the message.
pub resolver: InvokeResolver<P>,
}
/// Error response from an [`InvokeMessage`].
#[derive(Debug)]
pub struct InvokeError(JsonValue);
impl InvokeError {
/// Create an [`InvokeError`] as a string of the [`serde_json::Error`] message.
pub fn from_serde_json(error: serde_json::Error) -> Self {
Self(JsonValue::String(error.to_string()))
}
}
impl<T: Serialize> From<T> for InvokeError {
fn from(value: T) -> Self {
serde_json::to_value(value)
.map(Self)
.unwrap_or_else(Self::from_serde_json)
}
}
impl From<crate::Error> for InvokeError {
fn from(error: crate::Error) -> Self {
Self(JsonValue::String(error.to_string()))
}
}
/// Response from a [`InvokeMessage`] passed to the [`InvokeResolver`].
#[derive(Debug)]
pub enum InvokeResponse {
/// Resolve the promise.
Ok(JsonValue),
/// Reject the promise.
Err(JsonValue),
}
impl<T: Serialize, E: Serialize> From<Result<T, E>> for InvokeResponse {
fn from(result: Result<T, E>) -> Self {
match result {
Result::Ok(t) => match serde_json::to_value(t) {
Ok(v) => Self::Ok(v),
Err(e) => Self::Err(JsonValue::String(e.to_string())),
},
Result::Err(e) => Self::error(e),
}
}
Err(InvokeError),
}
impl InvokeResponse {
#[doc(hidden)]
pub fn error<T: Serialize>(value: T) -> Self {
match serde_json::to_value(value) {
Ok(v) => Self::Err(v),
Err(e) => Self::Err(JsonValue::String(e.to_string())),
/// Turn a [`InvokeResponse`] back into a serializable result.
pub fn into_result(self) -> Result<JsonValue, JsonValue> {
match self {
Self::Ok(v) => Ok(v),
Self::Err(e) => Err(e.0),
}
}
}
impl<T: Serialize> From<Result<T, InvokeError>> for InvokeResponse {
fn from(result: Result<T, InvokeError>) -> Self {
match result {
Ok(ok) => match serde_json::to_value(ok) {
Ok(value) => Self::Ok(value),
Err(err) => Self::Err(InvokeError::from_serde_json(err)),
},
Err(err) => Self::Err(err),
}
}
}
@ -72,19 +106,8 @@ pub struct InvokeResolver<M: Params> {
pub(crate) error: String,
}
/*impl<P: Params> Clone for InvokeResolver<P> {
fn clone(&self) -> Self {
Self {
window: self.window.clone(),
main_thread: self.main_thread,
callback: self.callback.clone(),
error: self.error.clone(),
}
}
}*/
impl<M: Params> InvokeResolver<M> {
pub(crate) fn new(window: Window<M>, main_thread: bool, callback: String, error: String) -> Self {
impl<P: Params> InvokeResolver<P> {
pub(crate) fn new(window: Window<P>, main_thread: bool, callback: String, error: String) -> Self {
Self {
window,
main_thread,
@ -94,7 +117,11 @@ impl<M: Params> InvokeResolver<M> {
}
/// Reply to the invoke promise with an async task.
pub fn respond_async<F: Future<Output = InvokeResponse> + Send + 'static>(self, task: F) {
pub fn respond_async<T, F>(self, task: F)
where
T: Serialize,
F: Future<Output = Result<T, InvokeError>> + Send + 'static,
{
if self.main_thread {
crate::async_runtime::block_on(async move {
Self::return_task(self.window, task, self.callback, self.error).await;
@ -107,25 +134,24 @@ impl<M: Params> InvokeResolver<M> {
}
/// Reply to the invoke promise running the given closure.
pub fn respond_closure<F: FnOnce() -> InvokeResponse>(self, f: F) {
pub fn respond_closure<T, F>(self, f: F)
where
T: Serialize,
F: FnOnce() -> Result<T, InvokeError>,
{
Self::return_closure(self.window, f, self.callback, self.error)
}
/// Resolve the invoke promise with a value.
pub fn resolve<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<S, ()>::Ok(value).into(),
self.callback,
self.error,
)
Self::return_result(self.window, Ok(value), self.callback, self.error)
}
/// Reject the invoke promise with a value.
pub fn reject<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<(), S>::Err(value).into(),
Result::<(), _>::Err(value.into()),
self.callback,
self.error,
)
@ -136,18 +162,21 @@ impl<M: Params> InvokeResolver<M> {
///
/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
pub async fn return_task<F: std::future::Future<Output = InvokeResponse> + Send + 'static>(
window: Window<M>,
pub async fn return_task<T, F>(
window: Window<P>,
task: F,
success_callback: String,
error_callback: String,
) {
) where
T: Serialize,
F: Future<Output = Result<T, InvokeError>> + Send + 'static,
{
let result = task.await;
Self::return_closure(window, || result, success_callback, error_callback)
}
pub(crate) fn return_closure<F: FnOnce() -> InvokeResponse>(
window: Window<M>,
pub(crate) fn return_closure<T: Serialize, F: FnOnce() -> Result<T, InvokeError>>(
window: Window<P>,
f: F,
success_callback: String,
error_callback: String,
@ -155,17 +184,14 @@ impl<M: Params> InvokeResolver<M> {
Self::return_result(window, f(), success_callback, error_callback)
}
pub(crate) fn return_result(
window: Window<M>,
response: InvokeResponse,
pub(crate) fn return_result<T: Serialize>(
window: Window<P>,
response: Result<T, InvokeError>,
success_callback: String,
error_callback: String,
) {
let callback_string = match format_callback_result(
match response {
InvokeResponse::Ok(t) => std::result::Result::Ok(t),
InvokeResponse::Err(e) => std::result::Result::Err(e),
},
InvokeResponse::from(response).into_result(),
success_callback,
error_callback.clone(),
) {
@ -190,10 +216,10 @@ pub struct InvokeMessage<M: Params> {
pub(crate) payload: JsonValue,
}
impl<M: Params> InvokeMessage<M> {
impl<P: Params> InvokeMessage<P> {
/// Create an new [`InvokeMessage`] from a payload send to a window.
pub(crate) fn new(
window: Window<M>,
window: Window<P>,
state: Arc<StateManager>,
command: String,
payload: JsonValue,
@ -207,12 +233,38 @@ impl<M: Params> InvokeMessage<M> {
}
/// The invoke command.
#[inline(always)]
pub fn command(&self) -> &str {
&self.command
}
/// The window that received the invoke.
pub fn window(&self) -> Window<M> {
#[inline(always)]
pub fn window(&self) -> Window<P> {
self.window.clone()
}
/// A reference to window that received the invoke.
#[inline(always)]
pub fn window_ref(&self) -> &Window<P> {
&self.window
}
/// A reference to the payload the invoke received.
#[inline(always)]
pub fn payload(&self) -> &JsonValue {
&self.payload
}
/// The state manager associated with the application
#[inline(always)]
pub fn state(&self) -> Arc<StateManager> {
self.state.clone()
}
/// A reference to the state manager associated with application.
#[inline(always)]
pub fn state_ref(&self) -> &StateManager {
&self.state
}
}

View File

@ -54,8 +54,8 @@ use std::{borrow::Borrow, collections::HashMap, path::PathBuf};
pub use {
self::api::config::WindowUrl,
self::hooks::{
InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad, PageLoadPayload,
SetupHook,
Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokeResolver, InvokeResponse, OnPageLoad,
PageLoadPayload, SetupHook,
},
self::runtime::app::{App, Builder},
self::runtime::flavors::wry::Wry,

View File

@ -4,11 +4,7 @@
//! Extend Tauri functionality.
use crate::{
api::config::PluginConfig,
hooks::{InvokeMessage, InvokeResolver, PageLoadPayload},
App, Params, Window,
};
use crate::{api::config::PluginConfig, App, Invoke, PageLoadPayload, Params, Window};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
@ -16,13 +12,13 @@ use std::collections::HashMap;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
/// The plugin interface.
pub trait Plugin<M: Params>: Send {
pub trait Plugin<P: Params>: Send {
/// The plugin name. Used as key on the plugin config object.
fn name(&self) -> &'static str;
/// Initialize the plugin.
#[allow(unused_variables)]
fn initialize(&mut self, app: &App<M>, config: JsonValue) -> Result<()> {
fn initialize(&mut self, app: &App<P>, config: JsonValue) -> Result<()> {
Ok(())
}
@ -37,23 +33,23 @@ pub trait Plugin<M: Params>: Send {
/// Callback invoked when the webview is created.
#[allow(unused_variables)]
fn created(&mut self, window: Window<M>) {}
fn created(&mut self, window: Window<P>) {}
/// Callback invoked when the webview performs a navigation.
#[allow(unused_variables)]
fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {}
fn on_page_load(&mut self, window: Window<P>, payload: PageLoadPayload) {}
/// Add invoke_handler API extension commands.
#[allow(unused_variables)]
fn extend_api(&mut self, message: InvokeMessage<M>, resolver: InvokeResolver<M>) {}
fn extend_api(&mut self, invoke: Invoke<P>) {}
}
/// Plugin collection type.
pub(crate) struct PluginStore<M: Params> {
store: HashMap<&'static str, Box<dyn Plugin<M>>>,
pub(crate) struct PluginStore<P: Params> {
store: HashMap<&'static str, Box<dyn Plugin<P>>>,
}
impl<M: Params> Default for PluginStore<M> {
impl<P: Params> Default for PluginStore<P> {
fn default() -> Self {
Self {
store: HashMap::new(),
@ -61,16 +57,16 @@ impl<M: Params> Default for PluginStore<M> {
}
}
impl<M: Params> PluginStore<M> {
impl<P: Params> PluginStore<P> {
/// Adds a plugin to the store.
///
/// Returns `true` if a plugin with the same name is already in the store.
pub fn register<P: Plugin<M> + 'static>(&mut self, plugin: P) -> bool {
pub fn register<Plug: Plugin<P> + 'static>(&mut self, plugin: Plug) -> bool {
self.store.insert(plugin.name(), Box::new(plugin)).is_some()
}
/// Initializes all plugins in the store.
pub(crate) fn initialize(&mut self, app: &App<M>, config: &PluginConfig) -> crate::Result<()> {
pub(crate) fn initialize(&mut self, app: &App<P>, config: &PluginConfig) -> crate::Result<()> {
self.store.values_mut().try_for_each(|plugin| {
plugin
.initialize(
@ -93,7 +89,7 @@ impl<M: Params> PluginStore<M> {
}
/// Runs the created hook for all plugins in the store.
pub(crate) fn created(&mut self, window: Window<M>) {
pub(crate) fn created(&mut self, window: Window<P>) {
self
.store
.values_mut()
@ -101,27 +97,29 @@ impl<M: Params> PluginStore<M> {
}
/// Runs the on_page_load hook for all plugins in the store.
pub(crate) fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {
pub(crate) fn on_page_load(&mut self, window: Window<P>, payload: PageLoadPayload) {
self
.store
.values_mut()
.for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
}
pub(crate) fn extend_api(&mut self, mut message: InvokeMessage<M>, resolver: InvokeResolver<M>) {
let command = message.command.replace("plugin:", "");
pub(crate) fn extend_api(&mut self, mut invoke: Invoke<P>) {
let command = invoke.message.command.replace("plugin:", "");
let mut tokens = command.split('|');
// safe to unwrap: split always has a least one item
let target = tokens.next().unwrap();
if let Some(plugin) = self.store.get_mut(target) {
message.command = tokens
invoke.message.command = tokens
.next()
.map(|c| c.to_string())
.unwrap_or_else(String::new);
plugin.extend_api(message, resolver);
plugin.extend_api(invoke);
} else {
resolver.reject(format!("plugin {} not found", target));
invoke
.resolver
.reject(format!("plugin {} not found", target));
}
}
}

View File

@ -4,7 +4,7 @@
use crate::{
api::{assets::Assets, config::WindowUrl},
hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload, SetupHook},
hooks::{InvokeHandler, OnPageLoad, PageLoadPayload, SetupHook},
plugin::{Plugin, PluginStore},
runtime::{
flavors::wry::Wry,
@ -15,7 +15,7 @@ use crate::{
Dispatch, Runtime,
},
sealed::{ManagerBase, RuntimeOrDispatch},
Context, Manager, Params, StateManager, Window,
Context, Invoke, Manager, Params, StateManager, Window,
};
use std::{collections::HashMap, sync::Arc};
@ -142,7 +142,7 @@ where
pub fn new() -> Self {
Self {
setup: Box::new(|_| Ok(())),
invoke_handler: Box::new(|_, _| ()),
invoke_handler: Box::new(|_| ()),
on_page_load: Box::new(|_, _| ()),
pending_windows: Default::default(),
plugins: PluginStore::default(),
@ -154,8 +154,7 @@ where
/// Defines the JS message handler callback.
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
where
F:
Fn(InvokeMessage<Args<E, L, A, R>>, InvokeResolver<Args<E, L, A, R>>) + Send + Sync + 'static,
F: Fn(Invoke<Args<E, L, A, R>>) + Send + Sync + 'static,
{
self.invoke_handler = Box::new(invoke_handler);
self

View File

@ -10,7 +10,7 @@ use crate::{
PackageInfo,
},
event::{Event, EventHandler, Listeners},
hooks::{InvokeHandler, InvokeMessage, InvokeResolver, OnPageLoad, PageLoadPayload},
hooks::{InvokeHandler, OnPageLoad, PageLoadPayload},
plugin::PluginStore,
runtime::{
tag::{tags_to_javascript_array, Tag, TagRef, ToJsString},
@ -22,7 +22,7 @@ use crate::{
Icon, Runtime,
},
sealed::ParamsBase,
App, Context, Params, StateManager, Window,
App, Context, Invoke, Params, StateManager, Window,
};
use serde::Serialize;
use serde_json::Value as JsonValue;
@ -401,7 +401,7 @@ mod test {
let manager: WindowManager<Args<String, String, _, Wry>> = WindowManager::with_handlers(
context,
PluginStore::default(),
Box::new(|_, _| ()),
Box::new(|_| ()),
Box::new(|_, _| ()),
Default::default(),
StateManager::new(),
@ -416,8 +416,8 @@ mod test {
}
impl<P: Params> WindowManager<P> {
pub fn run_invoke_handler(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
(self.inner.invoke_handler)(message, resolver);
pub fn run_invoke_handler(&self, invoke: Invoke<P>) {
(self.inner.invoke_handler)(invoke);
}
pub fn run_on_page_load(&self, window: Window<P>, payload: PageLoadPayload) {
@ -430,13 +430,13 @@ impl<P: Params> WindowManager<P> {
.on_page_load(window, payload);
}
pub fn extend_api(&self, message: InvokeMessage<P>, resolver: InvokeResolver<P>) {
pub fn extend_api(&self, invoke: Invoke<P>) {
self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.extend_api(message, resolver);
.extend_api(invoke);
}
pub fn initialize_plugins(&self, app: &App<P>) -> crate::Result<()> {

View File

@ -114,8 +114,9 @@ impl<M: Params> PartialEq for DetachedWindow<M> {
/// We want to export the runtime related window at the crate root, but not look like a re-export.
pub(crate) mod export {
use super::*;
use crate::command::FromCommand;
use crate::command::{CommandArg, CommandItem};
use crate::runtime::{manager::WindowManager, tag::TagRef};
use crate::{Invoke, InvokeError};
use std::borrow::Borrow;
/// A webview window managed by Tauri.
@ -167,13 +168,10 @@ pub(crate) mod export {
}
}
impl<'de, P: Params> FromCommand<'de, P> for Window<P> {
fn from_command(
_: &'de str,
_: &'de str,
message: &'de InvokeMessage<P>,
) -> Result<Self, serde_json::Error> {
Ok(message.window())
impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
/// Grabs the [`Window`] from the [`CommandItem`]. This will never fail.
fn from_command(command: CommandItem<'de, P>) -> Result<Self, InvokeError> {
Ok(command.message.window())
}
}
@ -205,19 +203,14 @@ pub(crate) mod export {
);
let resolver =
InvokeResolver::new(self, payload.main_thread, payload.callback, payload.error);
let invoke = Invoke { message, resolver };
if let Some(module) = &payload.tauri_module {
let module = module.to_string();
crate::endpoints::handle(
module,
message,
resolver,
manager.config(),
manager.package_info(),
);
crate::endpoints::handle(module, invoke, manager.config(), manager.package_info());
} else if command.starts_with("plugin:") {
manager.extend_api(message, resolver);
manager.extend_api(invoke);
} else {
manager.run_invoke_handler(message, resolver);
manager.run_invoke_handler(invoke);
}
}
}

View File

@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::command::FromCommand;
use crate::{InvokeMessage, Params};
use crate::command::{CommandArg, CommandItem};
use crate::{InvokeError, Params};
use state::Container;
/// A guard for a state value.
@ -34,13 +34,10 @@ impl<T: Send + Sync + 'static> Clone for State<'_, T> {
}
}
impl<'r, 'de: 'r, T: Send + Sync + 'static, P: Params> FromCommand<'de, P> for State<'r, T> {
fn from_command(
_: &'de str,
_: &'de str,
message: &'de InvokeMessage<P>,
) -> Result<Self, serde_json::Error> {
Ok(message.state.get())
impl<'r, 'de: 'r, T: Send + Sync + 'static, P: Params> CommandArg<'de, P> for State<'r, T> {
/// Grabs the [`State`] from the [`CommandItem`]. This will never fail.
fn from_command(command: CommandItem<'de, P>) -> Result<Self, InvokeError> {
Ok(command.message.state_ref().get())
}
}

View File

@ -42,7 +42,7 @@ type Result<T> = std::result::Result<T, ()>;
#[tauri::command]
fn simple_command_with_result(argument: String) -> Result<String> {
println!("{}", argument);
Ok(argument)
(!argument.is_empty()).then(|| argument).ok_or(())
}
#[tauri::command]
@ -51,7 +51,7 @@ fn stateful_command_with_result(
state: tauri::State<'_, MyState>,
) -> Result<String> {
println!("{:?} {:?}", argument, state.inner());
Ok(argument.unwrap_or_else(|| "".to_string()))
argument.ok_or(())
}
// Async commands