statsig_rust/user/
statsig_user_internal.rs

1use super::StatsigUserLoggable;
2use crate::evaluation::dynamic_value::DynamicValue;
3use crate::hashing::djb2_number;
4use crate::StatsigUser;
5use crate::{evaluation::dynamic_string::DynamicString, Statsig};
6
7pub type FullUserKey = (
8    u64,      // app_version
9    u64,      // country
10    u64,      // email
11    u64,      // ip
12    u64,      // locale
13    u64,      // user_agent
14    u64,      // user_id
15    Vec<u64>, // custom_ids
16    Vec<u64>, // custom
17    Vec<u64>, // private_attributes
18    Vec<u64>, // statsig_env
19);
20
21#[derive(Clone)]
22pub struct StatsigUserInternal<'statsig, 'user> {
23    pub user_ref: &'user StatsigUser,
24    pub statsig_instance: Option<&'statsig Statsig>,
25}
26
27impl<'statsig, 'user> StatsigUserInternal<'statsig, 'user> {
28    pub fn new(user: &'user StatsigUser, statsig_instance: Option<&'statsig Statsig>) -> Self {
29        Self {
30            user_ref: user,
31            statsig_instance,
32        }
33    }
34
35    pub fn get_unit_id(&self, id_type: &DynamicString) -> Option<&DynamicValue> {
36        if id_type.lowercased_value.eq("userid") {
37            return self.user_ref.data.user_id.as_ref();
38        }
39
40        let custom_ids = self.user_ref.data.custom_ids.as_ref()?;
41
42        if let Some(custom_id) = custom_ids.get(&id_type.value) {
43            return Some(custom_id);
44        }
45
46        custom_ids.get(&id_type.lowercased_value)
47    }
48
49    pub fn get_user_value(&self, field: &Option<DynamicString>) -> Option<&DynamicValue> {
50        let field = field.as_ref()?;
51
52        let lowered_field = &field.lowercased_value;
53
54        let str_value = match lowered_field as &str {
55            "userid" => &self.user_ref.data.user_id,
56            "email" => &self.user_ref.data.email,
57            "ip" => &self.user_ref.data.ip,
58            "country" => &self.user_ref.data.country,
59            "locale" => &self.user_ref.data.locale,
60            "appversion" => &self.user_ref.data.app_version,
61            "useragent" => &self.user_ref.data.user_agent,
62            _ => &None,
63        };
64
65        if let Some(value) = str_value {
66            if let Some(str_val) = &value.string_value {
67                if !str_val.value.is_empty() {
68                    return Some(value);
69                }
70            }
71        }
72
73        if let Some(custom) = &self.user_ref.data.custom {
74            if let Some(found) = custom.get(&field.value) {
75                return Some(found);
76            }
77            if let Some(lowered_found) = custom.get(lowered_field) {
78                return Some(lowered_found);
79            }
80        }
81
82        if let Some(instance) = &self.statsig_instance {
83            if let Some(val) = instance.get_value_from_global_custom_fields(&field.value) {
84                return Some(val);
85            }
86
87            if let Some(val) = instance.get_value_from_global_custom_fields(&field.lowercased_value)
88            {
89                return Some(val);
90            }
91        }
92
93        if let Some(private_attributes) = &self.user_ref.data.private_attributes {
94            if let Some(found) = private_attributes.get(&field.value) {
95                return Some(found);
96            }
97            if let Some(lowered_found) = private_attributes.get(lowered_field) {
98                return Some(lowered_found);
99            }
100        }
101
102        let str_value_alt = match lowered_field as &str {
103            "user_id" => &self.user_ref.data.user_id,
104            "app_version" => &self.user_ref.data.app_version,
105            "user_agent" => &self.user_ref.data.user_agent,
106            _ => &None,
107        };
108
109        if str_value_alt.is_some() {
110            return str_value_alt.as_ref();
111        }
112
113        None
114    }
115
116    pub fn get_value_from_environment(
117        &self,
118        field: &Option<DynamicString>,
119    ) -> Option<DynamicValue> {
120        let field = field.as_ref()?;
121
122        if let Some(result) = self.statsig_instance?.get_from_statsig_env(&field.value) {
123            return Some(result);
124        }
125
126        self.statsig_instance?
127            .get_from_statsig_env(&field.lowercased_value)
128    }
129
130    pub fn to_loggable(&self) -> StatsigUserLoggable {
131        let (environment, global_custom) = match self.statsig_instance {
132            Some(statsig) => (
133                statsig.use_statsig_env(|e| e.cloned()),
134                statsig.use_global_custom_fields(|gc| gc.cloned()),
135            ),
136            None => (None, None),
137        };
138
139        StatsigUserLoggable::new(&self.user_ref.data, environment, global_custom)
140    }
141
142    pub fn get_hashed_private_attributes(&self) -> Option<String> {
143        let private_attributes = match &self.user_ref.data.private_attributes {
144            Some(attrs) => attrs,
145            None => return None,
146        };
147
148        if private_attributes.is_empty() {
149            return None;
150        }
151
152        let mut val: i64 = 0;
153        for (key, value) in private_attributes {
154            let hash_key = match value.string_value {
155                Some(ref s) => key.to_owned() + ":" + &s.value,
156                None => key.to_owned() + ":",
157            };
158            val += djb2_number(&hash_key);
159            val &= 0xFFFF_FFFF;
160        }
161        Some(val.to_string())
162    }
163}