1use std::io::{BufRead, BufReader, Write};
42use std::path::PathBuf;
43use std::process::{Command, Stdio};
44use std::sync::atomic::{AtomicU64, Ordering};
45use std::time::Duration;
46
47use bronzite_types::{Query, QueryData, QueryResult, Request, Response};
48
49#[cfg(unix)]
50use std::os::unix::net::UnixStream;
51
52#[derive(Debug, thiserror::Error)]
54pub enum Error {
55 #[error("Failed to connect to Bronzite daemon: {0}")]
56 ConnectionFailed(#[from] std::io::Error),
57
58 #[error("Failed to serialize request: {0}")]
59 SerializationError(#[from] serde_json::Error),
60
61 #[error("Daemon returned an error: {0}")]
62 DaemonError(String),
63
64 #[error("Response ID mismatch: expected {expected}, got {got}")]
65 ResponseMismatch { expected: u64, got: u64 },
66
67 #[error("Daemon is not running. Start it with: bronzite-daemon --ensure")]
68 DaemonNotRunning,
69
70 #[error("Unexpected response type")]
71 UnexpectedResponse,
72
73 #[error("Failed to start daemon: {0}")]
74 DaemonStartFailed(String),
75
76 #[error("Timeout waiting for daemon to start")]
77 DaemonStartTimeout,
78}
79
80pub type Result<T> = std::result::Result<T, Error>;
82
83static REQUEST_ID: AtomicU64 = AtomicU64::new(1);
85
86const DEFAULT_DAEMON_TIMEOUT: Duration = Duration::from_secs(30);
88
89#[derive(Debug)]
91pub struct BronziteClient {
92 #[cfg(unix)]
93 stream: UnixStream,
94 #[cfg(windows)]
95 stream: std::net::TcpStream,
96}
97
98impl BronziteClient {
99 pub fn connect() -> Result<Self> {
101 let socket_path = bronzite_types::default_socket_path();
102 Self::connect_to(socket_path)
103 }
104
105 pub fn connect_for_workspace(workspace_root: &std::path::Path) -> Result<Self> {
107 let socket_path = bronzite_types::socket_path_for_workspace(workspace_root);
108 Self::connect_to(socket_path)
109 }
110
111 #[cfg(unix)]
113 pub fn connect_to(socket_path: PathBuf) -> Result<Self> {
114 if !socket_path.exists() {
115 return Err(Error::DaemonNotRunning);
116 }
117
118 let stream = UnixStream::connect(&socket_path)?;
119 Ok(Self { stream })
120 }
121
122 #[cfg(windows)]
124 pub fn connect_to(socket_path: PathBuf) -> Result<Self> {
125 use std::collections::hash_map::DefaultHasher;
128 use std::hash::{Hash, Hasher};
129
130 let mut hasher = DefaultHasher::new();
131 socket_path.hash(&mut hasher);
132 let port = 10000 + (hasher.finish() % 50000) as u16;
133
134 let stream = std::net::TcpStream::connect(("127.0.0.1", port))?;
135 Ok(Self { stream })
136 }
137
138 pub fn query(&mut self, crate_name: &str, query: Query) -> Result<QueryData> {
140 let id = REQUEST_ID.fetch_add(1, Ordering::SeqCst);
141
142 let request = Request {
143 id,
144 crate_name: crate_name.to_string(),
145 query,
146 };
147
148 let mut request_json = serde_json::to_string(&request)?;
150 request_json.push('\n');
151 self.stream.write_all(request_json.as_bytes())?;
152 self.stream.flush()?;
153
154 let mut reader = BufReader::new(&self.stream);
156 let mut response_line = String::new();
157 reader.read_line(&mut response_line)?;
158
159 let response: Response = serde_json::from_str(&response_line)?;
160
161 if response.id != id {
163 return Err(Error::ResponseMismatch {
164 expected: id,
165 got: response.id,
166 });
167 }
168
169 match response.result {
171 QueryResult::Success { data } => Ok(data),
172 QueryResult::Error { message } => Err(Error::DaemonError(message)),
173 }
174 }
175
176 pub fn ping(&mut self) -> Result<bool> {
178 match self.query("", Query::Ping) {
179 Ok(QueryData::Pong) => Ok(true),
180 Ok(_) => Err(Error::UnexpectedResponse),
181 Err(e) => Err(e),
182 }
183 }
184
185 pub fn shutdown(&mut self) -> Result<()> {
187 match self.query("", Query::Shutdown) {
188 Ok(QueryData::ShuttingDown) => Ok(()),
189 Ok(_) => Err(Error::UnexpectedResponse),
190 Err(e) => Err(e),
191 }
192 }
193
194 pub fn list_items(&mut self, crate_name: &str) -> Result<Vec<bronzite_types::ItemInfo>> {
196 match self.query(crate_name, Query::ListItems)? {
197 QueryData::Items { items } => Ok(items),
198 _ => Err(Error::UnexpectedResponse),
199 }
200 }
201
202 pub fn get_trait_impls(
204 &mut self,
205 crate_name: &str,
206 type_path: &str,
207 ) -> Result<Vec<bronzite_types::TraitImplDetails>> {
208 let query = Query::GetTraitImpls {
209 type_path: type_path.to_string(),
210 };
211
212 match self.query(crate_name, query)? {
213 QueryData::TraitImpls { impls } => Ok(impls),
214 _ => Err(Error::UnexpectedResponse),
215 }
216 }
217
218 pub fn get_inherent_impls(
220 &mut self,
221 crate_name: &str,
222 type_path: &str,
223 ) -> Result<Vec<bronzite_types::InherentImplDetails>> {
224 let query = Query::GetInherentImpls {
225 type_path: type_path.to_string(),
226 };
227
228 match self.query(crate_name, query)? {
229 QueryData::InherentImpls { impls } => Ok(impls),
230 _ => Err(Error::UnexpectedResponse),
231 }
232 }
233
234 pub fn check_impl(
236 &mut self,
237 crate_name: &str,
238 type_path: &str,
239 trait_path: &str,
240 ) -> Result<(bool, Option<bronzite_types::TraitImplDetails>)> {
241 let query = Query::CheckImpl {
242 type_path: type_path.to_string(),
243 trait_path: trait_path.to_string(),
244 };
245
246 match self.query(crate_name, query)? {
247 QueryData::ImplCheck {
248 implements,
249 impl_info,
250 } => Ok((implements, impl_info)),
251 _ => Err(Error::UnexpectedResponse),
252 }
253 }
254
255 pub fn get_fields(
257 &mut self,
258 crate_name: &str,
259 type_path: &str,
260 ) -> Result<Vec<bronzite_types::FieldInfo>> {
261 let query = Query::GetFields {
262 type_path: type_path.to_string(),
263 };
264
265 match self.query(crate_name, query)? {
266 QueryData::Fields { fields } => Ok(fields),
267 _ => Err(Error::UnexpectedResponse),
268 }
269 }
270
271 pub fn get_type(
273 &mut self,
274 crate_name: &str,
275 type_path: &str,
276 ) -> Result<bronzite_types::TypeDetails> {
277 let query = Query::GetType {
278 path: type_path.to_string(),
279 };
280
281 match self.query(crate_name, query)? {
282 QueryData::TypeInfo(info) => Ok(info),
283 _ => Err(Error::UnexpectedResponse),
284 }
285 }
286
287 pub fn get_traits(&mut self, crate_name: &str) -> Result<Vec<bronzite_types::TraitInfo>> {
289 match self.query(crate_name, Query::GetTraits)? {
290 QueryData::Traits { traits } => Ok(traits),
291 _ => Err(Error::UnexpectedResponse),
292 }
293 }
294
295 pub fn get_trait(
297 &mut self,
298 crate_name: &str,
299 trait_path: &str,
300 ) -> Result<bronzite_types::TraitDetails> {
301 let query = Query::GetTrait {
302 path: trait_path.to_string(),
303 };
304
305 match self.query(crate_name, query)? {
306 QueryData::TraitDetails(details) => Ok(details),
307 _ => Err(Error::UnexpectedResponse),
308 }
309 }
310
311 pub fn find_types(
313 &mut self,
314 crate_name: &str,
315 pattern: &str,
316 ) -> Result<Vec<bronzite_types::TypeSummary>> {
317 let query = Query::FindTypes {
318 pattern: pattern.to_string(),
319 };
320
321 match self.query(crate_name, query)? {
322 QueryData::Types { types } => Ok(types),
323 _ => Err(Error::UnexpectedResponse),
324 }
325 }
326
327 pub fn resolve_alias(
329 &mut self,
330 crate_name: &str,
331 path: &str,
332 ) -> Result<(String, String, Vec<String>)> {
333 let query = Query::ResolveAlias {
334 path: path.to_string(),
335 };
336
337 match self.query(crate_name, query)? {
338 QueryData::ResolvedType {
339 original,
340 resolved,
341 chain,
342 } => Ok((original, resolved, chain)),
343 _ => Err(Error::UnexpectedResponse),
344 }
345 }
346
347 pub fn get_implementors(
349 &mut self,
350 crate_name: &str,
351 trait_path: &str,
352 ) -> Result<Vec<bronzite_types::TypeSummary>> {
353 let query = Query::GetImplementors {
354 trait_path: trait_path.to_string(),
355 };
356
357 match self.query(crate_name, query)? {
358 QueryData::Implementors { types } => Ok(types),
359 _ => Err(Error::UnexpectedResponse),
360 }
361 }
362
363 pub fn get_layout(
365 &mut self,
366 crate_name: &str,
367 type_path: &str,
368 ) -> Result<bronzite_types::LayoutInfo> {
369 let query = Query::GetLayout {
370 type_path: type_path.to_string(),
371 };
372
373 match self.query(crate_name, query)? {
374 QueryData::Layout(layout) => Ok(layout),
375 _ => Err(Error::UnexpectedResponse),
376 }
377 }
378}
379
380pub fn connect() -> Result<BronziteClient> {
385 BronziteClient::connect()
386}
387
388pub fn connect_for_workspace(workspace_root: &std::path::Path) -> Result<BronziteClient> {
390 BronziteClient::connect_for_workspace(workspace_root)
391}
392
393pub fn is_daemon_running() -> bool {
395 is_daemon_running_at(&bronzite_types::default_socket_path())
396}
397
398#[cfg(unix)]
400pub fn is_daemon_running_at(socket_path: &PathBuf) -> bool {
401 if !socket_path.exists() {
402 return false;
403 }
404
405 match UnixStream::connect(socket_path) {
407 Ok(mut stream) => {
408 let request = Request {
410 id: 0,
411 crate_name: String::new(),
412 query: Query::Ping,
413 };
414
415 if let Ok(json) = serde_json::to_string(&request) {
416 let msg = format!("{}\n", json);
417 if stream.write_all(msg.as_bytes()).is_ok() {
418 let _ = stream.flush();
419
420 let _ = stream.set_read_timeout(Some(Duration::from_secs(5)));
422
423 let mut reader = BufReader::new(&stream);
425 let mut response = String::new();
426 if reader.read_line(&mut response).is_ok() {
427 return response.contains("pong");
428 }
429 }
430 }
431 false
432 }
433 Err(_) => false,
434 }
435}
436
437#[cfg(windows)]
438pub fn is_daemon_running_at(socket_path: &PathBuf) -> bool {
439 use std::collections::hash_map::DefaultHasher;
440 use std::hash::{Hash, Hasher};
441
442 let mut hasher = DefaultHasher::new();
443 socket_path.hash(&mut hasher);
444 let port = 10000 + (hasher.finish() % 50000) as u16;
445
446 std::net::TcpStream::connect(("127.0.0.1", port)).is_ok()
447}
448
449pub fn ensure_daemon_running(manifest_path: Option<&std::path::Path>) -> Result<()> {
473 ensure_daemon_running_with_timeout(manifest_path, DEFAULT_DAEMON_TIMEOUT)
474}
475
476pub fn ensure_daemon_running_with_timeout(
478 manifest_path: Option<&std::path::Path>,
479 timeout: Duration,
480) -> Result<()> {
481 let socket_path = bronzite_types::default_socket_path();
482
483 if is_daemon_running_at(&socket_path) {
485 return Ok(());
486 }
487
488 let daemon_path = find_daemon_binary()?;
490
491 let mut cmd = Command::new(&daemon_path);
493 cmd.arg("--ensure");
494 cmd.arg("--ensure-timeout")
495 .arg(timeout.as_secs().to_string());
496
497 if let Some(path) = manifest_path {
498 cmd.arg("--manifest-path").arg(path);
499 }
500
501 let output = cmd
503 .stdin(Stdio::null())
504 .stdout(Stdio::piped())
505 .stderr(Stdio::piped())
506 .output()
507 .map_err(|e| Error::DaemonStartFailed(format!("Failed to run bronzite-daemon: {}", e)))?;
508
509 if !output.status.success() {
510 let stderr = String::from_utf8_lossy(&output.stderr);
511 return Err(Error::DaemonStartFailed(format!(
512 "bronzite-daemon --ensure failed: {}",
513 stderr.trim()
514 )));
515 }
516
517 if !is_daemon_running_at(&socket_path) {
519 return Err(Error::DaemonStartTimeout);
520 }
521
522 Ok(())
523}
524
525fn find_daemon_binary() -> Result<PathBuf> {
527 if let Ok(output) = Command::new("which").arg("bronzite-daemon").output() {
529 if output.status.success() {
530 let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
531 if !path.is_empty() {
532 return Ok(PathBuf::from(path));
533 }
534 }
535 }
536
537 if let Ok(current_exe) = std::env::current_exe() {
539 if let Some(parent) = current_exe.parent() {
540 let daemon_path = parent.join("bronzite-daemon");
541 if daemon_path.exists() {
542 return Ok(daemon_path);
543 }
544 }
545 }
546
547 if let Ok(home) = std::env::var("CARGO_HOME") {
549 let daemon_path = PathBuf::from(home).join("bin").join("bronzite-daemon");
550 if daemon_path.exists() {
551 return Ok(daemon_path);
552 }
553 }
554
555 if let Ok(home) = std::env::var("HOME") {
557 let daemon_path = PathBuf::from(home)
558 .join(".cargo")
559 .join("bin")
560 .join("bronzite-daemon");
561 if daemon_path.exists() {
562 return Ok(daemon_path);
563 }
564 }
565
566 Ok(PathBuf::from("bronzite-daemon"))
568}
569
570pub fn connect_or_start(manifest_path: Option<&std::path::Path>) -> Result<BronziteClient> {
581 ensure_daemon_running(manifest_path)?;
582 connect()
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn test_is_daemon_running_when_not_running() {
591 assert!(!is_daemon_running() || true); }
595
596 #[test]
597 fn test_connect_fails_when_daemon_not_running() {
598 let fake_path = PathBuf::from("/tmp/bronzite-nonexistent-12345.sock");
600 let result = BronziteClient::connect_to(fake_path);
601 assert!(result.is_err());
602 }
603
604 #[test]
605 fn test_find_daemon_binary() {
606 let _ = find_daemon_binary();
608 }
609}
610
611pub mod reflection;
616
617pub use reflection::{
619 Crate, EnumDef, Field, Item, Method, StructDef, TraitDef, TraitImpl, TraitMethod, TypeAliasDef,
620 UnionDef,
621};