Skip to main content

rust_igraph/core/
attributes.rs

1//! Vertex, edge, and graph attribute storage.
2//!
3//! Mirrors the three igraph C attribute types (numeric, boolean, string)
4//! with a Rust enum [`AttributeValue`]. Attribute storage lives directly
5//! on [`Graph`](super::graph::Graph) as `HashMap<String, ...>` fields.
6
7use std::fmt;
8
9/// A single attribute value attached to a graph, vertex, or edge.
10///
11/// ```
12/// use rust_igraph::AttributeValue;
13///
14/// let v = AttributeValue::Numeric(3.14);
15/// assert!(v.as_f64().is_some());
16/// assert!(v.as_str().is_none());
17/// ```
18#[derive(Debug, Clone, PartialEq)]
19pub enum AttributeValue {
20    /// Floating-point number (mirrors `IGRAPH_ATTRIBUTE_NUMERIC`).
21    Numeric(f64),
22    /// Boolean (mirrors `IGRAPH_ATTRIBUTE_BOOLEAN`).
23    Boolean(bool),
24    /// UTF-8 string (mirrors `IGRAPH_ATTRIBUTE_STRING`).
25    String(String),
26}
27
28impl AttributeValue {
29    /// Returns the numeric value if this is `Numeric`, otherwise `None`.
30    pub fn as_f64(&self) -> Option<f64> {
31        match self {
32            Self::Numeric(v) => Some(*v),
33            _ => None,
34        }
35    }
36
37    /// Returns the boolean value if this is `Boolean`, otherwise `None`.
38    pub fn as_bool(&self) -> Option<bool> {
39        match self {
40            Self::Boolean(v) => Some(*v),
41            _ => None,
42        }
43    }
44
45    /// Returns a string slice if this is `String`, otherwise `None`.
46    pub fn as_str(&self) -> Option<&str> {
47        match self {
48            Self::String(v) => Some(v.as_str()),
49            _ => None,
50        }
51    }
52
53    /// Default value for extending attribute vectors when new
54    /// vertices/edges are added and the attribute already exists.
55    pub(crate) fn default_for_same_type(&self) -> Self {
56        match self {
57            Self::Numeric(_) => Self::Numeric(f64::NAN),
58            Self::Boolean(_) => Self::Boolean(false),
59            Self::String(_) => Self::String(std::string::String::new()),
60        }
61    }
62}
63
64impl fmt::Display for AttributeValue {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            Self::Numeric(v) => write!(f, "{v}"),
68            Self::Boolean(v) => write!(f, "{v}"),
69            Self::String(v) => write!(f, "{v}"),
70        }
71    }
72}
73
74impl From<f64> for AttributeValue {
75    fn from(v: f64) -> Self {
76        Self::Numeric(v)
77    }
78}
79
80impl From<bool> for AttributeValue {
81    fn from(v: bool) -> Self {
82        Self::Boolean(v)
83    }
84}
85
86impl From<String> for AttributeValue {
87    fn from(v: String) -> Self {
88        Self::String(v)
89    }
90}
91
92impl From<&str> for AttributeValue {
93    fn from(v: &str) -> Self {
94        Self::String(v.to_owned())
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn accessors() {
104        let n = AttributeValue::Numeric(2.5);
105        assert!((n.as_f64().unwrap() - 2.5).abs() < f64::EPSILON);
106        assert!(n.as_bool().is_none());
107        assert!(n.as_str().is_none());
108
109        let b = AttributeValue::Boolean(true);
110        assert_eq!(b.as_bool(), Some(true));
111        assert!(b.as_f64().is_none());
112
113        let s = AttributeValue::String("hello".into());
114        assert_eq!(s.as_str(), Some("hello"));
115    }
116
117    #[test]
118    fn from_conversions() {
119        let v: AttributeValue = 1.0_f64.into();
120        assert_eq!(v, AttributeValue::Numeric(1.0));
121
122        let v: AttributeValue = true.into();
123        assert_eq!(v, AttributeValue::Boolean(true));
124
125        let v: AttributeValue = "test".into();
126        assert_eq!(v, AttributeValue::String("test".into()));
127    }
128
129    #[test]
130    fn display() {
131        assert_eq!(format!("{}", AttributeValue::Numeric(2.71)), "2.71");
132        assert_eq!(format!("{}", AttributeValue::Boolean(false)), "false");
133        assert_eq!(format!("{}", AttributeValue::String("hi".into())), "hi");
134    }
135}