Skip to main content

greplm_core/
client.rs

1//! Client for the greplm daemon.
2
3#[cfg(unix)]
4pub use unix_impl::Client;
5
6#[cfg(not(unix))]
7pub use stub_impl::Client;
8
9// The daemon is built on Unix domain sockets and is unavailable on other
10// platforms. This stub lets the CLI compile everywhere; `try_connect` always
11// returns `None`, so callers transparently fall back to in-process queries.
12#[cfg(not(unix))]
13mod stub_impl {
14    use std::path::Path;
15
16    use crate::error::{Error, Result};
17    use crate::proto::{Request, Response};
18
19    /// A connected client to a running greplm daemon (unsupported on this platform).
20    pub struct Client {
21        _private: (),
22    }
23
24    impl Client {
25        /// Always returns `None`: no daemon is available on this platform.
26        pub fn try_connect(_socket: &Path) -> Option<Client> {
27            None
28        }
29
30        /// Always errors: no daemon is available on this platform.
31        pub fn request(&mut self, _req: &Request) -> Result<Response> {
32            Err(Error::other(
33                "greplm daemon is not supported on this platform",
34            ))
35        }
36
37        /// Always errors: no daemon is available on this platform.
38        pub fn request_routed(
39            &mut self,
40            _root: &std::path::Path,
41            _req: &Request,
42        ) -> Result<Response> {
43            Err(Error::other(
44                "greplm daemon is not supported on this platform",
45            ))
46        }
47    }
48}
49
50#[cfg(unix)]
51mod unix_impl {
52    use std::io::{BufRead, BufReader, Write};
53    use std::os::unix::net::UnixStream;
54    use std::path::Path;
55
56    use crate::error::{Error, Result};
57    use crate::proto::{Request, Response, RoutedRequest};
58
59    /// A connected client to a running greplm daemon.
60    pub struct Client {
61        reader: BufReader<UnixStream>,
62        writer: UnixStream,
63    }
64
65    impl Client {
66        /// Connect to a daemon listening on `socket`. Returns `None` if no daemon
67        /// is reachable (so callers can fall back to in-process queries).
68        pub fn try_connect(socket: &Path) -> Option<Client> {
69            let stream = UnixStream::connect(socket).ok()?;
70            let reader = BufReader::new(stream.try_clone().ok()?);
71            Some(Client {
72                reader,
73                writer: stream,
74            })
75        }
76
77        /// Send a request to a per-project daemon and read the response.
78        pub fn request(&mut self, req: &Request) -> Result<Response> {
79            self.round_trip(req)
80        }
81
82        /// Send a request to the global multi-root daemon, addressed to a
83        /// specific project `root`, and read the response.
84        pub fn request_routed(&mut self, root: &Path, req: &Request) -> Result<Response> {
85            let routed = RoutedRequest {
86                root: root.to_path_buf(),
87                req: req.clone(),
88            };
89            self.round_trip(&routed)
90        }
91
92        fn round_trip<T: serde::Serialize>(&mut self, value: &T) -> Result<Response> {
93            let mut bytes = serde_json::to_vec(value)?;
94            bytes.push(b'\n');
95            self.writer.write_all(&bytes).map_err(Error::PlainIo)?;
96            self.writer.flush().map_err(Error::PlainIo)?;
97            let mut line = String::new();
98            let n = self.reader.read_line(&mut line).map_err(Error::PlainIo)?;
99            if n == 0 {
100                return Err(Error::other("daemon closed connection"));
101            }
102            let resp: Response = serde_json::from_str(line.trim())?;
103            Ok(resp)
104        }
105    }
106}