1use std::collections::HashMap;
30
31use crate::types::Value;
32
33#[derive(Debug, Clone, PartialEq)]
38pub struct Node {
39 pub id: i64,
41 pub labels: Vec<String>,
43 pub properties: HashMap<String, Value>,
45}
46
47#[derive(Debug, Clone, PartialEq)]
52pub struct Edge {
53 pub id: i64,
55 pub start_node: i64,
57 pub end_node: i64,
59 pub edge_type: String,
61 pub properties: HashMap<String, Value>,
63}
64
65#[derive(Debug, Clone, PartialEq)]
71pub struct Path {
72 pub nodes: Vec<Node>,
74 pub edges: Vec<Edge>,
76}
77
78pub fn as_node(v: &Value) -> Option<Node> {
84 let obj = v.as_object().ok()?;
85 if !obj.contains_key("labels") {
86 return None;
87 }
88 if obj.contains_key("start_node") {
89 return None; }
91 let id = obj.get("id").and_then(|v| v.as_int().ok()).unwrap_or(0);
92 let labels = obj
93 .get("labels")
94 .and_then(|v| v.as_array().ok())
95 .map(|arr| {
96 arr.iter()
97 .filter_map(|item| item.as_string().ok().map(String::from))
98 .collect()
99 })
100 .unwrap_or_default();
101 let properties = obj
102 .get("properties")
103 .and_then(|v| v.as_object().ok())
104 .cloned()
105 .unwrap_or_default();
106 Some(Node {
107 id,
108 labels,
109 properties,
110 })
111}
112
113pub fn as_edge(v: &Value) -> Option<Edge> {
118 let obj = v.as_object().ok()?;
119 if !obj.contains_key("start_node") || !obj.contains_key("end_node") {
120 return None;
121 }
122 let id = obj.get("id").and_then(|v| v.as_int().ok()).unwrap_or(0);
123 let start_node = obj
124 .get("start_node")
125 .and_then(|v| v.as_int().ok())
126 .unwrap_or(0);
127 let end_node = obj
128 .get("end_node")
129 .and_then(|v| v.as_int().ok())
130 .unwrap_or(0);
131 let edge_type = obj
132 .get("type")
133 .and_then(|v| v.as_string().ok())
134 .map(String::from)
135 .unwrap_or_default();
136 let properties = obj
137 .get("properties")
138 .and_then(|v| v.as_object().ok())
139 .cloned()
140 .unwrap_or_default();
141 Some(Edge {
142 id,
143 start_node,
144 end_node,
145 edge_type,
146 properties,
147 })
148}
149
150pub fn as_path(v: &Value) -> Option<Path> {
156 let obj = v.as_object().ok()?;
157 if !obj.contains_key("nodes") || !obj.contains_key("edges") {
158 return None;
159 }
160 let nodes = obj
161 .get("nodes")
162 .and_then(|v| v.as_array().ok())
163 .map(|arr| arr.iter().filter_map(as_node).collect())
164 .unwrap_or_default();
165 let edges = obj
166 .get("edges")
167 .and_then(|v| v.as_array().ok())
168 .map(|arr| arr.iter().filter_map(as_edge).collect())
169 .unwrap_or_default();
170 Some(Path { nodes, edges })
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 fn node_value(id: i64, labels: &[&str], props: &[(&str, Value)]) -> Value {
178 let mut obj = HashMap::new();
179 obj.insert("id".to_string(), Value::int(id));
180 obj.insert(
181 "labels".to_string(),
182 Value::array(labels.iter().map(|l| Value::string(*l)).collect()),
183 );
184 let mut p = HashMap::new();
185 for (k, v) in props {
186 p.insert(k.to_string(), v.clone());
187 }
188 obj.insert("properties".to_string(), Value::object(p));
189 Value::object(obj)
190 }
191
192 fn edge_value(id: i64, start: i64, end: i64, typ: &str) -> Value {
193 let mut obj = HashMap::new();
194 obj.insert("id".to_string(), Value::int(id));
195 obj.insert("start_node".to_string(), Value::int(start));
196 obj.insert("end_node".to_string(), Value::int(end));
197 obj.insert("type".to_string(), Value::string(typ));
198 obj.insert("properties".to_string(), Value::object(HashMap::new()));
199 Value::object(obj)
200 }
201
202 #[test]
203 fn test_as_node_from_object() {
204 let v = node_value(42, &["Person", "User"], &[("name", Value::string("Alice"))]);
205 let node = as_node(&v).expect("should decode node");
206 assert_eq!(node.id, 42);
207 assert_eq!(node.labels, vec!["Person".to_string(), "User".to_string()]);
208 assert_eq!(
209 node.properties.get("name").unwrap().as_string().unwrap(),
210 "Alice"
211 );
212 }
213
214 #[test]
215 fn test_as_edge_from_object() {
216 let v = edge_value(100, 1, 2, "KNOWS");
217 let edge = as_edge(&v).expect("should decode edge");
218 assert_eq!(edge.id, 100);
219 assert_eq!(edge.start_node, 1);
220 assert_eq!(edge.end_node, 2);
221 assert_eq!(edge.edge_type, "KNOWS");
222 assert!(edge.properties.is_empty());
223 }
224
225 #[test]
226 fn test_as_path_from_object() {
227 let n1 = node_value(1, &["Person"], &[]);
228 let n2 = node_value(2, &["Person"], &[]);
229 let e1 = edge_value(10, 1, 2, "KNOWS");
230 let mut obj = HashMap::new();
231 obj.insert("nodes".to_string(), Value::array(vec![n1, n2]));
232 obj.insert("edges".to_string(), Value::array(vec![e1]));
233 let v = Value::object(obj);
234
235 let path = as_path(&v).expect("should decode path");
236 assert_eq!(path.nodes.len(), 2);
237 assert_eq!(path.edges.len(), 1);
238 assert_eq!(path.nodes[0].id, 1);
239 assert_eq!(path.nodes[1].id, 2);
240 assert_eq!(path.edges[0].edge_type, "KNOWS");
241 }
242
243 #[test]
244 fn test_as_node_rejects_scalar() {
245 assert!(as_node(&Value::int(5)).is_none());
246 assert!(as_node(&Value::string("x")).is_none());
247 assert!(as_node(&Value::null()).is_none());
248 assert!(as_node(&edge_value(1, 2, 3, "T")).is_none());
250 }
251
252 #[test]
253 fn test_as_edge_rejects_node() {
254 let v = node_value(1, &["Person"], &[]);
255 assert!(as_edge(&v).is_none());
256 assert!(as_edge(&Value::int(5)).is_none());
257 }
258
259 #[test]
260 fn test_as_path_rejects_non_path() {
261 assert!(as_path(&node_value(1, &["Person"], &[])).is_none());
262 assert!(as_path(&Value::int(5)).is_none());
263 }
264}