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
6const EMPTY_HASHES: &[u64] = &[0];
7
8#[skip_serializing_none]
9#[derive(Clone, Deserialize, Serialize, Default)]
10#[serde(rename_all = "camelCase")]
11pub struct UserData {
12    #[serde(rename = "userID")]
13    pub user_id: Option<DynamicValue>,
14    #[serde(rename = "customIDs")]
15    pub custom_ids: Option<HashMap<String, DynamicValue>>,
16
17    pub email: Option<DynamicValue>,
18    pub ip: Option<DynamicValue>,
19    pub user_agent: Option<DynamicValue>,
20    pub country: Option<DynamicValue>,
21    pub locale: Option<DynamicValue>,
22    pub app_version: Option<DynamicValue>,
23    pub statsig_environment: Option<HashMap<String, DynamicValue>>,
24
25    #[serde(skip_serializing)]
26    pub private_attributes: Option<HashMap<String, DynamicValue>>,
27    pub custom: Option<HashMap<String, DynamicValue>>,
28}
29
30impl UserData {
31    pub fn create_user_values_hash(&self) -> u64 {
32        let hashes = self.get_all_user_hashes();
33        hashing::hash_one(hashes)
34    }
35
36    fn get_all_user_hashes(&self) -> Vec<u64> {
37        let mut hashes = Vec::new();
38        push_string_field_hashes(&mut hashes, &self.user_id);
39        push_map_field_hashes(&mut hashes, &self.custom_ids);
40
41        push_string_field_hashes(&mut hashes, &self.app_version);
42        push_string_field_hashes(&mut hashes, &self.country);
43        push_string_field_hashes(&mut hashes, &self.email);
44        push_string_field_hashes(&mut hashes, &self.ip);
45        push_string_field_hashes(&mut hashes, &self.locale);
46        push_string_field_hashes(&mut hashes, &self.user_agent);
47
48        push_map_field_hashes(&mut hashes, &self.custom);
49        push_map_field_hashes(&mut hashes, &self.private_attributes);
50
51        hashes
52    }
53
54    pub fn to_bytes(&self) -> Option<Vec<u8>> {
55        serde_json::to_vec(self).ok()
56    }
57}
58
59fn push_string_field_hashes(hashes: &mut Vec<u64>, field: &Option<DynamicValue>) {
60    if let Some(field) = field {
61        hashes.push(field.hash_value);
62    } else {
63        hashes.push(0);
64    }
65}
66
67fn push_map_field_hashes(hashes: &mut Vec<u64>, field: &Option<HashMap<String, DynamicValue>>) {
68    if let Some(field) = field {
69        hashes.extend(field.values().map(|id| id.hash_value));
70    } else {
71        hashes.extend(EMPTY_HASHES);
72    }
73}