Skip to main content

statsig_rust/evaluation/
evaluator_value.rs

1use chrono::{DateTime, NaiveDateTime};
2use fancy_regex::Regex as FancyRegex;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use serde_json::{
5    value::{to_raw_value, RawValue},
6    Value as JsonValue, Value,
7};
8use std::{borrow::Cow, collections::HashMap, sync::Arc};
9
10use crate::{
11    interned_string::InternedString, interned_values::InternedStore, log_e, unwrap_or_return,
12    DynamicValue,
13};
14
15use super::dynamic_string::DynamicString;
16
17lazy_static::lazy_static! {
18    pub(crate) static ref EMPTY_EVALUATOR_VALUE: EvaluatorValue = EvaluatorValue {
19        hash: 0,
20        inner: EvaluatorValueInner::Pointer(Arc::new(MemoizedEvaluatorValue::new(EvaluatorValueType::Null))),
21    };
22}
23
24const TAG: &str = "EvaluatorValue";
25
26#[derive(Clone, Debug)]
27pub enum EvaluatorValueInner {
28    Pointer(Arc<MemoizedEvaluatorValue>),
29    Static(&'static MemoizedEvaluatorValue),
30}
31
32#[derive(Clone, Debug)]
33pub struct EvaluatorValue {
34    pub hash: u64,
35    pub inner: EvaluatorValueInner,
36}
37
38impl EvaluatorValue {
39    pub fn empty() -> &'static Self {
40        &EMPTY_EVALUATOR_VALUE
41    }
42
43    pub fn from_json_value(value: Value) -> Self {
44        let raw_value = match to_raw_value(&value) {
45            Ok(raw_value) => raw_value,
46            Err(e) => {
47                log_e!(TAG, "Failed to convert map to raw value: {}", e);
48                return Self::empty().clone();
49            }
50        };
51
52        InternedStore::get_or_intern_evaluator_value(Cow::Owned(raw_value))
53    }
54
55    pub fn compile_regex(&mut self) {
56        match &mut self.inner {
57            EvaluatorValueInner::Pointer(inner) => {
58                if inner.regex_value.is_some() {
59                    return;
60                }
61
62                let mut_inner = Arc::make_mut(inner);
63                mut_inner.compile_regex();
64                InternedStore::replace_evaluator_value(self.hash, inner.clone());
65            }
66            EvaluatorValueInner::Static(inner) => {
67                if inner.regex_value.is_some() {
68                    return;
69                }
70
71                // static values are immutable and should already be compiled during `InternedStore::preload(..)`
72                log_e!(TAG, "Cannot compile regex for static EvaluatorValue");
73            }
74        }
75    }
76}
77
78impl AsRef<MemoizedEvaluatorValue> for EvaluatorValue {
79    fn as_ref(&self) -> &MemoizedEvaluatorValue {
80        match &self.inner {
81            EvaluatorValueInner::Pointer(inner) => inner,
82            EvaluatorValueInner::Static(inner) => inner,
83        }
84    }
85}
86
87impl<'de> Deserialize<'de> for EvaluatorValue {
88    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89    where
90        D: Deserializer<'de>,
91    {
92        let raw_value_ref: Box<RawValue> = Deserialize::deserialize(deserializer)?;
93        Ok(InternedStore::get_or_intern_evaluator_value(Cow::Owned(
94            raw_value_ref,
95        )))
96    }
97}
98
99impl Serialize for EvaluatorValue {
100    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101    where
102        S: Serializer,
103    {
104        match &self.inner {
105            EvaluatorValueInner::Pointer(inner) => inner.serialize(serializer),
106            EvaluatorValueInner::Static(inner) => inner.serialize(serializer),
107        }
108    }
109}
110
111impl PartialEq for EvaluatorValue {
112    fn eq(&self, other: &Self) -> bool {
113        let left = match &self.inner {
114            EvaluatorValueInner::Pointer(inner) => inner,
115            EvaluatorValueInner::Static(inner) => *inner,
116        };
117        let right = match &other.inner {
118            EvaluatorValueInner::Pointer(inner) => inner,
119            EvaluatorValueInner::Static(inner) => *inner,
120        };
121
122        left == right
123    }
124}
125
126impl Drop for EvaluatorValue {
127    fn drop(&mut self) {
128        self.inner = EMPTY_EVALUATOR_VALUE.inner.clone();
129        InternedStore::release_evaluator_value(self.hash);
130    }
131}
132
133// ------------------------------------------------------------------------------- [ MemoizedEvaluatorValue ]
134
135#[derive(Debug, PartialEq, Clone)]
136pub enum EvaluatorValueType {
137    Null,
138
139    Bool,
140    Number,
141    String,
142    Array,
143    Object,
144}
145
146#[derive(Debug, Clone)]
147pub struct MemoizedEvaluatorValue {
148    pub value_type: EvaluatorValueType,
149    pub bool_value: Option<bool>,
150    pub float_value: Option<f64>,
151    pub string_value: Option<DynamicString>,
152    pub regex_value: Option<FancyRegex>,
153    pub timestamp_value: Option<i64>,
154    pub object_value: Option<HashMap<InternedString, DynamicString>>,
155
156    // - Note on Array Value ------------------------------------------------------------
157    // - Keyed by lowercase string so we can lookup with O(1) during evaluation.
158    // - Format is `{ lower_case_str: (index, str) }` i.e: ["Apple", "Banana"] becomes { "apple": (0, "Apple"), "banana": (1, "Banana") }
159    // - The index is what position in the array it is, currently this is only used to serialzie back to the original JSON.
160    // ----------------------------------------------------------------------------------
161    pub array_value: Option<HashMap<InternedString, (usize, InternedString)>>,
162}
163
164impl MemoizedEvaluatorValue {
165    pub fn from_raw_value(raw_value: Cow<'_, RawValue>) -> Self {
166        match serde_json::from_str(raw_value.get()) {
167            Ok(value) => value,
168            Err(e) => {
169                log_e!(
170                    TAG,
171                    "Failed to convert raw value to MemoizedEvaluatorValue: {}",
172                    e
173                );
174                Self::null()
175            }
176        }
177    }
178}
179
180impl MemoizedEvaluatorValue {
181    pub fn new(value_type: EvaluatorValueType) -> Self {
182        Self {
183            value_type,
184            bool_value: None,
185            float_value: None,
186            string_value: None,
187            regex_value: None,
188            timestamp_value: None,
189            array_value: None,
190            object_value: None,
191        }
192    }
193
194    pub fn null() -> Self {
195        Self::new(EvaluatorValueType::Null)
196    }
197
198    pub fn compile_regex(&mut self) {
199        let str_value = match &self.string_value {
200            Some(dyn_str) => &dyn_str.value,
201            None => return,
202        };
203
204        if let Ok(regex) = FancyRegex::new(str_value) {
205            self.regex_value = Some(regex);
206        }
207    }
208
209    pub fn is_equal_to_dynamic_value(&self, other: &DynamicValue) -> bool {
210        match self.value_type {
211            EvaluatorValueType::Null => other.json_value == Value::Null,
212            EvaluatorValueType::Bool => self.bool_value == other.bool_value,
213            EvaluatorValueType::Number => self.float_value == other.float_value,
214            EvaluatorValueType::String => self.string_value == other.string_value,
215            EvaluatorValueType::Array => {
216                let self_keyed_arr = match &self.array_value {
217                    Some(map) => map,
218                    None => return other.array_value.is_none(),
219                };
220
221                let other_arr = match &other.array_value {
222                    Some(arr) => arr,
223                    None => return false,
224                };
225
226                if self_keyed_arr.len() != other_arr.len() {
227                    return false;
228                }
229
230                for (i, self_value) in self_keyed_arr.values() {
231                    let other_dyn_str = unwrap_or_return!(&other_arr[*i].string_value, false);
232                    if *self_value != other_dyn_str.value {
233                        return false;
234                    }
235                }
236
237                true
238            }
239            EvaluatorValueType::Object => {
240                let self_obj = match &self.object_value {
241                    Some(map) => map,
242                    None => return other.object_value.is_none(),
243                };
244
245                let other_obj = match &other.object_value {
246                    Some(arr) => arr,
247                    None => return false,
248                };
249
250                if self_obj.len() != other_obj.len() {
251                    return false;
252                }
253
254                for (k, v) in self_obj {
255                    let other_dyn_val = unwrap_or_return!(other_obj.get(k.as_str()), false);
256                    let other_str_val = unwrap_or_return!(&other_dyn_val.string_value, false);
257                    if other_str_val.value != v.value {
258                        return false;
259                    }
260                }
261
262                true
263            }
264        }
265    }
266}
267
268// Used during evaluation:
269// - ua_parser
270impl From<String> for MemoizedEvaluatorValue {
271    fn from(value: String) -> Self {
272        MemoizedEvaluatorValue {
273            timestamp_value: try_parse_timestamp(&value),
274            float_value: value.parse::<f64>().ok(),
275            string_value: Some(DynamicString::from(value)),
276            ..MemoizedEvaluatorValue::new(EvaluatorValueType::String)
277        }
278    }
279}
280
281// Used during Deserialization
282impl From<JsonValue> for MemoizedEvaluatorValue {
283    fn from(value: JsonValue) -> Self {
284        match value {
285            JsonValue::Null => MemoizedEvaluatorValue::new(EvaluatorValueType::Null),
286
287            JsonValue::Bool(b) => MemoizedEvaluatorValue {
288                bool_value: Some(b),
289                ..MemoizedEvaluatorValue::new(EvaluatorValueType::Bool)
290            },
291
292            JsonValue::Number(n) => MemoizedEvaluatorValue {
293                float_value: n.as_f64(),
294                ..MemoizedEvaluatorValue::new(EvaluatorValueType::Number)
295            },
296
297            JsonValue::String(s) => MemoizedEvaluatorValue::from(s),
298
299            JsonValue::Array(arr) => {
300                let keyed_array: HashMap<InternedString, (usize, InternedString)> = arr
301                    .into_iter()
302                    .enumerate()
303                    .map(|(idx, val)| {
304                        let str_value = match val.as_str() {
305                            Some(s) => s.to_string(), // Value is a String
306                            None => val.to_string(),  // Value was not a String, but can be made one
307                        };
308
309                        let interned_lowercased_str =
310                            InternedString::from_string(str_value.to_lowercase());
311                        let interned_str = InternedString::from_string(str_value);
312
313                        (interned_lowercased_str, (idx, interned_str))
314                    })
315                    .collect();
316
317                MemoizedEvaluatorValue {
318                    array_value: Some(keyed_array),
319                    ..MemoizedEvaluatorValue::new(EvaluatorValueType::Array)
320                }
321            }
322
323            JsonValue::Object(obj) => MemoizedEvaluatorValue {
324                object_value: Some(
325                    obj.into_iter()
326                        .map(|(k, v)| (InternedString::from_string(k), DynamicString::from(v)))
327                        .collect(),
328                ),
329                ..MemoizedEvaluatorValue::new(EvaluatorValueType::Object)
330            },
331        }
332    }
333}
334
335impl Serialize for MemoizedEvaluatorValue {
336    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
337    where
338        S: Serializer,
339    {
340        match &self.value_type {
341            EvaluatorValueType::Null => JsonValue::Null.serialize(serializer),
342            EvaluatorValueType::Bool => self.bool_value.serialize(serializer),
343            EvaluatorValueType::Number => self.float_value.serialize(serializer),
344            EvaluatorValueType::String => self.string_value.serialize(serializer),
345            EvaluatorValueType::Array => {
346                let array_map = match &self.array_value {
347                    Some(a) => a,
348                    None => return JsonValue::Null.serialize(serializer),
349                };
350
351                let mut entries: Vec<(usize, String)> = array_map
352                    .values()
353                    .map(|(idx, val)| (*idx, val.unperformant_to_string()))
354                    .collect();
355                entries.sort_by_key(|(idx, _)| *idx);
356                let result: Vec<String> = entries.into_iter().map(|(_, val)| val).collect();
357
358                result.serialize(serializer)
359            }
360            EvaluatorValueType::Object => self.object_value.serialize(serializer),
361        }
362    }
363}
364
365impl<'de> Deserialize<'de> for MemoizedEvaluatorValue {
366    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
367    where
368        D: Deserializer<'de>,
369    {
370        let json_value = JsonValue::deserialize(deserializer)?;
371        Ok(MemoizedEvaluatorValue::from(json_value))
372    }
373}
374
375impl PartialEq for MemoizedEvaluatorValue {
376    fn eq(&self, other: &Self) -> bool {
377        self.value_type == other.value_type
378            && self.bool_value == other.bool_value
379            && self.float_value == other.float_value
380            && self.string_value == other.string_value
381            && self.array_value == other.array_value
382            && self.object_value == other.object_value
383    }
384}
385
386fn try_parse_timestamp(s: &str) -> Option<i64> {
387    if let Ok(ts) = s.parse::<i64>() {
388        return Some(ts);
389    }
390
391    if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
392        return Some(dt.timestamp_millis());
393    }
394
395    if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
396        return Some(ndt.and_utc().timestamp_millis());
397    }
398
399    None
400}
401
402#[macro_export]
403macro_rules! test_only_make_eval_value {
404    ($x:expr) => {
405        $crate::evaluation::evaluator_value::MemoizedEvaluatorValue::from(serde_json::json!($x))
406    };
407}
408
409#[cfg(test)]
410mod tests {
411    use super::MemoizedEvaluatorValue;
412    use serde_json::json;
413
414    #[test]
415    fn serialize_array_with_case_collision_is_safe() {
416        let value = MemoizedEvaluatorValue::from(json!(["A", "a"]));
417        let serialized = serde_json::to_value(&value).expect("serialize MemoizedEvaluatorValue");
418        assert_eq!(serialized, json!(["a"]));
419    }
420}