aimdb_client/
discovery.rs1use crate::connection::AimxClient;
6use crate::error::{ClientError, ClientResult};
7use crate::protocol::WelcomeMessage;
8use std::path::PathBuf;
9use std::time::Duration;
10
11const SOCKET_SEARCH_DIRS: &[&str] = &["/tmp", "/var/run/aimdb"];
13
14#[derive(Debug, Clone)]
16pub struct InstanceInfo {
17 pub socket_path: PathBuf,
18 pub server_version: String,
19 pub protocol_version: String,
20 pub permissions: Vec<String>,
21 pub writable_records: Vec<String>,
22 pub max_subscriptions: Option<usize>,
23 pub authenticated: bool,
24}
25
26impl From<(PathBuf, WelcomeMessage)> for InstanceInfo {
27 fn from((socket_path, welcome): (PathBuf, WelcomeMessage)) -> Self {
28 Self {
29 socket_path,
30 server_version: welcome.server,
31 protocol_version: welcome.version,
32 permissions: welcome.permissions,
33 writable_records: welcome.writable_records,
34 max_subscriptions: welcome.max_subscriptions,
35 authenticated: welcome.authenticated.unwrap_or(false),
36 }
37 }
38}
39
40pub async fn discover_instances() -> ClientResult<Vec<InstanceInfo>> {
42 let mut instances = Vec::new();
43
44 for dir_path in SOCKET_SEARCH_DIRS {
45 if let Ok(entries) = tokio::fs::read_dir(dir_path).await {
46 instances.extend(scan_directory(entries).await);
47 }
48 }
49
50 if instances.is_empty() {
51 return Err(ClientError::NoInstancesFound);
52 }
53
54 Ok(instances)
55}
56
57async fn scan_directory(mut entries: tokio::fs::ReadDir) -> Vec<InstanceInfo> {
59 let mut instances = Vec::new();
60
61 while let Ok(Some(entry)) = entries.next_entry().await {
62 let path = entry.path();
63
64 if path.extension().and_then(|s| s.to_str()) == Some("sock") {
66 if let Ok(info) = probe_instance(&path).await {
68 instances.push(info);
69 }
70 }
71 }
72
73 instances
74}
75
76async fn probe_instance(socket_path: &PathBuf) -> ClientResult<InstanceInfo> {
78 let connect_timeout = Duration::from_millis(500);
80
81 let client = tokio::time::timeout(connect_timeout, AimxClient::connect(socket_path))
82 .await
83 .map_err(|_| {
84 ClientError::connection_failed(
85 socket_path.display().to_string(),
86 "timeout during discovery probe",
87 )
88 })??;
89
90 let welcome = client.server_info().clone();
91
92 Ok(InstanceInfo::from((socket_path.clone(), welcome)))
93}
94
95pub async fn find_instance(socket_hint: Option<&str>) -> ClientResult<InstanceInfo> {
97 if let Some(socket_path) = socket_hint {
99 let path = PathBuf::from(socket_path);
100 if path.exists() {
101 return probe_instance(&path).await;
102 } else {
103 return Err(ClientError::connection_failed(
104 socket_path.to_string(),
105 "socket file does not exist",
106 ));
107 }
108 }
109
110 let instances = discover_instances().await?;
112
113 instances
114 .into_iter()
115 .next()
116 .ok_or(ClientError::NoInstancesFound)
117}