1use std::collections::{HashMap, HashSet, VecDeque};
34use std::sync::atomic::{AtomicUsize, Ordering};
35use std::sync::{Arc, Mutex};
36
37use rmpv::Value;
38
39use crate::function::AppError;
40
41const RESERVED_NAMES: &[&str] = &[
44 "input",
45 "output",
46 "model",
47 "response",
48 "result",
49 "parameter",
50 "none",
51 "next",
52 "api",
53 "error",
54];
55
56fn invalid(message: impl Into<String>) -> AppError {
57 AppError::new(400, message)
58}
59
60fn validate_name(name: &str) -> Result<(), AppError> {
63 let valid = !name.is_empty()
64 && name
65 .bytes()
66 .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-');
67 if valid {
68 Ok(())
69 } else {
70 Err(invalid(format!(
71 "Invalid syntax ({name}). Please use 0-9, A-Z, a-z, underscore and hyphen characters."
72 )))
73 }
74}
75
76#[derive(Debug, Default)]
78pub struct GraphProperties {
79 properties: Mutex<HashMap<String, Value>>,
80}
81
82impl GraphProperties {
83 pub fn get_properties(&self) -> HashMap<String, Value> {
85 self.properties.lock().expect("graph properties").clone()
86 }
87
88 pub fn get_property(&self, key: &str) -> Option<Value> {
89 self.properties
90 .lock()
91 .expect("graph properties")
92 .get(key)
93 .cloned()
94 }
95
96 pub fn add_property(&self, key: &str, value: Value) -> Result<(), AppError> {
98 if key.is_empty() {
99 return Err(invalid("key cannot be empty"));
100 }
101 if matches!(value, Value::Nil) {
102 return Err(invalid("value cannot be null"));
103 }
104 validate_name(key)?;
105 self.properties
106 .lock()
107 .expect("graph properties")
108 .insert(key.to_string(), value);
109 Ok(())
110 }
111
112 pub fn remove_property(&self, key: &str) -> Result<(), AppError> {
113 if key.is_empty() {
114 return Err(invalid("key cannot be empty"));
115 }
116 self.properties
117 .lock()
118 .expect("graph properties")
119 .remove(key);
120 Ok(())
121 }
122}
123
124#[derive(Debug)]
127pub struct SimpleNode {
128 id: String,
129 alias: String,
130 types: Mutex<HashSet<String>>,
131 properties: GraphProperties,
132}
133
134impl PartialEq for SimpleNode {
135 fn eq(&self, other: &Self) -> bool {
136 self.id == other.id
137 }
138}
139impl Eq for SimpleNode {}
140
141impl SimpleNode {
142 fn new(id: &str, alias: &str, node_type: &str) -> Result<Self, AppError> {
143 validate_name(alias)?;
144 validate_name(node_type)?;
145 let node = SimpleNode {
146 id: id.to_string(),
147 alias: alias.to_string(),
148 types: Mutex::new(HashSet::new()),
149 properties: GraphProperties::default(),
150 };
151 node.types
152 .lock()
153 .expect("node types")
154 .insert(node_type.to_string());
155 Ok(node)
156 }
157
158 pub fn get_id(&self) -> &str {
159 &self.id
160 }
161
162 pub fn get_alias(&self) -> &str {
163 &self.alias
164 }
165
166 pub fn get_types(&self) -> HashSet<String> {
167 self.types.lock().expect("node types").clone()
168 }
169
170 pub fn add_type(&self, node_type: &str) -> Result<(), AppError> {
171 if node_type.is_empty() {
172 return Err(invalid("type cannot be empty"));
173 }
174 validate_name(node_type)?;
175 self.types
176 .lock()
177 .expect("node types")
178 .insert(node_type.to_string());
179 Ok(())
180 }
181
182 pub fn reset_types(&self, initial: &str) -> Result<(), AppError> {
185 validate_name(initial)?;
186 let mut types = self.types.lock().expect("node types");
187 types.clear();
188 types.insert(initial.to_string());
189 Ok(())
190 }
191
192 pub fn clear_properties(&self) {
194 self.properties
195 .properties
196 .lock()
197 .expect("graph properties")
198 .clear();
199 }
200
201 pub fn remove_type(&self, node_type: &str) -> Result<(), AppError> {
203 if node_type.is_empty() {
204 return Err(invalid("type cannot be empty"));
205 }
206 let mut types = self.types.lock().expect("node types");
207 if types.len() == 1 {
208 return Err(invalid(
209 "Cannot remove type because a node must have at least one type",
210 ));
211 }
212 types.remove(node_type);
213 Ok(())
214 }
215
216 pub fn get_properties(&self) -> HashMap<String, Value> {
217 self.properties.get_properties()
218 }
219
220 pub fn get_property(&self, key: &str) -> Option<Value> {
221 self.properties.get_property(key)
222 }
223
224 pub fn add_property(&self, key: &str, value: Value) -> Result<(), AppError> {
225 self.properties.add_property(key, value)
226 }
227
228 pub fn remove_property(&self, key: &str) -> Result<(), AppError> {
229 self.properties.remove_property(key)
230 }
231}
232
233#[derive(Debug)]
235pub struct SimpleRelationship {
236 relation_type: String,
237 source_alias: String,
238 target_alias: String,
239 properties: GraphProperties,
240}
241
242impl PartialEq for SimpleRelationship {
243 fn eq(&self, other: &Self) -> bool {
244 self.relation_type == other.relation_type
245 && self.source_alias == other.source_alias
246 && self.target_alias == other.target_alias
247 }
248}
249
250impl SimpleRelationship {
251 pub fn get_type(&self) -> &str {
252 &self.relation_type
253 }
254
255 pub fn get_source_alias(&self) -> &str {
256 &self.source_alias
257 }
258
259 pub fn get_target_alias(&self) -> &str {
260 &self.target_alias
261 }
262
263 pub fn get_properties(&self) -> HashMap<String, Value> {
264 self.properties.get_properties()
265 }
266
267 pub fn get_property(&self, key: &str) -> Option<Value> {
268 self.properties.get_property(key)
269 }
270
271 pub fn add_property(&self, key: &str, value: Value) -> Result<(), AppError> {
272 self.properties.add_property(key, value)
273 }
274}
275
276#[derive(Debug)]
279pub struct SimpleConnection {
280 id: String,
281 source: Arc<SimpleNode>,
282 target: Arc<SimpleNode>,
283 relationships: Mutex<HashMap<String, Arc<SimpleRelationship>>>,
284}
285
286impl PartialEq for SimpleConnection {
287 fn eq(&self, other: &Self) -> bool {
288 self.id == other.id
289 }
290}
291
292impl SimpleConnection {
293 pub fn get_id(&self) -> &str {
294 &self.id
295 }
296
297 pub fn get_source(&self) -> &Arc<SimpleNode> {
298 &self.source
299 }
300
301 pub fn get_target(&self) -> &Arc<SimpleNode> {
302 &self.target
303 }
304
305 pub fn add_relation(&self, relation_type: &str) -> Arc<SimpleRelationship> {
307 let relation = Arc::new(SimpleRelationship {
308 relation_type: relation_type.to_string(),
309 source_alias: self.source.get_alias().to_string(),
310 target_alias: self.target.get_alias().to_string(),
311 properties: GraphProperties::default(),
312 });
313 self.relationships
314 .lock()
315 .expect("relations")
316 .insert(relation_type.to_lowercase(), relation.clone());
317 relation
318 }
319
320 pub fn get_relation(&self, relation_type: &str) -> Option<Arc<SimpleRelationship>> {
322 self.relationships
323 .lock()
324 .expect("relations")
325 .get(&relation_type.to_lowercase())
326 .cloned()
327 }
328
329 pub fn get_relations(&self) -> Vec<Arc<SimpleRelationship>> {
330 self.relationships
331 .lock()
332 .expect("relations")
333 .values()
334 .cloned()
335 .collect()
336 }
337}
338
339struct GraphState {
340 nodes_by_alias: HashMap<String, Arc<SimpleNode>>,
341 nodes_by_id: HashMap<String, Arc<SimpleNode>>,
342 connections: HashMap<String, Arc<SimpleConnection>>,
343 successors: HashMap<String, HashSet<String>>,
344 predecessors: HashMap<String, HashSet<String>>,
345}
346
347pub struct MiniGraph {
350 graph_id: String,
351 max_nodes: usize,
352 node_count: AtomicUsize,
353 state: Mutex<GraphState>,
354}
355
356impl Default for MiniGraph {
357 fn default() -> Self {
358 Self::new()
359 }
360}
361
362impl MiniGraph {
363 pub fn new() -> Self {
365 Self::with_max_nodes(750)
366 }
367
368 pub fn with_max_nodes(max_nodes: usize) -> Self {
371 MiniGraph {
372 graph_id: uuid::Uuid::new_v4().simple().to_string(),
373 max_nodes,
374 node_count: AtomicUsize::new(0),
375 state: Mutex::new(GraphState {
376 nodes_by_alias: HashMap::new(),
377 nodes_by_id: HashMap::new(),
378 connections: HashMap::new(),
379 successors: HashMap::new(),
380 predecessors: HashMap::new(),
381 }),
382 }
383 }
384
385 pub fn get_id(&self) -> &str {
387 &self.graph_id
388 }
389
390 pub fn get_node_count(&self) -> usize {
391 self.node_count.load(Ordering::SeqCst)
392 }
393
394 pub fn is_empty(&self) -> bool {
395 self.get_node_count() == 0
396 }
397
398 pub fn create_root_node(&self) -> Result<Arc<SimpleNode>, AppError> {
399 self.create_node("root", "root")
400 }
401
402 pub fn create_end_node(&self) -> Result<Arc<SimpleNode>, AppError> {
403 self.create_node("end", "end")
404 }
405
406 pub fn get_root_node(&self) -> Option<Arc<SimpleNode>> {
407 self.find_node_by_alias("root").ok().flatten()
408 }
409
410 pub fn get_end_node(&self) -> Option<Arc<SimpleNode>> {
411 self.find_node_by_alias("end").ok().flatten()
412 }
413
414 pub fn create_node(&self, alias: &str, node_type: &str) -> Result<Arc<SimpleNode>, AppError> {
416 if alias.is_empty() {
417 return Err(invalid("alias must not be empty"));
418 }
419 if node_type.is_empty() {
420 return Err(invalid("type must not be empty"));
421 }
422 let alias_lower = alias.to_lowercase();
423 if RESERVED_NAMES.contains(&alias_lower.as_str()) {
424 return Err(invalid(format!("alias '{alias_lower}' is a reserved name")));
425 }
426 let mut state = self.state.lock().expect("graph state");
427 if state.nodes_by_alias.contains_key(&alias_lower) {
428 return Err(invalid(format!("alias '{alias_lower}' already exists")));
429 }
430 if self.node_count.load(Ordering::SeqCst) > self.max_nodes {
431 return Err(invalid(format!(
432 "max number of nodes is {}",
433 self.max_nodes
434 )));
435 }
436 let count = self.node_count.fetch_add(1, Ordering::SeqCst) + 1;
437 let id = uuid::Uuid::new_v4().simple().to_string();
438 let node = Arc::new(SimpleNode::new(&id, alias, node_type)?);
439 state.nodes_by_alias.insert(alias_lower, node.clone());
440 state.nodes_by_id.insert(id, node.clone());
441 log::debug!("Created {node_type} as {alias}, total={count}");
442 Ok(node)
443 }
444
445 pub fn remove_node(&self, alias: &str) -> Result<(), AppError> {
447 if alias.is_empty() {
448 return Err(invalid("alias must not be empty"));
449 }
450 let Some(node) = self.find_node_by_alias(alias)? else {
451 return Ok(());
452 };
453 for neighbor in self.get_forward_links(alias)? {
454 self.remove_connection(alias, neighbor.get_alias())?;
455 }
456 for neighbor in self.get_backward_links(alias)? {
457 self.remove_connection(neighbor.get_alias(), alias)?;
458 }
459 let mut state = self.state.lock().expect("graph state");
460 state.nodes_by_id.remove(node.get_id());
461 state.nodes_by_alias.remove(&alias.to_lowercase());
465 let count = self.node_count.fetch_sub(1, Ordering::SeqCst) - 1;
466 log::debug!("Removed {alias}, total={count}");
467 Ok(())
468 }
469
470 pub fn get_nodes(&self) -> Vec<Arc<SimpleNode>> {
471 self.state
472 .lock()
473 .expect("graph state")
474 .nodes_by_id
475 .values()
476 .cloned()
477 .collect()
478 }
479
480 pub fn get_connections(&self) -> Vec<Arc<SimpleConnection>> {
481 self.state
482 .lock()
483 .expect("graph state")
484 .connections
485 .values()
486 .cloned()
487 .collect()
488 }
489
490 pub fn reset(&self) {
492 let mut state = self.state.lock().expect("graph state");
493 state.connections.clear();
494 state.successors.clear();
495 state.predecessors.clear();
496 state.nodes_by_alias.clear();
497 state.nodes_by_id.clear();
498 self.node_count.store(0, Ordering::SeqCst);
499 }
500
501 fn resolve_pair(
502 &self,
503 source_alias: &str,
504 target_alias: &str,
505 ) -> Result<(Arc<SimpleNode>, Arc<SimpleNode>), AppError> {
506 if source_alias.is_empty() {
507 return Err(invalid("source alias cannot be null"));
508 }
509 if target_alias.is_empty() {
510 return Err(invalid("target alias cannot be null"));
511 }
512 if source_alias.eq_ignore_ascii_case(target_alias) {
513 return Err(invalid("source and target aliases cannot be the same"));
514 }
515 let source = self
516 .find_node_by_alias(source_alias)?
517 .ok_or_else(|| invalid("source node does not exist"))?;
518 let target = self
519 .find_node_by_alias(target_alias)?
520 .ok_or_else(|| invalid("target node does not exist"))?;
521 Ok((source, target))
522 }
523
524 pub fn connect(
526 &self,
527 source_alias: &str,
528 target_alias: &str,
529 ) -> Result<Arc<SimpleConnection>, AppError> {
530 let (source, target) = self.resolve_pair(source_alias, target_alias)?;
531 let key = pair_key(source.get_id(), target.get_id());
532 let mut state = self.state.lock().expect("graph state");
533 if let Some(existing) = state.connections.get(&key) {
534 return Ok(existing.clone());
535 }
536 let connection = Arc::new(SimpleConnection {
537 id: uuid::Uuid::new_v4().simple().to_string(),
538 source: source.clone(),
539 target: target.clone(),
540 relationships: Mutex::new(HashMap::new()),
541 });
542 state.connections.insert(key, connection.clone());
543 state
544 .successors
545 .entry(source.get_id().to_string())
546 .or_default()
547 .insert(target.get_id().to_string());
548 state
549 .predecessors
550 .entry(target.get_id().to_string())
551 .or_default()
552 .insert(source.get_id().to_string());
553 log::debug!("Created connection {source_alias} to {target_alias}");
554 Ok(connection)
555 }
556
557 pub fn remove_connection(
559 &self,
560 source_alias: &str,
561 target_alias: &str,
562 ) -> Result<(), AppError> {
563 let (source, target) = self.resolve_pair(source_alias, target_alias)?;
564 let key = pair_key(source.get_id(), target.get_id());
565 let mut state = self.state.lock().expect("graph state");
566 if state.connections.remove(&key).is_some() {
567 if let Some(targets) = state.successors.get_mut(source.get_id()) {
568 targets.remove(target.get_id());
569 if targets.is_empty() {
570 state.successors.remove(source.get_id());
571 }
572 }
573 if let Some(sources) = state.predecessors.get_mut(target.get_id()) {
574 sources.remove(source.get_id());
575 if sources.is_empty() {
576 state.predecessors.remove(target.get_id());
577 }
578 }
579 log::debug!(
580 "Removed connection {} to {}",
581 source.get_alias(),
582 target.get_alias()
583 );
584 }
585 Ok(())
586 }
587
588 pub fn find_node_by_alias(&self, alias: &str) -> Result<Option<Arc<SimpleNode>>, AppError> {
590 if alias.is_empty() {
591 return Err(invalid("alias cannot be null"));
592 }
593 Ok(self
594 .state
595 .lock()
596 .expect("graph state")
597 .nodes_by_alias
598 .get(&alias.to_lowercase())
599 .cloned())
600 }
601
602 pub fn find_node_by_id(&self, id: &str) -> Result<Option<Arc<SimpleNode>>, AppError> {
603 if id.is_empty() {
604 return Err(invalid("id cannot be null"));
605 }
606 Ok(self
607 .state
608 .lock()
609 .expect("graph state")
610 .nodes_by_id
611 .get(id)
612 .cloned())
613 }
614
615 pub fn find_nodes_by_type(&self, node_type: &str) -> Result<Vec<Arc<SimpleNode>>, AppError> {
617 if node_type.is_empty() {
618 return Err(invalid("type cannot be empty"));
619 }
620 Ok(self
621 .get_nodes()
622 .into_iter()
623 .filter(|node| {
624 node.get_types()
625 .iter()
626 .any(|t| t.eq_ignore_ascii_case(node_type))
627 })
628 .collect())
629 }
630
631 pub fn find_relation_by_type(
633 &self,
634 relation_type: &str,
635 ) -> Result<Vec<Arc<SimpleRelationship>>, AppError> {
636 if relation_type.is_empty() {
637 return Err(invalid("type cannot be empty"));
638 }
639 Ok(self
640 .get_connections()
641 .into_iter()
642 .filter_map(|conn| conn.get_relation(relation_type))
643 .collect())
644 }
645
646 pub fn find_nodes_by_property(
649 &self,
650 key: &str,
651 value: &Value,
652 ) -> Result<Vec<Arc<SimpleNode>>, AppError> {
653 if key.is_empty() {
654 return Err(invalid("key cannot be empty"));
655 }
656 Ok(self
657 .get_nodes()
658 .into_iter()
659 .filter(|node| {
660 node.get_properties()
661 .iter()
662 .any(|(k, v)| k.eq_ignore_ascii_case(key) && v == value)
663 })
664 .collect())
665 }
666
667 pub fn find_connection(
668 &self,
669 source_alias: &str,
670 target_alias: &str,
671 ) -> Result<Option<Arc<SimpleConnection>>, AppError> {
672 let (source, target) = self.resolve_pair(source_alias, target_alias)?;
673 Ok(self
674 .state
675 .lock()
676 .expect("graph state")
677 .connections
678 .get(&pair_key(source.get_id(), target.get_id()))
679 .cloned())
680 }
681
682 pub fn find_bi_directional_connection(
684 &self,
685 source_alias: &str,
686 target_alias: &str,
687 ) -> Result<Vec<Arc<SimpleConnection>>, AppError> {
688 let mut both = Vec::new();
689 if let Some(forward) = self.find_connection(source_alias, target_alias)? {
690 both.push(forward);
691 }
692 if let Some(backward) = self.find_connection(target_alias, source_alias)? {
693 both.push(backward);
694 }
695 Ok(both)
696 }
697
698 fn require_node(&self, alias: &str) -> Result<Arc<SimpleNode>, AppError> {
699 self.find_node_by_alias(alias)?
700 .ok_or_else(|| invalid("node does not exist"))
701 }
702
703 fn linked_nodes(&self, node_id: &str, forward: bool) -> Vec<Arc<SimpleNode>> {
704 let state = self.state.lock().expect("graph state");
705 let map = if forward {
706 &state.successors
707 } else {
708 &state.predecessors
709 };
710 map.get(node_id)
711 .map(|ids| {
712 ids.iter()
713 .filter_map(|id| state.nodes_by_id.get(id).cloned())
714 .collect()
715 })
716 .unwrap_or_default()
717 }
718
719 pub fn get_neighbors(&self, alias: &str) -> Result<Vec<Arc<SimpleNode>>, AppError> {
721 let node = self.require_node(alias)?;
722 let mut seen = HashSet::new();
723 let mut result = Vec::new();
724 for neighbor in self
725 .linked_nodes(node.get_id(), true)
726 .into_iter()
727 .chain(self.linked_nodes(node.get_id(), false))
728 {
729 if seen.insert(neighbor.get_id().to_string()) {
730 result.push(neighbor);
731 }
732 }
733 Ok(result)
734 }
735
736 pub fn get_forward_links(&self, alias: &str) -> Result<Vec<Arc<SimpleNode>>, AppError> {
737 let node = self.require_node(alias)?;
738 Ok(self.linked_nodes(node.get_id(), true))
739 }
740
741 pub fn get_backward_links(&self, alias: &str) -> Result<Vec<Arc<SimpleNode>>, AppError> {
742 let node = self.require_node(alias)?;
743 Ok(self.linked_nodes(node.get_id(), false))
744 }
745
746 pub fn find_paths(&self, alias: &str) -> Result<Vec<Vec<String>>, AppError> {
749 let start = self.require_node(alias)?;
750 let mut distances: HashMap<String, i64> = HashMap::new();
751 for node in self.get_nodes() {
752 distances.insert(node.get_id().to_string(), -1);
753 }
754 distances.insert(start.get_id().to_string(), 0);
755 let mut queue: VecDeque<String> = VecDeque::new();
756 queue.push_back(start.get_id().to_string());
757 while let Some(current) = queue.pop_front() {
758 let current_distance = distances[¤t];
759 let mut adjacent: HashSet<String> = HashSet::new();
760 for n in self
761 .linked_nodes(¤t, true)
762 .into_iter()
763 .chain(self.linked_nodes(¤t, false))
764 {
765 adjacent.insert(n.get_id().to_string());
766 }
767 for next in adjacent {
768 if distances.get(&next) == Some(&-1) {
769 distances.insert(next.clone(), current_distance + 1);
770 queue.push_back(next);
771 }
772 }
773 }
774 let mut levels: HashMap<i64, Vec<String>> = HashMap::new();
775 for (id, level) in &distances {
776 if *level != -1 {
777 if let Some(node) = self.find_node_by_id(id)? {
778 levels
779 .entry(*level)
780 .or_default()
781 .push(node.get_alias().to_string());
782 }
783 }
784 }
785 let mut sorted_levels: Vec<i64> = levels.keys().copied().collect();
786 sorted_levels.sort_unstable();
787 Ok(sorted_levels
788 .into_iter()
789 .map(|level| levels.remove(&level).unwrap_or_default())
790 .collect())
791 }
792
793 pub fn export_graph(&self) -> Value {
796 let mut node_entries: Vec<(String, Value)> = self
797 .get_nodes()
798 .into_iter()
799 .map(|node| {
800 let mut types: Vec<String> = node.get_types().into_iter().collect();
801 types.sort_unstable();
802 let mut properties: Vec<(String, Value)> =
803 node.get_properties().into_iter().collect();
804 properties.sort_by(|a, b| a.0.cmp(&b.0));
805 let entry = Value::Map(vec![
806 (Value::from("alias"), Value::from(node.get_alias())),
807 (
808 Value::from("types"),
809 Value::Array(types.into_iter().map(Value::from).collect()),
810 ),
811 (
812 Value::from("properties"),
813 Value::Map(
814 properties
815 .into_iter()
816 .map(|(k, v)| (Value::from(k.as_str()), v))
817 .collect(),
818 ),
819 ),
820 ]);
821 (node.get_alias().to_string(), entry)
822 })
823 .collect();
824 node_entries.sort_by(|a, b| a.0.cmp(&b.0));
825 let mut connection_entries: Vec<(String, Value)> = self
826 .get_connections()
827 .into_iter()
828 .map(|conn| {
829 let mut relations: Vec<(String, Value)> = conn
830 .get_relations()
831 .into_iter()
832 .map(|relation| {
833 let mut properties: Vec<(String, Value)> =
834 relation.get_properties().into_iter().collect();
835 properties.sort_by(|a, b| a.0.cmp(&b.0));
836 let entry = Value::Map(vec![
837 (Value::from("type"), Value::from(relation.get_type())),
838 (
839 Value::from("properties"),
840 Value::Map(
841 properties
842 .into_iter()
843 .map(|(k, v)| (Value::from(k.as_str()), v))
844 .collect(),
845 ),
846 ),
847 ]);
848 (relation.get_type().to_string(), entry)
849 })
850 .collect();
851 relations.sort_by(|a, b| a.0.cmp(&b.0));
852 let key = format!(
853 "{}:{}",
854 conn.get_source().get_alias(),
855 conn.get_target().get_alias()
856 );
857 let entry = Value::Map(vec![
858 (
859 Value::from("source"),
860 Value::from(conn.get_source().get_alias()),
861 ),
862 (
863 Value::from("target"),
864 Value::from(conn.get_target().get_alias()),
865 ),
866 (
867 Value::from("relations"),
868 Value::Array(relations.into_iter().map(|(_, v)| v).collect()),
869 ),
870 ]);
871 (key, entry)
872 })
873 .collect();
874 connection_entries.sort_by(|a, b| a.0.cmp(&b.0));
875 Value::Map(vec![
876 (
877 Value::from("nodes"),
878 Value::Array(node_entries.into_iter().map(|(_, v)| v).collect()),
879 ),
880 (
881 Value::from("connections"),
882 Value::Array(connection_entries.into_iter().map(|(_, v)| v).collect()),
883 ),
884 ])
885 }
886
887 pub fn import_graph(&self, map: &Value) -> Result<(), AppError> {
890 self.reset();
891 let outcome = self.import_nodes_and_connections(map);
892 if outcome.is_err() {
893 self.reset();
894 }
895 outcome
896 }
897
898 fn import_nodes_and_connections(&self, map: &Value) -> Result<(), AppError> {
899 let Value::Map(entries) = map else {
900 return Ok(());
901 };
902 let get = |key: &str| -> Option<&Value> {
903 entries
904 .iter()
905 .find(|(k, _)| k.as_str() == Some(key))
906 .map(|(_, v)| v)
907 };
908 let Some(Value::Array(nodes)) = get("nodes") else {
909 return Ok(());
910 };
911 for (i, entry) in nodes.iter().enumerate() {
912 self.import_node(entry, i)?;
913 }
914 if let Some(Value::Array(connections)) = get("connections") {
915 for (i, entry) in connections.iter().enumerate() {
916 self.import_connection(entry, i)?;
917 }
918 }
919 Ok(())
920 }
921
922 fn import_node(&self, entry: &Value, index: usize) -> Result<(), AppError> {
923 let get = |key: &str| -> Option<&Value> {
924 match entry {
925 Value::Map(map) => map
926 .iter()
927 .find(|(k, _)| k.as_str() == Some(key))
928 .map(|(_, v)| v),
929 _ => None,
930 }
931 };
932 let alias = get("alias")
933 .and_then(|v| v.as_str())
934 .ok_or_else(|| invalid(format!("missing alias in node entry-{}", index + 1)))?;
935 let Some(Value::Array(types)) = get("types") else {
936 return Err(invalid(format!(
937 "invalid types in node entry-{}",
938 index + 1
939 )));
940 };
941 if types.is_empty() {
942 return Err(invalid(format!(
943 "invalid types in node entry-{}",
944 index + 1
945 )));
946 }
947 let first = types[0].as_str().unwrap_or_default().to_string();
948 let node = self.create_node(alias, &first)?;
949 for t in types.iter().skip(1) {
950 node.add_type(t.as_str().unwrap_or_default())?;
951 }
952 if let Some(Value::Map(properties)) = get("properties") {
953 for (k, v) in properties {
954 node.add_property(k.as_str().unwrap_or_default(), v.clone())?;
955 }
956 }
957 Ok(())
958 }
959
960 fn import_connection(&self, entry: &Value, index: usize) -> Result<(), AppError> {
961 let get = |key: &str| -> Option<&Value> {
962 match entry {
963 Value::Map(map) => map
964 .iter()
965 .find(|(k, _)| k.as_str() == Some(key))
966 .map(|(_, v)| v),
967 _ => None,
968 }
969 };
970 let (Some(source), Some(target)) = (
971 get("source").and_then(|v| v.as_str()),
972 get("target").and_then(|v| v.as_str()),
973 ) else {
974 return Err(invalid(format!(
975 "invalid source/target alias in connection entry-{}",
976 index + 1
977 )));
978 };
979 let connection = self.connect(source, target)?;
980 if let Some(Value::Array(relations)) = get("relations") {
981 for relation_entry in relations {
982 if let Value::Map(relation_map) = relation_entry {
983 let rget = |key: &str| -> Option<&Value> {
984 relation_map
985 .iter()
986 .find(|(k, _)| k.as_str() == Some(key))
987 .map(|(_, v)| v)
988 };
989 if let Some(relation_type) = rget("type").and_then(|v| v.as_str()) {
990 let relation = connection.add_relation(relation_type);
991 if let Some(Value::Map(properties)) = rget("properties") {
992 for (k, v) in properties {
993 relation.add_property(k.as_str().unwrap_or_default(), v.clone())?;
994 }
995 }
996 }
997 }
998 }
999 }
1000 Ok(())
1001 }
1002
1003 pub fn same_as(&self, that: &MiniGraph) -> bool {
1006 self.same_nodes(that) && self.same_connections(that)
1007 }
1008
1009 fn same_nodes(&self, that: &MiniGraph) -> bool {
1010 let nodes1 = self.get_nodes();
1011 if nodes1.len() != that.get_node_count() {
1012 return false;
1013 }
1014 for n1 in nodes1 {
1015 let Ok(Some(n2)) = that.find_node_by_alias(n1.get_alias()) else {
1016 return false;
1017 };
1018 if n1.get_types() != n2.get_types() {
1019 return false;
1020 }
1021 if !same_properties(&n1.get_properties(), &n2.get_properties()) {
1022 return false;
1023 }
1024 }
1025 true
1026 }
1027
1028 fn same_connections(&self, that: &MiniGraph) -> bool {
1029 let conn1 = self.get_connections();
1030 if conn1.len() != that.get_connections().len() {
1031 return false;
1032 }
1033 for c1 in conn1 {
1034 let Ok(Some(c2)) =
1035 that.find_connection(c1.get_source().get_alias(), c1.get_target().get_alias())
1036 else {
1037 return false;
1038 };
1039 let relations1 = c1.get_relations();
1040 if relations1.len() != c2.get_relations().len() {
1041 return false;
1042 }
1043 for r1 in relations1 {
1044 let Some(r2) = c2.get_relation(r1.get_type()) else {
1045 return false;
1046 };
1047 if !same_properties(&r1.get_properties(), &r2.get_properties()) {
1048 return false;
1049 }
1050 }
1051 }
1052 true
1053 }
1054
1055 pub fn to_json(&self) -> String {
1057 serde_json::to_value(self.export_graph())
1058 .map(|v| v.to_string())
1059 .unwrap_or_default()
1060 }
1061}
1062
1063fn pair_key(source_id: &str, target_id: &str) -> String {
1064 format!("{source_id}:{target_id}")
1065}
1066
1067fn same_properties(a: &HashMap<String, Value>, b: &HashMap<String, Value>) -> bool {
1070 let mut flat_a = Vec::new();
1071 let mut flat_b = Vec::new();
1072 for (k, v) in a {
1073 flatten(k, v, &mut flat_a);
1074 }
1075 for (k, v) in b {
1076 flatten(k, v, &mut flat_b);
1077 }
1078 flat_a.sort_by(|x, y| x.0.cmp(&y.0));
1079 flat_b.sort_by(|x, y| x.0.cmp(&y.0));
1080 flat_a == flat_b
1081}
1082
1083fn flatten(prefix: &str, value: &Value, target: &mut Vec<(String, Value)>) {
1084 match value {
1085 Value::Map(entries) => {
1086 for (k, v) in entries {
1087 flatten(
1088 &format!("{prefix}.{}", k.as_str().unwrap_or_default()),
1089 v,
1090 target,
1091 );
1092 }
1093 }
1094 Value::Array(items) => {
1095 for (i, v) in items.iter().enumerate() {
1096 flatten(&format!("{prefix}[{i}]"), v, target);
1097 }
1098 }
1099 leaf => target.push((prefix.to_string(), leaf.clone())),
1100 }
1101}