Skip to main content

statsig_rust/user/
user_data.rs

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