diff --git a/Cargo.lock b/Cargo.lock index 9062293..6473212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,7 +842,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "strum", + "strum 0.24.1", "test-log", "tokio", ] @@ -914,7 +914,7 @@ dependencies = [ "serde_bytes", "serde_json", "sha2 0.10.6", - "strum", + "strum 0.24.1", "tempfile", "test-log", "tokio", @@ -934,7 +934,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "strum", + "strum 0.25.0", ] [[package]] @@ -3221,7 +3221,16 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros 0.25.3", ] [[package]] @@ -3237,6 +3246,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.36", +] + [[package]] name = "subtle" version = "2.4.1" diff --git a/distant-core-protocol/Cargo.toml b/distant-core-protocol/Cargo.toml index 3d73af7..5809cfd 100644 --- a/distant-core-protocol/Cargo.toml +++ b/distant-core-protocol/Cargo.toml @@ -23,7 +23,7 @@ regex = "1.8.3" semver = { version = "1.0.17", features = ["serde"] } serde = { version = "1.0.163", features = ["derive"] } serde_bytes = "0.11.9" -strum = { version = "0.24.1", features = ["derive"] } +strum = { version = "0.25.0", features = ["derive"] } [dev-dependencies] rmp = "0.8.11" diff --git a/distant-core-protocol/src/response.rs b/distant-core-protocol/src/response.rs index 2ce3a54..e24f261 100644 --- a/distant-core-protocol/src/response.rs +++ b/distant-core-protocol/src/response.rs @@ -2,7 +2,7 @@ use std::io; use derive_more::IsVariant; use serde::{Deserialize, Serialize}; -use strum::{AsRefStr, EnumDiscriminants, EnumIter, EnumMessage, EnumString}; +use strum::{AsRefStr, EnumDiscriminants, EnumIter, EnumMessage, EnumString, IntoStaticStr}; use crate::common::{ Change, DirEntry, Error, Metadata, ProcessId, SearchId, SearchQueryMatch, SystemInfo, Version, @@ -10,7 +10,16 @@ use crate::common::{ /// Represents the payload of a successful response #[derive( - Clone, Debug, PartialEq, Eq, AsRefStr, IsVariant, EnumDiscriminants, Serialize, Deserialize, + Clone, + Debug, + PartialEq, + Eq, + AsRefStr, + IsVariant, + EnumDiscriminants, + Serialize, + Deserialize, + IntoStaticStr, )] #[strum_discriminants(derive( AsRefStr, @@ -21,6 +30,7 @@ use crate::common::{ Hash, PartialOrd, Ord, + IntoStaticStr, IsVariant, Serialize, Deserialize diff --git a/distant-plugin/src/api/ctx.rs b/distant-plugin/src/api/ctx.rs index 9ae1f2d..87d5237 100644 --- a/distant-plugin/src/api/ctx.rs +++ b/distant-plugin/src/api/ctx.rs @@ -1,7 +1,8 @@ use std::io; use async_trait::async_trait; -use distant_core_protocol::Response; + +use crate::protocol; /// Represents a context associated when an API request is being executed, supporting the ability /// to send responses back asynchronously. @@ -14,5 +15,5 @@ pub trait Ctx: Send { fn clone_ctx(&self) -> Box; /// Sends some response back. - fn send(&self, response: Response) -> io::Result<()>; + fn send(&self, msg: protocol::Msg) -> io::Result<()>; } diff --git a/distant-plugin/src/client.rs b/distant-plugin/src/client.rs index eb49ba3..0cc5880 100644 --- a/distant-plugin/src/client.rs +++ b/distant-plugin/src/client.rs @@ -2,13 +2,19 @@ use std::io; use std::sync::Arc; use async_trait::async_trait; -use distant_core_protocol::{Error, Request, Response}; use tokio::sync::mpsc; use crate::api::{ Api, Ctx, FileSystemApi, ProcessApi, SearchApi, SystemInfoApi, VersionApi, WatchApi, }; -use crate::common::Stream; +use crate::common::{Request, Response, Stream}; +use crate::protocol; + +mod err; +mod ext; + +pub use err::{ClientError, ClientResult}; +pub use ext::ClientExt; /// Full API for a distant-compatible client. #[async_trait] @@ -66,9 +72,9 @@ impl Client for ClientBridge { Box::new(__Ctx(self.0, self.1.clone())) } - fn send(&self, response: Response) -> io::Result<()> { + fn send(&self, msg: protocol::Msg) -> io::Result<()> { self.1 - .send(response) + .send(msg) .map_err(|_| io::Error::new(io::ErrorKind::Other, "Bridge has closed")) } } @@ -95,62 +101,102 @@ impl Client for ClientBridge { } } -/// Processes an incoming request. async fn handle_request(api: Arc, ctx: Box, request: Request) -> Response +where + T: Api, +{ + let origin = request.id; + let sequence = request.flags.sequence; + + Response { + id: rand::random(), + origin, + payload: match request.payload { + protocol::Msg::Single(request) => { + protocol::Msg::Single(handle_protocol_request(api, ctx, request).await) + } + protocol::Msg::Batch(requests) if sequence => { + let mut responses = Vec::new(); + for request in requests { + responses.push( + handle_protocol_request(Arc::clone(&api), ctx.clone_ctx(), request).await, + ); + } + protocol::Msg::Batch(responses) + } + protocol::Msg::Batch(requests) => { + let mut responses = Vec::new(); + for request in requests { + responses.push( + handle_protocol_request(Arc::clone(&api), ctx.clone_ctx(), request).await, + ); + } + protocol::Msg::Batch(responses) + } + }, + } +} + +/// Processes a singular protocol request using the provided api and ctx. +async fn handle_protocol_request( + api: Arc, + ctx: Box, + request: protocol::Request, +) -> protocol::Response where T: Api, { match request { - Request::Version {} => { + protocol::Request::Version {} => { let api = api.version(); api.version(ctx) .await - .map(Response::Version) - .unwrap_or_else(Response::from) + .map(protocol::Response::Version) + .unwrap_or_else(protocol::Response::from) } - Request::FileRead { path } => { + protocol::Request::FileRead { path } => { let api = api.file_system(); api.read_file(ctx, path) .await - .map(|data| Response::Blob { data }) - .unwrap_or_else(Response::from) + .map(|data| protocol::Response::Blob { data }) + .unwrap_or_else(protocol::Response::from) } - Request::FileReadText { path } => { + protocol::Request::FileReadText { path } => { let api = api.file_system(); api.read_file_text(ctx, path) .await - .map(|data| Response::Text { data }) - .unwrap_or_else(Response::from) + .map(|data| protocol::Response::Text { data }) + .unwrap_or_else(protocol::Response::from) } - Request::FileWrite { path, data } => { + protocol::Request::FileWrite { path, data } => { let api = api.file_system(); api.write_file(ctx, path, data) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::FileWriteText { path, text } => { + protocol::Request::FileWriteText { path, text } => { let api = api.file_system(); api.write_file_text(ctx, path, text) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::FileAppend { path, data } => { + protocol::Request::FileAppend { path, data } => { let api = api.file_system(); api.append_file(ctx, path, data) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::FileAppendText { path, text } => { + protocol::Request::FileAppendText { path, text } => { let api = api.file_system(); api.append_file_text(ctx, path, text) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::DirRead { + protocol::Request::DirRead { path, depth, absolute, @@ -160,41 +206,41 @@ where let api = api.file_system(); api.read_dir(ctx, path, depth, absolute, canonicalize, include_root) .await - .map(|(entries, errors)| Response::DirEntries { + .map(|(entries, errors)| protocol::Response::DirEntries { entries, - errors: errors.into_iter().map(Error::from).collect(), + errors: errors.into_iter().map(protocol::Error::from).collect(), }) - .unwrap_or_else(Response::from) + .unwrap_or_else(protocol::Response::from) } - Request::DirCreate { path, all } => { + protocol::Request::DirCreate { path, all } => { let api = api.file_system(); api.create_dir(ctx, path, all) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::Remove { path, force } => { + protocol::Request::Remove { path, force } => { let api = api.file_system(); api.remove(ctx, path, force) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::Copy { src, dst } => { + protocol::Request::Copy { src, dst } => { let api = api.file_system(); api.copy(ctx, src, dst) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::Rename { src, dst } => { + protocol::Request::Rename { src, dst } => { let api = api.file_system(); api.rename(ctx, src, dst) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::Watch { + protocol::Request::Watch { path, recursive, only, @@ -203,24 +249,24 @@ where let api = api.watch(); api.watch(ctx, path, recursive, only, except) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::Unwatch { path } => { + protocol::Request::Unwatch { path } => { let api = api.watch(); api.unwatch(ctx, path) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::Exists { path } => { + protocol::Request::Exists { path } => { let api = api.file_system(); api.exists(ctx, path) .await - .map(|value| Response::Exists { value }) - .unwrap_or_else(Response::from) + .map(|value| protocol::Response::Exists { value }) + .unwrap_or_else(protocol::Response::from) } - Request::Metadata { + protocol::Request::Metadata { path, canonicalize, resolve_file_type, @@ -228,10 +274,10 @@ where let api = api.file_system(); api.metadata(ctx, path, canonicalize, resolve_file_type) .await - .map(Response::Metadata) - .unwrap_or_else(Response::from) + .map(protocol::Response::Metadata) + .unwrap_or_else(protocol::Response::from) } - Request::SetPermissions { + protocol::Request::SetPermissions { path, permissions, options, @@ -239,24 +285,24 @@ where let api = api.file_system(); api.set_permissions(ctx, path, permissions, options) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::Search { query } => { + protocol::Request::Search { query } => { let api = api.search(); api.search(ctx, query) .await - .map(|id| Response::SearchStarted { id }) - .unwrap_or_else(Response::from) + .map(|id| protocol::Response::SearchStarted { id }) + .unwrap_or_else(protocol::Response::from) } - Request::CancelSearch { id } => { + protocol::Request::CancelSearch { id } => { let api = api.search(); api.cancel_search(ctx, id) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::ProcSpawn { + protocol::Request::ProcSpawn { cmd, environment, current_dir, @@ -265,36 +311,36 @@ where let api = api.process(); api.proc_spawn(ctx, cmd.into(), environment, current_dir, pty) .await - .map(|id| Response::ProcSpawned { id }) - .unwrap_or_else(Response::from) + .map(|id| protocol::Response::ProcSpawned { id }) + .unwrap_or_else(protocol::Response::from) } - Request::ProcKill { id } => { + protocol::Request::ProcKill { id } => { let api = api.process(); api.proc_kill(ctx, id) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::ProcStdin { id, data } => { + protocol::Request::ProcStdin { id, data } => { let api = api.process(); api.proc_stdin(ctx, id, data) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::ProcResizePty { id, size } => { + protocol::Request::ProcResizePty { id, size } => { let api = api.process(); api.proc_resize_pty(ctx, id, size) .await - .map(|_| Response::Ok) - .unwrap_or_else(Response::from) + .map(|_| protocol::Response::Ok) + .unwrap_or_else(protocol::Response::from) } - Request::SystemInfo {} => { + protocol::Request::SystemInfo {} => { let api = api.system_info(); api.system_info(ctx) .await - .map(Response::SystemInfo) - .unwrap_or_else(Response::from) + .map(protocol::Response::SystemInfo) + .unwrap_or_else(protocol::Response::from) } } } diff --git a/distant-plugin/src/client/err.rs b/distant-plugin/src/client/err.rs new file mode 100644 index 0000000..861426f --- /dev/null +++ b/distant-plugin/src/client/err.rs @@ -0,0 +1,75 @@ +use std::error; +use std::fmt; +use std::io; + +use crate::common::Id; +use crate::protocol; + +pub type ClientResult = Result; + +/// Errors that can occur from sending data using a client. +#[derive(Debug)] +pub enum ClientError { + /// A networking error occurred when trying to submit the request. + Io(io::Error), + + /// An error occurred server-side. + Server(protocol::Error), + + /// A response was received, but its origin did not match the request. + WrongOrigin { expected: Id, actual: Id }, + + /// A response was received, but the payload was single when expected batch or vice versa. + WrongPayloadFormat, + + /// A response was received, but its payload type did not match any expected response type. + WrongPayloadType { + expected: &'static [&'static str], + actual: &'static str, + }, +} + +impl error::Error for ClientError {} + +impl fmt::Display for ClientError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(x) => fmt::Display::fmt(x, f), + Self::Server(x) => fmt::Display::fmt(x, f), + Self::WrongOrigin { expected, actual } => { + write!( + f, + "Wrong response origin! Expected {expected}, got {actual}." + ) + } + Self::WrongPayloadFormat => write!(f, "Wrong response payload format!"), + Self::WrongPayloadType { expected, actual } => { + if expected.len() == 1 { + let expected = expected[0]; + write!( + f, + "Wrong response payload type! Wanted {expected}, but got {actual}." + ) + } else { + let expected = expected.join(","); + write!( + f, + "Wrong response type! Wanted one of {expected}, but got {actual}." + ) + } + } + } + } +} + +impl From for ClientError { + fn from(x: io::Error) -> Self { + Self::Io(x) + } +} + +impl From for ClientError { + fn from(x: protocol::Error) -> Self { + Self::Server(x) + } +} diff --git a/distant-plugin/src/client/ext.rs b/distant-plugin/src/client/ext.rs new file mode 100644 index 0000000..84f434d --- /dev/null +++ b/distant-plugin/src/client/ext.rs @@ -0,0 +1,228 @@ +use async_trait::async_trait; + +use std::io; +use std::path::PathBuf; + +use super::{Client, ClientError, ClientResult}; +use crate::common::{Request, RequestFlags, Response}; +use crate::protocol; + +macro_rules! impl_client_fn { + ($this:ident, $res:expr, $payload:expr) => {{ + let id = rand::random(); + let request = Request { + id, + flags: RequestFlags { + sequence: false, + ..Default::default() + }, + payload: protocol::Msg::Single($payload), + }; + + let response = $this.ask(request).await?; + if response.origin != id { + return Err(ClientError::WrongOrigin { + expected: id, + actual: response.origin, + }); + } + + match response.payload { + protocol::Msg::Single(protocol::Response::Error(x)) => Err(ClientError::Server(x)), + protocol::Msg::Single(x) if protocol::ResponseKind::from(&x) == $res => Ok(()), + protocol::Msg::Single(x) => Err(ClientError::WrongPayloadType { + expected: &[$res.into()], + actual: x.into(), + }), + protocol::Msg::Batch(_) => Err(ClientError::WrongPayloadFormat), + } + }}; +} + +/// Provides convenience functions on top of a [`Client`]. +#[async_trait] +pub trait ClientExt: Client { + /// Appends to a remote file using the data from a collection of bytes. + async fn append_file( + &mut self, + path: impl Into + Send, + data: impl Into> + Send, + ) -> ClientResult<()> { + impl_client_fn!( + self, + protocol::ResponseKind::Ok, + protocol::Request::FileAppend { + path: path.into(), + data: data.into(), + } + ) + } + + /// Appends to a remote file using the data from a string. + async fn append_file_text( + &mut self, + path: impl Into + Send, + text: impl Into + Send, + ) -> ClientResult<()> { + impl_client_fn!( + self, + protocol::ResponseKind::Ok, + protocol::Request::FileAppendText { + path: path.into(), + text: text.into(), + } + ) + } + + /// Copies a remote file or directory from src to dst. + async fn copy( + &mut self, + src: impl Into + Send, + dst: impl Into + Send, + ) -> ClientResult<()> { + impl_client_fn!( + self, + protocol::ResponseKind::Ok, + protocol::Request::Copy { + src: src.into(), + dst: dst.into(), + } + ) + } + + /// Creates a remote directory, optionally creating all parent components if specified. + async fn create_dir(&mut self, path: impl Into + Send, all: bool) -> ClientResult<()> { + impl_client_fn!( + self, + protocol::ResponseKind::Ok, + protocol::Request::DirCreate { + path: path.into(), + all, + } + ) + } + + /// Checks whether the `path` exists on the remote machine. + async fn exists(&mut self, path: impl Into) -> ClientResult { + impl_client_fn!( + self, + protocol::ResponseKind::Exists, + protocol::Request::Exists { path: path.into() } + ) + } + + /// Checks whether this client is compatible with the remote server. + async fn is_compatible(&mut self) -> io::Result; + + /// Retrieves metadata about a path on a remote machine. + async fn metadata( + &mut self, + path: impl Into, + canonicalize: bool, + resolve_file_type: bool, + ) -> io::Result; + + /// Sets permissions for a path on a remote machine. + async fn set_permissions( + &mut self, + path: impl Into, + permissions: protocol::Permissions, + options: protocol::SetPermissionsOptions, + ) -> io::Result<()>; + + /// Perform a search. + async fn search( + &mut self, + query: impl Into, + ) -> io::Result; + + /// Cancel an active search query. + async fn cancel_search(&mut self, id: protocol::SearchId) -> io::Result<()>; + + /// Reads entries from a directory, returning a tuple of directory entries and errors. + async fn read_dir( + &mut self, + path: impl Into, + depth: usize, + absolute: bool, + canonicalize: bool, + include_root: bool, + ) -> io::Result<(Vec, Vec)>; + + /// Reads a remote file as a collection of bytes. + async fn read_file(&mut self, path: impl Into) -> io::Result>; + + /// Returns a remote file as a string. + async fn read_file_text(&mut self, path: impl Into) -> io::Result; + + /// Removes a remote file or directory, supporting removal of non-empty directories if + /// force is true. + async fn remove(&mut self, path: impl Into, force: bool) -> io::Result<()>; + + /// Renames a remote file or directory from src to dst. + async fn rename(&mut self, src: impl Into, dst: impl Into) -> io::Result<()>; + + /// Watches a remote file or directory. + async fn watch( + &mut self, + path: impl Into, + recursive: bool, + only: impl Into, + except: impl Into, + ) -> io::Result; + + /// Unwatches a remote file or directory. + async fn unwatch(&mut self, path: impl Into) -> io::Result<()>; + + /// Spawns a process on the remote machine. + async fn spawn( + &mut self, + cmd: impl Into, + environment: Environment, + current_dir: Option, + pty: Option, + ) -> io::Result; + + /// Spawns an LSP process on the remote machine. + async fn spawn_lsp( + &mut self, + cmd: impl Into, + environment: Environment, + current_dir: Option, + pty: Option, + ) -> io::Result; + + /// Spawns a process on the remote machine and wait for it to complete. + async fn output( + &mut self, + cmd: impl Into, + environment: Environment, + current_dir: Option, + pty: Option, + ) -> io::Result; + + /// Retrieves information about the remote system. + async fn system_info(&mut self) -> io::Result; + + /// Retrieves server version information. + async fn version(&mut self) -> io::Result; + + /// Returns version of protocol that the client uses. + async fn protocol_version(&self) -> protocol::semver::Version; + + /// Writes a remote file with the data from a collection of bytes. + async fn write_file( + &mut self, + path: impl Into, + data: impl Into>, + ) -> io::Result<()>; + + /// Writes a remote file with the data from a string. + async fn write_file_text( + &mut self, + path: impl Into, + data: impl Into, + ) -> io::Result<()>; +} + +impl ClientExt for T {} diff --git a/distant-plugin/src/common.rs b/distant-plugin/src/common.rs index 1b724ae..ae04c8e 100644 --- a/distant-plugin/src/common.rs +++ b/distant-plugin/src/common.rs @@ -1,8 +1,10 @@ mod destination; mod map; +mod msg; mod stream; mod utils; pub use destination::{Destination, Host, HostParseError}; pub use map::{Map, MapParseError}; +pub use msg::{Id, Request, RequestFlags, Response}; pub use stream::Stream; diff --git a/distant-plugin/src/common/msg.rs b/distant-plugin/src/common/msg.rs new file mode 100644 index 0000000..30b2171 --- /dev/null +++ b/distant-plugin/src/common/msg.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +use crate::protocol; + +/// Represents an id associated with a request or response. +pub type Id = u64; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Request { + pub id: Id, + pub flags: RequestFlags, + pub payload: protocol::Msg, +} + +impl From> for Request { + fn from(msg: protocol::Msg) -> Self { + Self { + id: rand::random(), + flags: Default::default(), + payload: msg, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct RequestFlags { + /// If true, payload should be executed in sequence; otherwise, + /// a batch payload can be executed in any order. + pub sequence: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Response { + pub id: Id, + pub origin: Id, + pub payload: protocol::Msg, +} diff --git a/distant-plugin/src/lib.rs b/distant-plugin/src/lib.rs index fa2083a..ccf1b42 100644 --- a/distant-plugin/src/lib.rs +++ b/distant-plugin/src/lib.rs @@ -112,7 +112,8 @@ impl PluginRegistry { impl std::fmt::Debug for PluginRegistry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PluginHandlerRegistry") + f.debug_struct("PluginRegistry") + .field("loaded", &self.loaded) .field("launch_handlers", &self.launch_handlers.keys()) .field("connect_handlers", &self.connect_handlers.keys()) .finish()