statsig_rdp/
models.rs

1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5use std::collections::HashMap;
6
7#[derive(Serialize, Deserialize)]
8pub struct StatsigConfig<T> {
9    pub value: Option<T>,
10    pub name: String,
11    pub group_name: Option<String>,
12    pub rule_id: String,
13    pub group: String,
14}
15
16#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct StatsigPost {
19    pub events: Vec<StatsigEvent>,
20}
21
22#[derive(Debug, PartialEq, Clone, Eq, Deserialize, Serialize)]
23#[serde(rename_all = "camelCase")]
24pub struct StatsigEvent {
25    pub event_name: String,
26    pub value: String,
27    pub time: String, // unix timestamp
28    pub user: StatsigUser,
29    pub metadata: HashMap<String, String>,
30    // secondary_exposures
31}
32
33#[skip_serializing_none]
34#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
35#[serde(rename_all = "camelCase")]
36pub struct StatsigUser {
37    #[serde(rename = "userID")]
38    pub user_id: String,
39    pub email: Option<String>,
40    pub ip: Option<String>,
41    pub user_agent: Option<String>,
42    pub country: Option<String>,
43    pub locale: Option<String>,
44    pub app_version: Option<String>,
45    pub custom: Option<HashMap<String, String>>,
46    pub private_atributes: Option<HashMap<String, String>>,
47    #[serde(rename = "customIDs")]
48    pub custom_ids: Option<HashMap<String, String>>,
49    pub statsig_environment: StatsigEnvironment,
50}
51
52/// Options to use when creating the client, they will override default values, if they exist.
53///
54/// The default value for api_url is https://api.statsig.com/v1
55/// The default value for config_sync_interval is 15s
56pub struct StatsigOptions {
57    pub api_url: Option<String>,
58    pub cdn_url: Option<String>,
59    pub events_url: Option<String>,
60    pub disable_cache: bool,
61    pub config_sync_interval: Option<Duration>,
62}
63
64impl StatsigOptions {
65    pub fn default() -> Self {
66        Self {
67            api_url: None,
68            cdn_url: None,
69            disable_cache: false,
70            config_sync_interval: None,
71            events_url: None,
72        }
73    }
74
75    pub fn cache_disabled() -> Self {
76        Self {
77            api_url: None,
78            cdn_url: None,
79            disable_cache: true,
80            config_sync_interval: None,
81            events_url: None,
82        }
83    }
84}
85
86#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
87#[serde(rename_all = "camelCase")]
88pub struct StatsigEnvironment {
89    pub tier: String,
90}
91
92impl StatsigEnvironment {
93    pub fn get_field(&self, field: &str) -> String {
94        let empty = "".to_string();
95        match field.to_ascii_lowercase().as_str() {
96            "tier" => self.tier.clone(),
97            _ => empty,
98        }
99    }
100}
101
102impl StatsigUser {
103    pub fn new(user_id: String, tier: String) -> Self {
104        StatsigUser {
105            user_id,
106            email: None,
107            ip: None,
108            user_agent: None,
109            country: None,
110            locale: None,
111            app_version: None,
112            custom: None,
113            private_atributes: None,
114            custom_ids: None,
115            statsig_environment: StatsigEnvironment { tier },
116        }
117    }
118
119    /// Fetch the id of id_type for the user, defaults to user_id.
120    pub fn get_unit_id(&self, id_type: &String) -> String {
121        if id_type.to_ascii_lowercase() == *"userid" {
122            return self.user_id.clone();
123        }
124        if let Some(custom_ids) = &self.custom_ids {
125            if custom_ids.contains_key(id_type) {
126                return custom_ids.get(id_type).unwrap_or(&self.user_id).clone();
127            }
128            let lower = id_type.to_ascii_lowercase();
129            if custom_ids.contains_key(&lower) {
130                return custom_ids.get(&lower).unwrap_or(&self.user_id).clone();
131            }
132        }
133        self.user_id.clone()
134    }
135
136    pub fn get_field(&self, field: &String) -> String {
137        let empty = "".to_string();
138        match field.to_ascii_lowercase().as_str() {
139            "userid" | "user_id" => self.user_id.clone(),
140            "email" => self.email.as_ref().unwrap_or(&empty).clone(),
141            "ip" | "ipaddress" | "ip_address" => self.ip.as_ref().unwrap_or(&empty).clone(),
142            "useragent" | "user_agent" => self.user_agent.as_ref().unwrap_or(&empty).clone(),
143            "country" => self.country.as_ref().unwrap_or(&empty).clone(),
144            "locale" => self.locale.as_ref().unwrap_or(&empty).clone(),
145            "appversion" | "app_version" => self.app_version.as_ref().unwrap_or(&empty).clone(),
146            _ => {
147                let mut ret = "".to_string();
148                if let Some(custom) = &self.custom {
149                    if let Some(v) = custom.get(field) {
150                        ret = v.clone();
151                    } else if let Some(v) = custom.get(&field.to_ascii_lowercase()) {
152                        ret = v.clone();
153                    }
154                } else if let Some(private) = &self.private_atributes {
155                    if let Some(v) = private.get(field) {
156                        ret = v.clone();
157                    } else if let Some(v) = private.get(&field.to_ascii_lowercase()) {
158                        ret = v.clone();
159                    }
160                }
161                ret
162            }
163        }
164    }
165}
166
167#[cfg(test)]
168mod test {
169    use std::collections::HashMap;
170
171    use crate::models::StatsigEnvironment;
172
173    use super::StatsigUser;
174
175    #[test]
176    fn test_get_unit_id_default_user() {
177        let user = StatsigUser::new("user_id".to_string(), "prod".to_string());
178        assert_eq!(
179            user.get_unit_id(&"userid".to_string()),
180            "user_id".to_string()
181        );
182        assert_eq!(
183            user.get_unit_id(&"not_userid".to_string()),
184            "user_id".to_string()
185        );
186    }
187
188    #[test]
189    fn test_get_unit_id_custom_ids() {
190        let mut user = StatsigUser::new("user_id".to_string(), "prod".to_string());
191        let mut custom_ids = HashMap::new();
192        custom_ids.insert("not_userid".to_string(), "not_userid".to_string());
193        custom_ids.insert("ALL_CAPS".to_string(), "ALL_CAPS".to_string());
194        user.custom_ids = Some(custom_ids);
195        assert_eq!(
196            user.get_unit_id(&"userid".to_string()),
197            "user_id".to_string()
198        );
199        assert_eq!(
200            user.get_unit_id(&"Not_userid".to_string()),
201            "not_userid".to_string()
202        );
203        assert_eq!(
204            user.get_unit_id(&"ALL_CAPS".to_string()),
205            "ALL_CAPS".to_string()
206        );
207        assert_eq!(
208            user.get_unit_id(&"non_existing".to_string()),
209            "user_id".to_string()
210        );
211    }
212
213    #[test]
214    fn test_get_field() {
215        let user = StatsigUser {
216            user_id: "userid".to_string(),
217            email: Some("abc@email.com".to_string()),
218            ip: Some("192.168.0.1".to_string()),
219            user_agent: None,
220            country: None,
221            locale: None,
222            app_version: None,
223            custom: Some(HashMap::from([("custom1".to_string(), "val1".to_string())])),
224            private_atributes: None,
225            custom_ids: None,
226            statsig_environment: StatsigEnvironment {
227                tier: "prod".to_string(),
228            },
229        };
230        assert_eq!("userid".to_string(), user.get_field(&"userID".to_string()));
231        assert_eq!(
232            "abc@email.com".to_string(),
233            user.get_field(&"email".to_string())
234        );
235        assert_eq!("192.168.0.1".to_string(), user.get_field(&"ip".to_string()));
236        assert_eq!("val1".to_string(), user.get_field(&"custom1".to_string()));
237    }
238}