Skip to main content

libgrite_ipc/
discovery.rs

1//! Discovery protocol types
2//!
3//! Discovery uses NNG SURVEY sockets to find running daemons.
4
5use rkyv::{Archive, Deserialize, Serialize};
6
7use crate::{IPC_SCHEMA_VERSION, PROTOCOL_NAME};
8
9/// Discovery request sent via SURVEY socket
10#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
11#[rkyv(derive(Debug))]
12pub struct DiscoverRequest {
13    /// Protocol name (should be "grit-ipc")
14    pub protocol: String,
15    /// Minimum supported version
16    pub min_version: u32,
17}
18
19impl DiscoverRequest {
20    /// Create a new discovery request
21    pub fn new() -> Self {
22        Self {
23            protocol: PROTOCOL_NAME.to_string(),
24            min_version: 1,
25        }
26    }
27
28    /// Create with a specific minimum version
29    pub fn with_min_version(min_version: u32) -> Self {
30        Self {
31            protocol: PROTOCOL_NAME.to_string(),
32            min_version,
33        }
34    }
35}
36
37impl Default for DiscoverRequest {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43/// Discovery response from a daemon
44#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
45#[rkyv(derive(Debug))]
46pub struct DiscoverResponse {
47    /// Protocol name
48    pub protocol: String,
49    /// IPC schema version
50    pub ipc_schema_version: u32,
51    /// Unique daemon instance ID
52    pub daemon_id: String,
53    /// IPC endpoint to connect to
54    pub endpoint: String,
55    /// Workers managed by this daemon
56    pub workers: Vec<WorkerInfo>,
57}
58
59impl DiscoverResponse {
60    /// Create a new discovery response
61    pub fn new(daemon_id: String, endpoint: String, workers: Vec<WorkerInfo>) -> Self {
62        Self {
63            protocol: PROTOCOL_NAME.to_string(),
64            ipc_schema_version: IPC_SCHEMA_VERSION,
65            daemon_id,
66            endpoint,
67            workers,
68        }
69    }
70
71    /// Check if this daemon manages a specific repo/actor
72    pub fn has_worker(&self, repo_root: &str, actor_id: &str) -> bool {
73        self.workers
74            .iter()
75            .any(|w| w.repo_root == repo_root && w.actor_id == actor_id)
76    }
77}
78
79/// Information about a worker managed by the daemon
80#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
81#[rkyv(derive(Debug))]
82pub struct WorkerInfo {
83    /// Repository root path
84    pub repo_root: String,
85    /// Actor ID (hex-encoded)
86    pub actor_id: String,
87    /// Data directory path
88    pub data_dir: String,
89}
90
91impl WorkerInfo {
92    /// Create a new worker info
93    pub fn new(repo_root: String, actor_id: String, data_dir: String) -> Self {
94        Self {
95            repo_root,
96            actor_id,
97            data_dir,
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_discover_request() {
108        let req = DiscoverRequest::new();
109        assert_eq!(req.protocol, PROTOCOL_NAME);
110        assert_eq!(req.min_version, 1);
111    }
112
113    #[test]
114    fn test_discover_response() {
115        let workers = vec![
116            WorkerInfo::new(
117                "/repo1".to_string(),
118                "actor1".to_string(),
119                "/repo1/.git/grite/actors/actor1".to_string(),
120            ),
121            WorkerInfo::new(
122                "/repo2".to_string(),
123                "actor2".to_string(),
124                "/repo2/.git/grite/actors/actor2".to_string(),
125            ),
126        ];
127
128        let resp = DiscoverResponse::new(
129            "daemon-123".to_string(),
130            "ipc:///tmp/grite-daemon.sock".to_string(),
131            workers,
132        );
133
134        assert!(resp.has_worker("/repo1", "actor1"));
135        assert!(resp.has_worker("/repo2", "actor2"));
136        assert!(!resp.has_worker("/repo3", "actor3"));
137    }
138
139    #[test]
140    fn test_rkyv_roundtrip() {
141        let req = DiscoverRequest::new();
142        let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&req).unwrap();
143        let archived =
144            rkyv::access::<ArchivedDiscoverRequest, rkyv::rancor::Error>(&bytes).unwrap();
145        assert_eq!(archived.protocol.as_str(), PROTOCOL_NAME);
146    }
147}