Skip to main content

helix_ast/
value.rs

1use std::collections::{BTreeMap, HashMap};
2
3use chrono::{SecondsFormat, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::expr::Expr;
7/// Arbitrary nested parameter value.
8pub type ParamValue = PropertyValue;
9
10/// Object-shaped parameter payload.
11pub type ParamObject = BTreeMap<String, PropertyValue>;
12/// A property value that can be stored on nodes or edges.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum PropertyValue {
16    /// Null value.
17    Null,
18    /// Boolean value.
19    Bool(bool),
20    /// 64-bit signed integer.
21    I64(i64),
22    /// UTC datetime stored as epoch milliseconds.
23    DateTime(i64),
24    /// 64-bit floating point.
25    F64(f64),
26    /// 32-bit floating point.
27    F32(f32),
28    /// UTF-8 string.
29    String(String),
30    /// Raw bytes.
31    Bytes(Vec<u8>),
32    /// Array of i64 values.
33    I64Array(Vec<i64>),
34    /// Array of f64 values.
35    F64Array(Vec<f64>),
36    /// Array of f32 values.
37    F32Array(Vec<f32>),
38    /// Array of strings.
39    StringArray(Vec<String>),
40    /// Heterogeneous array.
41    Array(Vec<PropertyValue>),
42    /// Object/map value.
43    Object(BTreeMap<String, PropertyValue>),
44}
45
46impl PropertyValue {
47    /// Create a heterogeneous array value.
48    pub fn array<V>(values: impl IntoIterator<Item = V>) -> Self
49    where
50        V: Into<PropertyValue>,
51    {
52        Self::Array(values.into_iter().map(Into::into).collect())
53    }
54
55    /// Create an object/map value.
56    pub fn object<K, V>(values: impl IntoIterator<Item = (K, V)>) -> Self
57    where
58        K: Into<String>,
59        V: Into<PropertyValue>,
60    {
61        Self::Object(
62            values
63                .into_iter()
64                .map(|(key, value)| (key.into(), value.into()))
65                .collect(),
66        )
67    }
68
69    /// Get value as string reference.
70    pub fn as_str(&self) -> Option<&str> {
71        match self {
72            Self::String(value) => Some(value),
73            _ => None,
74        }
75    }
76
77    /// Get value as i64.
78    pub fn as_i64(&self) -> Option<i64> {
79        match self {
80            Self::I64(value) => Some(*value),
81            _ => None,
82        }
83    }
84
85    /// Create a datetime value from UTC epoch milliseconds.
86    pub fn datetime_millis(millis: i64) -> Self {
87        Self::DateTime(millis)
88    }
89
90    /// Get datetime as UTC epoch milliseconds.
91    pub fn as_datetime_millis(&self) -> Option<i64> {
92        match self {
93            Self::DateTime(value) => Some(*value),
94            _ => None,
95        }
96    }
97
98    /// Get value as f64.
99    pub fn as_f64(&self) -> Option<f64> {
100        match self {
101            Self::F64(value) => Some(*value),
102            Self::F32(value) => Some(*value as f64),
103            _ => None,
104        }
105    }
106
107    /// Get value as bool.
108    pub fn as_bool(&self) -> Option<bool> {
109        match self {
110            Self::Bool(value) => Some(*value),
111            _ => None,
112        }
113    }
114
115    /// Get value as array reference.
116    pub fn as_array(&self) -> Option<&[PropertyValue]> {
117        match self {
118            Self::Array(values) => Some(values),
119            _ => None,
120        }
121    }
122
123    /// Get value as object reference.
124    pub fn as_object(&self) -> Option<&BTreeMap<String, PropertyValue>> {
125        match self {
126            Self::Object(values) => Some(values),
127            _ => None,
128        }
129    }
130}
131
132impl From<&str> for PropertyValue {
133    fn from(value: &str) -> Self {
134        Self::String(value.to_string())
135    }
136}
137
138impl From<String> for PropertyValue {
139    fn from(value: String) -> Self {
140        Self::String(value)
141    }
142}
143
144impl From<i64> for PropertyValue {
145    fn from(value: i64) -> Self {
146        Self::I64(value)
147    }
148}
149
150impl From<i32> for PropertyValue {
151    fn from(value: i32) -> Self {
152        Self::I64(value as i64)
153    }
154}
155
156impl From<f64> for PropertyValue {
157    fn from(value: f64) -> Self {
158        Self::F64(value)
159    }
160}
161
162impl From<f32> for PropertyValue {
163    fn from(value: f32) -> Self {
164        Self::F32(value)
165    }
166}
167
168impl From<bool> for PropertyValue {
169    fn from(value: bool) -> Self {
170        Self::Bool(value)
171    }
172}
173
174impl From<Vec<u8>> for PropertyValue {
175    fn from(value: Vec<u8>) -> Self {
176        Self::Bytes(value)
177    }
178}
179
180impl From<Vec<i64>> for PropertyValue {
181    fn from(value: Vec<i64>) -> Self {
182        Self::I64Array(value)
183    }
184}
185
186impl From<Vec<f64>> for PropertyValue {
187    fn from(value: Vec<f64>) -> Self {
188        Self::F64Array(value)
189    }
190}
191
192impl From<Vec<f32>> for PropertyValue {
193    fn from(value: Vec<f32>) -> Self {
194        Self::F32Array(value)
195    }
196}
197
198impl From<Vec<String>> for PropertyValue {
199    fn from(value: Vec<String>) -> Self {
200        Self::StringArray(value)
201    }
202}
203
204impl From<Vec<PropertyValue>> for PropertyValue {
205    fn from(value: Vec<PropertyValue>) -> Self {
206        Self::Array(value)
207    }
208}
209
210impl From<BTreeMap<String, PropertyValue>> for PropertyValue {
211    fn from(value: BTreeMap<String, PropertyValue>) -> Self {
212        Self::Object(value)
213    }
214}
215
216impl From<HashMap<String, PropertyValue>> for PropertyValue {
217    fn from(value: HashMap<String, PropertyValue>) -> Self {
218        Self::Object(value.into_iter().collect())
219    }
220}
221
222/// UTC datetime represented as epoch milliseconds.
223#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
224pub struct DateTime(i64);
225
226impl DateTime {
227    /// Create from UTC epoch milliseconds.
228    pub fn from_millis(millis: i64) -> Self {
229        Self(millis)
230    }
231
232    /// Parse an RFC3339 datetime string and normalize it to UTC.
233    pub fn parse_rfc3339(input: &str) -> Result<Self, chrono::ParseError> {
234        Ok(Self(
235            chrono::DateTime::parse_from_rfc3339(input)?
236                .with_timezone(&Utc)
237                .timestamp_millis(),
238        ))
239    }
240
241    /// Return UTC epoch milliseconds.
242    pub fn millis(self) -> i64 {
243        self.0
244    }
245
246    /// Format as canonical RFC3339 UTC.
247    pub fn to_rfc3339(self) -> Option<String> {
248        chrono::DateTime::<Utc>::from_timestamp_millis(self.0)
249            .map(|dt| dt.to_rfc3339_opts(SecondsFormat::Millis, true))
250    }
251}
252
253impl From<DateTime> for PropertyValue {
254    fn from(value: DateTime) -> Self {
255        Self::DateTime(value.millis())
256    }
257}
258/// Mutation input value.
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260#[serde(rename_all = "snake_case")]
261pub enum PropertyInput {
262    /// Literal value.
263    Value(PropertyValue),
264    /// Runtime expression.
265    Expr(Expr),
266}
267
268impl PropertyInput {
269    /// Create an input from a runtime parameter.
270    pub fn param(name: impl Into<String>) -> Self {
271        Self::Expr(Expr::param(name))
272    }
273
274    /// Convert to expression.
275    pub fn into_expr(self) -> Expr {
276        match self {
277            Self::Value(value) => Expr::Constant(value),
278            Self::Expr(expr) => expr,
279        }
280    }
281}
282
283impl<T> From<T> for PropertyInput
284where
285    PropertyValue: From<T>,
286{
287    fn from(value: T) -> Self {
288        Self::Value(value.into())
289    }
290}
291
292impl From<Expr> for PropertyInput {
293    fn from(value: Expr) -> Self {
294        Self::Expr(value)
295    }
296}
297/// Helper type alias for property maps.
298pub type PropertyMap = HashMap<String, PropertyValue>;