scirs2_graph/
attributes.rs

1//! Node and edge attribute system
2//!
3//! This module provides a flexible system for associating arbitrary metadata
4//! with nodes and edges in graphs. Attributes can store any serializable data
5//! and provide type-safe access patterns.
6
7use 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/// A type-erased attribute value that can store any serializable data
17#[derive(Debug, Clone, PartialEq)]
18pub enum AttributeValue {
19    /// String attribute
20    String(String),
21    /// Integer attribute  
22    Integer(i64),
23    /// Float attribute
24    Float(f64),
25    /// Boolean attribute
26    Boolean(bool),
27    /// JSON-serialized arbitrary data
28    Json(serde_json::Value),
29}
30
31impl AttributeValue {
32    /// Create a string attribute
33    pub fn string<S: Into<String>>(value: S) -> Self {
34        AttributeValue::String(value.into())
35    }
36
37    /// Create an integer attribute
38    pub fn integer(value: i64) -> Self {
39        AttributeValue::Integer(value)
40    }
41
42    /// Create a float attribute
43    pub fn float(value: f64) -> Self {
44        AttributeValue::Float(value)
45    }
46
47    /// Create a boolean attribute
48    pub fn boolean(value: bool) -> Self {
49        AttributeValue::Boolean(value)
50    }
51
52    /// Create a JSON attribute from any serializable type
53    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    /// Get the attribute as a string
63    pub fn as_string(&self) -> Option<&str> {
64        match self {
65            AttributeValue::String(s) => Some(s),
66            _ => None,
67        }
68    }
69
70    /// Get the attribute as an integer
71    pub fn as_integer(&self) -> Option<i64> {
72        match self {
73            AttributeValue::Integer(i) => Some(*i),
74            _ => None,
75        }
76    }
77
78    /// Get the attribute as a float
79    pub fn as_float(&self) -> Option<f64> {
80        match self {
81            AttributeValue::Float(f) => Some(*f),
82            _ => None,
83        }
84    }
85
86    /// Get the attribute as a boolean
87    pub fn as_boolean(&self) -> Option<bool> {
88        match self {
89            AttributeValue::Boolean(b) => Some(*b),
90            _ => None,
91        }
92    }
93
94    /// Get the attribute as a typed value from JSON
95    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    /// Convert any attribute value to a string representation
112    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                // Use the bit representation for consistent hashing
139                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                // Use string representation for consistent hashing
148                json.to_string().hash(state);
149            }
150        }
151    }
152}
153
154/// A collection of attributes (key-value pairs)
155pub type Attributes = HashMap<String, AttributeValue>;
156
157/// Graph with node and edge attributes
158pub struct AttributedGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
159    /// The underlying graph structure
160    graph: Graph<N, E, Ix>,
161    /// Node attributes indexed by node
162    node_attributes: HashMap<N, Attributes>,
163    /// Edge attributes indexed by (source, target) pair
164    edge_attributes: HashMap<(N, N), Attributes>,
165    /// Graph-level attributes
166    graph_attributes: Attributes,
167}
168
169/// Directed graph with node and edge attributes
170pub struct AttributedDiGraph<N: Node, E: EdgeWeight, Ix: IndexType = u32> {
171    /// The underlying directed graph structure
172    graph: DiGraph<N, E, Ix>,
173    /// Node attributes indexed by node
174    node_attributes: HashMap<N, Attributes>,
175    /// Edge attributes indexed by (source, target) pair
176    edge_attributes: HashMap<(N, N), Attributes>,
177    /// Graph-level attributes
178    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    /// Create a new empty attributed graph
193    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    /// Create an attributed graph from an existing graph
203    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    /// Add a node to the graph
213    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    /// Add a node with initial attributes
219    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    /// Add an edge between two nodes with a given weight
225    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    /// Add an edge with initial attributes
233    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    /// Set a node attribute
247    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    /// Get a node attribute
255    pub fn get_node_attribute(&self, node: &N, key: &str) -> Option<&AttributeValue> {
256        self.node_attributes.get(node)?.get(key)
257    }
258
259    /// Get all node attributes
260    pub fn get_node_attributes(&self, node: &N) -> Option<&Attributes> {
261        self.node_attributes.get(node)
262    }
263
264    /// Get mutable reference to node attributes
265    pub fn get_node_attributes_mut(&mut self, node: &N) -> Option<&mut Attributes> {
266        self.node_attributes.get_mut(node)
267    }
268
269    /// Remove a node attribute
270    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    /// Set an edge attribute
275    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    /// Get an edge attribute
293    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    /// Get all edge attributes
300    pub fn get_edge_attributes(&self, source: &N, target: &N) -> Option<&Attributes> {
301        self.edge_attributes.get(&(source.clone(), target.clone()))
302    }
303
304    /// Get mutable reference to edge attributes
305    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    /// Remove an edge attribute
311    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    /// Set a graph-level attribute
323    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    /// Get a graph-level attribute
328    pub fn get_graph_attribute(&self, key: &str) -> Option<&AttributeValue> {
329        self.graph_attributes.get(key)
330    }
331
332    /// Get all graph-level attributes
333    pub fn get_graph_attributes(&self) -> &Attributes {
334        &self.graph_attributes
335    }
336
337    /// Remove a graph-level attribute
338    pub fn remove_graph_attribute(&mut self, key: &str) -> Option<AttributeValue> {
339        self.graph_attributes.remove(key)
340    }
341
342    /// Get nodes with a specific attribute
343    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    /// Get nodes where an attribute matches a specific value
357    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    /// Get edges with a specific attribute
375    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    /// Get edges where an attribute matches a specific value
389    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    /// Get a reference to the underlying graph
407    pub fn graph(&self) -> &Graph<N, E, Ix> {
408        &self.graph
409    }
410
411    /// Get a mutable reference to the underlying graph
412    pub fn graph_mut(&mut self) -> &mut Graph<N, E, Ix> {
413        &mut self.graph
414    }
415
416    /// Convert back to a regular graph (losing attributes)
417    pub fn into_graph(self) -> Graph<N, E, Ix> {
418        self.graph
419    }
420
421    /// Create a subgraph containing only nodes with specific attributes
422    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        // Add matching nodes with their attributes
436        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        // Add edges between matching nodes
445        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        // Copy graph-level attributes
469        new_graph.graph_attributes = self.graph_attributes.clone();
470
471        new_graph
472    }
473
474    /// Get summary statistics about attributes
475    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    /// Create a new empty attributed directed graph
515    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    /// Create an attributed directed graph from an existing directed graph
525    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    /// Add a node to the graph
535    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    /// Add a node with initial attributes
541    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    /// Add an edge between two nodes with a given weight
547    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    /// Add an edge with initial attributes
555    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    /// Set a node attribute
569    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    /// Get a node attribute
577    pub fn get_node_attribute(&self, node: &N, key: &str) -> Option<&AttributeValue> {
578        self.node_attributes.get(node)?.get(key)
579    }
580
581    /// Set an edge attribute
582    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    /// Get an edge attribute
600    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    /// Set a graph-level attribute
607    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    /// Get a graph-level attribute
612    pub fn get_graph_attribute(&self, key: &str) -> Option<&AttributeValue> {
613        self.graph_attributes.get(key)
614    }
615
616    /// Get predecessors of a node
617    pub fn predecessors(&self, node: &N) -> Result<Vec<N>>
618    where
619        N: Clone,
620    {
621        self.graph.predecessors(node)
622    }
623
624    /// Get successors of a node
625    pub fn successors(&self, node: &N) -> Result<Vec<N>>
626    where
627        N: Clone,
628    {
629        self.graph.successors(node)
630    }
631
632    /// Get the underlying directed graph
633    pub fn graph(&self) -> &DiGraph<N, E, Ix> {
634        &self.graph
635    }
636
637    /// Get a mutable reference to the underlying directed graph
638    pub fn graph_mut(&mut self) -> &mut DiGraph<N, E, Ix> {
639        &mut self.graph
640    }
641
642    /// Convert back to a regular directed graph (losing attributes)
643    pub fn into_digraph(self) -> DiGraph<N, E, Ix> {
644        self.graph
645    }
646}
647
648/// Summary information about attributes in a graph
649#[derive(Debug, Clone)]
650pub struct AttributeSummary {
651    /// Number of nodes that have attributes
652    pub nodes_with_attributes: usize,
653    /// Number of edges that have attributes
654    pub edges_with_attributes: usize,
655    /// Number of unique node attribute keys
656    pub unique_node_attribute_keys: usize,
657    /// Number of unique edge attribute keys
658    pub unique_edge_attribute_keys: usize,
659    /// Number of graph-level attribute keys
660    pub graph_attribute_keys: usize,
661    /// List of all node attribute keys
662    pub node_attribute_keys: Vec<String>,
663    /// List of all edge attribute keys
664    pub edge_attribute_keys: Vec<String>,
665}
666
667/// Helper function to compare attribute values with flexible type matching
668#[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        // Type conversion attempts
677        (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
687/// Attribute view for efficient querying and filtering
688pub struct AttributeView<'a, N: Node> {
689    attributes: &'a HashMap<N, Attributes>,
690}
691
692impl<'a, N: Node> AttributeView<'a, N> {
693    /// Create a new attribute view
694    pub fn new(attributes: &'a HashMap<N, Attributes>) -> Self {
695        AttributeView { attributes }
696    }
697
698    /// Find nodes with numeric attributes in a range
699    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    /// Find nodes with string attributes matching a pattern
727    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    /// Get all unique values for a specific attribute key
745    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        // Add nodes with attributes
798        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        // Set attributes
806        graph.set_node_attribute(&"Bob", "type", AttributeValue::string("person"));
807        graph.set_node_attribute(&"Bob", "age", AttributeValue::integer(25));
808
809        // Add edges with attributes
810        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        // Test retrieval
821        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        // Add nodes with different types
849        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        // Add edges
859        graph.add_edge(1, 2, 1.0).unwrap();
860        graph.add_edge(1, 3, 2.0).unwrap();
861
862        // Filter nodes by attribute
863        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        // Create subgraph
869        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); // Only edge between servers
873    }
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); // "category" and "weight"
897        assert_eq!(summary.unique_edge_attribute_keys, 1); // "type"
898        assert_eq!(summary.graph_attribute_keys, 1); // "name"
899    }
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        // Test numeric range
923        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        // Test pattern matching (case-sensitive)
928        let names_with_a = view.nodes_matching_pattern("name", "a");
929        assert_eq!(names_with_a.len(), 1); // Only Charlie has lowercase 'a'
930        assert!(names_with_a.contains(&&"person3"));
931
932        // Test pattern matching for uppercase A
933        let names_with_capital_a = view.nodes_matching_pattern("name", "A");
934        assert_eq!(names_with_capital_a.len(), 1); // Only Alice has uppercase 'A'
935        assert!(names_with_capital_a.contains(&&"person1"));
936
937        // Test unique values
938        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        // Test type-aware matching
949        assert!(matches_attribute_value(
950            &int_val,
951            &AttributeValue::integer(42)
952        ));
953        assert!(matches_attribute_value(&int_val, &float_val)); // int-float conversion
954        assert!(!matches_attribute_value(&int_val, &string_val)); // no string conversion
955
956        assert!(matches_attribute_value(
957            &float_val,
958            &AttributeValue::float(42.0)
959        ));
960        assert!(matches_attribute_value(&float_val, &int_val)); // float-int conversion
961
962        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        // Remove attribute
988        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")); // Directed
1005    }
1006}