1use crate::types::{Edge, Node, Path, Value};
13use anyhow::{Result, anyhow};
14use std::collections::HashMap;
15use uni_common::core::id::{Eid, Vid};
16
17pub struct ResultNormalizer;
18
19impl ResultNormalizer {
20 pub fn normalize_row(row: HashMap<String, Value>) -> Result<HashMap<String, Value>> {
22 row.into_iter()
23 .map(|(k, v)| Ok((k, Self::normalize_value(v)?)))
24 .collect()
25 }
26
27 pub fn normalize_value(value: Value) -> Result<Value> {
29 match value {
30 Value::List(items) => {
31 let normalized: Result<Vec<_>> =
32 items.into_iter().map(Self::normalize_value).collect();
33 Ok(Value::List(normalized?))
34 }
35
36 Value::Map(map) => {
37 if Self::is_path_map(&map) {
39 Self::map_to_path(map)
40 } else if Self::is_node_map(&map) {
41 Self::map_to_node(map)
42 } else if Self::is_edge_map(&map) {
43 Self::map_to_edge(map)
44 } else {
45 let normalized: Result<HashMap<_, _>> = map
46 .into_iter()
47 .map(|(k, v)| Ok((k, Self::normalize_value(v)?)))
48 .collect();
49 Ok(Value::Map(normalized?))
50 }
51 }
52
53 _ => Ok(value),
55 }
56 }
57
58 fn normalize_property_value(value: Value) -> Value {
64 match value {
65 Value::List(items) => Value::List(
66 items
67 .into_iter()
68 .map(Self::normalize_property_value)
69 .collect(),
70 ),
71 Value::Map(map) => Value::Map(
72 map.into_iter()
73 .map(|(k, v)| (k, Self::normalize_property_value(v)))
74 .collect(),
75 ),
76 other => other,
77 }
78 }
79
80 fn is_node_map(map: &HashMap<String, Value>) -> bool {
85 map.contains_key("_vid") || (map.contains_key("_id") && map.contains_key("label"))
86 }
87
88 fn is_edge_map(map: &HashMap<String, Value>) -> bool {
93 map.contains_key("_eid")
94 || (map.contains_key("_id") && map.contains_key("_src") && map.contains_key("_dst"))
95 }
96
97 fn is_path_map(map: &HashMap<String, Value>) -> bool {
99 map.contains_key("nodes")
100 && (map.contains_key("relationships") || map.contains_key("edges"))
101 }
102
103 fn value_to_u64(value: &Value) -> Option<u64> {
105 match value {
106 Value::Int(i) => u64::try_from(*i).ok(),
107 Value::String(s) => s.parse().ok(),
108 _ => None,
109 }
110 }
111
112 fn value_to_string(value: &Value) -> Option<String> {
114 if let Value::String(s) = value {
115 Some(s.clone())
116 } else {
117 None
118 }
119 }
120
121 fn is_user_property(key: &str) -> bool {
123 !key.starts_with('_')
124 && key != "properties"
125 && key != "label"
126 && key != "type"
127 && key != "overflow_json"
128 }
129
130 fn extract_properties_from_field_or_inline(
136 map: &HashMap<String, Value>,
137 ) -> HashMap<String, Value> {
138 if let Some(props_value) = map.get("properties") {
140 match props_value {
141 Value::Map(m) => {
143 return Self::prune_null_properties(
144 m.iter()
145 .map(|(k, v)| (k.clone(), Self::normalize_property_value(v.clone())))
146 .collect(),
147 );
148 }
149 Value::Bytes(bytes) => {
151 if let Ok(props) =
152 serde_json::from_slice::<HashMap<String, serde_json::Value>>(bytes)
153 {
154 return Self::prune_null_properties(
155 props
156 .into_iter()
157 .map(|(k, v)| (k, Self::json_value_to_value(v)))
158 .collect(),
159 );
160 }
161 }
162 _ => {}
163 }
164 }
165
166 if let Some(Value::Map(all_props)) = map.get("_all_props") {
169 let mut properties: HashMap<String, Value> = all_props
170 .iter()
171 .map(|(k, v)| (k.clone(), Self::normalize_property_value(v.clone())))
172 .collect();
173 for (k, v) in map.iter() {
175 if Self::is_user_property(k) {
176 properties
177 .entry(k.clone())
178 .or_insert_with(|| Self::normalize_property_value(v.clone()));
179 }
180 }
181 return Self::prune_null_properties(properties);
182 }
183
184 Self::prune_null_properties(
186 map.iter()
187 .filter(|(k, _)| Self::is_user_property(k))
188 .map(|(k, v)| (k.clone(), Self::normalize_property_value(v.clone())))
189 .collect(),
190 )
191 }
192
193 fn prune_null_properties(mut properties: HashMap<String, Value>) -> HashMap<String, Value> {
195 properties.retain(|_, v| !v.is_null());
196 properties
197 }
198
199 fn json_value_to_value(json: serde_json::Value) -> Value {
201 match json {
202 serde_json::Value::Null => Value::Null,
203 serde_json::Value::Bool(b) => Value::Bool(b),
204 serde_json::Value::Number(n) => n
205 .as_i64()
206 .map(Value::Int)
207 .or_else(|| n.as_f64().map(Value::Float))
208 .unwrap_or_else(|| Value::String(n.to_string())),
209 serde_json::Value::String(s) => Value::String(s),
210 serde_json::Value::Array(arr) => {
211 Value::List(arr.into_iter().map(Self::json_value_to_value).collect())
212 }
213 serde_json::Value::Object(obj) => Value::Map(
214 obj.into_iter()
215 .map(|(k, v)| (k, Self::json_value_to_value(v)))
216 .collect(),
217 ),
218 }
219 }
220
221 fn map_to_node(map: HashMap<String, Value>) -> Result<Value> {
223 let vid = map
224 .get("_vid")
225 .or_else(|| map.get("_id"))
226 .and_then(Self::value_to_u64)
227 .map(Vid::new)
228 .ok_or_else(|| anyhow!("Missing or invalid _vid in node map"))?;
229
230 let labels = if let Some(Value::List(label_list)) = map.get("_labels") {
231 label_list
232 .iter()
233 .filter_map(|v| {
234 if let Value::String(s) = v {
235 Some(s.clone())
236 } else {
237 None
238 }
239 })
240 .collect()
241 } else if let Some(Value::String(s)) = map.get("_labels") {
242 if s.is_empty() {
244 vec![]
245 } else {
246 vec![s.clone()]
247 }
248 } else {
249 Vec::new()
250 };
251
252 let properties = Self::extract_properties_from_field_or_inline(&map);
255
256 Ok(Value::Node(Node {
257 vid,
258 labels,
259 properties,
260 }))
261 }
262
263 fn map_to_edge(map: HashMap<String, Value>) -> Result<Value> {
265 let eid = map
266 .get("_eid")
267 .or_else(|| map.get("_id"))
268 .and_then(Self::value_to_u64)
269 .map(Eid::new)
270 .ok_or_else(|| anyhow!("Missing or invalid _eid in edge map"))?;
271
272 let edge_type = ["_type_name", "_type", "type"]
274 .iter()
275 .find_map(|key| map.get(*key).and_then(Self::value_to_string))
276 .filter(|s| !s.is_empty())
277 .unwrap_or_default();
278
279 let src = map
280 .get("_src")
281 .and_then(Self::value_to_u64)
282 .map(Vid::new)
283 .ok_or_else(|| anyhow!("Missing _src in edge map"))?;
284
285 let dst = map
286 .get("_dst")
287 .and_then(Self::value_to_u64)
288 .map(Vid::new)
289 .ok_or_else(|| anyhow!("Missing _dst in edge map"))?;
290
291 let properties = Self::extract_properties_from_field_or_inline(&map);
294
295 Ok(Value::Edge(Edge {
296 eid,
297 edge_type,
298 src,
299 dst,
300 properties,
301 }))
302 }
303
304 fn map_to_path(mut map: HashMap<String, Value>) -> Result<Value> {
306 let nodes = Self::extract_path_nodes(
307 map.remove("nodes")
308 .ok_or_else(|| anyhow!("Missing nodes in path map"))?,
309 )?;
310
311 let edges = Self::extract_path_edges(
312 map.remove("relationships")
313 .or_else(|| map.remove("edges"))
314 .ok_or_else(|| anyhow!("Missing relationships/edges in path map"))?,
315 )?;
316
317 Ok(Value::Path(Path { nodes, edges }))
318 }
319
320 fn extract_path_elements<T>(
326 value: Value,
327 extract_native: fn(Value) -> Option<T>,
328 convert_map: fn(HashMap<String, Value>) -> Result<Value>,
329 type_name: &str,
330 ) -> Result<Vec<T>> {
331 let Value::List(items) = value else {
332 return Err(anyhow!("Path {} must be a list", type_name));
333 };
334
335 items
336 .into_iter()
337 .map(|item| match item {
338 Value::Map(m) => extract_native(convert_map(m)?)
339 .ok_or_else(|| anyhow!("Failed to convert map to {} in path", type_name)),
340 other => extract_native(other)
341 .ok_or_else(|| anyhow!("Invalid {} type in path list", type_name)),
342 })
343 .collect()
344 }
345
346 fn extract_path_nodes(value: Value) -> Result<Vec<Node>> {
348 Self::extract_path_elements(
349 value,
350 |v| {
351 if let Value::Node(n) = v {
352 Some(n)
353 } else {
354 None
355 }
356 },
357 Self::map_to_node,
358 "nodes",
359 )
360 }
361
362 fn extract_path_edges(value: Value) -> Result<Vec<Edge>> {
364 Self::extract_path_elements(
365 value,
366 |v| {
367 if let Value::Edge(e) = v {
368 Some(e)
369 } else {
370 None
371 }
372 },
373 Self::map_to_edge,
374 "edges",
375 )
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn test_normalize_node_map() {
385 let mut map = HashMap::new();
386 map.insert("_vid".to_string(), Value::Int(123));
387 map.insert(
388 "_labels".to_string(),
389 Value::List(vec![Value::String("Person".to_string())]),
390 );
391 map.insert("name".to_string(), Value::String("Alice".to_string()));
392 map.insert("age".to_string(), Value::Int(30));
393
394 let result = ResultNormalizer::normalize_value(Value::Map(map)).unwrap();
395
396 match result {
397 Value::Node(node) => {
398 assert_eq!(node.vid.as_u64(), 123);
399 assert_eq!(node.labels, vec!["Person".to_string()]);
400 assert_eq!(
401 node.properties.get("name"),
402 Some(&Value::String("Alice".to_string()))
403 );
404 assert_eq!(node.properties.get("age"), Some(&Value::Int(30)));
405 assert!(!node.properties.contains_key("_vid"));
407 assert!(!node.properties.contains_key("_labels"));
408 }
409 _ => panic!("Expected Node variant"),
410 }
411 }
412
413 #[test]
414 fn test_normalize_edge_map() {
415 let mut map = HashMap::new();
416 map.insert("_eid".to_string(), Value::Int(456));
417 map.insert("_type".to_string(), Value::String("KNOWS".to_string()));
418 map.insert("_src".to_string(), Value::Int(123));
419 map.insert("_dst".to_string(), Value::Int(789));
420 map.insert("since".to_string(), Value::Int(2020));
421
422 let result = ResultNormalizer::normalize_value(Value::Map(map)).unwrap();
423
424 match result {
425 Value::Edge(edge) => {
426 assert_eq!(edge.eid.as_u64(), 456);
427 assert_eq!(edge.edge_type, "KNOWS");
428 assert_eq!(edge.src.as_u64(), 123);
429 assert_eq!(edge.dst.as_u64(), 789);
430 assert_eq!(edge.properties.get("since"), Some(&Value::Int(2020)));
431 assert!(!edge.properties.contains_key("_eid"));
433 assert!(!edge.properties.contains_key("_type"));
434 }
435 _ => panic!("Expected Edge variant"),
436 }
437 }
438
439 #[test]
440 fn test_normalize_nested_structures() {
441 let mut inner_map = HashMap::new();
442 inner_map.insert("_vid".to_string(), Value::Int(100));
443 inner_map.insert(
444 "_labels".to_string(),
445 Value::List(vec![Value::String("Node".to_string())]),
446 );
447
448 let list = vec![Value::Map(inner_map.clone()), Value::Int(42)];
449
450 let result = ResultNormalizer::normalize_value(Value::List(list)).unwrap();
451
452 match result {
453 Value::List(items) => {
454 assert_eq!(items.len(), 2);
455 assert!(matches!(items[0], Value::Node(_)));
456 assert_eq!(items[1], Value::Int(42));
457 }
458 _ => panic!("Expected List variant"),
459 }
460 }
461
462 #[test]
463 fn test_normalize_regular_map() {
464 let mut map = HashMap::new();
465 map.insert("key1".to_string(), Value::String("value1".to_string()));
466 map.insert("key2".to_string(), Value::Int(42));
467
468 let result = ResultNormalizer::normalize_value(Value::Map(map)).unwrap();
469
470 match result {
471 Value::Map(m) => {
472 assert_eq!(m.get("key1"), Some(&Value::String("value1".to_string())));
473 assert_eq!(m.get("key2"), Some(&Value::Int(42)));
474 }
475 _ => panic!("Expected Map variant for regular map"),
476 }
477 }
478
479 #[test]
480 fn test_normalize_row() {
481 let mut node_map = HashMap::new();
482 node_map.insert("_vid".to_string(), Value::Int(123));
483 node_map.insert(
484 "_labels".to_string(),
485 Value::List(vec![Value::String("Person".to_string())]),
486 );
487 node_map.insert("name".to_string(), Value::String("Alice".to_string()));
488
489 let mut row = HashMap::new();
490 row.insert("n".to_string(), Value::Map(node_map));
491 row.insert("count".to_string(), Value::Int(5));
492
493 let result = ResultNormalizer::normalize_row(row).unwrap();
494
495 assert!(matches!(result.get("n"), Some(Value::Node(_))));
496 assert_eq!(result.get("count"), Some(&Value::Int(5)));
497 }
498
499 #[test]
500 fn test_map_with_vid_at_top_level_becomes_node() {
501 let mut map = HashMap::new();
504 map.insert("_vid".to_string(), Value::Int(123));
505 map.insert("name".to_string(), Value::String("test".to_string()));
506
507 let result = ResultNormalizer::normalize_value(Value::Map(map)).unwrap();
508
509 match result {
510 Value::Node(node) => {
511 assert_eq!(node.vid.as_u64(), 123);
512 assert!(node.labels.is_empty()); assert_eq!(
514 node.properties.get("name"),
515 Some(&Value::String("test".to_string()))
516 );
517 }
518 _ => panic!("Expected Node variant, got {:?}", result),
519 }
520 }
521
522 #[test]
523 fn test_normalize_node_with_nested_map_containing_vid_key() {
524 let mut nested = HashMap::new();
527 nested.insert("_vid".to_string(), Value::String("user-data".to_string()));
528 nested.insert("other".to_string(), Value::Int(42));
529
530 let mut node_map = HashMap::new();
531 node_map.insert("_vid".to_string(), Value::Int(123));
532 node_map.insert(
533 "_labels".to_string(),
534 Value::List(vec![Value::String("Person".to_string())]),
535 );
536 node_map.insert("metadata".to_string(), Value::Map(nested));
537
538 let result = ResultNormalizer::normalize_value(Value::Map(node_map)).unwrap();
539
540 match result {
541 Value::Node(node) => {
542 assert_eq!(node.vid.as_u64(), 123);
543 assert_eq!(node.labels, vec!["Person".to_string()]);
544 match node.properties.get("metadata") {
546 Some(Value::Map(m)) => {
547 assert_eq!(m.get("_vid"), Some(&Value::String("user-data".to_string())));
548 assert_eq!(m.get("other"), Some(&Value::Int(42)));
549 }
550 other => panic!("Expected metadata to be Map, got {:?}", other),
551 }
552 }
553 _ => panic!("Expected Node variant"),
554 }
555 }
556
557 #[test]
558 fn test_normalize_edge_with_nested_map_containing_eid_key() {
559 let mut nested = HashMap::new();
562 nested.insert("_eid".to_string(), Value::String("ref-123".to_string()));
563
564 let mut edge_map = HashMap::new();
565 edge_map.insert("_eid".to_string(), Value::Int(456));
566 edge_map.insert("_type".to_string(), Value::String("KNOWS".to_string()));
567 edge_map.insert("_src".to_string(), Value::Int(123));
568 edge_map.insert("_dst".to_string(), Value::Int(789));
569 edge_map.insert("reference".to_string(), Value::Map(nested));
570
571 let result = ResultNormalizer::normalize_value(Value::Map(edge_map)).unwrap();
572
573 match result {
574 Value::Edge(edge) => {
575 assert_eq!(edge.eid.as_u64(), 456);
576 match edge.properties.get("reference") {
578 Some(Value::Map(m)) => {
579 assert_eq!(m.get("_eid"), Some(&Value::String("ref-123".to_string())));
580 }
581 other => panic!("Expected reference to be Map, got {:?}", other),
582 }
583 }
584 _ => panic!("Expected Edge variant"),
585 }
586 }
587
588 #[test]
589 fn test_normalize_node_prunes_null_properties() {
590 let mut map = HashMap::new();
591 map.insert("_vid".to_string(), Value::Int(1));
592 map.insert(
593 "_labels".to_string(),
594 Value::List(vec![Value::String("Person".to_string())]),
595 );
596 map.insert("name".to_string(), Value::String("Alice".to_string()));
597 map.insert("age".to_string(), Value::Null);
598
599 let result = ResultNormalizer::normalize_value(Value::Map(map)).unwrap();
600 let Value::Node(node) = result else {
601 panic!("Expected Node variant");
602 };
603
604 assert_eq!(
605 node.properties.get("name"),
606 Some(&Value::String("Alice".to_string()))
607 );
608 assert!(!node.properties.contains_key("age"));
609 }
610
611 #[test]
612 fn test_normalize_edge_prunes_null_properties_from_all_props_and_inline() {
613 let mut all_props = HashMap::new();
614 all_props.insert("since".to_string(), Value::Null);
615 all_props.insert("weight".to_string(), Value::Int(7));
616
617 let mut edge_map = HashMap::new();
618 edge_map.insert("_eid".to_string(), Value::Int(10));
619 edge_map.insert("_type".to_string(), Value::String("REL".to_string()));
620 edge_map.insert("_src".to_string(), Value::Int(1));
621 edge_map.insert("_dst".to_string(), Value::Int(2));
622 edge_map.insert("_all_props".to_string(), Value::Map(all_props));
623 edge_map.insert("name".to_string(), Value::Null);
624
625 let result = ResultNormalizer::normalize_value(Value::Map(edge_map)).unwrap();
626 let Value::Edge(edge) = result else {
627 panic!("Expected Edge variant");
628 };
629
630 assert_eq!(edge.properties.get("weight"), Some(&Value::Int(7)));
631 assert!(!edge.properties.contains_key("since"));
632 assert!(!edge.properties.contains_key("name"));
633 }
634}