Skip to main content

statsig_rust/evaluation/
dynamic_value.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::{json, Map as JsonMap, Number as JsonNumber, Value as JsonValue};
3use std::{collections::HashMap, fmt::Debug};
4
5use crate::{
6    hashing::{self, ahash_str},
7    log_w,
8    value_parsing::{maybe_parse_f64, maybe_parse_i64, try_parse_timestamp},
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    #[must_use]
56    pub fn from_string(value: impl Into<String>) -> Self {
57        let value = value.into();
58        let int_value = maybe_parse_i64(&value);
59        let float_value = maybe_parse_f64(&value);
60        let timestamp_value = try_parse_timestamp(&value, int_value);
61        let hash_value = ahash_str(&value);
62
63        DynamicValue {
64            string_value: Some(DynamicString::from(value.clone())),
65            json_value: JsonValue::String(value),
66            timestamp_value,
67            int_value,
68            float_value,
69            hash_value,
70            ..DynamicValue::new()
71        }
72    }
73
74    #[must_use]
75    pub fn from_bool(value: bool) -> Self {
76        let string_value = if value { "true" } else { "false" };
77
78        DynamicValue {
79            bool_value: Some(value),
80            string_value: Some(DynamicString::from_bool(value)),
81            json_value: JsonValue::Bool(value),
82            hash_value: ahash_str(string_value),
83            ..DynamicValue::new()
84        }
85    }
86
87    #[must_use]
88    pub fn from_i64(value: i64) -> Self {
89        let string_value = value.to_string();
90
91        DynamicValue {
92            int_value: Some(value),
93            float_value: Some(value as f64),
94            string_value: Some(DynamicString::from(string_value.clone())),
95            json_value: JsonValue::Number(JsonNumber::from(value)),
96            hash_value: ahash_str(&string_value),
97            ..DynamicValue::new()
98        }
99    }
100
101    #[must_use]
102    pub fn from_f64(value: f64) -> Self {
103        let num = match JsonNumber::from_f64(value) {
104            Some(num) => num,
105            None => {
106                log_w!(
107                    TAG,
108                    "Failed to convert f64 to serde_json::Number: {}",
109                    value
110                );
111                return Self::from_i64(value as i64);
112            }
113        };
114
115        let json_string = JsonValue::Number(num.clone()).to_string();
116        let mut float_value = num.as_f64();
117        let mut int_value = num.as_i64();
118        if let (Some(f), None) = (float_value, int_value) {
119            let iv = f as i64;
120            if iv as f64 == f {
121                int_value = Some(iv);
122            }
123        } else if let (None, Some(i)) = (float_value, int_value) {
124            let fv = i as f64;
125            if fv as i64 == i {
126                float_value = Some(fv)
127            }
128        }
129
130        let string_value = float_value
131            .map(|f| f.to_string())
132            .or_else(|| int_value.map(|i| i.to_string()))
133            .unwrap_or_else(|| json_string.clone());
134
135        DynamicValue {
136            float_value,
137            int_value,
138            string_value: Some(DynamicString::from(string_value)),
139            json_value: JsonValue::Number(num),
140            hash_value: ahash_str(&json_string),
141            ..DynamicValue::new()
142        }
143    }
144
145    #[must_use]
146    pub fn from_dynamic_array(value: Vec<DynamicValue>) -> Self {
147        let json_value =
148            JsonValue::Array(value.iter().map(|value| value.json_value.clone()).collect());
149        let string_value = DynamicString::from(json_value.to_string());
150        let hash_value = hashing::hash_one(
151            value
152                .iter()
153                .map(|value| value.hash_value)
154                .collect::<Vec<_>>(),
155        );
156
157        DynamicValue {
158            hash_value,
159            array_value: Some(value),
160            json_value,
161            string_value: Some(string_value),
162            ..DynamicValue::default()
163        }
164    }
165
166    #[must_use]
167    pub fn from_dynamic_object(value: HashMap<String, DynamicValue>) -> Self {
168        let json_value = JsonValue::Object(
169            value
170                .iter()
171                .map(|(key, value)| (key.clone(), value.json_value.clone()))
172                .collect::<JsonMap<String, JsonValue>>(),
173        );
174        let hash_value = hashing::hash_one(
175            value
176                .values()
177                .map(|value| value.hash_value)
178                .collect::<Vec<_>>(),
179        );
180
181        DynamicValue {
182            hash_value,
183            object_value: Some(value),
184            json_value,
185            ..DynamicValue::default()
186        }
187    }
188}
189
190impl PartialEq for DynamicValue {
191    fn eq(&self, other: &Self) -> bool {
192        self.null == other.null
193            && self.bool_value == other.bool_value
194            && self.int_value == other.int_value
195            && self.float_value == other.float_value
196            && self.string_value == other.string_value
197            && self.array_value == other.array_value
198            && self.object_value == other.object_value
199    }
200}
201
202// ------------------------------------------------------------------------------- [Serialization]
203
204impl Serialize for DynamicValue {
205    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
206    where
207        S: Serializer,
208    {
209        self.json_value.serialize(serializer)
210    }
211}
212
213impl<'de> Deserialize<'de> for DynamicValue {
214    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215    where
216        D: Deserializer<'de>,
217    {
218        let json_value = JsonValue::deserialize(deserializer)?;
219        Ok(DynamicValue::from(json_value))
220    }
221}
222
223// ------------------------------------------------------------------------------- [From<T> Implementations]
224
225impl From<JsonValue> for DynamicValue {
226    fn from(json_value: JsonValue) -> Self {
227        // perf optimization: avoid stringifying the json value if it's already a string
228        let mut stringified_json_value = None;
229        let hash_value = if let JsonValue::String(s) = &json_value {
230            ahash_str(s)
231        } else {
232            let actual = json_value.to_string();
233            let hash = ahash_str(&actual);
234            stringified_json_value = Some(actual);
235            hash
236        };
237
238        match &json_value {
239            JsonValue::Null => DynamicValue {
240                null: Some(()),
241                json_value,
242                hash_value,
243                ..DynamicValue::new()
244            },
245
246            JsonValue::Bool(b) => DynamicValue {
247                bool_value: Some(*b),
248                string_value: Some(DynamicString::from(b.to_string())),
249                json_value,
250                hash_value,
251                ..DynamicValue::new()
252            },
253
254            JsonValue::Number(n) => {
255                let mut float_value = n.as_f64();
256                let mut int_value = n.as_i64();
257                if let (Some(f), None) = (float_value, int_value) {
258                    let iv = f as i64;
259                    if iv as f64 == f {
260                        int_value = Some(iv);
261                    }
262                } else if let (None, Some(i)) = (float_value, int_value) {
263                    let fv = i as f64;
264                    if fv as i64 == i {
265                        float_value = Some(fv)
266                    }
267                }
268
269                let string_value = float_value
270                    .map(|f| f.to_string())
271                    .or_else(|| int_value.map(|i| i.to_string()))
272                    .or(stringified_json_value);
273
274                DynamicValue {
275                    float_value,
276                    int_value,
277                    string_value: string_value.map(DynamicString::from),
278                    json_value,
279                    hash_value,
280                    ..DynamicValue::new()
281                }
282            }
283
284            JsonValue::String(s) => {
285                let int_value = maybe_parse_i64(s);
286                let float_value = maybe_parse_f64(s);
287                let timestamp_value = try_parse_timestamp(s, int_value);
288                DynamicValue {
289                    string_value: Some(DynamicString::from(s.clone())),
290                    json_value,
291                    timestamp_value,
292                    int_value,
293                    float_value,
294                    hash_value,
295                    ..DynamicValue::new()
296                }
297            }
298
299            JsonValue::Array(arr) => DynamicValue {
300                array_value: Some(arr.iter().map(|v| DynamicValue::from(v.clone())).collect()),
301                string_value: Some(DynamicString::from(
302                    stringified_json_value.unwrap_or(json_value.to_string()),
303                )),
304                json_value,
305                hash_value,
306                ..DynamicValue::new()
307            },
308
309            JsonValue::Object(obj) => DynamicValue {
310                object_value: Some(
311                    obj.into_iter()
312                        .map(|(k, v)| (k.clone(), DynamicValue::from(v.clone())))
313                        .collect(),
314                ),
315                json_value,
316                hash_value,
317                ..DynamicValue::new()
318            },
319        }
320    }
321}
322
323impl From<String> for DynamicValue {
324    fn from(value: String) -> Self {
325        Self::from_string(value)
326    }
327}
328
329impl From<&str> for DynamicValue {
330    fn from(value: &str) -> Self {
331        Self::from_string(value)
332    }
333}
334
335impl From<usize> for DynamicValue {
336    fn from(value: usize) -> Self {
337        Self::from(serde_json::Value::Number(serde_json::Number::from(value)))
338    }
339}
340
341impl From<i64> for DynamicValue {
342    fn from(value: i64) -> Self {
343        Self::from_i64(value)
344    }
345}
346
347impl From<i32> for DynamicValue {
348    fn from(value: i32) -> Self {
349        Self::from_i64(i64::from(value))
350    }
351}
352
353impl From<f64> for DynamicValue {
354    fn from(value: f64) -> Self {
355        Self::from_f64(value)
356    }
357}
358
359impl From<bool> for DynamicValue {
360    fn from(value: bool) -> Self {
361        Self::from_bool(value)
362    }
363}
364
365impl From<Vec<JsonValue>> for DynamicValue {
366    fn from(value: Vec<JsonValue>) -> Self {
367        DynamicValue::from(serde_json::Value::Array(value))
368    }
369}
370
371impl From<Vec<DynamicValue>> for DynamicValue {
372    fn from(value: Vec<DynamicValue>) -> Self {
373        Self::from_dynamic_array(value)
374    }
375}
376
377impl From<HashMap<String, DynamicValue>> for DynamicValue {
378    fn from(value: HashMap<String, DynamicValue>) -> Self {
379        Self::from_dynamic_object(value)
380    }
381}