statsig_rust/user/
statsig_user_loggable.rs1use super::user_data::{UserData, UserDataMap};
2use crate::{DynamicValue, StatsigUser};
3use serde::{
4 ser::{SerializeMap, SerializeStruct},
5 Deserialize, Serialize,
6};
7use serde_json::Value;
8use std::{collections::HashMap, sync::Arc};
9
10const TAG: &str = "StatsigUserLoggable";
11
12#[derive(Clone, Default)]
13pub struct StatsigUserLoggable {
14 pub data: Arc<UserData>,
15 pub environment: Option<UserDataMap>,
16 pub global_custom: Option<HashMap<String, DynamicValue>>,
17}
18
19impl StatsigUserLoggable {
20 pub fn new(
21 user_inner: &Arc<UserData>,
22 environment: Option<UserDataMap>,
23 global_custom: Option<HashMap<String, DynamicValue>>,
24 ) -> Self {
25 Self {
26 data: user_inner.clone(),
27 environment,
28 global_custom,
29 }
30 }
31
32 pub fn null() -> Self {
33 Self::default()
34 }
35
36 pub fn default_console_capture_user(
37 environment: Option<UserDataMap>,
38 global_custom: Option<HashMap<String, DynamicValue>>,
39 ) -> Self {
40 Self::new(
41 &StatsigUser::with_user_id("console-capture-user").data,
42 environment,
43 global_custom,
44 )
45 }
46}
47
48impl Serialize for StatsigUserLoggable {
51 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52 where
53 S: serde::Serializer,
54 {
55 let mut state = serializer.serialize_struct(TAG, 10)?;
56
57 let data = self.data.as_ref();
58 serialize_data_field(&mut state, "userID", &data.user_id)?;
59 serialize_data_field(&mut state, "customIDs", &data.custom_ids)?;
60 serialize_data_field(&mut state, "email", &data.email)?;
61 serialize_data_field(&mut state, "ip", &data.ip)?;
62 serialize_data_field(&mut state, "userAgent", &data.user_agent)?;
63 serialize_data_field(&mut state, "country", &data.country)?;
64 serialize_data_field(&mut state, "locale", &data.locale)?;
65 serialize_data_field(&mut state, "appVersion", &data.app_version)?;
66
67 serialize_custom_field(&mut state, &data.custom, &self.global_custom)?;
68 serialize_data_field(&mut state, "statsigEnvironment", &self.environment)?;
69
70 state.end()
73 }
74}
75
76impl<'de> Deserialize<'de> for StatsigUserLoggable {
77 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78 where
79 D: serde::Deserializer<'de>,
80 {
81 let mut value = Value::deserialize(deserializer)?;
82 let env = value["statsigEnvironment"].take();
83 let data = serde_json::from_value::<UserData>(value).map_err(|e| {
84 serde::de::Error::custom(format!("Error deserializing StatsigUserInner: {e}"))
85 })?;
86
87 let environment = serde_json::from_value::<Option<UserDataMap>>(env).map_err(|e| {
88 serde::de::Error::custom(format!("Error deserializing StatsigUserInner: {e}"))
89 })?;
90
91 Ok(StatsigUserLoggable {
92 data: Arc::new(data),
93 environment,
94 global_custom: None, })
96 }
97}
98
99fn serialize_data_field<S, T>(
100 state: &mut S,
101 field: &'static str,
102 value: &Option<T>,
103) -> Result<(), S::Error>
104where
105 S: SerializeStruct,
106 T: Serialize,
107{
108 if let Some(value) = value {
109 state.serialize_field(field, value)?;
110 }
111 Ok(())
112}
113
114fn serialize_custom_field<S>(
115 state: &mut S,
116 custom: &Option<UserDataMap>,
117 global_custom: &Option<HashMap<String, DynamicValue>>,
118) -> Result<(), S::Error>
119where
120 S: SerializeStruct,
121{
122 if global_custom.is_none() && custom.is_none() {
123 return Ok(());
124 }
125
126 state.serialize_field(
127 "custom",
128 &MergedCustomFields {
129 custom: custom.as_ref(),
130 global_custom: global_custom.as_ref(),
131 },
132 )
133}
134
135struct MergedCustomFields<'a> {
136 custom: Option<&'a UserDataMap>,
137 global_custom: Option<&'a HashMap<String, DynamicValue>>,
138}
139
140impl MergedCustomFields<'_> {
141 fn serialized_len(&self) -> usize {
142 let global_len = self.global_custom.map_or(0, HashMap::len);
143 let custom_only_len = self.custom.map_or(0, |custom| {
144 custom
145 .keys()
146 .filter(|key| {
147 !self
148 .global_custom
149 .is_some_and(|global_custom| global_custom.contains_key(*key))
150 })
151 .count()
152 });
153
154 global_len + custom_only_len
155 }
156}
157
158impl Serialize for MergedCustomFields<'_> {
159 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
160 where
161 S: serde::Serializer,
162 {
163 let mut map = serializer.serialize_map(Some(self.serialized_len()))?;
164
165 if let Some(global_custom) = self.global_custom {
166 for (key, global_value) in global_custom {
167 let value = self
168 .custom
169 .and_then(|custom| custom.get(key))
170 .unwrap_or(global_value);
171 map.serialize_entry(key, value)?;
172 }
173 }
174
175 if let Some(custom) = self.custom {
176 for (key, value) in custom {
177 if self
178 .global_custom
179 .is_some_and(|global_custom| global_custom.contains_key(key))
180 {
181 continue;
182 }
183
184 map.serialize_entry(key, value)?;
185 }
186 }
187
188 map.end()
189 }
190}