Kaynağa Gözat

feat(core): implement `Debug` on public API structs/enums, closes #2292 (#2387)

Lucas Fernandes Nogueira 4 yıl önce
ebeveyn
işleme
fa9341ba18

+ 9 - 0
.changes/implement-debug.md

@@ -0,0 +1,9 @@
+---
+"tauri": patch
+"tauri-build": patch
+"tauri-utils": patch
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+Implement `Debug` on public API structs and enums.

+ 2 - 1
core/tauri-build/src/lib.rs

@@ -16,6 +16,7 @@ pub use codegen::context::CodegenContext;
 
 /// Attributes used on Windows.
 #[allow(dead_code)]
+#[derive(Debug)]
 pub struct WindowsAttributes {
   window_icon_path: PathBuf,
 }
@@ -43,7 +44,7 @@ impl WindowsAttributes {
 }
 
 /// The attributes used on the build.
-#[derive(Default)]
+#[derive(Debug, Default)]
 pub struct Attributes {
   #[allow(dead_code)]
   windows_attributes: WindowsAttributes,

+ 27 - 7
core/tauri-runtime-wry/src/lib.rs

@@ -69,6 +69,7 @@ use std::{
     HashMap,
   },
   convert::TryFrom,
+  fmt,
   fs::read,
   path::PathBuf,
   sync::{
@@ -126,7 +127,7 @@ macro_rules! getter {
   }};
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 struct EventLoopContext {
   main_thread_id: ThreadId,
   is_event_loop_running: Arc<AtomicBool>,
@@ -146,6 +147,15 @@ pub struct GlobalShortcutManagerHandle {
   listeners: GlobalShortcutListeners,
 }
 
+impl fmt::Debug for GlobalShortcutManagerHandle {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    f.debug_struct("GlobalShortcutManagerHandle")
+      .field("context", &self.context)
+      .field("shortcuts", &self.shortcuts)
+      .finish()
+  }
+}
+
 impl GlobalShortcutManager for GlobalShortcutManagerHandle {
   fn is_registered(&self, accelerator: &str) -> Result<bool> {
     let (tx, rx) = channel();
@@ -205,7 +215,7 @@ impl GlobalShortcutManager for GlobalShortcutManagerHandle {
   }
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct ClipboardManagerWrapper {
   context: EventLoopContext,
 }
@@ -728,7 +738,7 @@ pub enum WebviewEvent {
 }
 
 #[cfg(feature = "system-tray")]
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub enum TrayMessage {
   UpdateItem(u16, menu::MenuUpdate),
   UpdateIcon(Icon),
@@ -736,7 +746,7 @@ pub enum TrayMessage {
   UpdateIconAsTemplate(bool),
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub enum GlobalShortcutMessage {
   IsRegistered(Accelerator, Sender<bool>),
   Register(Accelerator, Sender<Result<GlobalShortcutWrapper>>),
@@ -744,7 +754,7 @@ pub enum GlobalShortcutMessage {
   UnregisterAll(Sender<Result<()>>),
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub enum ClipboardMessage {
   WriteText(String, Sender<()>),
   ReadText(Sender<Option<String>>),
@@ -780,8 +790,18 @@ struct DispatcherContext {
   menu_event_listeners: MenuEventListeners,
 }
 
+impl fmt::Debug for DispatcherContext {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    f.debug_struct("DispatcherContext")
+      .field("main_thread_id", &self.main_thread_id)
+      .field("is_event_loop_running", &self.is_event_loop_running)
+      .field("proxy", &self.proxy)
+      .finish()
+  }
+}
+
 /// The Tauri [`Dispatch`] for [`Wry`].
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct WryDispatcher {
   window_id: WindowId,
   context: DispatcherContext,
@@ -1261,7 +1281,7 @@ pub struct Wry {
 }
 
 /// A handle to the Wry runtime.
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct WryHandle {
   dispatcher_context: DispatcherContext,
 }

+ 2 - 1
core/tauri-runtime-wry/src/menu.rs

@@ -56,7 +56,7 @@ pub type SystemTrayEventListeners = Arc<Mutex<HashMap<Uuid, SystemTrayEventHandl
 pub type SystemTrayItems = Arc<Mutex<HashMap<u16, WryCustomMenuItem>>>;
 
 #[cfg(feature = "system-tray")]
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct SystemTrayHandle {
   pub(crate) proxy: EventLoopProxy<super::Message>,
 }
@@ -87,6 +87,7 @@ impl TrayHandle for SystemTrayHandle {
 }
 
 #[cfg(target_os = "macos")]
+#[derive(Debug)]
 pub struct NativeImageWrapper(pub WryNativeImage);
 
 #[cfg(target_os = "macos")]

+ 6 - 4
core/tauri-runtime/src/lib.rs

@@ -31,6 +31,7 @@ use window::{
 
 #[cfg(feature = "system-tray")]
 #[non_exhaustive]
+#[derive(Debug)]
 pub struct SystemTray {
   pub icon: Option<Icon>,
   pub menu: Option<menu::SystemTrayMenu>,
@@ -193,6 +194,7 @@ pub enum ExitRequestedEventAction {
 }
 
 /// A system tray event.
+#[derive(Debug)]
 pub enum SystemTrayEvent {
   MenuItemClick(u16),
   LeftClick {
@@ -216,7 +218,7 @@ pub struct RunIteration {
 }
 
 /// A [`Send`] handle to the runtime.
-pub trait RuntimeHandle: Send + Sized + Clone + 'static {
+pub trait RuntimeHandle: Debug + Send + Sized + Clone + 'static {
   type Runtime: Runtime<Handle = Self>;
   /// Create a new webview window.
   fn create_window(
@@ -230,7 +232,7 @@ pub trait RuntimeHandle: Send + Sized + Clone + 'static {
 }
 
 /// A global shortcut manager.
-pub trait GlobalShortcutManager {
+pub trait GlobalShortcutManager: Debug {
   /// Whether the application has registered the given `accelerator`.
   ///
   /// # Panics
@@ -269,7 +271,7 @@ pub trait GlobalShortcutManager {
 }
 
 /// Clipboard manager.
-pub trait ClipboardManager {
+pub trait ClipboardManager: Debug {
   /// Writes the text into the clipboard as plain text.
   ///
   /// # Panics
@@ -335,7 +337,7 @@ pub trait Runtime: Sized + 'static {
 }
 
 /// Webview dispatcher. A thread-safe handle to the webview API.
-pub trait Dispatch: Clone + Send + Sized + 'static {
+pub trait Dispatch: Debug + Clone + Send + Sized + 'static {
   /// The runtime this [`Dispatch`] runs under.
   type Runtime: Runtime;
 

+ 2 - 1
core/tauri-runtime/src/menu.rs

@@ -4,6 +4,7 @@
 
 use std::{
   collections::hash_map::DefaultHasher,
+  fmt,
   hash::{Hash, Hasher},
 };
 
@@ -145,7 +146,7 @@ pub enum MenuUpdate {
   SetNativeImage(NativeImage),
 }
 
-pub trait TrayHandle {
+pub trait TrayHandle: fmt::Debug {
   fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
   fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
   #[cfg(target_os = "macos")]

+ 15 - 3
core/tauri-runtime/src/webview.rs

@@ -16,7 +16,7 @@ use tauri_utils::config::{WindowConfig, WindowUrl};
 #[cfg(windows)]
 use winapi::shared::windef::HWND;
 
-use std::{collections::HashMap, path::PathBuf};
+use std::{collections::HashMap, fmt, path::PathBuf};
 
 type UriSchemeProtocol =
   dyn Fn(&str) -> Result<Vec<u8>, Box<dyn std::error::Error>> + Send + Sync + 'static;
@@ -30,6 +30,17 @@ pub struct WebviewAttributes {
   pub file_drop_handler_enabled: bool,
 }
 
+impl fmt::Debug for WebviewAttributes {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    f.debug_struct("WebviewAttributes")
+      .field("url", &self.url)
+      .field("initialization_scripts", &self.initialization_scripts)
+      .field("data_directory", &self.data_directory)
+      .field("file_drop_handler_enabled", &self.file_drop_handler_enabled)
+      .finish()
+  }
+}
+
 impl WebviewAttributes {
   /// Initializes the default attributes for a webview.
   pub fn new(url: WindowUrl) -> Self {
@@ -93,7 +104,7 @@ impl WebviewAttributes {
 /// Do **NOT** implement this trait except for use in a custom [`Runtime`](crate::Runtime).
 ///
 /// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
-pub trait WindowBuilderBase: Sized {}
+pub trait WindowBuilderBase: fmt::Debug + Sized {}
 
 /// A builder for all attributes related to a single webview.
 ///
@@ -189,6 +200,7 @@ pub trait WindowBuilder: WindowBuilderBase {
 }
 
 /// Rpc request.
+#[derive(Debug)]
 pub struct RpcRequest {
   /// RPC command.
   pub command: String,
@@ -222,7 +234,7 @@ pub type WebviewRpcHandler<R> = Box<dyn Fn(DetachedWindow<R>, RpcRequest) + Send
 /// Return `true` in the callback to block the OS' default behavior of handling a file drop.
 pub type FileDropHandler<R> = Box<dyn Fn(FileDropEvent, DetachedWindow<R>) -> bool + Send>;
 
-#[derive(Deserialize)]
+#[derive(Debug, Deserialize)]
 pub struct InvokePayload {
   #[serde(rename = "__tauriModule")]
   pub tauri_module: Option<String>,

+ 2 - 1
core/tauri-runtime/src/window.rs

@@ -48,7 +48,7 @@ pub enum WindowEvent {
 }
 
 /// A menu event.
-#[derive(Serialize)]
+#[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct MenuEvent {
   pub menu_item_id: u16,
@@ -110,6 +110,7 @@ impl<R: Runtime> PendingWindow<R> {
 }
 
 /// A webview window that is not yet managed by Tauri.
+#[derive(Debug)]
 pub struct DetachedWindow<R: Runtime> {
   /// Name of the window
   pub label: String,

+ 1 - 0
core/tauri-utils/src/assets.rs

@@ -80,6 +80,7 @@ pub trait Assets: Send + Sync + 'static {
 }
 
 /// [`Assets`] implementation that only contains compile-time compressed and embedded assets.
+#[derive(Debug)]
 pub struct EmbeddedAssets(phf::Map<&'static str, &'static [u8]>);
 
 impl EmbeddedAssets {

+ 1 - 1
core/tauri/src/api/dialog.rs

@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
 /// The file dialog builder.
 /// Constructs file picker dialogs that can select single/multiple files or directories.
 #[cfg(any(dialog_open, dialog_save))]
-#[derive(Default)]
+#[derive(Debug, Default)]
 pub struct FileDialogBuilder(rfd::FileDialog);
 
 #[cfg(any(dialog_open, dialog_save))]

+ 11 - 8
core/tauri/src/api/http.rs

@@ -10,7 +10,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
 use std::{collections::HashMap, path::PathBuf, time::Duration};
 
 /// Client builder.
-#[derive(Clone, Default, Deserialize)]
+#[derive(Debug, Clone, Default, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct ClientBuilder {
   /// Max number of redirections to follow
@@ -63,12 +63,12 @@ impl ClientBuilder {
 
 /// The HTTP client.
 #[cfg(feature = "reqwest-client")]
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct Client(reqwest::Client);
 
 /// The HTTP client.
 #[cfg(not(feature = "reqwest-client"))]
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct Client(ClientBuilder);
 
 #[cfg(not(feature = "reqwest-client"))]
@@ -197,7 +197,7 @@ pub enum ResponseType {
 }
 
 /// FormBody data types.
-#[derive(Deserialize)]
+#[derive(Debug, Deserialize)]
 #[serde(untagged)]
 #[non_exhaustive]
 pub enum FormPart {
@@ -210,7 +210,7 @@ pub enum FormPart {
 }
 
 /// Form body definition.
-#[derive(Deserialize)]
+#[derive(Debug, Deserialize)]
 pub struct FormBody(HashMap<String, FormPart>);
 
 impl FormBody {
@@ -221,7 +221,7 @@ impl FormBody {
 }
 
 /// A body for the request.
-#[derive(Deserialize)]
+#[derive(Debug, Deserialize)]
 #[serde(tag = "type", content = "payload")]
 #[non_exhaustive]
 pub enum Body {
@@ -255,7 +255,7 @@ pub enum Body {
 ///   }
 /// }
 /// ```
-#[derive(Deserialize)]
+#[derive(Debug, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct HttpRequestBuilder {
   /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
@@ -321,9 +321,11 @@ impl HttpRequestBuilder {
 
 /// The HTTP response.
 #[cfg(feature = "reqwest-client")]
+#[derive(Debug)]
 pub struct Response(ResponseType, reqwest::Response);
 /// The HTTP response.
 #[cfg(not(feature = "reqwest-client"))]
+#[derive(Debug)]
 pub struct Response(ResponseType, attohttpc::Response, String);
 
 impl Response {
@@ -375,6 +377,7 @@ impl Response {
 
 /// A response with raw bytes.
 #[non_exhaustive]
+#[derive(Debug)]
 pub struct RawResponse {
   /// Response status code.
   pub status: u16,
@@ -383,7 +386,7 @@ pub struct RawResponse {
 }
 
 /// The response type.
-#[derive(Serialize)]
+#[derive(Debug, Serialize)]
 #[serde(rename_all = "camelCase")]
 #[non_exhaustive]
 pub struct ResponseData {

+ 1 - 1
core/tauri/src/api/notification.rs

@@ -18,7 +18,7 @@ use std::path::MAIN_SEPARATOR;
 ///   .show();
 /// ```
 #[allow(dead_code)]
-#[derive(Default)]
+#[derive(Debug, Default)]
 pub struct Notification {
   /// The notification body.
   body: Option<String>,

+ 4 - 0
core/tauri/src/api/process/command.rs

@@ -86,6 +86,7 @@ macro_rules! get_std_command {
 }
 
 /// API to spawn commands.
+#[derive(Debug)]
 pub struct Command {
   program: String,
   args: Vec<String>,
@@ -95,6 +96,7 @@ pub struct Command {
 }
 
 /// Child spawned.
+#[derive(Debug)]
 pub struct CommandChild {
   inner: Arc<SharedChild>,
   stdin_writer: PipeWriter,
@@ -120,6 +122,7 @@ impl CommandChild {
 }
 
 /// Describes the result of a process after it has terminated.
+#[derive(Debug)]
 pub struct ExitStatus {
   code: Option<i32>,
 }
@@ -137,6 +140,7 @@ impl ExitStatus {
 }
 
 /// The output of a finished process.
+#[derive(Debug)]
 pub struct Output {
   /// The status (exit code) of the process.
   pub status: ExitStatus,

+ 4 - 0
core/tauri/src/app.rs

@@ -97,6 +97,7 @@ pub enum Event {
 #[cfg(feature = "menu")]
 #[cfg_attr(doc_cfg, doc(cfg(feature = "menu")))]
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct WindowMenuEvent<R: Runtime> {
   pub(crate) menu_item_id: MenuId,
   pub(crate) window: Window<R>,
@@ -117,6 +118,7 @@ impl<R: Runtime> WindowMenuEvent<R> {
 
 /// A window event that was triggered on the specified window.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct GlobalWindowEvent<R: Runtime> {
   pub(crate) event: WindowEvent,
   pub(crate) window: Window<R>,
@@ -157,6 +159,7 @@ impl PathResolver {
 ///
 /// This type implements [`Manager`] which allows for manipulation of global application items.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct AppHandle<R: Runtime> {
   runtime_handle: R::Handle,
   manager: WindowManager<R>,
@@ -280,6 +283,7 @@ impl<R: Runtime> ManagerBase<R> for AppHandle<R> {
 ///
 /// This type implements [`Manager`] which allows for manipulation of global application items.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct App<R: Runtime> {
   runtime: Option<R>,
   manager: WindowManager<R>,

+ 2 - 0
core/tauri/src/app/tray.rs

@@ -78,6 +78,7 @@ pub enum SystemTrayEvent {
 
 /// A handle to a system tray. Allows updating the context menu items.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct SystemTrayHandle<R: Runtime> {
   pub(crate) ids: Arc<HashMap<MenuHash, MenuId>>,
   pub(crate) inner: R::TrayHandler,
@@ -94,6 +95,7 @@ impl<R: Runtime> Clone for SystemTrayHandle<R> {
 
 /// A handle to a system tray menu item.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct SystemTrayMenuItemHandle<R: Runtime> {
   id: MenuHash,
   tray_handler: R::TrayHandler,

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

@@ -39,6 +39,7 @@ impl PageLoadPayload {
 
 /// The message and resolver given to a custom command.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct Invoke<R: Runtime> {
   /// The message passed.
   pub message: InvokeMessage<R>,
@@ -116,6 +117,7 @@ impl From<InvokeError> for InvokeResponse {
 
 /// Resolver of a invoke message.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct InvokeResolver<R: Runtime> {
   window: Window<R>,
   pub(crate) callback: String,
@@ -235,6 +237,7 @@ impl<R: Runtime> InvokeResolver<R> {
 
 /// An invoke message.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct InvokeMessage<R: Runtime> {
   /// The window that received the invoke message.
   pub(crate) window: Window<R>,

+ 1 - 0
core/tauri/src/lib.rs

@@ -155,6 +155,7 @@ macro_rules! tauri_build_context {
 /// # Stability
 /// This is the output of the `tauri::generate_context!` macro, and is not considered part of the stable API.
 /// Unless you know what you are doing and are prepared for this type to have breaking changes, do not create it yourself.
+#[derive(Debug)]
 pub struct Context<A: Assets> {
   pub(crate) config: Config,
   pub(crate) assets: Arc<A>,

+ 23 - 0
core/tauri/src/manager.rs

@@ -40,6 +40,7 @@ use serde_json::Value as JsonValue;
 use std::{
   borrow::Cow,
   collections::{HashMap, HashSet},
+  fmt,
   fs::create_dir_all,
   sync::{Arc, Mutex, MutexGuard},
 };
@@ -92,7 +93,29 @@ pub struct InnerWindowManager<R: Runtime> {
   window_event_listeners: Arc<Vec<GlobalWindowEventListener<R>>>,
 }
 
+impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    let mut s = f.debug_struct("InnerWindowManager");
+    #[allow(unused_mut)]
+    let mut w = s
+      .field("plugins", &self.plugins)
+      .field("state", &self.state)
+      .field("config", &self.config)
+      .field("default_window_icon", &self.default_window_icon)
+      .field("salts", &self.salts)
+      .field("package_info", &self.package_info);
+    #[cfg(feature = "menu")]
+    {
+      w = w
+        .field("menu", &self.menu)
+        .field("menu_ids", &self.menu_ids);
+    }
+    w.finish()
+  }
+}
+
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct WindowManager<R: Runtime> {
   pub inner: Arc<InnerWindowManager<R>>,
   invoke_keys: Arc<Mutex<Vec<u32>>>,

+ 10 - 2
core/tauri/src/plugin.rs

@@ -8,10 +8,10 @@ use crate::{
   api::config::PluginConfig, runtime::Runtime, AppHandle, Invoke, PageLoadPayload, Window,
 };
 use serde_json::Value as JsonValue;
-use std::collections::HashMap;
-
 use tauri_macros::default_runtime;
 
+use std::{collections::HashMap, fmt};
+
 /// The plugin result type.
 pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
 
@@ -54,6 +54,14 @@ pub(crate) struct PluginStore<R: Runtime> {
   store: HashMap<&'static str, Box<dyn Plugin<R>>>,
 }
 
+impl<R: Runtime> fmt::Debug for PluginStore<R> {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    f.debug_struct("PluginStore")
+      .field("plugins", &self.store.keys())
+      .finish()
+  }
+}
+
 impl<R: Runtime> Default for PluginStore<R> {
   fn default() -> Self {
     Self {

+ 1 - 1
core/tauri/src/settings.rs

@@ -18,7 +18,7 @@ use std::{
 };
 
 /// Tauri Settings.
-#[derive(Default, Deserialize, Serialize)]
+#[derive(Debug, Clone, Default, Deserialize, Serialize)]
 #[non_exhaustive]
 pub struct Settings {
   /// Whether the user allows notifications or not.

+ 1 - 0
core/tauri/src/state.rs

@@ -45,6 +45,7 @@ impl<'r, 'de: 'r, T: Send + Sync + 'static, R: Runtime> CommandArg<'de, R> for S
 }
 
 /// The Tauri state manager.
+#[derive(Debug)]
 pub struct StateManager(pub(crate) Container![Send + Sync]);
 
 impl StateManager {

+ 2 - 1
core/tauri/src/updater/core.rs

@@ -169,6 +169,7 @@ impl RemoteRelease {
   }
 }
 
+#[derive(Debug)]
 pub struct UpdateBuilder<'a> {
   /// Current version we are running to compare with announced version
   pub current_version: &'a str,
@@ -375,7 +376,7 @@ pub fn builder<'a>() -> UpdateBuilder<'a> {
   UpdateBuilder::new()
 }
 
-#[derive(Clone)]
+#[derive(Debug, Clone)]
 pub struct Update {
   /// Update description
   pub body: Option<String>,

+ 1 - 0
core/tauri/src/window.rs

@@ -86,6 +86,7 @@ impl Monitor {
 /// This type also implements [`Manager`] which allows you to manage other windows attached to
 /// the same application.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct Window<R: Runtime> {
   /// The webview window created by the runtime.
   window: DetachedWindow<R>,

+ 2 - 0
core/tauri/src/window/menu.rs

@@ -28,6 +28,7 @@ impl MenuEvent {
 
 /// A handle to a system tray. Allows updating the context menu items.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct MenuHandle<R: Runtime> {
   pub(crate) ids: HashMap<MenuHash, MenuId>,
   pub(crate) dispatcher: R::Dispatcher,
@@ -44,6 +45,7 @@ impl<R: Runtime> Clone for MenuHandle<R> {
 
 /// A handle to a system tray menu item.
 #[default_runtime(crate::Wry, wry)]
+#[derive(Debug)]
 pub struct MenuItemHandle<R: Runtime> {
   id: u16,
   dispatcher: R::Dispatcher,