statsig_rust/evaluation/
evaluator_value.rs

1use chrono::{DateTime, NaiveDateTime};
2use regex::Regex;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use serde_json::{Value as JsonValue, Value};
5use std::collections::HashMap;
6
7use crate::{unwrap_or_return, DynamicValue};
8
9use super::dynamic_string::DynamicString;
10
11#[macro_export]
12macro_rules! test_only_make_eval_value {
13    ($x:expr) => {
14        $crate::evaluation::evaluator_value::EvaluatorValue::from(serde_json::json!($x))
15    };
16}
17
18#[derive(Debug, PartialEq)]
19pub enum EvaluatorValueType {
20    Null,
21
22    Bool,
23    Number,
24    String,
25    Array,
26    Object,
27}
28
29#[derive(Debug)]
30pub struct EvaluatorValue {
31    pub value_type: EvaluatorValueType,
32    pub bool_value: Option<bool>,
33    pub float_value: Option<f64>,
34    pub string_value: Option<DynamicString>,
35    pub regex_value: Option<Regex>,
36    pub timestamp_value: Option<i64>,
37    // { lower_case_str: (index, str) } -- Keyed by lowercase string so we can lookup with O(1)
38    pub array_value: Option<HashMap<String, (usize, String)>>,
39    pub object_value: Option<HashMap<String, DynamicString>>,
40}
41
42impl EvaluatorValue {
43    pub fn new(value_type: EvaluatorValueType) -> Self {
44        Self {
45            value_type,
46            bool_value: None,
47            float_value: None,
48            string_value: None,
49            regex_value: None,
50            timestamp_value: None,
51            array_value: None,
52            object_value: None,
53        }
54    }
55
56    pub fn null() -> Self {
57        Self::new(EvaluatorValueType::Null)
58    }
59
60    pub fn compile_regex(&mut self) {
61        let str_value = match &self.string_value {
62            Some(dyn_str) => &dyn_str.value,
63            None => return,
64        };
65
66        if let Ok(regex) = Regex::new(str_value) {
67            self.regex_value = Some(regex);
68        }
69    }
70
71    pub fn is_equal_to_dynamic_value(&self, other: &DynamicValue) -> bool {
72        match self.value_type {
73            EvaluatorValueType::Null => other.json_value == Value::Null,
74            EvaluatorValueType::Bool => self.bool_value == other.bool_value,
75            EvaluatorValueType::Number => self.float_value == other.float_value,
76            EvaluatorValueType::String => self.string_value == other.string_value,
77            EvaluatorValueType::Array => {
78                let self_keyed_arr = match &self.array_value {
79                    Some(map) => map,
80                    None => return other.array_value.is_none(),
81                };
82
83                let other_arr = match &other.array_value {
84                    Some(arr) => arr,
85                    None => return false,
86                };
87
88                if self_keyed_arr.len() != other_arr.len() {
89                    return false;
90                }
91
92                for (i, self_value) in self_keyed_arr.values() {
93                    let other_dyn_str = unwrap_or_return!(&other_arr[*i].string_value, false);
94                    if *self_value != other_dyn_str.value {
95                        return false;
96                    }
97                }
98
99                true
100            }
101            EvaluatorValueType::Object => {
102                let self_obj = match &self.object_value {
103                    Some(map) => map,
104                    None => return other.object_value.is_none(),
105                };
106
107                let other_obj = match &other.object_value {
108                    Some(arr) => arr,
109                    None => return false,
110                };
111
112                if self_obj.len() != other_obj.len() {
113                    return false;
114                }
115
116                for (k, v) in self_obj {
117                    let other_dyn_val = unwrap_or_return!(other_obj.get(k), false);
118                    let other_str_val = unwrap_or_return!(&other_dyn_val.string_value, false);
119                    if other_str_val.value != v.value {
120                        return false;
121                    }
122                }
123
124                true
125            }
126        }
127    }
128}
129
130// Used during evaluation:
131// - ua_parser
132impl From<String> for EvaluatorValue {
133    fn from(value: String) -> Self {
134        EvaluatorValue {
135            timestamp_value: try_parse_timestamp(&value),
136            float_value: value.parse::<f64>().ok(),
137            string_value: Some(DynamicString::from(value)),
138            ..EvaluatorValue::new(EvaluatorValueType::String)
139        }
140    }
141}
142
143// Used during Deserialization
144impl From<JsonValue> for EvaluatorValue {
145    fn from(value: JsonValue) -> Self {
146        match value {
147            JsonValue::Null => EvaluatorValue::new(EvaluatorValueType::Null),
148
149            JsonValue::Bool(b) => EvaluatorValue {
150                bool_value: Some(b),
151                ..EvaluatorValue::new(EvaluatorValueType::Bool)
152            },
153
154            JsonValue::Number(n) => EvaluatorValue {
155                float_value: n.as_f64(),
156                ..EvaluatorValue::new(EvaluatorValueType::Number)
157            },
158
159            JsonValue::String(s) => EvaluatorValue::from(s),
160
161            JsonValue::Array(arr) => {
162                let keyed_array: HashMap<String, (usize, String)> = arr
163                    .into_iter()
164                    .enumerate()
165                    .map(|(idx, val)| {
166                        let str_value = match val.as_str() {
167                            Some(s) => s.to_string(), // Value is a String
168                            None => val.to_string(),  // Value was not a String, but can be made one
169                        };
170
171                        (str_value.to_lowercase(), (idx, str_value))
172                    })
173                    .collect();
174
175                EvaluatorValue {
176                    array_value: Some(keyed_array),
177                    ..EvaluatorValue::new(EvaluatorValueType::Array)
178                }
179            }
180
181            JsonValue::Object(obj) => EvaluatorValue {
182                object_value: Some(
183                    obj.into_iter()
184                        .map(|(k, v)| (k, DynamicString::from(v)))
185                        .collect(),
186                ),
187                ..EvaluatorValue::new(EvaluatorValueType::Object)
188            },
189        }
190    }
191}
192
193impl Serialize for EvaluatorValue {
194    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
195    where
196        S: Serializer,
197    {
198        match &self.value_type {
199            EvaluatorValueType::Null => JsonValue::Null.serialize(serializer),
200            EvaluatorValueType::Bool => self.bool_value.serialize(serializer),
201            EvaluatorValueType::Number => self.float_value.serialize(serializer),
202            EvaluatorValueType::String => self.string_value.serialize(serializer),
203            EvaluatorValueType::Array => {
204                let array_map = match &self.array_value {
205                    Some(a) => a,
206                    None => return JsonValue::Null.serialize(serializer),
207                };
208
209                let mut result = vec![String::new(); array_map.len()];
210
211                for (idx, val) in array_map.values() {
212                    result[*idx] = val.clone();
213                }
214
215                result.serialize(serializer)
216            }
217            EvaluatorValueType::Object => self.object_value.serialize(serializer),
218        }
219    }
220}
221
222impl<'de> Deserialize<'de> for EvaluatorValue {
223    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224    where
225        D: Deserializer<'de>,
226    {
227        let json_value = JsonValue::deserialize(deserializer)?;
228        Ok(EvaluatorValue::from(json_value))
229    }
230}
231
232impl PartialEq for EvaluatorValue {
233    fn eq(&self, other: &Self) -> bool {
234        self.value_type == other.value_type
235            && self.bool_value == other.bool_value
236            && self.float_value == other.float_value
237            && self.string_value == other.string_value
238            && self.array_value == other.array_value
239            && self.object_value == other.object_value
240    }
241}
242
243fn try_parse_timestamp(s: &str) -> Option<i64> {
244    if let Ok(ts) = s.parse::<i64>() {
245        return Some(ts);
246    }
247
248    if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
249        return Some(dt.timestamp_millis());
250    }
251
252    if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
253        return Some(ndt.and_utc().timestamp_millis());
254    }
255
256    None
257}