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