use std::path::Path;
use anyhow::Context;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines, ReadHalf, WriteHalf};
use tokio::net::UnixStream;
use tracing::{Level, instrument};
pub struct IpcClient {
writer: WriteHalf<UnixStream>,
reader: Lines<BufReader<ReadHalf<UnixStream>>>,
}
impl IpcClient {
pub async fn new(socket_path: &Path) -> anyhow::Result<Self> {
let stream = UnixStream::connect(socket_path)
.await
.with_context(|| format!("Failed to connect to socket at {}", socket_path.display()))?;
let (reader, writer) = tokio::io::split(stream);
Ok(Self {
writer,
reader: BufReader::new(reader).lines(),
})
}
#[instrument(skip_all, level = Level::DEBUG)]
pub async fn request<R: ?Sized + serde::Serialize>(
&mut self,
request: &R,
bytes: Option<&[u8]>,
) -> anyhow::Result<serde_json::Value> {
let mut request = serde_json::to_string(request)?;
request.push('\n');
self.writer.write_all(request.as_bytes()).await?;
if let Some(bytes) = bytes {
self.writer.write_all(bytes).await?;
}
self.writer.flush().await?;
match self.reader.next_line().await {
Ok(Some(response)) => serde_json::from_str(&response)
.map_err(|error| anyhow::anyhow!("Failed to deserialize response: {error:?}")),
Ok(None) => Err(anyhow::anyhow!("Unexpected EOF from IPC server")),
Err(error) => Err(anyhow::anyhow!(
"Unexpected error reading from IPC server: {error:?}"
)),
}
}
pub async fn shutdown(mut self) -> std::io::Result<()> {
self.writer.shutdown().await
}
}