statsig_rust/user/
user_data.rs1use crate::{evaluation::dynamic_value::DynamicValue, hashing};
2#[cfg(feature = "ordered_user_data_maps")]
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5use serde_with::skip_serializing_none;
6#[cfg(not(feature = "ordered_user_data_maps"))]
7use std::collections::HashMap;
8
9#[cfg(feature = "ordered_user_data_maps")]
10pub type UserDataMapOf<T> = IndexMap<String, T>;
11#[cfg(not(feature = "ordered_user_data_maps"))]
12pub type UserDataMapOf<T> = HashMap<String, T>;
13
14pub type UserDataMap = UserDataMapOf<DynamicValue>;
15pub type UserDataStringMap = UserDataMapOf<String>;
16
17#[skip_serializing_none]
18#[derive(Clone, Deserialize, Serialize, Default)]
19#[serde(rename_all = "camelCase")]
20pub struct UserData {
21 #[serde(rename = "userID")]
22 pub user_id: Option<DynamicValue>,
23 #[serde(rename = "customIDs")]
24 pub custom_ids: Option<UserDataMap>,
25
26 pub email: Option<DynamicValue>,
27 pub ip: Option<DynamicValue>,
28 pub user_agent: Option<DynamicValue>,
29 pub country: Option<DynamicValue>,
30 pub locale: Option<DynamicValue>,
31 pub app_version: Option<DynamicValue>,
32 pub statsig_environment: Option<UserDataMap>,
33
34 #[serde(skip_serializing)]
35 pub private_attributes: Option<UserDataMap>,
36 pub custom: Option<UserDataMap>,
37}
38
39impl UserData {
40 pub fn create_exposure_dedupe_user_hash(&self, unit_id_type: Option<&str>) -> u64 {
41 let user_id_hash = self
42 .user_id
43 .as_ref()
44 .map_or(0, |user_id| user_id.hash_value);
45 let stable_id_hash = self.get_unit_id_hash("stableID");
46 let unit_type_hash = unit_id_type.map_or(0, |id_type| self.get_unit_id_hash(id_type));
47 let custom_ids_hash_sum = self.sum_custom_id_hashes();
48
49 hashing::hash_one(vec![
50 user_id_hash,
51 stable_id_hash,
52 unit_type_hash,
53 custom_ids_hash_sum,
54 ])
55 }
56
57 pub fn sum_custom_id_hashes(&self) -> u64 {
58 self.custom_ids.as_ref().map_or(0, |custom_ids| {
59 custom_ids
60 .values()
61 .fold(0u64, |acc, value| acc.wrapping_add(value.hash_value))
62 })
63 }
64
65 pub fn get_unit_id_hash(&self, id_type: &str) -> u64 {
66 if id_type.eq_ignore_ascii_case("userid") {
67 return self
68 .user_id
69 .as_ref()
70 .map_or(0, |user_id| user_id.hash_value);
71 }
72
73 if let Some(custom_ids) = &self.custom_ids {
74 if let Some(id) = custom_ids.get(id_type) {
75 return id.hash_value;
76 }
77
78 if let Some(id) = custom_ids.get(&id_type.to_lowercase()) {
79 return id.hash_value;
80 }
81 }
82
83 0
84 }
85
86 pub fn to_bytes(&self) -> Option<Vec<u8>> {
87 serde_json::to_vec(self).ok()
88 }
89}