Skip to main content

statsig_rust/user/
statsig_user_loggable.rs

1use super::user_data::UserData;
2use crate::{DynamicValue, StatsigUser};
3use serde::{ser::SerializeStruct, Deserialize, Serialize};
4use serde_json::Value;
5use std::{collections::HashMap, sync::Arc};
6
7const TAG: &str = "StatsigUserLoggable";
8
9#[derive(Clone, Default)]
10pub struct StatsigUserLoggable {
11    pub data: Arc<UserData>,
12    pub environment: Option<HashMap<String, DynamicValue>>,
13    pub global_custom: Option<HashMap<String, DynamicValue>>,
14}
15
16impl StatsigUserLoggable {
17    pub fn new(
18        user_inner: &Arc<UserData>,
19        environment: Option<HashMap<String, DynamicValue>>,
20        global_custom: Option<HashMap<String, DynamicValue>>,
21    ) -> Self {
22        Self {
23            data: user_inner.clone(),
24            environment,
25            global_custom,
26        }
27    }
28
29    pub fn null() -> Self {
30        Self::default()
31    }
32
33    pub fn default_console_capture_user(
34        environment: Option<HashMap<String, DynamicValue>>,
35        global_custom: Option<HashMap<String, DynamicValue>>,
36    ) -> Self {
37        Self::new(
38            &StatsigUser::with_user_id("console-capture-user").data,
39            environment,
40            global_custom,
41        )
42    }
43}
44
45// ----------------------------------------------------- [Serialization]
46
47impl Serialize for StatsigUserLoggable {
48    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49    where
50        S: serde::Serializer,
51    {
52        let mut state = serializer.serialize_struct(TAG, 10)?;
53
54        let data = self.data.as_ref();
55        serialize_data_field(&mut state, "userID", &data.user_id)?;
56        serialize_data_field(&mut state, "customIDs", &data.custom_ids)?;
57        serialize_data_field(&mut state, "email", &data.email)?;
58        serialize_data_field(&mut state, "ip", &data.ip)?;
59        serialize_data_field(&mut state, "userAgent", &data.user_agent)?;
60        serialize_data_field(&mut state, "country", &data.country)?;
61        serialize_data_field(&mut state, "locale", &data.locale)?;
62        serialize_data_field(&mut state, "appVersion", &data.app_version)?;
63
64        serialize_custom_field(&mut state, &data.custom, &self.global_custom)?;
65        serialize_data_field(&mut state, "statsigEnvironment", &self.environment)?;
66
67        // DO NOT SERIALIZE "privateAttributes"
68
69        state.end()
70    }
71}
72
73impl<'de> Deserialize<'de> for StatsigUserLoggable {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: serde::Deserializer<'de>,
77    {
78        let mut value = Value::deserialize(deserializer)?;
79        let env = value["statsigEnvironment"].take();
80        let data = serde_json::from_value::<UserData>(value).map_err(|e| {
81            serde::de::Error::custom(format!("Error deserializing StatsigUserInner: {e}"))
82        })?;
83
84        let environment = serde_json::from_value::<Option<HashMap<String, DynamicValue>>>(env)
85            .map_err(|e| {
86                serde::de::Error::custom(format!("Error deserializing StatsigUserInner: {e}"))
87            })?;
88
89        Ok(StatsigUserLoggable {
90            data: Arc::new(data),
91            environment,
92            global_custom: None, // there is no way to discern between user-defined and global custom fields
93        })
94    }
95}
96
97fn serialize_data_field<S, T>(
98    state: &mut S,
99    field: &'static str,
100    value: &Option<T>,
101) -> Result<(), S::Error>
102where
103    S: SerializeStruct,
104    T: Serialize,
105{
106    if let Some(value) = value {
107        state.serialize_field(field, value)?;
108    }
109    Ok(())
110}
111
112fn serialize_custom_field<S>(
113    state: &mut S,
114    custom: &Option<HashMap<String, DynamicValue>>,
115    global_custom: &Option<HashMap<String, DynamicValue>>,
116) -> Result<(), S::Error>
117where
118    S: SerializeStruct,
119{
120    if global_custom.is_none() && custom.is_none() {
121        return Ok(());
122    }
123
124    let mut map = HashMap::new();
125
126    if let Some(global_custom) = global_custom.as_ref() {
127        for (k, v) in global_custom {
128            map.insert(k, v);
129        }
130    }
131
132    if let Some(custom) = custom.as_ref() {
133        for (k, v) in custom {
134            map.insert(k, v);
135        }
136    }
137
138    serialize_data_field(state, "custom", &Some(map))
139}