use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct IpcEndpoint {
path: PathBuf,
}
#[cfg(unix)]
pub(crate) type IpcStream = tokio::net::UnixStream;
#[cfg(windows)]
pub(crate) type IpcStream = tokio::net::windows::named_pipe::NamedPipeClient;
impl IpcEndpoint {
#[must_use]
pub fn new(path: PathBuf) -> Self {
Self { path }
}
#[cfg_attr(not(test), expect(dead_code))]
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub(crate) async fn connect(&self) -> io::Result<IpcStream> {
#[cfg(unix)]
{
tokio::net::UnixStream::connect(&self.path).await
}
#[cfg(windows)]
{
tokio::net::windows::named_pipe::ClientOptions::new().open(&self.path)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn endpoint_stores_path() {
let endpoint = IpcEndpoint::new(PathBuf::from("/tmp/arcature-vite-test.sock"));
assert_eq!(endpoint.path(), Path::new("/tmp/arcature-vite-test.sock"));
}
#[cfg(unix)]
#[tokio::test]
async fn connect_returns_error_when_socket_absent() {
let endpoint = IpcEndpoint::new(PathBuf::from(
"/tmp/arcature-vite-nonexistent-socket-test.sock",
));
let result = endpoint.connect().await;
assert!(result.is_err());
let error = result.expect_err("connect should fail for absent socket");
assert_eq!(
error.kind(),
io::ErrorKind::NotFound,
"expected NotFound for absent Unix socket"
);
}
#[cfg(unix)]
#[tokio::test]
async fn connect_succeeds_when_socket_listens() {
let listener =
tokio::net::UnixListener::bind("/tmp/arcature-vite-endpoint-listen-test.sock")
.expect("bind should succeed");
let endpoint = IpcEndpoint::new(PathBuf::from(
"/tmp/arcature-vite-endpoint-listen-test.sock",
));
let result = endpoint.connect().await;
assert!(result.is_ok());
drop(listener);
let _ = std::fs::remove_file("/tmp/arcature-vite-endpoint-listen-test.sock");
}
}