statsig_rust/evaluation/
dynamic_value.rs

1use chrono::{DateTime, NaiveDateTime};
2use regex_lite::Regex;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use serde_json::{json, Value as JsonValue};
5use std::collections::HashMap;
6
7#[macro_export]
8macro_rules! dyn_value {
9    ($x:expr) => {
10        $crate::DynamicValue::from($x)
11    };
12}
13
14#[derive(Debug, Clone, Default)]
15pub struct DynamicValue {
16    pub null: Option<()>,
17    pub bool_value: Option<bool>,
18    pub int_value: Option<i64>,
19    pub float_value: Option<f64>,
20    pub timestamp_value: Option<i64>,
21    pub string_value: Option<String>,
22    pub lowercase_string_value: Option<String>,
23    pub array_value: Option<Vec<DynamicValue>>,
24    pub object_value: Option<HashMap<String, DynamicValue>>,
25    pub regex_value: Option<Regex>,
26    pub json_value: JsonValue,
27}
28
29impl From<String> for DynamicValue {
30    fn from(value: String) -> Self {
31        let json_value = json!(value);
32        let float_value = value.parse().ok();
33        let int_value = value.parse().ok();
34
35        let timestamp_value = Self::try_parse_timestamp(&value);
36
37        DynamicValue {
38            float_value,
39            int_value,
40            json_value,
41            timestamp_value,
42            lowercase_string_value: Some(value.to_lowercase()),
43            string_value: Some(value),
44            ..Self::default()
45        }
46    }
47}
48
49impl From<&str> for DynamicValue {
50    fn from(value: &str) -> Self {
51        DynamicValue::from(value.to_string())
52    }
53}
54
55impl From<usize> for DynamicValue {
56    fn from(value: usize) -> Self {
57        DynamicValue {
58            json_value: json!(value),
59            int_value: Some(value as i64),
60            float_value: Some(value as f64),
61            string_value: Some(value.to_string()),
62            lowercase_string_value: Some(value.to_string()),
63            ..Self::default()
64        }
65    }
66}
67
68impl From<i64> for DynamicValue {
69    fn from(value: i64) -> Self {
70        DynamicValue {
71            int_value: Some(value),
72            float_value: Some(value as f64),
73            string_value: Some(value.to_string()),
74            lowercase_string_value: Some(value.to_string()),
75            json_value: json!(value),
76            ..Self::default()
77        }
78    }
79}
80
81impl From<i32> for DynamicValue {
82    fn from(value: i32) -> Self {
83        Self::from(i64::from(value))
84    }
85}
86
87impl From<f64> for DynamicValue {
88    fn from(value: f64) -> Self {
89        DynamicValue {
90            int_value: Some(value as i64),
91            float_value: Some(value),
92            string_value: Some(value.to_string()),
93            lowercase_string_value: Some(value.to_string()),
94            json_value: json!(value),
95            ..Self::default()
96        }
97    }
98}
99
100impl From<bool> for DynamicValue {
101    fn from(value: bool) -> Self {
102        DynamicValue {
103            bool_value: Some(value),
104            string_value: Some(value.to_string()),
105            lowercase_string_value: Some(value.to_string()),
106            json_value: json!(value),
107            ..Self::default()
108        }
109    }
110}
111
112impl From<Vec<JsonValue>> for DynamicValue {
113    fn from(value: Vec<JsonValue>) -> Self {
114        DynamicValue::from(json!(value))
115    }
116}
117
118impl From<JsonValue> for DynamicValue {
119    fn from(value: JsonValue) -> Self {
120        let json_value = value.clone();
121        match value {
122            JsonValue::Null => DynamicValue {
123                null: Some(()),
124                json_value,
125                ..DynamicValue::new()
126            },
127            JsonValue::Bool(b) => DynamicValue {
128                bool_value: Some(b),
129                string_value: Some(b.to_string()),
130                json_value,
131                lowercase_string_value: Some(b.to_string().to_lowercase()),
132                ..DynamicValue::new()
133            },
134            JsonValue::Number(n) => DynamicValue {
135                float_value: n.as_f64(),
136                int_value: n.as_i64(),
137                string_value: Some(json_value.to_string()),
138                lowercase_string_value: Some(json_value.to_string()),
139                json_value,
140                ..DynamicValue::new()
141            },
142            JsonValue::String(s) => DynamicValue::from(s),
143            JsonValue::Array(arr) => DynamicValue {
144                array_value: Some(arr.into_iter().map(DynamicValue::from).collect()),
145                string_value: Some(json_value.to_string()),
146                lowercase_string_value: Some(json_value.to_string().to_lowercase()),
147                json_value,
148                ..DynamicValue::new()
149            },
150            JsonValue::Object(obj) => DynamicValue {
151                object_value: Some(
152                    obj.into_iter()
153                        .map(|(k, v)| (k, DynamicValue::from(v)))
154                        .collect(),
155                ),
156                json_value,
157                ..DynamicValue::new()
158            },
159        }
160    }
161}
162
163impl DynamicValue {
164    #[must_use]
165    pub fn new() -> Self {
166        Self::default()
167    }
168    pub fn from<T: Into<DynamicValue>>(value: T) -> Self {
169        value.into()
170    }
171
172    #[must_use]
173    pub fn for_timestamp_evaluation(timestamp: i64) -> DynamicValue {
174        DynamicValue {
175            int_value: Some(timestamp),
176            ..DynamicValue::default()
177        }
178    }
179
180    pub fn compile_regex(&mut self) {
181        if let Some(value) = &self.string_value {
182            if let Ok(regex) = Regex::new(value) {
183                self.regex_value = Some(regex);
184            }
185        }
186    }
187
188    fn try_parse_timestamp(s: &str) -> Option<i64> {
189        if let Ok(ts) = s.parse::<i64>() {
190            return Some(ts);
191        }
192
193        if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
194            return Some(dt.timestamp_millis());
195        }
196
197        if let Ok(ndt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
198            return Some(ndt.and_utc().timestamp_millis());
199        }
200
201        None
202    }
203}
204
205impl PartialEq for DynamicValue {
206    fn eq(&self, other: &Self) -> bool {
207        self.null == other.null
208            && self.bool_value == other.bool_value
209            && self.int_value == other.int_value
210            && self.float_value == other.float_value
211            && self.string_value == other.string_value
212            && self.array_value == other.array_value
213            && self.object_value == other.object_value
214    }
215}
216
217impl Serialize for DynamicValue {
218    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
219    where
220        S: Serializer,
221    {
222        self.json_value.serialize(serializer)
223    }
224}
225
226impl<'de> Deserialize<'de> for DynamicValue {
227    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
228    where
229        D: Deserializer<'de>,
230    {
231        let json_value = JsonValue::deserialize(deserializer)?;
232        Ok(DynamicValue::from(json_value))
233    }
234}