use anyhow::{Context as _, Result, anyhow};
use serde::{Deserialize, Serialize};
use crate::{
ipc_transport::{Transport, decode_frame, encode_frame},
plugin::Contributions,
process_model::IpcMessage,
};
pub const EXTENSION_RPC_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExtensionRequest {
Activate,
Deactivate,
ExecuteCommand {
command_id: String,
args: Option<serde_json::Value>,
},
GetContributions,
Shutdown,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExtensionResponse {
Ack,
Contributions(Contributions),
Error(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExtensionNotification {
CommandExecuted {
command_id: String,
result: Option<serde_json::Value>,
},
PanelUpdated {
panel_id: String,
state: Option<serde_json::Value>,
},
SettingsChanged {
key: String,
value: serde_json::Value,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExtensionHandshake {
Host {
version: u32,
capabilities: Vec<serde_json::Value>,
},
Extension {
version: u32,
accepted: bool,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExtensionMessage {
Rpc(IpcMessage<ExtensionRequest, ExtensionResponse, (), String>),
Notification(ExtensionNotification),
Handshake(ExtensionHandshake),
}
pub struct ExtensionTransport {
inner: Box<dyn Transport>,
}
impl ExtensionTransport {
pub fn new(inner: Box<dyn Transport>) -> Self {
Self { inner }
}
pub fn send_request(&mut self, id: u64, body: ExtensionRequest) -> Result<()> {
let msg = ExtensionMessage::Rpc(IpcMessage::Request { id, body });
let payload = serde_json::to_vec(&msg).context("failed to serialize request")?;
self.inner.send_frame(&encode_frame(&payload))
}
pub fn send_response(
&mut self,
id: u64,
result: Result<ExtensionResponse, String>,
) -> Result<()> {
let msg = ExtensionMessage::Rpc(IpcMessage::Response { id, result });
let payload = serde_json::to_vec(&msg).context("failed to serialize response")?;
self.inner.send_frame(&encode_frame(&payload))
}
pub fn send_notification(&mut self, notification: ExtensionNotification) -> Result<()> {
let msg = ExtensionMessage::Notification(notification);
let payload = serde_json::to_vec(&msg).context("failed to serialize notification")?;
self.inner.send_frame(&encode_frame(&payload))
}
pub fn send_handshake(&mut self, handshake: ExtensionHandshake) -> Result<()> {
let msg = ExtensionMessage::Handshake(handshake);
let payload = serde_json::to_vec(&msg).context("failed to serialize handshake")?;
self.inner.send_frame(&encode_frame(&payload))
}
pub fn recv_message(&mut self) -> Result<ExtensionMessage> {
let frame = self.inner.recv_frame()?;
let (payload, _) = decode_frame(&frame)?.ok_or_else(|| anyhow!("incomplete frame"))?;
serde_json::from_slice(&payload).context("failed to deserialize message")
}
pub fn into_inner(self) -> Box<dyn Transport> {
self.inner
}
}
pub struct ExtensionRpcClient {
transport: ExtensionTransport,
}
impl ExtensionRpcClient {
pub fn new(transport: Box<dyn Transport>) -> Self {
Self {
transport: ExtensionTransport::new(transport),
}
}
pub fn recv_request(&mut self) -> Result<(u64, ExtensionRequest)> {
match self.transport.recv_message()? {
ExtensionMessage::Rpc(IpcMessage::Request { id, body }) => Ok((id, body)),
other => Err(anyhow!("unexpected message: {:?}", other)),
}
}
pub fn send_response(
&mut self,
id: u64,
result: Result<ExtensionResponse, String>,
) -> Result<()> {
self.transport.send_response(id, result)
}
pub fn send_notification(&mut self, notification: ExtensionNotification) -> Result<()> {
self.transport.send_notification(notification)
}
}
pub struct ExtensionRpcHost {
transport: ExtensionTransport,
}
impl ExtensionRpcHost {
pub fn new(transport: Box<dyn Transport>) -> Self {
Self {
transport: ExtensionTransport::new(transport),
}
}
pub fn send_request(&mut self, id: u64, request: ExtensionRequest) -> Result<()> {
self.transport.send_request(id, request)
}
pub fn recv_response(&mut self) -> Result<(u64, Result<ExtensionResponse, String>)> {
match self.transport.recv_message()? {
ExtensionMessage::Rpc(IpcMessage::Response { id, result }) => Ok((id, result)),
other => Err(anyhow!("unexpected message: {:?}", other)),
}
}
pub fn recv_notification(&mut self) -> Result<ExtensionNotification> {
match self.transport.recv_message()? {
ExtensionMessage::Notification(notification) => Ok(notification),
other => Err(anyhow!("unexpected message: {:?}", other)),
}
}
pub fn send_ack(&mut self, id: u64) -> Result<()> {
self.transport.send_response(id, Ok(ExtensionResponse::Ack))
}
pub fn send_error(&mut self, id: u64, error: String) -> Result<()> {
self.transport.send_response(id, Err(error))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ipc_transport::InMemoryTransport;
#[test]
fn test_extension_rpc_roundtrip() {
let (ta, tb) = InMemoryTransport::pair();
let mut host = ExtensionTransport::new(Box::new(ta));
let mut client = ExtensionTransport::new(Box::new(tb));
host.send_request(1, ExtensionRequest::GetContributions)
.unwrap();
let msg = client.recv_message().unwrap();
assert!(matches!(
msg,
ExtensionMessage::Rpc(IpcMessage::Request {
id: 1,
body: ExtensionRequest::GetContributions
})
));
client.send_response(1, Ok(ExtensionResponse::Ack)).unwrap();
let msg = host.recv_message().unwrap();
assert!(matches!(
msg,
ExtensionMessage::Rpc(IpcMessage::Response {
id: 1,
result: Ok(ExtensionResponse::Ack)
})
));
}
#[test]
fn test_extension_notification_roundtrip() {
let (ta, tb) = InMemoryTransport::pair();
let mut host = ExtensionTransport::new(Box::new(ta));
let mut client = ExtensionTransport::new(Box::new(tb));
let notification = ExtensionNotification::SettingsChanged {
key: "theme".to_string(),
value: serde_json::json!("dark"),
};
client.send_notification(notification.clone()).unwrap();
let received = host.recv_message().unwrap();
assert_eq!(received, ExtensionMessage::Notification(notification));
}
#[test]
fn test_extension_handshake_roundtrip() {
let (ta, tb) = InMemoryTransport::pair();
let mut host = ExtensionTransport::new(Box::new(ta));
let mut client = ExtensionTransport::new(Box::new(tb));
let handshake = ExtensionHandshake::Host {
version: 1,
capabilities: vec![serde_json::json!("network")],
};
host.send_handshake(handshake.clone()).unwrap();
let received = client.recv_message().unwrap();
assert_eq!(received, ExtensionMessage::Handshake(handshake));
}
#[test]
fn test_extension_rpc_client_api() {
let (ta, tb) = InMemoryTransport::pair();
let mut client = ExtensionRpcClient::new(Box::new(ta));
let mut host_transport = ExtensionTransport::new(Box::new(tb));
host_transport
.send_request(42, ExtensionRequest::Activate)
.unwrap();
let (id, req) = client.recv_request().unwrap();
assert_eq!(id, 42);
assert_eq!(req, ExtensionRequest::Activate);
client
.send_response(42, Ok(ExtensionResponse::Ack))
.unwrap();
let (id, result) = ExtensionRpcHost::new(host_transport.into_inner())
.recv_response()
.unwrap();
assert_eq!(id, 42);
assert_eq!(result, Ok(ExtensionResponse::Ack));
}
}