Skip to main content

debugger/ipc/
client.rs

1//! CLI-side IPC client for communicating with the daemon
2
3use tokio::io::{ReadHalf, WriteHalf};
4
5use crate::common::{Error, Result};
6
7use super::protocol::{Command, Request, Response};
8use super::transport::{self, Stream};
9
10/// Client for communicating with the debugger daemon
11pub struct DaemonClient {
12    reader: ReadHalf<Stream>,
13    writer: WriteHalf<Stream>,
14    next_id: u64,
15}
16
17impl DaemonClient {
18    /// Connect to the running daemon
19    pub async fn connect() -> Result<Self> {
20        let stream = transport::connect().await.map_err(|e| {
21            if e.kind() == std::io::ErrorKind::NotFound
22                || e.kind() == std::io::ErrorKind::ConnectionRefused
23            {
24                Error::DaemonNotRunning
25            } else {
26                Error::DaemonConnectionFailed(e)
27            }
28        })?;
29
30        let (reader, writer) = tokio::io::split(stream);
31
32        Ok(Self {
33            reader,
34            writer,
35            next_id: 1,
36        })
37    }
38
39    /// Send a command and wait for the response
40    pub async fn send_command(&mut self, command: Command) -> Result<serde_json::Value> {
41        let id = self.next_id;
42        self.next_id += 1;
43
44        let request = Request { id, command };
45        let json = serde_json::to_vec(&request)?;
46
47        transport::send_message(&mut self.writer, &json)
48            .await
49            .map_err(|e| Error::DaemonCommunication(e.to_string()))?;
50
51        let response_data = transport::recv_message(&mut self.reader)
52            .await
53            .map_err(|e| Error::DaemonCommunication(e.to_string()))?;
54
55        let response: Response = serde_json::from_slice(&response_data)?;
56
57        if response.id != id {
58            return Err(Error::DaemonCommunication(format!(
59                "Response ID mismatch: expected {}, got {}",
60                id, response.id
61            )));
62        }
63
64        if response.success {
65            Ok(response.result.unwrap_or(serde_json::json!({})))
66        } else {
67            let error = response
68                .error
69                .unwrap_or_else(|| crate::common::error::IpcError {
70                    code: "UNKNOWN".to_string(),
71                    message: "Unknown error".to_string(),
72                });
73            Err(error.into())
74        }
75    }
76
77    /// Check if daemon is responding
78    pub async fn ping(&mut self) -> Result<bool> {
79        match self.send_command(Command::Status).await {
80            Ok(_) => Ok(true),
81            Err(Error::DaemonNotRunning) => Ok(false),
82            Err(e) => Err(e),
83        }
84    }
85}