1use crate::base::{DiGraph, EdgeWeight, Graph, IndexType, Node};
8use crate::error::{GraphError, Result};
9#[cfg(test)]
10use serde::Deserialize;
11use serde::{de::DeserializeOwned, Serialize};
12use std::collections::HashMap;
13use std::fmt::Debug;
14use std::hash::{Hash, Hasher};
15
16#[derive(Debug, Clone, PartialEq)]
18pub enum AttributeValue {
19 String(String),
21 Integer(i64),
23 Float(f64),
25 Boolean(bool),
27 Json(serde_json::Value),
29}
30
31impl AttributeValue {
32 pub fn string<S: Into<String>>(value: S) -> Self {
34 AttributeValue::String(value.into())
35 }
36
37 pub fn integer(value: i64) -> Self {
39 AttributeValue::Integer(value)
40 }
41
42 pub fn float(value: f64) -> Self {
44 AttributeValue::Float(value)
45 }
46
47 pub fn boolean(value: bool) -> Self {
49 AttributeValue::Boolean(value)
50 }
51
52 pub fn json<T: Serialize>(value: &T) -> Result<Self> {
54 let json_value =
55 serde_json::to_value(value).map_err(|_| GraphError::SerializationError {
56 format: "JSON".to_string(),
57 details: "Failed to serialize to JSON".to_string(),
58 })?;
59 Ok(AttributeValue::Json(json_value))
60 }
61
62 pub fn as_string(&self) -> Option<&str> {
64 match self {
65 AttributeValue::String(s) => Some(s),
66 _ => None,
67 }
68 }
69
70 pub fn as_integer(&self) -> Option<i64> {
72 match self {
73 AttributeValue::Integer(i) => Some(*i),
74 _ => None,
75 }
76 }
77
78 pub fn as_float(&self) -> Option<f64> {
80 match self {
81 AttributeValue::Float(f) => Some(*f),
82 _ => None,
83 }
84 }
85
86 pub fn as_boolean(&self) -> Option<bool> {
88 match self {
89 AttributeValue::Boolean(b) => Some(*b),
90 _ => None,
91 }
92 }
93
94 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {
96 match self {
97 AttributeValue::Json(json) => {
98 serde_json::from_value(json.clone()).map_err(|_| GraphError::SerializationError {
99 format: "JSON".to_string(),
100 details: "Failed to deserialize from JSON".to_string(),
101 })
102 }
103 _ => Err(GraphError::InvalidAttribute {
104 attribute: "value".to_string(),
105 target_type: "JSON".to_string(),
106 details: "Attribute is not JSON".to_string(),
107 }),
108 }
109 }
110
111 pub fn to_string_repr(&self) -> String {
113 match self {
114 AttributeValue::String(s) => s.clone(),
115 AttributeValue::Integer(i) => i.to_string(),
116 AttributeValue::Float(f) => f.to_string(),
117 AttributeValue::Boolean(b) => b.to_string(),
118 AttributeValue::Json(json) => json.to_string(),
119 }
120 }
121}
122
123impl Eq for AttributeValue {}
124
125impl Hash for AttributeValue {
126 fn hash<H: Hasher>(&self, state: &mut H) {
127 match self {
128 AttributeValue::String(s) => {
129 0u8.hash(state);
130 s.hash(state);
131 }
132 AttributeValue::Integer(i) => {
133 1u8.hash(state);
134 i.hash(state);
135 }
136 AttributeValue::Float(f) => {
137 2u8.hash(state);
138 f.to_bits().hash(state);
140 }
141 AttributeValue::Boolean(b) => {
142 3u8.hash(state);
143 b.hash(state);
144 }
145 AttributeValue::Json(json) => {
146 4u8.hash(state);
147 json.to_string().hash(state);
149 }
150 }
151 }
152}
153
154pub type Attributes = HashMap<String, AttributeValue>;
156
157pub struct AttributedGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
159 graph: Graph<N, E, Ix>,
161 node_attributes: HashMap<N, Attributes>,
163 edge_attributes: HashMap<(N, N), Attributes>,
165 graph_attributes: Attributes,
167}
168
169pub struct AttributedDiGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
171 graph: DiGraph<N, E, Ix>,
173 node_attributes: HashMap<N, Attributes>,
175 edge_attributes: HashMap<(N, N), Attributes>,
177 graph_attributes: Attributes,
179}
180
181impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType> Default
182 for AttributedGraph<N, E, Ix>
183{
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType>
190 AttributedGraph<N, E, Ix>
191{
192 pub fn new() -> Self {
194 AttributedGraph {
195 graph: Graph::new(),
196 node_attributes: HashMap::new(),
197 edge_attributes: HashMap::new(),
198 graph_attributes: HashMap::new(),
199 }
200 }
201
202 pub fn from_graph(graph: Graph<N, E, Ix>) -> Self {
204 AttributedGraph {
205 graph,
206 node_attributes: HashMap::new(),
207 edge_attributes: HashMap::new(),
208 graph_attributes: HashMap::new(),
209 }
210 }
211
212 pub fn add_node(&mut self, node: N) {
214 self.graph.add_node(node.clone());
215 self.node_attributes.entry(node).or_default();
216 }
217
218 pub fn add_node_with_attributes(&mut self, node: N, attributes: Attributes) {
220 self.graph.add_node(node.clone());
221 self.node_attributes.insert(node, attributes);
222 }
223
224 pub fn add_edge(&mut self, source: N, target: N, weight: E) -> Result<()> {
226 self.graph
227 .add_edge(source.clone(), target.clone(), weight)?;
228 self.edge_attributes.entry((source, target)).or_default();
229 Ok(())
230 }
231
232 pub fn add_edge_with_attributes(
234 &mut self,
235 source: N,
236 target: N,
237 weight: E,
238 attributes: Attributes,
239 ) -> Result<()> {
240 self.graph
241 .add_edge(source.clone(), target.clone(), weight)?;
242 self.edge_attributes.insert((source, target), attributes);
243 Ok(())
244 }
245
246 pub fn set_node_attribute<K: Into<String>>(&mut self, node: &N, key: K, value: AttributeValue) {
248 self.node_attributes
249 .entry(node.clone())
250 .or_default()
251 .insert(key.into(), value);
252 }
253
254 pub fn get_node_attribute(&self, node: &N, key: &str) -> Option<&AttributeValue> {
256 self.node_attributes.get(node)?.get(key)
257 }
258
259 pub fn get_node_attributes(&self, node: &N) -> Option<&Attributes> {
261 self.node_attributes.get(node)
262 }
263
264 pub fn get_node_attributes_mut(&mut self, node: &N) -> Option<&mut Attributes> {
266 self.node_attributes.get_mut(node)
267 }
268
269 pub fn remove_node_attribute(&mut self, node: &N, key: &str) -> Option<AttributeValue> {
271 self.node_attributes.get_mut(node)?.remove(key)
272 }
273
274 pub fn set_edge_attribute<K: Into<String>>(
276 &mut self,
277 source: &N,
278 target: &N,
279 key: K,
280 value: AttributeValue,
281 ) -> Result<()> {
282 if !self.graph.has_edge(source, target) {
283 return Err(GraphError::edge_not_found(source, target));
284 }
285 self.edge_attributes
286 .entry((source.clone(), target.clone()))
287 .or_default()
288 .insert(key.into(), value);
289 Ok(())
290 }
291
292 pub fn get_edge_attribute(&self, source: &N, target: &N, key: &str) -> Option<&AttributeValue> {
294 self.edge_attributes
295 .get(&(source.clone(), target.clone()))?
296 .get(key)
297 }
298
299 pub fn get_edge_attributes(&self, source: &N, target: &N) -> Option<&Attributes> {
301 self.edge_attributes.get(&(source.clone(), target.clone()))
302 }
303
304 pub fn get_edge_attributes_mut(&mut self, source: &N, target: &N) -> Option<&mut Attributes> {
306 self.edge_attributes
307 .get_mut(&(source.clone(), target.clone()))
308 }
309
310 pub fn remove_edge_attribute(
312 &mut self,
313 source: &N,
314 target: &N,
315 key: &str,
316 ) -> Option<AttributeValue> {
317 self.edge_attributes
318 .get_mut(&(source.clone(), target.clone()))?
319 .remove(key)
320 }
321
322 pub fn set_graph_attribute<K: Into<String>>(&mut self, key: K, value: AttributeValue) {
324 self.graph_attributes.insert(key.into(), value);
325 }
326
327 pub fn get_graph_attribute(&self, key: &str) -> Option<&AttributeValue> {
329 self.graph_attributes.get(key)
330 }
331
332 pub fn get_graph_attributes(&self) -> &Attributes {
334 &self.graph_attributes
335 }
336
337 pub fn remove_graph_attribute(&mut self, key: &str) -> Option<AttributeValue> {
339 self.graph_attributes.remove(key)
340 }
341
342 pub fn nodes_with_attribute(&self, key: &str) -> Vec<&N> {
344 self.node_attributes
345 .iter()
346 .filter_map(|(node, attrs)| {
347 if attrs.contains_key(key) {
348 Some(node)
349 } else {
350 None
351 }
352 })
353 .collect()
354 }
355
356 pub fn nodes_with_attribute_value(&self, key: &str, value: &AttributeValue) -> Vec<&N> {
358 self.node_attributes
359 .iter()
360 .filter_map(|(node, attrs)| {
361 if let Some(attr_value) = attrs.get(key) {
362 if matches_attribute_value(attr_value, value) {
363 Some(node)
364 } else {
365 None
366 }
367 } else {
368 None
369 }
370 })
371 .collect()
372 }
373
374 pub fn edges_with_attribute(&self, key: &str) -> Vec<(&N, &N)> {
376 self.edge_attributes
377 .iter()
378 .filter_map(|((source, target), attrs)| {
379 if attrs.contains_key(key) {
380 Some((source, target))
381 } else {
382 None
383 }
384 })
385 .collect()
386 }
387
388 pub fn edges_with_attribute_value(&self, key: &str, value: &AttributeValue) -> Vec<(&N, &N)> {
390 self.edge_attributes
391 .iter()
392 .filter_map(|((source, target), attrs)| {
393 if let Some(attr_value) = attrs.get(key) {
394 if matches_attribute_value(attr_value, value) {
395 Some((source, target))
396 } else {
397 None
398 }
399 } else {
400 None
401 }
402 })
403 .collect()
404 }
405
406 pub fn graph(&self) -> &Graph<N, E, Ix> {
408 &self.graph
409 }
410
411 pub fn graph_mut(&mut self) -> &mut Graph<N, E, Ix> {
413 &mut self.graph
414 }
415
416 pub fn into_graph(self) -> Graph<N, E, Ix> {
418 self.graph
419 }
420
421 pub fn filter_nodes_by_attribute(&self, key: &str, value: &AttributeValue) -> Self
423 where
424 N: Clone,
425 E: Clone,
426 {
427 let matching_nodes: std::collections::HashSet<N> = self
428 .nodes_with_attribute_value(key, value)
429 .into_iter()
430 .cloned()
431 .collect();
432
433 let mut new_graph = AttributedGraph::new();
434
435 for node in &matching_nodes {
437 if let Some(attrs) = self.get_node_attributes(node) {
438 new_graph.add_node_with_attributes(node.clone(), attrs.clone());
439 } else {
440 new_graph.add_node(node.clone());
441 }
442 }
443
444 for edge in self.graph.edges() {
446 if matching_nodes.contains(&edge.source) && matching_nodes.contains(&edge.target) {
447 if let Some(attrs) = self.get_edge_attributes(&edge.source, &edge.target) {
448 new_graph
449 .add_edge_with_attributes(
450 edge.source.clone(),
451 edge.target.clone(),
452 edge.weight.clone(),
453 attrs.clone(),
454 )
455 .unwrap();
456 } else {
457 new_graph
458 .add_edge(
459 edge.source.clone(),
460 edge.target.clone(),
461 edge.weight.clone(),
462 )
463 .unwrap();
464 }
465 }
466 }
467
468 new_graph.graph_attributes = self.graph_attributes.clone();
470
471 new_graph
472 }
473
474 pub fn attribute_summary(&self) -> AttributeSummary {
476 let mut node_attribute_keys = std::collections::HashSet::new();
477 let mut edge_attribute_keys = std::collections::HashSet::new();
478
479 for attrs in self.node_attributes.values() {
480 for key in attrs.keys() {
481 node_attribute_keys.insert(key.clone());
482 }
483 }
484
485 for attrs in self.edge_attributes.values() {
486 for key in attrs.keys() {
487 edge_attribute_keys.insert(key.clone());
488 }
489 }
490
491 AttributeSummary {
492 nodes_with_attributes: self.node_attributes.len(),
493 edges_with_attributes: self.edge_attributes.len(),
494 unique_node_attribute_keys: node_attribute_keys.len(),
495 unique_edge_attribute_keys: edge_attribute_keys.len(),
496 graph_attribute_keys: self.graph_attributes.len(),
497 node_attribute_keys: node_attribute_keys.into_iter().collect(),
498 edge_attribute_keys: edge_attribute_keys.into_iter().collect(),
499 }
500 }
501}
502
503impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType> Default
504 for AttributedDiGraph<N, E, Ix>
505{
506 fn default() -> Self {
507 Self::new()
508 }
509}
510
511impl<N: Node + std::fmt::Debug + std::fmt::Display, E: EdgeWeight, Ix: IndexType>
512 AttributedDiGraph<N, E, Ix>
513{
514 pub fn new() -> Self {
516 AttributedDiGraph {
517 graph: DiGraph::new(),
518 node_attributes: HashMap::new(),
519 edge_attributes: HashMap::new(),
520 graph_attributes: HashMap::new(),
521 }
522 }
523
524 pub fn from_digraph(graph: DiGraph<N, E, Ix>) -> Self {
526 AttributedDiGraph {
527 graph,
528 node_attributes: HashMap::new(),
529 edge_attributes: HashMap::new(),
530 graph_attributes: HashMap::new(),
531 }
532 }
533
534 pub fn add_node(&mut self, node: N) {
536 self.graph.add_node(node.clone());
537 self.node_attributes.entry(node).or_default();
538 }
539
540 pub fn add_node_with_attributes(&mut self, node: N, attributes: Attributes) {
542 self.graph.add_node(node.clone());
543 self.node_attributes.insert(node, attributes);
544 }
545
546 pub fn add_edge(&mut self, source: N, target: N, weight: E) -> Result<()> {
548 self.graph
549 .add_edge(source.clone(), target.clone(), weight)?;
550 self.edge_attributes.entry((source, target)).or_default();
551 Ok(())
552 }
553
554 pub fn add_edge_with_attributes(
556 &mut self,
557 source: N,
558 target: N,
559 weight: E,
560 attributes: Attributes,
561 ) -> Result<()> {
562 self.graph
563 .add_edge(source.clone(), target.clone(), weight)?;
564 self.edge_attributes.insert((source, target), attributes);
565 Ok(())
566 }
567
568 pub fn set_node_attribute<K: Into<String>>(&mut self, node: &N, key: K, value: AttributeValue) {
570 self.node_attributes
571 .entry(node.clone())
572 .or_default()
573 .insert(key.into(), value);
574 }
575
576 pub fn get_node_attribute(&self, node: &N, key: &str) -> Option<&AttributeValue> {
578 self.node_attributes.get(node)?.get(key)
579 }
580
581 pub fn set_edge_attribute<K: Into<String>>(
583 &mut self,
584 source: &N,
585 target: &N,
586 key: K,
587 value: AttributeValue,
588 ) -> Result<()> {
589 if !self.graph.has_edge(source, target) {
590 return Err(GraphError::edge_not_found(source, target));
591 }
592 self.edge_attributes
593 .entry((source.clone(), target.clone()))
594 .or_default()
595 .insert(key.into(), value);
596 Ok(())
597 }
598
599 pub fn get_edge_attribute(&self, source: &N, target: &N, key: &str) -> Option<&AttributeValue> {
601 self.edge_attributes
602 .get(&(source.clone(), target.clone()))?
603 .get(key)
604 }
605
606 pub fn set_graph_attribute<K: Into<String>>(&mut self, key: K, value: AttributeValue) {
608 self.graph_attributes.insert(key.into(), value);
609 }
610
611 pub fn get_graph_attribute(&self, key: &str) -> Option<&AttributeValue> {
613 self.graph_attributes.get(key)
614 }
615
616 pub fn predecessors(&self, node: &N) -> Result<Vec<N>>
618 where
619 N: Clone,
620 {
621 self.graph.predecessors(node)
622 }
623
624 pub fn successors(&self, node: &N) -> Result<Vec<N>>
626 where
627 N: Clone,
628 {
629 self.graph.successors(node)
630 }
631
632 pub fn graph(&self) -> &DiGraph<N, E, Ix> {
634 &self.graph
635 }
636
637 pub fn graph_mut(&mut self) -> &mut DiGraph<N, E, Ix> {
639 &mut self.graph
640 }
641
642 pub fn into_digraph(self) -> DiGraph<N, E, Ix> {
644 self.graph
645 }
646}
647
648#[derive(Debug, Clone)]
650pub struct AttributeSummary {
651 pub nodes_with_attributes: usize,
653 pub edges_with_attributes: usize,
655 pub unique_node_attribute_keys: usize,
657 pub unique_edge_attribute_keys: usize,
659 pub graph_attribute_keys: usize,
661 pub node_attribute_keys: Vec<String>,
663 pub edge_attribute_keys: Vec<String>,
665}
666
667#[allow(dead_code)]
669fn matches_attribute_value(_attr_value: &AttributeValue, targetvalue: &AttributeValue) -> bool {
670 match (_attr_value, targetvalue) {
671 (AttributeValue::String(a), AttributeValue::String(b)) => a == b,
672 (AttributeValue::Integer(a), AttributeValue::Integer(b)) => a == b,
673 (AttributeValue::Float(a), AttributeValue::Float(b)) => (a - b).abs() < f64::EPSILON,
674 (AttributeValue::Boolean(a), AttributeValue::Boolean(b)) => a == b,
675 (AttributeValue::Json(a), AttributeValue::Json(b)) => a == b,
676 (AttributeValue::Integer(a), AttributeValue::Float(b)) => {
678 (*a as f64 - b).abs() < f64::EPSILON
679 }
680 (AttributeValue::Float(a), AttributeValue::Integer(b)) => {
681 (a - *b as f64).abs() < f64::EPSILON
682 }
683 _ => false,
684 }
685}
686
687pub struct AttributeView<'a, N: Node> {
689 attributes: &'a HashMap<N, Attributes>,
690}
691
692impl<'a, N: Node> AttributeView<'a, N> {
693 pub fn new(attributes: &'a HashMap<N, Attributes>) -> Self {
695 AttributeView { attributes }
696 }
697
698 pub fn nodes_in_numeric_range(&self, key: &str, min: f64, max: f64) -> Vec<&N> {
700 self.attributes
701 .iter()
702 .filter_map(|(node, attrs)| {
703 if let Some(attr_value) = attrs.get(key) {
704 let numeric_value = match attr_value {
705 AttributeValue::Integer(i) => Some(*i as f64),
706 AttributeValue::Float(f) => Some(*f),
707 _ => None,
708 };
709
710 if let Some(value) = numeric_value {
711 if value >= min && value <= max {
712 Some(node)
713 } else {
714 None
715 }
716 } else {
717 None
718 }
719 } else {
720 None
721 }
722 })
723 .collect()
724 }
725
726 pub fn nodes_matching_pattern(&self, key: &str, pattern: &str) -> Vec<&N> {
728 self.attributes
729 .iter()
730 .filter_map(|(node, attrs)| {
731 if let Some(AttributeValue::String(s)) = attrs.get(key) {
732 if s.contains(pattern) {
733 Some(node)
734 } else {
735 None
736 }
737 } else {
738 None
739 }
740 })
741 .collect()
742 }
743
744 pub fn unique_values(&self, key: &str) -> Vec<&AttributeValue> {
746 let mut values = std::collections::HashSet::new();
747 for attrs in self.attributes.values() {
748 if let Some(value) = attrs.get(key) {
749 values.insert(value);
750 }
751 }
752 values.into_iter().collect()
753 }
754}
755
756#[cfg(test)]
757mod tests {
758 use super::*;
759
760 #[test]
761 fn test_attribute_value_creation() {
762 let str_attr = AttributeValue::string("test");
763 assert_eq!(str_attr.as_string(), Some("test"));
764
765 let int_attr = AttributeValue::integer(42);
766 assert_eq!(int_attr.as_integer(), Some(42));
767
768 let float_attr = AttributeValue::float(3.15);
769 assert_eq!(float_attr.as_float(), Some(3.15));
770
771 let bool_attr = AttributeValue::boolean(true);
772 assert_eq!(bool_attr.as_boolean(), Some(true));
773 }
774
775 #[test]
776 fn test_attribute_value_json() {
777 #[derive(Serialize, Deserialize, PartialEq, Debug)]
778 struct TestData {
779 name: String,
780 value: i32,
781 }
782
783 let data = TestData {
784 name: "test".to_string(),
785 value: 123,
786 };
787
788 let json_attr = AttributeValue::json(&data).unwrap();
789 let recovered: TestData = json_attr.as_json().unwrap();
790 assert_eq!(recovered, data);
791 }
792
793 #[test]
794 fn test_attributed_graph_basic_operations() {
795 let mut graph: AttributedGraph<&str, f64> = AttributedGraph::new();
796
797 let mut node_attrs = HashMap::new();
799 node_attrs.insert("type".to_string(), AttributeValue::string("person"));
800 node_attrs.insert("age".to_string(), AttributeValue::integer(30));
801
802 graph.add_node_with_attributes("Alice", node_attrs);
803 graph.add_node("Bob");
804
805 graph.set_node_attribute(&"Bob", "type", AttributeValue::string("person"));
807 graph.set_node_attribute(&"Bob", "age", AttributeValue::integer(25));
808
809 graph.add_edge("Alice", "Bob", 1.0).unwrap();
811 graph
812 .set_edge_attribute(
813 &"Alice",
814 &"Bob",
815 "relationship",
816 AttributeValue::string("friend"),
817 )
818 .unwrap();
819
820 assert_eq!(
822 graph
823 .get_node_attribute(&"Alice", "type")
824 .unwrap()
825 .as_string(),
826 Some("person")
827 );
828 assert_eq!(
829 graph
830 .get_node_attribute(&"Alice", "age")
831 .unwrap()
832 .as_integer(),
833 Some(30)
834 );
835 assert_eq!(
836 graph
837 .get_edge_attribute(&"Alice", &"Bob", "relationship")
838 .unwrap()
839 .as_string(),
840 Some("friend")
841 );
842 }
843
844 #[test]
845 fn test_attributed_graph_filtering() {
846 let mut graph: AttributedGraph<i32, f64> = AttributedGraph::new();
847
848 graph.add_node(1);
850 graph.set_node_attribute(&1, "type", AttributeValue::string("server"));
851
852 graph.add_node(2);
853 graph.set_node_attribute(&2, "type", AttributeValue::string("client"));
854
855 graph.add_node(3);
856 graph.set_node_attribute(&3, "type", AttributeValue::string("server"));
857
858 graph.add_edge(1, 2, 1.0).unwrap();
860 graph.add_edge(1, 3, 2.0).unwrap();
861
862 let servers = graph.nodes_with_attribute_value("type", &AttributeValue::string("server"));
864 assert_eq!(servers.len(), 2);
865 assert!(servers.contains(&&1));
866 assert!(servers.contains(&&3));
867
868 let server_subgraph =
870 graph.filter_nodes_by_attribute("type", &AttributeValue::string("server"));
871 assert_eq!(server_subgraph.graph().node_count(), 2);
872 assert_eq!(server_subgraph.graph().edge_count(), 1); }
874
875 #[test]
876 fn test_attribute_summary() {
877 let mut graph: AttributedGraph<&str, f64> = AttributedGraph::new();
878
879 graph.add_node("A");
880 graph.set_node_attribute(&"A", "category", AttributeValue::string("important"));
881
882 graph.add_node("B");
883 graph.set_node_attribute(&"B", "category", AttributeValue::string("normal"));
884 graph.set_node_attribute(&"B", "weight", AttributeValue::float(1.5));
885
886 graph.add_edge("A", "B", 1.0).unwrap();
887 graph
888 .set_edge_attribute(&"A", &"B", "type", AttributeValue::string("connection"))
889 .unwrap();
890
891 graph.set_graph_attribute("name", AttributeValue::string("test_graph"));
892
893 let summary = graph.attribute_summary();
894 assert_eq!(summary.nodes_with_attributes, 2);
895 assert_eq!(summary.edges_with_attributes, 1);
896 assert_eq!(summary.unique_node_attribute_keys, 2); assert_eq!(summary.unique_edge_attribute_keys, 1); assert_eq!(summary.graph_attribute_keys, 1); }
900
901 #[test]
902 fn test_attribute_view() {
903 let mut attributes = HashMap::new();
904
905 let mut attrs1 = HashMap::new();
906 attrs1.insert("score".to_string(), AttributeValue::float(85.5));
907 attrs1.insert("name".to_string(), AttributeValue::string("Alice"));
908 attributes.insert("person1", attrs1);
909
910 let mut attrs2 = HashMap::new();
911 attrs2.insert("score".to_string(), AttributeValue::float(92.0));
912 attrs2.insert("name".to_string(), AttributeValue::string("Bob"));
913 attributes.insert("person2", attrs2);
914
915 let mut attrs3 = HashMap::new();
916 attrs3.insert("score".to_string(), AttributeValue::integer(88));
917 attrs3.insert("name".to_string(), AttributeValue::string("Charlie"));
918 attributes.insert("person3", attrs3);
919
920 let view = AttributeView::new(&attributes);
921
922 let high_scorers = view.nodes_in_numeric_range("score", 90.0, 100.0);
924 assert_eq!(high_scorers.len(), 1);
925 assert!(high_scorers.contains(&&"person2"));
926
927 let names_with_a = view.nodes_matching_pattern("name", "a");
929 assert_eq!(names_with_a.len(), 1); assert!(names_with_a.contains(&&"person3"));
931
932 let names_with_capital_a = view.nodes_matching_pattern("name", "A");
934 assert_eq!(names_with_capital_a.len(), 1); assert!(names_with_capital_a.contains(&&"person1"));
936
937 let unique_scores = view.unique_values("score");
939 assert_eq!(unique_scores.len(), 3);
940 }
941
942 #[test]
943 fn test_attribute_value_matching() {
944 let int_val = AttributeValue::integer(42);
945 let float_val = AttributeValue::float(42.0);
946 let string_val = AttributeValue::string("42");
947
948 assert!(matches_attribute_value(
950 &int_val,
951 &AttributeValue::integer(42)
952 ));
953 assert!(matches_attribute_value(&int_val, &float_val)); assert!(!matches_attribute_value(&int_val, &string_val)); assert!(matches_attribute_value(
957 &float_val,
958 &AttributeValue::float(42.0)
959 ));
960 assert!(matches_attribute_value(&float_val, &int_val)); assert!(matches_attribute_value(
963 &string_val,
964 &AttributeValue::string("42")
965 ));
966 }
967
968 #[test]
969 fn test_graph_level_attributes() {
970 let mut graph: AttributedGraph<i32, f64> = AttributedGraph::new();
971
972 graph.set_graph_attribute("title", AttributeValue::string("Test Network"));
973 graph.set_graph_attribute("created", AttributeValue::string("2024"));
974 graph.set_graph_attribute("version", AttributeValue::float(1.0));
975
976 assert_eq!(
977 graph.get_graph_attribute("title").unwrap().as_string(),
978 Some("Test Network")
979 );
980 assert_eq!(
981 graph.get_graph_attribute("version").unwrap().as_float(),
982 Some(1.0)
983 );
984
985 assert_eq!(graph.get_graph_attributes().len(), 3);
986
987 let removed = graph.remove_graph_attribute("created");
989 assert!(removed.is_some());
990 assert_eq!(graph.get_graph_attributes().len(), 2);
991 }
992
993 #[test]
994 fn test_attributed_digraph() {
995 let mut digraph: AttributedDiGraph<&str, f64> = AttributedDiGraph::new();
996
997 digraph.add_node("A");
998 digraph.add_node("B");
999 digraph.add_edge("A", "B", 1.0).unwrap();
1000
1001 assert_eq!(digraph.graph().node_count(), 2);
1002 assert_eq!(digraph.graph().edge_count(), 1);
1003 assert!(digraph.graph().has_edge(&"A", &"B"));
1004 assert!(!digraph.graph().has_edge(&"B", &"A")); }
1006}