Skip to main content

statsig_rust/evaluation/
dynamic_value.rs

1use chrono::{DateTime, NaiveDateTime};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use serde_json::{json, Value as JsonValue};
4use std::{collections::HashMap, fmt::Debug};
5
6use crate::{
7    hashing::{self, ahash_str},
8    log_w,
9};
10
11use super::dynamic_string::DynamicString;
12
13const TAG: &str = "DynamicValue";
14
15#[macro_export]
16macro_rules! dyn_value {
17    ($x:expr) => {{
18        $crate::DynamicValue::from_json_value($x)
19    }};
20}
21
22#[derive(Debug, Clone, Default)]
23pub struct DynamicValue {
24    pub null: Option<()>,
25    pub bool_value: Option<bool>,
26    pub int_value: Option<i64>,
27    pub float_value: Option<f64>,
28    pub timestamp_value: Option<i64>,
29    pub string_value: Option<DynamicString>,
30    pub array_value: Option<Vec<DynamicValue>>,
31    pub object_value: Option<HashMap<String, DynamicValue>>,
32    pub json_value: JsonValue,
33    pub hash_value: u64,
34}
35
36impl DynamicValue {
37    #[must_use]
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    #[must_use]
43    pub fn from_json_value(value: impl Serialize) -> Self {
44        Self::from(json!(value))
45    }
46
47    #[must_use]
48    pub fn for_timestamp_evaluation(timestamp: i64) -> DynamicValue {
49        DynamicValue {
50            int_value: Some(timestamp),
51            ..DynamicValue::default()
52        }
53    }
54
55    fn try_parse_timestamp(s: &str) -> Option<i64> {
56        // Fast-path: try parsing as integer first
57        if let Ok(ts) = s.parse::<i64>() {
58            return Some(ts);
59        }
60
61        // Fast-reject: if the string is out of range or lacks typical date delimiters
62        if s.len() < 8 || s.len() > 20 || (!s.contains('-') && !s.contains('T') && !s.contains(':'))
63        {
64            return None;
65        }
66
67        // Try RFC3339 (e.g. "2023-01-01T12:00:00Z")
68        if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
69            return Some(dt.timestamp_millis());
70        }
71
72        // Try common datetime format (e.g. "2023-01-01 12:00:00")
73        if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
74            return Some(ndt.and_utc().timestamp_millis());
75        }
76
77        None
78    }
79}
80
81impl PartialEq for DynamicValue {
82    fn eq(&self, other: &Self) -> bool {
83        self.null == other.null
84            && self.bool_value == other.bool_value
85            && self.int_value == other.int_value
86            && self.float_value == other.float_value
87            && self.string_value == other.string_value
88            && self.array_value == other.array_value
89            && self.object_value == other.object_value
90    }
91}
92
93// ------------------------------------------------------------------------------- [Serialization]
94
95impl Serialize for DynamicValue {
96    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97    where
98        S: Serializer,
99    {
100        self.json_value.serialize(serializer)
101    }
102}
103
104impl<'de> Deserialize<'de> for DynamicValue {
105    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106    where
107        D: Deserializer<'de>,
108    {
109        let json_value = JsonValue::deserialize(deserializer)?;
110        Ok(DynamicValue::from(json_value))
111    }
112}
113
114// ------------------------------------------------------------------------------- [From<T> Implementations]
115
116impl From<JsonValue> for DynamicValue {
117    fn from(json_value: JsonValue) -> Self {
118        // perf optimization: avoid stringifying the json value if it's already a string
119        let mut stringified_json_value = None;
120        let hash_value = if let JsonValue::String(s) = &json_value {
121            ahash_str(s)
122        } else {
123            let actual = json_value.to_string();
124            let hash = ahash_str(&actual);
125            stringified_json_value = Some(actual);
126            hash
127        };
128
129        match &json_value {
130            JsonValue::Null => DynamicValue {
131                null: Some(()),
132                json_value,
133                hash_value,
134                ..DynamicValue::new()
135            },
136
137            JsonValue::Bool(b) => DynamicValue {
138                bool_value: Some(*b),
139                string_value: Some(DynamicString::from(b.to_string())),
140                json_value,
141                hash_value,
142                ..DynamicValue::new()
143            },
144
145            JsonValue::Number(n) => {
146                let mut float_value = n.as_f64();
147                let mut int_value = n.as_i64();
148                if let (Some(f), None) = (float_value, int_value) {
149                    let iv = f as i64;
150                    if iv as f64 == f {
151                        int_value = Some(iv);
152                    }
153                } else if let (None, Some(i)) = (float_value, int_value) {
154                    let fv = i as f64;
155                    if fv as i64 == i {
156                        float_value = Some(fv)
157                    }
158                }
159
160                let string_value = float_value
161                    .map(|f| f.to_string())
162                    .or_else(|| int_value.map(|i| i.to_string()))
163                    .or(stringified_json_value);
164
165                DynamicValue {
166                    float_value,
167                    int_value,
168                    string_value: string_value.map(DynamicString::from),
169                    json_value,
170                    hash_value,
171                    ..DynamicValue::new()
172                }
173            }
174
175            JsonValue::String(s) => {
176                let timestamp_value = Self::try_parse_timestamp(s);
177                let float_value = s.parse().ok();
178                let int_value = s.parse().ok();
179                DynamicValue {
180                    string_value: Some(DynamicString::from(s.clone())),
181                    json_value,
182                    timestamp_value,
183                    int_value,
184                    float_value,
185                    hash_value,
186                    ..DynamicValue::new()
187                }
188            }
189
190            JsonValue::Array(arr) => DynamicValue {
191                array_value: Some(arr.iter().map(|v| DynamicValue::from(v.clone())).collect()),
192                string_value: Some(DynamicString::from(
193                    stringified_json_value.unwrap_or(json_value.to_string()),
194                )),
195                json_value,
196                hash_value,
197                ..DynamicValue::new()
198            },
199
200            JsonValue::Object(obj) => DynamicValue {
201                object_value: Some(
202                    obj.into_iter()
203                        .map(|(k, v)| (k.clone(), DynamicValue::from(v.clone())))
204                        .collect(),
205                ),
206                json_value,
207                hash_value,
208                ..DynamicValue::new()
209            },
210        }
211    }
212}
213
214impl From<String> for DynamicValue {
215    fn from(value: String) -> Self {
216        Self::from(serde_json::Value::String(value))
217    }
218}
219
220impl From<&str> for DynamicValue {
221    fn from(value: &str) -> Self {
222        Self::from(serde_json::Value::String(value.to_string()))
223    }
224}
225
226impl From<usize> for DynamicValue {
227    fn from(value: usize) -> Self {
228        Self::from(serde_json::Value::Number(serde_json::Number::from(value)))
229    }
230}
231
232impl From<i64> for DynamicValue {
233    fn from(value: i64) -> Self {
234        Self::from(serde_json::Value::Number(serde_json::Number::from(value)))
235    }
236}
237
238impl From<i32> for DynamicValue {
239    fn from(value: i32) -> Self {
240        Self::from(serde_json::Value::Number(serde_json::Number::from(value)))
241    }
242}
243
244impl From<f64> for DynamicValue {
245    fn from(value: f64) -> Self {
246        let num = match serde_json::Number::from_f64(value) {
247            Some(num) => num,
248            None => {
249                log_w!(
250                    TAG,
251                    "Failed to convert f64 to serde_json::Number: {}",
252                    value
253                );
254                serde_json::Number::from(value as i64)
255            }
256        };
257
258        Self::from(serde_json::Value::Number(num))
259    }
260}
261
262impl From<bool> for DynamicValue {
263    fn from(value: bool) -> Self {
264        Self::from(serde_json::Value::Bool(value))
265    }
266}
267
268impl From<Vec<JsonValue>> for DynamicValue {
269    fn from(value: Vec<JsonValue>) -> Self {
270        DynamicValue::from(serde_json::Value::Array(value))
271    }
272}
273
274impl From<Vec<DynamicValue>> for DynamicValue {
275    fn from(value: Vec<DynamicValue>) -> Self {
276        let json_value = json!(value);
277        let string_value = DynamicString::from(json_value.to_string());
278        let hash_value = hashing::hash_one(value.iter().map(|v| v.hash_value).collect::<Vec<_>>());
279
280        DynamicValue {
281            hash_value,
282            array_value: Some(value),
283            json_value,
284            string_value: Some(string_value),
285            ..DynamicValue::default()
286        }
287    }
288}
289
290impl From<HashMap<String, DynamicValue>> for DynamicValue {
291    fn from(value: HashMap<String, DynamicValue>) -> Self {
292        let json_value = json!(value);
293        let hash_value =
294            hashing::hash_one(value.values().map(|v| v.hash_value).collect::<Vec<_>>());
295
296        DynamicValue {
297            hash_value,
298            object_value: Some(value),
299            json_value,
300            ..DynamicValue::default()
301        }
302    }
303}