1use crate::statsig_metadata;
2use crate::{evaluation::dynamic_value::DynamicValue, hashing};
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5use std::{collections::HashMap, sync::Arc};
6
7use super::{
8 into_optional::IntoOptional,
9 unit_id::UnitID,
10 user_data::{UserData, UserDataMap, UserDataMapOf},
11 user_value::{UserValue, UserValueMap},
12};
13
14pub type FastUserCustomMap = UserValueMap;
15pub type FastUserUnitIDMap = UserDataMapOf<UserValue>;
16
17#[skip_serializing_none]
18#[derive(Clone, Deserialize, Serialize, Default)]
19#[serde(rename_all = "camelCase")]
20pub struct FastUserData {
21 #[serde(rename = "userID")]
22 pub user_id: Option<UserValue>,
23 #[serde(rename = "customIDs")]
24 pub custom_ids: Option<FastUserUnitIDMap>,
25
26 pub email: Option<UserValue>,
27 pub ip: Option<UserValue>,
28 pub user_agent: Option<UserValue>,
29 pub country: Option<UserValue>,
30 pub locale: Option<UserValue>,
31 pub app_version: Option<UserValue>,
32 pub statsig_environment: Option<UserDataMap>,
33
34 #[serde(skip_serializing)]
35 pub private_attributes: Option<UserDataMap>,
36 pub custom: Option<FastUserCustomMap>,
37}
38
39impl FastUserData {
40 pub(crate) fn create_exposure_dedupe_user_hash(&self, unit_id_type: Option<&str>) -> u64 {
41 let user_id_hash = self.user_id.as_ref().map_or(0, UserValue::hash_value);
42 let stable_id_hash = self.get_unit_id_hash("stableID");
43 let unit_type_hash = unit_id_type.map_or(0, |id_type| self.get_unit_id_hash(id_type));
44 let custom_ids_hash_sum = self.sum_custom_id_hashes();
45
46 hashing::hash_one(vec![
47 user_id_hash,
48 stable_id_hash,
49 unit_type_hash,
50 custom_ids_hash_sum,
51 ])
52 }
53
54 pub(crate) fn sum_custom_id_hashes(&self) -> u64 {
55 self.custom_ids.as_ref().map_or(0, |custom_ids| {
56 custom_ids
57 .values()
58 .fold(0u64, |acc, value| acc.wrapping_add(value.hash_value()))
59 })
60 }
61
62 pub(crate) fn get_unit_id_hash(&self, id_type: &str) -> u64 {
63 if id_type.eq_ignore_ascii_case("userid") {
64 return self.user_id.as_ref().map_or(0, UserValue::hash_value);
65 }
66
67 if let Some(custom_ids) = &self.custom_ids {
68 if let Some(id) = custom_ids.get(id_type) {
69 return id.hash_value();
70 }
71
72 if let Some(id) = custom_ids.get(&id_type.to_lowercase()) {
73 return id.hash_value();
74 }
75 }
76
77 0
78 }
79
80 pub(crate) fn to_bytes(&self) -> Option<Vec<u8>> {
81 serde_json::to_vec(self).ok()
82 }
83}
84
85#[derive(Clone)]
86pub struct FastStatsigUser {
87 pub data: Arc<FastUserData>,
88 pub(crate) sdk_version: &'static str,
89}
90
91impl FastStatsigUser {
92 pub fn new(inner: FastUserData) -> Self {
93 Self {
94 data: Arc::new(inner),
95 sdk_version: statsig_metadata::SDK_VERSION,
96 }
97 }
98
99 pub fn get_user_id(&self) -> Option<&str> {
100 self.data.user_id.as_ref().and_then(UserValue::string_value)
101 }
102
103 pub fn set_user_id(&mut self, user_id: impl Into<UnitID>) {
104 let unit_id = user_id.into();
105 let mut_data = Arc::make_mut(&mut self.data);
106 mut_data.user_id = Some(UserValue::from(unit_id));
107 }
108
109 pub fn get_custom_ids(&self) -> Option<HashMap<&str, &str>> {
110 let mapped = self
111 .data
112 .custom_ids
113 .as_ref()?
114 .iter()
115 .map(|(key, value)| (key.as_str(), value.string_value().unwrap_or("")))
116 .collect();
117
118 Some(mapped)
119 }
120
121 pub fn set_custom_ids<K, U>(&mut self, custom_ids: HashMap<K, U>)
122 where
123 K: Into<String>,
124 U: Into<UnitID>,
125 {
126 let custom_ids = custom_ids
127 .into_iter()
128 .map(|(key, value)| (key.into(), UserValue::from(value.into())))
129 .collect();
130
131 let mut_data = Arc::make_mut(&mut self.data);
132 mut_data.custom_ids = Some(custom_ids);
133 }
134
135 pub(crate) fn get_unit_id_by_name(
136 &self,
137 id_type: &str,
138 lowercased_id_type: &str,
139 ) -> Option<&UserValue> {
140 if lowercased_id_type == "userid" {
141 return self.data.user_id.as_ref();
142 }
143
144 let custom_ids = self.data.custom_ids.as_ref()?;
145
146 if let Some(custom_id) = custom_ids.get(id_type) {
147 return Some(custom_id);
148 }
149
150 custom_ids.get(lowercased_id_type)
151 }
152
153 pub fn get_statsig_environment(&self) -> Option<HashMap<&str, &str>> {
154 let mapped = self
155 .data
156 .statsig_environment
157 .as_ref()?
158 .iter()
159 .map(dynamic_entry_to_key_value_refs)
160 .collect();
161
162 Some(mapped)
163 }
164
165 pub fn set_statsig_environment<K, U>(&mut self, statsig_environment: Option<HashMap<K, U>>)
166 where
167 K: Into<String>,
168 U: Into<String>,
169 {
170 let mut_data = Arc::make_mut(&mut self.data);
171 let statsig_environment = match statsig_environment {
172 Some(value) => value,
173 None => {
174 mut_data.statsig_environment = None;
175 return;
176 }
177 };
178
179 let statsig_environment: UserDataMap = statsig_environment
180 .into_iter()
181 .map(|(key, value)| (key.into(), value.into().into()))
182 .collect();
183
184 mut_data.statsig_environment = Some(statsig_environment);
185 }
186
187 pub fn get_custom(&self) -> Option<&FastUserCustomMap> {
188 self.data.custom.as_ref()
189 }
190
191 pub fn set_custom<K, V>(&mut self, value: impl IntoOptional<HashMap<K, V>>)
192 where
193 K: Into<String>,
194 V: Into<UserValue>,
195 {
196 let mut_data = Arc::make_mut(&mut self.data);
197 let value = match value.into_optional() {
198 Some(value) => value,
199 None => {
200 mut_data.custom = None;
201 return;
202 }
203 };
204
205 mut_data.custom = Some(
206 value
207 .into_iter()
208 .map(|(key, value)| (key.into(), value.into()))
209 .collect(),
210 );
211 }
212
213 pub fn get_private_attributes(&self) -> Option<&UserDataMap> {
214 self.data.private_attributes.as_ref()
215 }
216
217 pub fn set_private_attributes<K, V>(&mut self, value: impl IntoOptional<HashMap<K, V>>)
218 where
219 K: Into<String>,
220 V: Into<DynamicValue>,
221 {
222 let mut_data = Arc::make_mut(&mut self.data);
223 let value = match value.into_optional() {
224 Some(value) => value,
225 None => {
226 mut_data.private_attributes = None;
227 return;
228 }
229 };
230
231 mut_data.private_attributes = Some(
232 value
233 .into_iter()
234 .map(|(key, value)| (key.into(), value.into()))
235 .collect(),
236 );
237 }
238
239 pub fn to_public_user(&self) -> crate::StatsigUser {
240 crate::StatsigUser::new(self.to_public_user_data())
241 }
242
243 pub(crate) fn to_public_user_data(&self) -> UserData {
244 UserData {
245 user_id: self.data.user_id.as_ref().map(user_value_to_dynamic_value),
246 custom_ids: self
247 .data
248 .custom_ids
249 .as_ref()
250 .map(user_value_map_to_dynamic_value_map),
251 email: self.data.email.as_ref().map(user_value_to_dynamic_value),
252 ip: self.data.ip.as_ref().map(user_value_to_dynamic_value),
253 user_agent: self
254 .data
255 .user_agent
256 .as_ref()
257 .map(user_value_to_dynamic_value),
258 country: self.data.country.as_ref().map(user_value_to_dynamic_value),
259 locale: self.data.locale.as_ref().map(user_value_to_dynamic_value),
260 app_version: self
261 .data
262 .app_version
263 .as_ref()
264 .map(user_value_to_dynamic_value),
265 statsig_environment: self.data.statsig_environment.clone(),
266 private_attributes: self.data.private_attributes.clone(),
267 custom: self.data.custom.as_ref().map(|values| {
268 values
269 .iter()
270 .map(|(key, value)| (key.clone(), user_value_to_dynamic_value(value)))
271 .collect()
272 }),
273 }
274 }
275}
276
277macro_rules! string_field_accessor {
278 ($getter_name:ident, $setter_name:ident, $field:ident) => {
279 pub fn $getter_name(&self) -> Option<&str> {
280 self.data.$field.as_ref().and_then(UserValue::string_value)
281 }
282
283 pub fn $setter_name(&mut self, value: impl IntoOptional<String>) {
284 let value = value.into_optional();
285 let mut_data = Arc::make_mut(&mut self.data);
286 match value {
287 Some(value) => {
288 mut_data.$field = Some(UserValue::from(value));
289 }
290 None => mut_data.$field = None,
291 }
292 }
293 };
294}
295
296impl FastStatsigUser {
297 string_field_accessor!(get_email, set_email, email);
298 string_field_accessor!(get_ip, set_ip, ip);
299 string_field_accessor!(get_user_agent, set_user_agent, user_agent);
300 string_field_accessor!(get_country, set_country, country);
301 string_field_accessor!(get_locale, set_locale, locale);
302 string_field_accessor!(get_app_version, set_app_version, app_version);
303}
304
305impl FastStatsigUser {
306 pub fn to_bytes(&self) -> Option<Vec<u8>> {
307 self.data.to_bytes()
308 }
309}
310
311fn dynamic_entry_to_key_value_refs<'a>(
312 entry: (&'a String, &'a DynamicValue),
313) -> (&'a str, &'a str) {
314 let (key, value) = entry;
315
316 (
317 key.as_str(),
318 value
319 .string_value
320 .as_ref()
321 .map(|value| value.value.as_str())
322 .unwrap_or(""),
323 )
324}
325
326fn user_value_to_dynamic_value(value: &UserValue) -> DynamicValue {
327 DynamicValue::from_json_value(value)
328}
329
330fn user_value_map_to_dynamic_value_map(values: &UserDataMapOf<UserValue>) -> UserDataMap {
331 values
332 .iter()
333 .map(|(key, value)| (key.clone(), user_value_to_dynamic_value(value)))
334 .collect()
335}