1use rkyv::{Archive, Deserialize, Serialize};
7use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
8
9use crate::IPC_SCHEMA_VERSION;
10
11#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
13#[rkyv(derive(Debug))]
14pub struct IpcRequest {
15 pub ipc_schema_version: u32,
17 pub request_id: String,
19 pub repo_root: String,
21 pub actor_id: String,
23 pub data_dir: String,
25 pub command: IpcCommand,
27}
28
29impl IpcRequest {
30 pub fn new(
32 request_id: String,
33 repo_root: String,
34 actor_id: String,
35 data_dir: String,
36 command: IpcCommand,
37 ) -> Self {
38 Self {
39 ipc_schema_version: IPC_SCHEMA_VERSION,
40 request_id,
41 repo_root,
42 actor_id,
43 data_dir,
44 command,
45 }
46 }
47}
48
49#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
51#[rkyv(derive(Debug))]
52pub struct IpcResponse {
53 pub ipc_schema_version: u32,
55 pub request_id: String,
57 pub ok: bool,
59 pub data: Option<String>,
61 pub error: Option<IpcErrorPayload>,
63}
64
65impl IpcResponse {
66 pub fn success(request_id: String, data: Option<String>) -> Self {
68 Self {
69 ipc_schema_version: IPC_SCHEMA_VERSION,
70 request_id,
71 ok: true,
72 data,
73 error: None,
74 }
75 }
76
77 pub fn error(request_id: String, code: String, message: String) -> Self {
79 Self {
80 ipc_schema_version: IPC_SCHEMA_VERSION,
81 request_id,
82 ok: false,
83 data: None,
84 error: Some(IpcErrorPayload {
85 code,
86 message,
87 details: None,
88 }),
89 }
90 }
91}
92
93#[derive(Archive, Serialize, Deserialize, Debug, Clone, SerdeSerialize, SerdeDeserialize)]
95#[rkyv(derive(Debug))]
96pub struct IpcErrorPayload {
97 pub code: String,
99 pub message: String,
101 pub details: Option<String>,
103}
104
105#[derive(Archive, Serialize, Deserialize, Debug, Clone)]
109#[rkyv(derive(Debug))]
110pub enum IpcCommand {
111 IssueCreate {
113 title: String,
114 body: String,
115 labels: Vec<String>,
116 },
117 IssueList {
118 state: Option<String>,
119 label: Option<String>,
120 },
121 IssueShow {
122 issue_id: String,
123 },
124 IssueUpdate {
125 issue_id: String,
126 title: Option<String>,
127 body: Option<String>,
128 },
129 IssueComment {
130 issue_id: String,
131 body: String,
132 },
133 IssueLabel {
134 issue_id: String,
135 add: Vec<String>,
136 remove: Vec<String>,
137 },
138 IssueAssign {
139 issue_id: String,
140 add: Vec<String>,
141 remove: Vec<String>,
142 },
143 IssueClose {
144 issue_id: String,
145 },
146 IssueReopen {
147 issue_id: String,
148 },
149 IssueLink {
150 issue_id: String,
151 url: String,
152 note: Option<String>,
153 },
154 IssueAttach {
155 issue_id: String,
156 file_path: String,
157 },
158 IssueDepAdd {
159 issue_id: String,
160 target_id: String,
161 dep_type: String,
162 },
163 IssueDepRemove {
164 issue_id: String,
165 target_id: String,
166 dep_type: String,
167 },
168 IssueDepList {
169 issue_id: String,
170 reverse: bool,
171 },
172 IssueDepTopo {
173 state: Option<String>,
174 label: Option<String>,
175 },
176
177 DbStats,
179
180 Export {
182 format: String,
183 since: Option<String>,
184 },
185
186 Rebuild,
188
189 Sync {
191 remote: String,
192 pull: bool,
193 push: bool,
194 },
195
196 SnapshotCreate,
198 SnapshotList,
199 SnapshotGc {
200 keep: u32,
201 },
202
203 DaemonStatus,
205 DaemonStop,
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_request_creation() {
214 let req = IpcRequest::new(
215 "test-123".to_string(),
216 "/path/to/repo".to_string(),
217 "abcd1234".to_string(),
218 ".git/grite/actors/abcd1234".to_string(),
219 IpcCommand::IssueList {
220 state: Some("open".to_string()),
221 label: None,
222 },
223 );
224
225 assert_eq!(req.ipc_schema_version, IPC_SCHEMA_VERSION);
226 assert_eq!(req.request_id, "test-123");
227 }
228
229 #[test]
230 fn test_response_success() {
231 let resp = IpcResponse::success(
232 "test-123".to_string(),
233 Some(r#"{"issues": []}"#.to_string()),
234 );
235
236 assert!(resp.ok);
237 assert!(resp.error.is_none());
238 assert!(resp.data.is_some());
239 }
240
241 #[test]
242 fn test_response_error() {
243 let resp = IpcResponse::error(
244 "test-123".to_string(),
245 "not_found".to_string(),
246 "Issue not found".to_string(),
247 );
248
249 assert!(!resp.ok);
250 assert!(resp.data.is_none());
251 assert!(resp.error.is_some());
252 assert_eq!(resp.error.as_ref().unwrap().code, "not_found");
253 }
254
255 #[test]
256 fn test_rkyv_roundtrip() {
257 let req = IpcRequest::new(
258 "test-456".to_string(),
259 "/repo".to_string(),
260 "actor123".to_string(),
261 ".git/grite/actors/actor123".to_string(),
262 IpcCommand::IssueCreate {
263 title: "Test Issue".to_string(),
264 body: "Description".to_string(),
265 labels: vec!["bug".to_string()],
266 },
267 );
268
269 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&req).unwrap();
271
272 let archived = rkyv::access::<ArchivedIpcRequest, rkyv::rancor::Error>(&bytes).unwrap();
274 assert_eq!(archived.request_id, "test-456");
275 assert_eq!(archived.ipc_schema_version, IPC_SCHEMA_VERSION);
276 }
277}