1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5#[serde(tag = "type")]
6pub enum Request {
7 CreateNode {
9 labels: Vec<String>,
10 properties: serde_json::Value,
11 #[serde(default)]
12 embedding: Option<Vec<f32>>,
13 },
14 CreateEdge {
16 source: u64,
17 target: u64,
18 edge_type: String,
19 #[serde(default = "default_properties")]
20 properties: serde_json::Value,
21 #[serde(default = "default_weight")]
22 weight: f64,
23 #[serde(default)]
25 valid_from: Option<i64>,
26 #[serde(default)]
28 valid_to: Option<i64>,
29 },
30 GetNode { id: u64 },
32 GetEdge { id: u64 },
34 UpdateNode {
36 id: u64,
37 properties: serde_json::Value,
38 },
39 UpdateEdge {
41 id: u64,
42 properties: serde_json::Value,
43 },
44 DeleteNode { id: u64 },
46 DeleteEdge { id: u64 },
48 Neighbors {
50 id: u64,
51 direction: String, #[serde(default)]
53 edge_type: Option<String>,
54 },
55 Bfs {
57 start: u64,
58 #[serde(default = "default_max_depth")]
59 max_depth: usize,
60 },
61 ShortestPath {
63 from: u64,
64 to: u64,
65 #[serde(default)]
66 weighted: bool,
67 },
68 VectorSearch {
70 query: Vec<f32>,
71 #[serde(default = "default_k")]
72 k: usize,
73 },
74 HybridSearch {
76 anchor: u64,
77 query: Vec<f32>,
78 #[serde(default = "default_max_depth")]
79 max_hops: usize,
80 #[serde(default = "default_k")]
81 k: usize,
82 #[serde(default = "default_alpha")]
83 alpha: f32,
84 },
85 SemanticNeighbors {
87 id: u64,
88 concept: Vec<f32>,
89 #[serde(default = "default_direction")]
90 direction: String,
91 #[serde(default = "default_k")]
92 k: usize,
93 },
94 SemanticWalk {
96 start: u64,
97 concept: Vec<f32>,
98 #[serde(default = "default_max_depth")]
99 max_hops: usize,
100 },
101 Query { gql: String },
103 ExtractSubgraph {
105 center: u64,
106 #[serde(default = "default_max_depth")]
107 hops: usize,
108 #[serde(default = "default_max_context_nodes")]
109 max_nodes: usize,
110 #[serde(default = "default_text_format")]
111 format: String,
112 },
113 GraphRag {
115 question: String,
116 #[serde(default)]
117 question_embedding: Option<Vec<f32>>,
118 #[serde(default)]
119 anchor: Option<u64>,
120 #[serde(default = "default_max_depth")]
121 hops: usize,
122 #[serde(default = "default_max_context_nodes")]
123 max_nodes: usize,
124 #[serde(default = "default_text_format")]
125 format: String,
126 },
127 NeighborsAt {
129 id: u64,
130 direction: String,
131 timestamp: i64,
132 #[serde(default)]
133 edge_type: Option<String>,
134 },
135 BfsAt {
137 start: u64,
138 #[serde(default = "default_max_depth")]
139 max_depth: usize,
140 timestamp: i64,
141 },
142 ShortestPathAt {
144 from: u64,
145 to: u64,
146 timestamp: i64,
147 #[serde(default)]
148 weighted: bool,
149 },
150 Dfs {
152 start: u64,
153 #[serde(default = "default_max_depth")]
154 max_depth: usize,
155 },
156 DfsAt {
158 start: u64,
159 #[serde(default = "default_max_depth")]
160 max_depth: usize,
161 timestamp: i64,
162 },
163 FindByLabel { label: String },
165 DeleteByLabel { label: String },
168 FindEdgeByType { edge_type: String },
172 RunPageRank {
174 #[serde(default)]
175 nodes: Option<Vec<u64>>,
176 #[serde(default = "default_damping")]
177 damping: f64,
178 #[serde(default = "default_max_iterations")]
179 max_iterations: usize,
180 #[serde(default = "default_tolerance")]
181 tolerance: f64,
182 },
183 RunLouvain {
185 #[serde(default)]
186 nodes: Option<Vec<u64>>,
187 },
188 RunConnectedComponents {
190 #[serde(default)]
191 nodes: Option<Vec<u64>>,
192 #[serde(default)]
193 strong: bool,
194 },
195 RunDegreeCentrality {
197 #[serde(default)]
198 nodes: Option<Vec<u64>>,
199 #[serde(default = "default_direction")]
200 direction: String,
201 },
202 RunBetweennessCentrality {
204 #[serde(default)]
205 nodes: Option<Vec<u64>>,
206 },
207 GraphStats,
209 GetSubgraph {
211 center: u64,
212 #[serde(default = "default_max_depth")]
213 hops: usize,
214 #[serde(default = "default_max_context_nodes")]
215 max_nodes: usize,
216 },
217 Ping,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223#[serde(tag = "status")]
224pub enum Response {
225 #[serde(rename = "ok")]
227 Ok { data: serde_json::Value },
228 #[serde(rename = "error")]
230 Error { message: String },
231}
232
233impl Response {
234 pub fn ok(data: impl Serialize) -> Self {
235 Self::Ok {
236 data: serde_json::to_value(data).unwrap_or(serde_json::Value::Null),
237 }
238 }
239
240 pub fn error(msg: impl Into<String>) -> Self {
241 Self::Error {
242 message: msg.into(),
243 }
244 }
245}
246
247fn default_properties() -> serde_json::Value {
248 serde_json::json!({})
249}
250
251fn default_weight() -> f64 {
252 1.0
253}
254
255fn default_max_depth() -> usize {
256 3
257}
258
259fn default_k() -> usize {
260 10
261}
262
263fn default_alpha() -> f32 {
264 0.5
265}
266
267fn default_direction() -> String {
268 "outgoing".to_string()
269}
270
271fn default_max_context_nodes() -> usize {
272 50
273}
274
275fn default_damping() -> f64 {
276 0.85
277}
278
279fn default_max_iterations() -> usize {
280 100
281}
282
283fn default_tolerance() -> f64 {
284 1e-6
285}
286
287fn default_text_format() -> String {
288 "structured".to_string()
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn serialize_create_node_request() {
297 let req = Request::CreateNode {
298 labels: vec!["Person".into()],
299 properties: serde_json::json!({"name": "Alice"}),
300 embedding: None,
301 };
302 let json = serde_json::to_string(&req).unwrap();
303 assert!(json.contains("CreateNode"));
304 assert!(json.contains("Alice"));
305 }
306
307 #[test]
308 fn deserialize_create_node_request() {
309 let json = r#"{"type":"CreateNode","labels":["Person"],"properties":{"name":"Bob"}}"#;
310 let req: Request = serde_json::from_str(json).unwrap();
311 match req {
312 Request::CreateNode {
313 labels, properties, ..
314 } => {
315 assert_eq!(labels, vec!["Person"]);
316 assert_eq!(properties["name"], "Bob");
317 }
318 _ => panic!("wrong variant"),
319 }
320 }
321
322 #[test]
323 fn response_ok() {
324 let resp = Response::ok(serde_json::json!({"id": 42}));
325 let json = serde_json::to_string(&resp).unwrap();
326 assert!(json.contains("ok"));
327 assert!(json.contains("42"));
328 }
329
330 #[test]
331 fn response_error() {
332 let resp = Response::error("not found");
333 let json = serde_json::to_string(&resp).unwrap();
334 assert!(json.contains("error"));
335 assert!(json.contains("not found"));
336 }
337
338 #[test]
339 fn deserialize_ping() {
340 let json = r#"{"type":"Ping"}"#;
341 let req: Request = serde_json::from_str(json).unwrap();
342 assert!(matches!(req, Request::Ping));
343 }
344}