Skip to main content

grafeo_common/types/
value.rs

1//! Property value types for graph elements.
2
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::fmt;
6use std::sync::Arc;
7
8use super::Timestamp;
9
10/// A property key (interned string identifier).
11///
12/// Property keys are commonly used strings that benefit from interning.
13/// This uses an `Arc<str>` for cheap cloning and comparison.
14#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
15pub struct PropertyKey(Arc<str>);
16
17impl PropertyKey {
18    /// Creates a new property key from a string.
19    #[must_use]
20    pub fn new(s: impl Into<Arc<str>>) -> Self {
21        Self(s.into())
22    }
23
24    /// Returns the string representation.
25    #[must_use]
26    pub fn as_str(&self) -> &str {
27        &self.0
28    }
29}
30
31impl fmt::Debug for PropertyKey {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        write!(f, "PropertyKey({:?})", self.0)
34    }
35}
36
37impl fmt::Display for PropertyKey {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "{}", self.0)
40    }
41}
42
43impl From<&str> for PropertyKey {
44    fn from(s: &str) -> Self {
45        Self::new(s)
46    }
47}
48
49impl From<String> for PropertyKey {
50    fn from(s: String) -> Self {
51        Self::new(s)
52    }
53}
54
55impl AsRef<str> for PropertyKey {
56    fn as_ref(&self) -> &str {
57        &self.0
58    }
59}
60
61/// A dynamically-typed property value.
62///
63/// This enum represents all possible value types that can be stored as
64/// properties on nodes and edges. It supports the GQL type system.
65#[derive(Clone, PartialEq, Serialize, Deserialize)]
66pub enum Value {
67    /// Null/missing value
68    Null,
69
70    /// Boolean value
71    Bool(bool),
72
73    /// 64-bit signed integer
74    Int64(i64),
75
76    /// 64-bit floating point
77    Float64(f64),
78
79    /// UTF-8 string (uses Arc for cheap cloning)
80    String(Arc<str>),
81
82    /// Binary data
83    Bytes(Arc<[u8]>),
84
85    /// Timestamp with timezone
86    Timestamp(Timestamp),
87
88    /// Ordered list of values
89    List(Arc<[Value]>),
90
91    /// Key-value map (uses BTreeMap for deterministic ordering)
92    Map(Arc<BTreeMap<PropertyKey, Value>>),
93}
94
95impl Value {
96    /// Returns `true` if this value is null.
97    #[inline]
98    #[must_use]
99    pub const fn is_null(&self) -> bool {
100        matches!(self, Value::Null)
101    }
102
103    /// Returns the boolean value if this is a Bool, otherwise None.
104    #[inline]
105    #[must_use]
106    pub const fn as_bool(&self) -> Option<bool> {
107        match self {
108            Value::Bool(b) => Some(*b),
109            _ => None,
110        }
111    }
112
113    /// Returns the integer value if this is an Int64, otherwise None.
114    #[inline]
115    #[must_use]
116    pub const fn as_int64(&self) -> Option<i64> {
117        match self {
118            Value::Int64(i) => Some(*i),
119            _ => None,
120        }
121    }
122
123    /// Returns the float value if this is a Float64, otherwise None.
124    #[inline]
125    #[must_use]
126    pub const fn as_float64(&self) -> Option<f64> {
127        match self {
128            Value::Float64(f) => Some(*f),
129            _ => None,
130        }
131    }
132
133    /// Returns the string value if this is a String, otherwise None.
134    #[inline]
135    #[must_use]
136    pub fn as_str(&self) -> Option<&str> {
137        match self {
138            Value::String(s) => Some(s),
139            _ => None,
140        }
141    }
142
143    /// Returns the bytes value if this is Bytes, otherwise None.
144    #[inline]
145    #[must_use]
146    pub fn as_bytes(&self) -> Option<&[u8]> {
147        match self {
148            Value::Bytes(b) => Some(b),
149            _ => None,
150        }
151    }
152
153    /// Returns the timestamp value if this is a Timestamp, otherwise None.
154    #[inline]
155    #[must_use]
156    pub const fn as_timestamp(&self) -> Option<Timestamp> {
157        match self {
158            Value::Timestamp(t) => Some(*t),
159            _ => None,
160        }
161    }
162
163    /// Returns the list value if this is a List, otherwise None.
164    #[inline]
165    #[must_use]
166    pub fn as_list(&self) -> Option<&[Value]> {
167        match self {
168            Value::List(l) => Some(l),
169            _ => None,
170        }
171    }
172
173    /// Returns the map value if this is a Map, otherwise None.
174    #[inline]
175    #[must_use]
176    pub fn as_map(&self) -> Option<&BTreeMap<PropertyKey, Value>> {
177        match self {
178            Value::Map(m) => Some(m),
179            _ => None,
180        }
181    }
182
183    /// Returns the type name of this value.
184    #[must_use]
185    pub const fn type_name(&self) -> &'static str {
186        match self {
187            Value::Null => "NULL",
188            Value::Bool(_) => "BOOL",
189            Value::Int64(_) => "INT64",
190            Value::Float64(_) => "FLOAT64",
191            Value::String(_) => "STRING",
192            Value::Bytes(_) => "BYTES",
193            Value::Timestamp(_) => "TIMESTAMP",
194            Value::List(_) => "LIST",
195            Value::Map(_) => "MAP",
196        }
197    }
198
199    /// Serializes this value to bytes.
200    #[must_use]
201    pub fn serialize(&self) -> Vec<u8> {
202        bincode::serde::encode_to_vec(self, bincode::config::standard())
203            .expect("Value serialization should not fail")
204    }
205
206    /// Deserializes a value from bytes.
207    ///
208    /// # Errors
209    ///
210    /// Returns an error if the bytes do not represent a valid Value.
211    pub fn deserialize(bytes: &[u8]) -> Result<Self, bincode::error::DecodeError> {
212        let (value, _) = bincode::serde::decode_from_slice(bytes, bincode::config::standard())?;
213        Ok(value)
214    }
215}
216
217impl fmt::Debug for Value {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        match self {
220            Value::Null => write!(f, "Null"),
221            Value::Bool(b) => write!(f, "Bool({b})"),
222            Value::Int64(i) => write!(f, "Int64({i})"),
223            Value::Float64(fl) => write!(f, "Float64({fl})"),
224            Value::String(s) => write!(f, "String({s:?})"),
225            Value::Bytes(b) => write!(f, "Bytes([{}; {} bytes])", b.first().unwrap_or(&0), b.len()),
226            Value::Timestamp(t) => write!(f, "Timestamp({t:?})"),
227            Value::List(l) => write!(f, "List({l:?})"),
228            Value::Map(m) => write!(f, "Map({m:?})"),
229        }
230    }
231}
232
233impl fmt::Display for Value {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        match self {
236            Value::Null => write!(f, "NULL"),
237            Value::Bool(b) => write!(f, "{b}"),
238            Value::Int64(i) => write!(f, "{i}"),
239            Value::Float64(fl) => write!(f, "{fl}"),
240            Value::String(s) => write!(f, "{s:?}"),
241            Value::Bytes(b) => write!(f, "<bytes: {} bytes>", b.len()),
242            Value::Timestamp(t) => write!(f, "{t}"),
243            Value::List(l) => {
244                write!(f, "[")?;
245                for (i, v) in l.iter().enumerate() {
246                    if i > 0 {
247                        write!(f, ", ")?;
248                    }
249                    write!(f, "{v}")?;
250                }
251                write!(f, "]")
252            }
253            Value::Map(m) => {
254                write!(f, "{{")?;
255                for (i, (k, v)) in m.iter().enumerate() {
256                    if i > 0 {
257                        write!(f, ", ")?;
258                    }
259                    write!(f, "{k}: {v}")?;
260                }
261                write!(f, "}}")
262            }
263        }
264    }
265}
266
267// Convenient From implementations
268impl From<bool> for Value {
269    fn from(b: bool) -> Self {
270        Value::Bool(b)
271    }
272}
273
274impl From<i64> for Value {
275    fn from(i: i64) -> Self {
276        Value::Int64(i)
277    }
278}
279
280impl From<i32> for Value {
281    fn from(i: i32) -> Self {
282        Value::Int64(i64::from(i))
283    }
284}
285
286impl From<f64> for Value {
287    fn from(f: f64) -> Self {
288        Value::Float64(f)
289    }
290}
291
292impl From<f32> for Value {
293    fn from(f: f32) -> Self {
294        Value::Float64(f64::from(f))
295    }
296}
297
298impl From<&str> for Value {
299    fn from(s: &str) -> Self {
300        Value::String(s.into())
301    }
302}
303
304impl From<String> for Value {
305    fn from(s: String) -> Self {
306        Value::String(s.into())
307    }
308}
309
310impl From<Arc<str>> for Value {
311    fn from(s: Arc<str>) -> Self {
312        Value::String(s)
313    }
314}
315
316impl From<Vec<u8>> for Value {
317    fn from(b: Vec<u8>) -> Self {
318        Value::Bytes(b.into())
319    }
320}
321
322impl From<&[u8]> for Value {
323    fn from(b: &[u8]) -> Self {
324        Value::Bytes(b.into())
325    }
326}
327
328impl From<Timestamp> for Value {
329    fn from(t: Timestamp) -> Self {
330        Value::Timestamp(t)
331    }
332}
333
334impl<T: Into<Value>> From<Vec<T>> for Value {
335    fn from(v: Vec<T>) -> Self {
336        Value::List(v.into_iter().map(Into::into).collect())
337    }
338}
339
340impl<T: Into<Value>> From<Option<T>> for Value {
341    fn from(opt: Option<T>) -> Self {
342        match opt {
343            Some(v) => v.into(),
344            None => Value::Null,
345        }
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    #[test]
354    fn test_value_type_checks() {
355        assert!(Value::Null.is_null());
356        assert!(!Value::Bool(true).is_null());
357
358        assert_eq!(Value::Bool(true).as_bool(), Some(true));
359        assert_eq!(Value::Bool(false).as_bool(), Some(false));
360        assert_eq!(Value::Int64(42).as_bool(), None);
361
362        assert_eq!(Value::Int64(42).as_int64(), Some(42));
363        assert_eq!(Value::String("test".into()).as_int64(), None);
364
365        assert_eq!(Value::Float64(1.234).as_float64(), Some(1.234));
366        assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
367    }
368
369    #[test]
370    fn test_value_from_conversions() {
371        let v: Value = true.into();
372        assert_eq!(v.as_bool(), Some(true));
373
374        let v: Value = 42i64.into();
375        assert_eq!(v.as_int64(), Some(42));
376
377        let v: Value = 1.234f64.into();
378        assert_eq!(v.as_float64(), Some(1.234));
379
380        let v: Value = "hello".into();
381        assert_eq!(v.as_str(), Some("hello"));
382
383        let v: Value = vec![1u8, 2, 3].into();
384        assert_eq!(v.as_bytes(), Some(&[1u8, 2, 3][..]));
385    }
386
387    #[test]
388    fn test_value_serialization_roundtrip() {
389        let values = vec![
390            Value::Null,
391            Value::Bool(true),
392            Value::Int64(i64::MAX),
393            Value::Float64(std::f64::consts::PI),
394            Value::String("hello world".into()),
395            Value::Bytes(vec![0, 1, 2, 255].into()),
396            Value::List(vec![Value::Int64(1), Value::Int64(2)].into()),
397        ];
398
399        for v in values {
400            let bytes = v.serialize();
401            let decoded = Value::deserialize(&bytes).unwrap();
402            assert_eq!(v, decoded);
403        }
404    }
405
406    #[test]
407    fn test_property_key() {
408        let key = PropertyKey::new("name");
409        assert_eq!(key.as_str(), "name");
410
411        let key2: PropertyKey = "age".into();
412        assert_eq!(key2.as_str(), "age");
413
414        // Keys should be comparable ("age" < "name" alphabetically)
415        assert!(key2 < key);
416    }
417
418    #[test]
419    fn test_value_type_name() {
420        assert_eq!(Value::Null.type_name(), "NULL");
421        assert_eq!(Value::Bool(true).type_name(), "BOOL");
422        assert_eq!(Value::Int64(0).type_name(), "INT64");
423        assert_eq!(Value::Float64(0.0).type_name(), "FLOAT64");
424        assert_eq!(Value::String("".into()).type_name(), "STRING");
425        assert_eq!(Value::Bytes(vec![].into()).type_name(), "BYTES");
426        assert_eq!(Value::List(vec![].into()).type_name(), "LIST");
427        assert_eq!(Value::Map(BTreeMap::new().into()).type_name(), "MAP");
428    }
429}