1use crate::gcir::feature_gates_processor::get_gate_evaluations;
2
3use crate::{
4 evaluation::evaluator_context::EvaluatorContext,
5 hashing::{HashAlgorithm, HashUtil},
6 initialize_evaluations_response::InitializeEvaluationsResponse,
7 initialize_response::InitializeResponse,
8 log_e, read_lock_or_else,
9 spec_store::{SpecStore, SpecStoreData},
10 statsig_metadata::StatsigMetadata,
11 user::StatsigUserInternal,
12 OverrideAdapter, StatsigErr,
13};
14
15use serde::Deserialize;
16use std::collections::HashMap;
17use std::sync::Arc;
18
19use super::dynamic_configs_processor::{
20 get_dynamic_config_evaluations, get_dynamic_config_evaluations_v2,
21};
22use super::feature_gates_processor::get_gate_evaluations_v2;
23use super::gcir_options::ClientInitResponseOptions;
24use super::layer_configs_processor::{get_layer_evaluations, get_layer_evaluations_v2};
25use super::param_stores_processor::get_serializeable_param_stores;
26use super::target_app_id_utils::select_app_id;
27
28pub struct GCIRFormatter {
29 spec_store: Arc<SpecStore>,
30 default_options: ClientInitResponseOptions,
31 override_adapter: Option<Arc<dyn OverrideAdapter>>,
32}
33
34#[derive(Deserialize)]
35pub enum GCIRResponseFormat {
36 Initialize, InitializeWithSecondaryExposureMapping, }
39
40impl GCIRResponseFormat {
41 #[must_use]
42 pub fn from_string(input: &str) -> Option<Self> {
43 match input {
44 "v1" => Some(GCIRResponseFormat::Initialize),
45 "v2" => Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping),
46 _ => None,
47 }
48 }
49}
50
51const TAG: &str = "GCIRFormatter";
52
53impl GCIRFormatter {
54 pub fn new(
55 spec_store: &Arc<SpecStore>,
56 override_adapter: &Option<Arc<dyn OverrideAdapter>>,
57 ) -> Self {
58 Self {
59 spec_store: spec_store.clone(),
60 override_adapter: override_adapter.as_ref().map(Arc::clone),
61 default_options: ClientInitResponseOptions {
62 hash_algorithm: Some(HashAlgorithm::Djb2),
63 client_sdk_key: None,
64 include_local_overrides: Some(false),
65 response_format: None,
66 },
67 }
68 }
69
70 pub fn get_default_options(&self) -> &ClientInitResponseOptions {
71 &self.default_options
72 }
73
74 pub fn get_as_v1_format(
75 &self,
76 user_internal: StatsigUserInternal,
77 hashing: &HashUtil,
78 options: &ClientInitResponseOptions,
79 ) -> InitializeResponse {
80 self.get_v1_impl(&user_internal, hashing, options)
81 .unwrap_or_else(|e| {
82 log_e!(TAG, "Error getting client init response: {}", e);
83 InitializeResponse::blank(user_internal)
84 })
85 }
86
87 pub fn get_as_v2_format(
88 &self,
89 user_internal: StatsigUserInternal,
90 hashing: &HashUtil,
91 options: &ClientInitResponseOptions,
92 ) -> InitializeEvaluationsResponse {
93 self.get_v2_impl(&user_internal, hashing, options)
94 .unwrap_or_else(|e| {
95 log_e!(TAG, "Error getting client init evaluations response: {}", e);
96 InitializeEvaluationsResponse::blank(user_internal)
97 })
98 }
99
100 fn get_v2_impl(
101 &self,
102 user_internal: &StatsigUserInternal,
103 hashing: &HashUtil,
104 options: &ClientInitResponseOptions,
105 ) -> Result<InitializeEvaluationsResponse, StatsigErr> {
106 let data = read_lock_or_else!(self.spec_store.data, {
107 return Err(StatsigErr::LockFailure(
108 "Failed to acquire read lock for spec store data".to_string(),
109 ));
110 });
111
112 let mut sec_expo_hash_memo = HashMap::new();
113 let mut context = self.setup_evaluator_context(user_internal, &data, options, hashing);
114 let mut exposures = HashMap::new();
115
116 let param_stores = get_serializeable_param_stores(&mut context, options);
117 let evaluated_keys = get_evaluated_keys(user_internal);
118
119 Ok(InitializeEvaluationsResponse {
120 feature_gates: get_gate_evaluations_v2(
121 &mut context,
122 options,
123 &mut sec_expo_hash_memo,
124 &mut exposures,
125 )?,
126 dynamic_configs: get_dynamic_config_evaluations_v2(
127 &mut context,
128 options,
129 &mut sec_expo_hash_memo,
130 &mut exposures,
131 )?,
132 layer_configs: get_layer_evaluations_v2(
133 &mut context,
134 options,
135 &mut sec_expo_hash_memo,
136 &mut exposures,
137 )?,
138 time: data.values.time,
139 has_updates: true,
140 hash_used: options.get_hash_algorithm().to_string(),
141 user: user_internal.to_loggable(),
142 sdk_params: HashMap::new(),
143 evaluated_keys,
144 sdk_info: get_sdk_info(),
145 param_stores,
146 exposures,
147 })
148 }
149
150 fn get_v1_impl(
151 &self,
152 user_internal: &StatsigUserInternal,
153 hashing: &HashUtil,
154 options: &ClientInitResponseOptions,
155 ) -> Result<InitializeResponse, StatsigErr> {
156 let data = read_lock_or_else!(self.spec_store.data, {
157 return Err(StatsigErr::LockFailure(
158 "Failed to acquire read lock for spec store data".to_string(),
159 ));
160 });
161
162 let mut sec_expo_hash_memo = HashMap::new();
163 let mut context = self.setup_evaluator_context(user_internal, &data, options, hashing);
164
165 let param_stores = get_serializeable_param_stores(&mut context, options);
166 let evaluated_keys = get_evaluated_keys(user_internal);
167
168 Ok(InitializeResponse {
169 feature_gates: get_gate_evaluations(&mut context, options, &mut sec_expo_hash_memo)?,
170 dynamic_configs: get_dynamic_config_evaluations(
171 &mut context,
172 options,
173 &mut sec_expo_hash_memo,
174 )?,
175 layer_configs: get_layer_evaluations(&mut context, options, &mut sec_expo_hash_memo)?,
176 time: data.values.time,
177 has_updates: true,
178 hash_used: options.get_hash_algorithm().to_string(),
179 user: user_internal.to_loggable(),
180 sdk_params: HashMap::new(),
181 evaluated_keys,
182 sdk_info: get_sdk_info(),
183 param_stores,
184 })
185 }
186
187 fn setup_evaluator_context<'a>(
188 &'a self,
189 user_internal: &'a StatsigUserInternal,
190 data: &'a SpecStoreData,
191 options: &'a ClientInitResponseOptions,
192 hashing: &'a HashUtil,
193 ) -> EvaluatorContext<'a> {
194 let app_id = select_app_id(options, &data.values, hashing);
195
196 let override_adapter = match options.include_local_overrides {
197 Some(true) => self.override_adapter.as_ref(),
198 _ => None,
199 };
200
201 EvaluatorContext::new(user_internal, data, hashing, app_id, override_adapter)
202 }
203}
204
205fn get_evaluated_keys(user_internal: &StatsigUserInternal) -> HashMap<String, String> {
206 let mut evaluated_keys = HashMap::new();
207
208 if let Some(user_id) = user_internal.user_ref.data.user_id.as_ref() {
209 evaluated_keys.insert(
210 "userID".to_string(),
211 user_id
212 .string_value
213 .as_ref()
214 .map(|s| s.value.clone())
215 .unwrap_or_default(),
216 );
217 }
218
219 if let Some(custom_ids) = user_internal.user_ref.data.custom_ids.as_ref() {
220 for (key, value) in custom_ids {
221 evaluated_keys.insert(
222 key.clone(),
223 value
224 .string_value
225 .as_ref()
226 .map(|s| s.value.clone())
227 .unwrap_or_default(),
228 );
229 }
230 }
231
232 evaluated_keys
233}
234
235fn get_sdk_info() -> HashMap<String, String> {
236 let metadata = StatsigMetadata::get_metadata();
237 HashMap::from([
238 ("sdkType".to_string(), metadata.sdk_type),
239 ("sdkVersion".to_string(), metadata.sdk_version),
240 ])
241}