statsig_rust/evaluation/
dynamic_value.rs1use 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}