Skip to main content

weavatrix_graph/
attribute.rs

1use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
2use std::collections::BTreeMap;
3use std::hash::{Hash, Hasher};
4
5/// Deterministic JSON-like attribute value for graph extensions.
6///
7/// The graph core keeps attributes typed enough to preserve booleans, numeric
8/// counters, nested ranges, and arrays without taking a JSON runtime dependency.
9#[derive(Debug, Clone, Copy, Serialize)]
10pub struct FiniteF64(f64);
11
12impl FiniteF64 {
13    /// Creates a finite floating-point attribute value.
14    ///
15    /// # Errors
16    ///
17    /// Returns an error for NaN and infinity, which are not valid JSON numbers.
18    pub fn new(value: f64) -> Result<Self, String> {
19        if value.is_finite() {
20            Ok(Self(value))
21        } else {
22            Err(format!("float attribute must be finite: {value}"))
23        }
24    }
25
26    #[must_use]
27    pub const fn get(self) -> f64 {
28        self.0
29    }
30}
31
32impl PartialEq for FiniteF64 {
33    fn eq(&self, other: &Self) -> bool {
34        self.0.to_bits() == other.0.to_bits()
35    }
36}
37
38impl Eq for FiniteF64 {}
39
40impl PartialOrd for FiniteF64 {
41    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
42        Some(self.cmp(other))
43    }
44}
45
46impl Ord for FiniteF64 {
47    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
48        self.0.total_cmp(&other.0)
49    }
50}
51
52impl Hash for FiniteF64 {
53    fn hash<H: Hasher>(&self, state: &mut H) {
54        self.0.to_bits().hash(state);
55    }
56}
57
58impl<'de> Deserialize<'de> for FiniteF64 {
59    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
60    where
61        D: Deserializer<'de>,
62    {
63        Self::new(f64::deserialize(deserializer)?).map_err(D::Error::custom)
64    }
65}
66
67/// Deterministic JSON-like attribute value for graph extensions.
68#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
69#[serde(untagged)]
70#[non_exhaustive]
71pub enum AttributeValue {
72    Null,
73    Bool(bool),
74    Integer(i64),
75    Unsigned(u64),
76    Float(FiniteF64),
77    String(String),
78    List(Vec<AttributeValue>),
79    Object(BTreeMap<String, AttributeValue>),
80}
81
82impl From<bool> for AttributeValue {
83    fn from(value: bool) -> Self {
84        Self::Bool(value)
85    }
86}
87
88impl From<i64> for AttributeValue {
89    fn from(value: i64) -> Self {
90        Self::Integer(value)
91    }
92}
93
94impl From<u64> for AttributeValue {
95    fn from(value: u64) -> Self {
96        Self::Unsigned(value)
97    }
98}
99
100impl From<i32> for AttributeValue {
101    fn from(value: i32) -> Self {
102        Self::Integer(i64::from(value))
103    }
104}
105
106impl From<u32> for AttributeValue {
107    fn from(value: u32) -> Self {
108        Self::Unsigned(u64::from(value))
109    }
110}
111
112impl TryFrom<f64> for AttributeValue {
113    type Error = String;
114
115    fn try_from(value: f64) -> Result<Self, Self::Error> {
116        Ok(Self::Float(FiniteF64::new(value)?))
117    }
118}
119
120impl From<String> for AttributeValue {
121    fn from(value: String) -> Self {
122        Self::String(value)
123    }
124}
125
126impl From<&str> for AttributeValue {
127    fn from(value: &str) -> Self {
128        Self::String(value.to_owned())
129    }
130}
131
132impl From<Vec<AttributeValue>> for AttributeValue {
133    fn from(value: Vec<AttributeValue>) -> Self {
134        Self::List(value)
135    }
136}
137
138impl From<BTreeMap<String, AttributeValue>> for AttributeValue {
139    fn from(value: BTreeMap<String, AttributeValue>) -> Self {
140        Self::Object(value)
141    }
142}