Browse Source

refactor(core): allow scope deserialization to run setup code (#8699)

* refactor(core): allow scope deserialization to run setup code

* Update core/tauri/src/command/authority.rs

---------

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Lucas Fernandes Nogueira 1 year ago
parent
commit
f998fa0ec2

+ 50 - 22
core/tauri/src/command/authority.rs

@@ -8,12 +8,14 @@ use std::{collections::BTreeMap, ops::Deref};
 use serde::de::DeserializeOwned;
 use serde::de::DeserializeOwned;
 use state::TypeMap;
 use state::TypeMap;
 
 
+use tauri_utils::acl::Value;
 use tauri_utils::acl::{
 use tauri_utils::acl::{
   resolved::{CommandKey, Resolved, ResolvedCommand, ResolvedScope, ScopeKey},
   resolved::{CommandKey, Resolved, ResolvedCommand, ResolvedScope, ScopeKey},
   ExecutionContext,
   ExecutionContext,
 };
 };
 
 
 use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
 use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
+use crate::{AppHandle, Manager};
 
 
 use super::{CommandArg, CommandItem};
 use super::{CommandArg, CommandItem};
 
 
@@ -95,12 +97,12 @@ impl RuntimeAuthority {
 
 
 /// List of allowed and denied objects that match either the command-specific or plugin global scope criterias.
 /// List of allowed and denied objects that match either the command-specific or plugin global scope criterias.
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct ScopeValue<T: Debug + DeserializeOwned + Send + Sync + 'static> {
+pub struct ScopeValue<T: ScopeObject> {
   allow: Vec<T>,
   allow: Vec<T>,
   deny: Vec<T>,
   deny: Vec<T>,
 }
 }
 
 
-impl<T: Debug + DeserializeOwned + Send + Sync + 'static> ScopeValue<T> {
+impl<T: ScopeObject> ScopeValue<T> {
   /// What this access scope allows.
   /// What this access scope allows.
   pub fn allows(&self) -> &Vec<T> {
   pub fn allows(&self) -> &Vec<T> {
     &self.allow
     &self.allow
@@ -130,11 +132,9 @@ impl<'a, T: Debug> Deref for OwnedOrRef<'a, T> {
 
 
 /// Access scope for a command that can be retrieved directly in the command function.
 /// Access scope for a command that can be retrieved directly in the command function.
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct CommandScope<'a, T: Debug + DeserializeOwned + Send + Sync + 'static>(
-  OwnedOrRef<'a, ScopeValue<T>>,
-);
+pub struct CommandScope<'a, T: ScopeObject>(OwnedOrRef<'a, ScopeValue<T>>);
 
 
-impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandScope<'a, T> {
+impl<'a, T: ScopeObject> CommandScope<'a, T> {
   /// What this access scope allows.
   /// What this access scope allows.
   pub fn allows(&self) -> &Vec<T> {
   pub fn allows(&self) -> &Vec<T> {
     &self.0.allow
     &self.0.allow
@@ -146,9 +146,7 @@ impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandScope<'a, T
   }
   }
 }
 }
 
 
-impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandArg<'a, R>
-  for CommandScope<'a, T>
-{
+impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<'a, T> {
   /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`CommandScope`].
   /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`CommandScope`].
   fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
   fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
     if let Some(scope_id) = command.acl.as_ref().and_then(|resolved| resolved.scope) {
     if let Some(scope_id) = command.acl.as_ref().and_then(|resolved| resolved.scope) {
@@ -159,7 +157,7 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman
           .manager()
           .manager()
           .runtime_authority
           .runtime_authority
           .scope_manager
           .scope_manager
-          .get_command_scope_typed(&scope_id)?,
+          .get_command_scope_typed(command.message.webview.app_handle(), &scope_id)?,
       )))
       )))
     } else {
     } else {
       Ok(CommandScope(OwnedOrRef::Owned(ScopeValue {
       Ok(CommandScope(OwnedOrRef::Owned(ScopeValue {
@@ -172,9 +170,9 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman
 
 
 /// Global access scope that can be retrieved directly in the command function.
 /// Global access scope that can be retrieved directly in the command function.
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct GlobalScope<'a, T: Debug + DeserializeOwned + Send + Sync + 'static>(&'a ScopeValue<T>);
+pub struct GlobalScope<'a, T: ScopeObject>(&'a ScopeValue<T>);
 
 
-impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> GlobalScope<'a, T> {
+impl<'a, T: ScopeObject> GlobalScope<'a, T> {
   /// What this access scope allows.
   /// What this access scope allows.
   pub fn allows(&self) -> &Vec<T> {
   pub fn allows(&self) -> &Vec<T> {
     &self.0.allow
     &self.0.allow
@@ -186,9 +184,7 @@ impl<'a, T: Debug + DeserializeOwned + Send + Sync + 'static> GlobalScope<'a, T>
   }
   }
 }
 }
 
 
-impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> CommandArg<'a, R>
-  for GlobalScope<'a, T>
-{
+impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<'a, T> {
   /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`].
   /// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`].
   fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
   fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
     command
     command
@@ -205,7 +201,7 @@ impl<'a, R: Runtime, T: Debug + DeserializeOwned + Send + Sync + 'static> Comman
           .manager()
           .manager()
           .runtime_authority
           .runtime_authority
           .scope_manager
           .scope_manager
-          .get_global_scope_typed(plugin)
+          .get_global_scope_typed(command.message.webview.app_handle(), plugin)
           .map_err(InvokeError::from_error)
           .map_err(InvokeError::from_error)
       })
       })
       .map(GlobalScope)
       .map(GlobalScope)
@@ -220,9 +216,28 @@ pub struct ScopeManager {
   global_scope_cache: TypeMap![Send + Sync],
   global_scope_cache: TypeMap![Send + Sync],
 }
 }
 
 
+/// Marks a type as a scope object.
+///
+/// Usually you will just rely on [`serde::de::DeserializeOwned`] instead of implementing it manually,
+/// though this is useful if you need to do some initialization logic on the type itself.
+pub trait ScopeObject: Sized + Send + Sync + Debug + 'static {
+  /// The error type.
+  type Error: std::error::Error;
+  /// Deserialize the raw scope value.
+  fn deserialize<R: Runtime>(app: &AppHandle<R>, raw: Value) -> Result<Self, Self::Error>;
+}
+
+impl<T: Send + Sync + Debug + DeserializeOwned + 'static> ScopeObject for T {
+  type Error = serde_json::Error;
+  fn deserialize<R: Runtime>(_app: &AppHandle<R>, raw: Value) -> Result<Self, Self::Error> {
+    serde_json::from_value(raw.into())
+  }
+}
+
 impl ScopeManager {
 impl ScopeManager {
-  pub(crate) fn get_global_scope_typed<T: Send + Sync + DeserializeOwned + Debug + 'static>(
+  pub(crate) fn get_global_scope_typed<R: Runtime, T: ScopeObject>(
     &self,
     &self,
+    app: &AppHandle<R>,
     plugin: &str,
     plugin: &str,
   ) -> crate::Result<&ScopeValue<T>> {
   ) -> crate::Result<&ScopeValue<T>> {
     match self.global_scope_cache.try_get() {
     match self.global_scope_cache.try_get() {
@@ -233,10 +248,16 @@ impl ScopeManager {
 
 
         if let Some(global_scope) = self.global_scope.get(plugin) {
         if let Some(global_scope) = self.global_scope.get(plugin) {
           for allowed in &global_scope.allow {
           for allowed in &global_scope.allow {
-            allow.push(serde_json::from_value(allowed.clone().into())?);
+            allow.push(
+              T::deserialize(app, allowed.clone())
+                .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?,
+            );
           }
           }
           for denied in &global_scope.deny {
           for denied in &global_scope.deny {
-            deny.push(serde_json::from_value(denied.clone().into())?);
+            deny.push(
+              T::deserialize(app, denied.clone())
+                .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?,
+            );
           }
           }
         }
         }
 
 
@@ -247,8 +268,9 @@ impl ScopeManager {
     }
     }
   }
   }
 
 
-  fn get_command_scope_typed<T: Send + Sync + DeserializeOwned + Debug + 'static>(
+  fn get_command_scope_typed<R: Runtime, T: ScopeObject>(
     &self,
     &self,
+    app: &AppHandle<R>,
     key: &ScopeKey,
     key: &ScopeKey,
   ) -> crate::Result<&ScopeValue<T>> {
   ) -> crate::Result<&ScopeValue<T>> {
     let cache = self.command_cache.get(key).unwrap();
     let cache = self.command_cache.get(key).unwrap();
@@ -264,10 +286,16 @@ impl ScopeManager {
         let mut deny: Vec<T> = Vec::new();
         let mut deny: Vec<T> = Vec::new();
 
 
         for allowed in &resolved_scope.allow {
         for allowed in &resolved_scope.allow {
-          allow.push(serde_json::from_value(allowed.clone().into())?);
+          allow.push(
+            T::deserialize(app, allowed.clone())
+              .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?,
+          );
         }
         }
         for denied in &resolved_scope.deny {
         for denied in &resolved_scope.deny {
-          deny.push(serde_json::from_value(denied.clone().into())?);
+          deny.push(
+            T::deserialize(app, denied.clone())
+              .map_err(|e| crate::Error::CannotDeserializeScope(Box::new(e)))?,
+          );
         }
         }
 
 
         let value = ScopeValue { allow, deny };
         let value = ScopeValue { allow, deny };

+ 1 - 1
core/tauri/src/command/mod.rs

@@ -18,7 +18,7 @@ use serde::{
 
 
 mod authority;
 mod authority;
 
 
-pub use authority::{CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeValue};
+pub use authority::{CommandScope, GlobalScope, Origin, RuntimeAuthority, ScopeObject, ScopeValue};
 use tauri_utils::acl::resolved::ResolvedCommand;
 use tauri_utils::acl::resolved::ResolvedCommand;
 
 
 /// Represents a custom command.
 /// Represents a custom command.

+ 3 - 0
core/tauri/src/error.rs

@@ -145,6 +145,9 @@ pub enum Error {
   /// API requires the unstable feature flag.
   /// API requires the unstable feature flag.
   #[error("this feature requires the `unstable` flag on Cargo.toml")]
   #[error("this feature requires the `unstable` flag on Cargo.toml")]
   UnstableFeatureNotSupported,
   UnstableFeatureNotSupported,
+  /// Failed to deserialize scope object.
+  #[error("error deserializing scope: {0}")]
+  CannotDeserializeScope(Box<dyn std::error::Error>),
 }
 }
 
 
 /// `Result<T, ::tauri::Error>`
 /// `Result<T, ::tauri::Error>`

+ 3 - 5
core/tauri/src/plugin.rs

@@ -6,7 +6,7 @@
 
 
 use crate::{
 use crate::{
   app::UriSchemeResponder,
   app::UriSchemeResponder,
-  command::ScopeValue,
+  command::{ScopeObject, ScopeValue},
   ipc::{Invoke, InvokeHandler},
   ipc::{Invoke, InvokeHandler},
   manager::webview::UriSchemeProtocol,
   manager::webview::UriSchemeProtocol,
   utils::config::PluginConfig,
   utils::config::PluginConfig,
@@ -140,15 +140,13 @@ impl<R: Runtime, C: DeserializeOwned> PluginApi<R, C> {
   }
   }
 
 
   /// Gets the global scope defined on the permissions that are part of the app ACL.
   /// Gets the global scope defined on the permissions that are part of the app ACL.
-  pub fn scope<T: Debug + DeserializeOwned + Send + Sync + 'static>(
-    &self,
-  ) -> crate::Result<&ScopeValue<T>> {
+  pub fn scope<T: ScopeObject>(&self) -> crate::Result<&ScopeValue<T>> {
     self
     self
       .handle
       .handle
       .manager
       .manager
       .runtime_authority
       .runtime_authority
       .scope_manager
       .scope_manager
-      .get_global_scope_typed(self.name)
+      .get_global_scope_typed(&self.handle, self.name)
   }
   }
 }
 }