1use crate::gcir::dynamic_configs_processor::get_dynamic_config_evaluations_init_v2;
2use crate::gcir::feature_gates_processor::{get_gate_evaluations, get_gate_evaluations_init_v2};
3use crate::gcir::layer_configs_processor::get_layer_evaluations_init_v2;
4use ahash::AHashMap;
5
6use crate::initialize_v2_response::InitializeV2Response;
7use crate::interned_string::InternedString;
8use crate::specs_response::spec_types::SessionReplayTrigger;
9use crate::{
10 evaluation::evaluator::{Evaluator, SpecType},
11 evaluation::evaluator_context::EvaluatorContext,
12 initialize_evaluations_response::InitializeEvaluationsResponse,
13 initialize_response::InitializeResponse,
14 statsig_metadata::StatsigMetadata,
15 StatsigErr,
16};
17
18use crate::StatsigUser;
19use rand::Rng;
20use serde::Deserialize;
21use std::collections::HashMap;
22
23use super::dynamic_configs_processor::{
24 get_dynamic_config_evaluations, get_dynamic_config_evaluations_v2,
25};
26use super::feature_gates_processor::get_gate_evaluations_v2;
27use super::gcir_options::ClientInitResponseOptions;
28use super::layer_configs_processor::{get_layer_evaluations, get_layer_evaluations_v2};
29use super::param_stores_processor::get_serializeable_param_stores;
30
31#[derive(Deserialize)]
32pub enum GCIRResponseFormat {
33 Initialize, InitializeWithSecondaryExposureMapping, InitializeV2, }
37
38impl GCIRResponseFormat {
39 #[must_use]
40 pub fn from_string(input: &str) -> Option<Self> {
41 match input {
42 "v1" => Some(GCIRResponseFormat::Initialize),
43 "v2" => Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping),
44 "init_v2" => Some(GCIRResponseFormat::InitializeV2),
45 _ => None,
46 }
47 }
48}
49
50pub struct GCIRFormatter;
51
52impl GCIRFormatter {
53 pub fn generate_v1_format(
54 context: &mut EvaluatorContext,
55 options: &ClientInitResponseOptions,
56 ) -> Result<InitializeResponse, StatsigErr> {
57 let mut sec_expo_hash_memo = HashMap::new();
58
59 let gates = get_gate_evaluations(context, options, &mut sec_expo_hash_memo)?;
60 let configs = get_dynamic_config_evaluations(context, options, &mut sec_expo_hash_memo)?;
61 let layers = get_layer_evaluations(context, options, &mut sec_expo_hash_memo)?;
62
63 let param_stores = get_serializeable_param_stores(context, options);
64 let evaluated_keys = get_evaluated_keys(context.user.user_ref);
65 let session_replay_info = get_session_replay_info(context, options);
66
67 Ok(InitializeResponse {
68 feature_gates: gates,
69 dynamic_configs: configs,
70 layer_configs: layers,
71 time: context.specs_data.time,
72 has_updates: true,
73 hash_used: options.get_hash_algorithm().to_string(),
74 user: context.user.to_loggable(),
75 sdk_params: HashMap::new(),
76 evaluated_keys,
77 sdk_info: get_sdk_info(),
78 param_stores,
79 can_record_session: session_replay_info.can_record_session,
80 session_recording_rate: session_replay_info.session_recording_rate,
81 recording_blocked: session_replay_info.recording_blocked,
82 passes_session_recording_targeting: session_replay_info
83 .passes_session_recording_targeting,
84 session_recording_event_triggers: session_replay_info.session_recording_event_triggers,
85 session_recording_exposure_triggers: session_replay_info
86 .session_recording_exposure_triggers,
87 pa_hash: context.user.get_hashed_private_attributes(),
88 })
89 }
90
91 pub fn generate_v2_format(
92 context: &mut EvaluatorContext,
93 options: &ClientInitResponseOptions,
94 ) -> Result<InitializeEvaluationsResponse, StatsigErr> {
95 let mut sec_expo_hash_memo = HashMap::new();
96 let mut exposures = HashMap::new();
97
98 let param_stores = get_serializeable_param_stores(context, options);
99 let evaluated_keys = get_evaluated_keys(context.user.user_ref);
100 let session_replay_info = get_session_replay_info(context, options);
101
102 Ok(InitializeEvaluationsResponse {
103 feature_gates: get_gate_evaluations_v2(
104 context,
105 options,
106 &mut sec_expo_hash_memo,
107 &mut exposures,
108 )?,
109 dynamic_configs: get_dynamic_config_evaluations_v2(
110 context,
111 options,
112 &mut sec_expo_hash_memo,
113 &mut exposures,
114 )?,
115 layer_configs: get_layer_evaluations_v2(
116 context,
117 options,
118 &mut sec_expo_hash_memo,
119 &mut exposures,
120 )?,
121 time: context.specs_data.time,
122 has_updates: true,
123 hash_used: options.get_hash_algorithm().to_string(),
124 user: context.user.to_loggable(),
125 pa_hash: context.user.get_hashed_private_attributes(),
126 sdk_params: HashMap::new(),
127 evaluated_keys,
128 sdk_info: get_sdk_info(),
129 param_stores,
130 exposures,
131 can_record_session: session_replay_info.can_record_session,
132 session_recording_rate: session_replay_info.session_recording_rate,
133 recording_blocked: session_replay_info.recording_blocked,
134 passes_session_recording_targeting: session_replay_info
135 .passes_session_recording_targeting,
136 session_recording_event_triggers: session_replay_info.session_recording_event_triggers,
137 session_recording_exposure_triggers: session_replay_info
138 .session_recording_exposure_triggers,
139 })
140 }
141
142 pub fn generate_init_v2_format(
143 context: &mut EvaluatorContext,
144 options: &ClientInitResponseOptions,
145 ) -> Result<InitializeV2Response, StatsigErr> {
146 let mut values = HashMap::new();
147 let mut val_map = AHashMap::new();
148 let mut exposure_map = AHashMap::new();
149 let mut exposures = HashMap::new();
150 let param_stores = get_serializeable_param_stores(context, options);
151 let evaluated_keys = get_evaluated_keys(context.user.user_ref);
152 let session_replay_info = get_session_replay_info(context, options);
153
154 Ok(InitializeV2Response {
155 feature_gates: get_gate_evaluations_init_v2(
156 context,
157 options,
158 &mut exposures,
159 &mut exposure_map,
160 )?,
161 dynamic_configs: get_dynamic_config_evaluations_init_v2(
162 context,
163 options,
164 &mut exposures,
165 &mut exposure_map,
166 &mut values,
167 &mut val_map,
168 )?,
169 layer_configs: get_layer_evaluations_init_v2(
170 context,
171 options,
172 &mut exposures,
173 &mut exposure_map,
174 &mut values,
175 &mut val_map,
176 )?,
177 param_stores,
178 time: context.specs_data.time,
179 has_updates: true,
180 hash_used: options.get_hash_algorithm().to_string(),
181 user: context.user.to_loggable(),
182 pa_hash: context.user.get_hashed_private_attributes(),
183 sdk_params: HashMap::new(),
184 evaluated_keys,
185 sdk_info: get_sdk_info(),
186 exposures,
187 can_record_session: session_replay_info.can_record_session,
188 session_recording_rate: session_replay_info.session_recording_rate,
189 recording_blocked: session_replay_info.recording_blocked,
190 passes_session_recording_targeting: session_replay_info
191 .passes_session_recording_targeting,
192 session_recording_event_triggers: session_replay_info.session_recording_event_triggers,
193 session_recording_exposure_triggers: session_replay_info
194 .session_recording_exposure_triggers,
195 values,
196 response_format: "init-v2".to_string(),
197 })
198 }
199}
200
201fn get_evaluated_keys(user: &StatsigUser) -> HashMap<InternedString, InternedString> {
202 let mut evaluated_keys = HashMap::new();
203
204 if let Some(user_id) = user.data.user_id.as_ref() {
205 evaluated_keys.insert(
206 InternedString::from_str_ref("userID"),
207 user_id
208 .string_value
209 .as_ref()
210 .map(|s| s.value.clone())
211 .unwrap_or_default(),
212 );
213 }
214
215 if let Some(custom_ids) = user.data.custom_ids.as_ref() {
216 for (key, value) in custom_ids {
217 evaluated_keys.insert(
218 InternedString::from_str_ref(key.as_str()),
219 value
220 .string_value
221 .as_ref()
222 .map(|s| s.value.clone())
223 .unwrap_or_default(),
224 );
225 }
226 }
227
228 evaluated_keys
229}
230
231fn get_sdk_info() -> HashMap<String, String> {
232 let metadata = StatsigMetadata::get_metadata();
233 HashMap::from([
234 ("sdkType".to_string(), metadata.sdk_type),
235 ("sdkVersion".to_string(), metadata.sdk_version),
236 ("sessionId".to_string(), metadata.session_id),
237 ])
238}
239
240pub struct GCIRSessionReplayInfo {
241 pub can_record_session: Option<bool>,
242 pub session_recording_rate: Option<f64>,
243 pub recording_blocked: Option<bool>,
244 pub passes_session_recording_targeting: Option<bool>,
245 pub session_recording_event_triggers: Option<HashMap<String, SessionReplayTrigger>>,
246 pub session_recording_exposure_triggers: Option<HashMap<String, SessionReplayTrigger>>,
247}
248
249fn get_session_replay_info(
250 context: &mut EvaluatorContext,
251 options: &ClientInitResponseOptions,
252) -> GCIRSessionReplayInfo {
253 let mut session_replay_info = GCIRSessionReplayInfo {
254 can_record_session: None,
255 session_recording_rate: None,
256 recording_blocked: None,
257 passes_session_recording_targeting: None,
258 session_recording_event_triggers: None,
259 session_recording_exposure_triggers: None,
260 };
261
262 let session_replay_data = match &context.specs_data.session_replay_info {
263 Some(data) => data,
264 None => return session_replay_info,
265 };
266
267 session_replay_info.can_record_session = Some(true);
268 session_replay_info.recording_blocked = session_replay_data.recording_blocked;
269 if session_replay_data.recording_blocked == Some(true) {
270 session_replay_info.can_record_session = Some(false);
271 }
272
273 let targeting_gate_name = &session_replay_data.targeting_gate;
274
275 if let Some(gate_name) = targeting_gate_name {
276 match Evaluator::evaluate(context, gate_name.clone().as_str(), &SpecType::Gate) {
277 Ok(_result) => {
278 session_replay_info.passes_session_recording_targeting =
279 Some(context.result.bool_value);
280 if !context.result.bool_value {
281 session_replay_info.can_record_session = Some(false);
282 }
283 }
284 Err(_e) => {
285 session_replay_info.passes_session_recording_targeting = Some(false);
286 session_replay_info.can_record_session = Some(false);
287 }
288 }
289 }
290
291 let mut rng = rand::thread_rng();
292 let random: f64 = rng.gen::<f64>();
293
294 if let Some(rate) = &session_replay_data.sampling_rate {
295 session_replay_info.session_recording_rate = Some(*rate);
296 if random > *rate {
297 session_replay_info.can_record_session = Some(false);
298 }
299 }
300
301 if let Some(triggers) = &session_replay_data.session_recording_event_triggers {
302 let mut new_event_triggers = HashMap::new();
303 for (key, trigger) in triggers {
304 let mut new_trigger = SessionReplayTrigger {
305 values: trigger.values.clone(),
306 sampling_rate: None,
307 passes_sampling: None,
308 };
309 if let Some(rate) = &trigger.sampling_rate {
310 new_trigger.passes_sampling = Some(random <= *rate);
311 }
312 new_event_triggers.insert(key.clone(), new_trigger);
313 }
314 session_replay_info.session_recording_event_triggers = Some(new_event_triggers);
315 }
316
317 if let Some(triggers) = &session_replay_data.session_recording_exposure_triggers {
318 let mut new_exposure_triggers = HashMap::new();
319 for (key, trigger) in triggers {
320 let mut new_trigger = SessionReplayTrigger {
321 values: trigger.values.clone(),
322 sampling_rate: None,
323 passes_sampling: None,
324 };
325 if let Some(rate) = &trigger.sampling_rate {
326 new_trigger.passes_sampling = Some(random <= *rate);
327 }
328 new_exposure_triggers.insert(
329 context
330 .hashing
331 .hash(key.as_str(), options.get_hash_algorithm()),
332 new_trigger,
333 );
334 }
335 session_replay_info.session_recording_exposure_triggers = Some(new_exposure_triggers);
336 }
337
338 session_replay_info
339}