Skip to main content

statsig_rust/user/
user_data.rs

1use 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>;
16pub type UserCustomMap = UserDataMap;
17pub type UserUnitIDMap = UserDataMap;
18
19#[skip_serializing_none]
20#[derive(Clone, Deserialize, Serialize, Default)]
21#[serde(rename_all = "camelCase")]
22pub struct UserData {
23    #[serde(rename = "userID")]
24    pub user_id: Option<DynamicValue>,
25    #[serde(rename = "customIDs")]
26    pub custom_ids: Option<UserDataMap>,
27
28    pub email: Option<DynamicValue>,
29    pub ip: Option<DynamicValue>,
30    pub user_agent: Option<DynamicValue>,
31    pub country: Option<DynamicValue>,
32    pub locale: Option<DynamicValue>,
33    pub app_version: Option<DynamicValue>,
34    pub statsig_environment: Option<UserDataMap>,
35
36    #[serde(skip_serializing)]
37    pub private_attributes: Option<UserDataMap>,
38    pub custom: Option<UserDataMap>,
39}
40
41impl UserData {
42    pub fn create_exposure_dedupe_user_hash(&self, unit_id_type: Option<&str>) -> u64 {
43        let user_id_hash = self
44            .user_id
45            .as_ref()
46            .map_or(0, |user_id| user_id.hash_value);
47        let stable_id_hash = self.get_unit_id_hash("stableID");
48        let unit_type_hash = unit_id_type.map_or(0, |id_type| self.get_unit_id_hash(id_type));
49        let custom_ids_hash_sum = self.sum_custom_id_hashes();
50
51        hashing::hash_one(vec![
52            user_id_hash,
53            stable_id_hash,
54            unit_type_hash,
55            custom_ids_hash_sum,
56        ])
57    }
58
59    pub fn sum_custom_id_hashes(&self) -> u64 {
60        self.custom_ids.as_ref().map_or(0, |custom_ids| {
61            custom_ids
62                .values()
63                .fold(0u64, |acc, value| acc.wrapping_add(value.hash_value))
64        })
65    }
66
67    pub fn get_unit_id_hash(&self, id_type: &str) -> u64 {
68        if id_type.eq_ignore_ascii_case("userid") {
69            return self
70                .user_id
71                .as_ref()
72                .map_or(0, |user_id| user_id.hash_value);
73        }
74
75        if let Some(custom_ids) = &self.custom_ids {
76            if let Some(id) = custom_ids.get(id_type) {
77                return id.hash_value;
78            }
79
80            if let Some(id) = custom_ids.get(&id_type.to_lowercase()) {
81                return id.hash_value;
82            }
83        }
84
85        0
86    }
87
88    pub fn to_bytes(&self) -> Option<Vec<u8>> {
89        serde_json::to_vec(self).ok()
90    }
91}