statsig_rust/user/
statsig_user_internal.rs

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