ruvector_graph/
property.rs

1//! Property value types for graph nodes and edges
2//!
3//! Supports Neo4j-compatible property types: primitives, arrays, and maps
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Property value that can be stored on nodes and edges
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(untagged)]
11pub enum PropertyValue {
12    /// Null value
13    Null,
14    /// Boolean value
15    Bool(bool),
16    /// 64-bit integer
17    Int(i64),
18    /// 64-bit floating point
19    Float(f64),
20    /// UTF-8 string
21    String(String),
22    /// Array of homogeneous values
23    Array(Vec<PropertyValue>),
24    /// Map of string keys to values
25    Map(HashMap<String, PropertyValue>),
26}
27
28impl PropertyValue {
29    /// Check if value is null
30    pub fn is_null(&self) -> bool {
31        matches!(self, PropertyValue::Null)
32    }
33
34    /// Try to get as boolean
35    pub fn as_bool(&self) -> Option<bool> {
36        match self {
37            PropertyValue::Bool(b) => Some(*b),
38            _ => None,
39        }
40    }
41
42    /// Try to get as integer
43    pub fn as_int(&self) -> Option<i64> {
44        match self {
45            PropertyValue::Int(i) => Some(*i),
46            _ => None,
47        }
48    }
49
50    /// Try to get as float
51    pub fn as_float(&self) -> Option<f64> {
52        match self {
53            PropertyValue::Float(f) => Some(*f),
54            PropertyValue::Int(i) => Some(*i as f64),
55            _ => None,
56        }
57    }
58
59    /// Try to get as string
60    pub fn as_str(&self) -> Option<&str> {
61        match self {
62            PropertyValue::String(s) => Some(s),
63            _ => None,
64        }
65    }
66
67    /// Try to get as array
68    pub fn as_array(&self) -> Option<&Vec<PropertyValue>> {
69        match self {
70            PropertyValue::Array(arr) => Some(arr),
71            _ => None,
72        }
73    }
74
75    /// Try to get as map
76    pub fn as_map(&self) -> Option<&HashMap<String, PropertyValue>> {
77        match self {
78            PropertyValue::Map(map) => Some(map),
79            _ => None,
80        }
81    }
82
83    /// Get type name for debugging
84    pub fn type_name(&self) -> &'static str {
85        match self {
86            PropertyValue::Null => "null",
87            PropertyValue::Bool(_) => "bool",
88            PropertyValue::Int(_) => "int",
89            PropertyValue::Float(_) => "float",
90            PropertyValue::String(_) => "string",
91            PropertyValue::Array(_) => "array",
92            PropertyValue::Map(_) => "map",
93        }
94    }
95}
96
97impl From<bool> for PropertyValue {
98    fn from(b: bool) -> Self {
99        PropertyValue::Bool(b)
100    }
101}
102
103impl From<i64> for PropertyValue {
104    fn from(i: i64) -> Self {
105        PropertyValue::Int(i)
106    }
107}
108
109impl From<i32> for PropertyValue {
110    fn from(i: i32) -> Self {
111        PropertyValue::Int(i as i64)
112    }
113}
114
115impl From<f64> for PropertyValue {
116    fn from(f: f64) -> Self {
117        PropertyValue::Float(f)
118    }
119}
120
121impl From<f32> for PropertyValue {
122    fn from(f: f32) -> Self {
123        PropertyValue::Float(f as f64)
124    }
125}
126
127impl From<String> for PropertyValue {
128    fn from(s: String) -> Self {
129        PropertyValue::String(s)
130    }
131}
132
133impl From<&str> for PropertyValue {
134    fn from(s: &str) -> Self {
135        PropertyValue::String(s.to_string())
136    }
137}
138
139impl From<Vec<PropertyValue>> for PropertyValue {
140    fn from(arr: Vec<PropertyValue>) -> Self {
141        PropertyValue::Array(arr)
142    }
143}
144
145impl From<HashMap<String, PropertyValue>> for PropertyValue {
146    fn from(map: HashMap<String, PropertyValue>) -> Self {
147        PropertyValue::Map(map)
148    }
149}
150
151/// Collection of properties (key-value pairs)
152pub type Properties = HashMap<String, PropertyValue>;
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_property_value_types() {
160        let null = PropertyValue::Null;
161        assert!(null.is_null());
162
163        let bool_val = PropertyValue::Bool(true);
164        assert_eq!(bool_val.as_bool(), Some(true));
165
166        let int_val = PropertyValue::Int(42);
167        assert_eq!(int_val.as_int(), Some(42));
168        assert_eq!(int_val.as_float(), Some(42.0));
169
170        let float_val = PropertyValue::Float(3.14);
171        assert_eq!(float_val.as_float(), Some(3.14));
172
173        let str_val = PropertyValue::String("hello".to_string());
174        assert_eq!(str_val.as_str(), Some("hello"));
175    }
176
177    #[test]
178    fn test_property_conversions() {
179        let _: PropertyValue = true.into();
180        let _: PropertyValue = 42i64.into();
181        let _: PropertyValue = 42i32.into();
182        let _: PropertyValue = 3.14f64.into();
183        let _: PropertyValue = 3.14f32.into();
184        let _: PropertyValue = "test".into();
185        let _: PropertyValue = "test".to_string().into();
186    }
187
188    #[test]
189    fn test_nested_properties() {
190        let mut map = HashMap::new();
191        map.insert("nested".to_string(), PropertyValue::Int(123));
192
193        let array = vec![
194            PropertyValue::Int(1),
195            PropertyValue::Int(2),
196            PropertyValue::Int(3),
197        ];
198
199        let complex = PropertyValue::Map({
200            let mut m = HashMap::new();
201            m.insert("array".to_string(), PropertyValue::Array(array));
202            m.insert("map".to_string(), PropertyValue::Map(map));
203            m
204        });
205
206        assert!(complex.as_map().is_some());
207    }
208}