1use serde::Deserialize;
2
3use crate::evaluation::dynamic_value::DynamicValue;
4use crate::evaluation::evaluation_types::{AnyConfigEvaluation, SecondaryExposure};
5use crate::evaluation::evaluator::{Evaluator, SpecType};
6use crate::evaluation::evaluator_context::EvaluatorContext;
7use crate::evaluation::evaluator_result::{
8 result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
9 result_to_layer_eval, EvaluatorResult,
10};
11use crate::hashing::{HashAlgorithm, HashUtil};
12use crate::initialize_response::InitializeResponse;
13use crate::spec_store::SpecStore;
14use crate::spec_types::{
15 DynamicConfigParameter, ExperimentParameter, GateParameter, LayerParameter, Parameter,
16 ParameterStore, Spec,
17};
18use crate::statsig_metadata::StatsigMetadata;
19use crate::statsig_user_internal::{StatsigUserInternal, StatsigUserLoggable};
20use crate::{read_lock_or_else, OverrideAdapter};
21use std::collections::HashMap;
22use std::sync::Arc;
23
24#[derive(Default, Deserialize)]
25pub struct ClientInitResponseOptions {
26 pub hash_algorithm: Option<HashAlgorithm>,
27 pub client_sdk_key: Option<String>,
28 pub include_local_overrides: Option<bool>,
29}
30
31pub struct ClientInitResponseFormatter {
32 spec_store: Arc<SpecStore>,
33 default_options: ClientInitResponseOptions,
34 override_adapter: Option<Arc<dyn OverrideAdapter>>,
35}
36
37impl ClientInitResponseFormatter {
38 pub fn new(
39 spec_store: &Arc<SpecStore>,
40 override_adapter: &Option<Arc<dyn OverrideAdapter>>,
41 ) -> Self {
42 Self {
43 spec_store: spec_store.clone(),
44 override_adapter: override_adapter.as_ref().map(Arc::clone),
45 default_options: ClientInitResponseOptions {
46 hash_algorithm: Some(HashAlgorithm::Djb2),
47 client_sdk_key: None,
48 include_local_overrides: Some(false),
49 },
50 }
51 }
52
53 pub fn get_default_options(&self) -> &ClientInitResponseOptions {
54 &self.default_options
55 }
56
57 pub fn get(
58 &self,
59 user_internal: StatsigUserInternal,
60 hashing: &HashUtil,
61 options: &ClientInitResponseOptions,
62 ) -> InitializeResponse {
63 let data = read_lock_or_else!(self.spec_store.data, {
64 return InitializeResponse::blank(user_internal);
65 });
66
67 let mut app_id = data.values.app_id.as_ref();
68
69 if let Some(client_sdk_key) = &options.client_sdk_key {
70 if let Some(app_id_value) = &data.values.sdk_keys_to_app_ids {
71 app_id = app_id_value.get(client_sdk_key);
72 }
73 if let Some(app_id_value) = &data.values.hashed_sdk_keys_to_app_ids {
74 let hashed_key = &hashing.hash(client_sdk_key, &HashAlgorithm::Djb2);
75 app_id = app_id_value.get(hashed_key);
76 }
77 }
78 let include_local_overrides = options.include_local_overrides.unwrap_or(false);
79 let mut feature_gates = HashMap::new();
80 let mut context = EvaluatorContext::new(
81 &user_internal,
82 &data,
83 hashing,
84 &app_id,
85 if include_local_overrides {
86 &self.override_adapter
87 } else {
88 &None
89 },
90 );
91
92 let hash_used = options
93 .hash_algorithm
94 .as_ref()
95 .unwrap_or(&HashAlgorithm::Djb2);
96
97 for (name, spec) in &data.values.feature_gates {
98 if spec.entity == "segment" || spec.entity == "holdout" {
99 continue;
100 }
101
102 if should_filter_spec_for_app(spec, &app_id, &options.client_sdk_key) {
103 continue;
104 }
105
106 context.reset_result();
107 if let Err(_err) = Evaluator::evaluate(&mut context, name, &SpecType::Gate) {
108 return InitializeResponse::blank(user_internal);
109 }
110
111 let hashed_name = context.hashing.hash(name, hash_used);
112 hash_secondary_exposures(&mut context.result, hashing, hash_used);
113
114 let eval = result_to_gate_eval(&hashed_name, &mut context.result);
115 feature_gates.insert(hashed_name, eval);
116 }
117
118 let mut dynamic_configs = HashMap::new();
119 for (name, spec) in &data.values.dynamic_configs {
120 if should_filter_spec_for_app(spec, &app_id, &options.client_sdk_key) {
121 continue;
122 }
123
124 context.reset_result();
125 let spec_type = if spec.entity == "dynamic_config" {
126 &SpecType::DynamicConfig
127 } else {
128 &SpecType::Experiment
129 };
130 if let Err(_err) = Evaluator::evaluate(&mut context, name, spec_type) {
131 return InitializeResponse::blank(user_internal);
132 }
133
134 let hashed_name = context.hashing.hash(name, hash_used);
135 hash_secondary_exposures(&mut context.result, hashing, hash_used);
136
137 if spec.entity == "dynamic_config" {
138 let evaluation = result_to_dynamic_config_eval(&hashed_name, &mut context.result);
139 dynamic_configs.insert(hashed_name, AnyConfigEvaluation::DynamicConfig(evaluation));
140 } else {
141 let evaluation =
142 result_to_experiment_eval(&hashed_name, Some(spec), &mut context.result);
143 dynamic_configs.insert(hashed_name, AnyConfigEvaluation::Experiment(evaluation));
144 }
145 }
146
147 let mut layer_configs = HashMap::new();
148 for (name, spec) in &data.values.layer_configs {
149 if should_filter_spec_for_app(spec, &app_id, &options.client_sdk_key) {
150 continue;
151 }
152
153 context.reset_result();
154 if let Err(_err) = Evaluator::evaluate(&mut context, name, &SpecType::Layer) {
155 return InitializeResponse::blank(user_internal);
156 }
157
158 let hashed_name = context.hashing.hash(name, hash_used);
159 hash_secondary_exposures(&mut context.result, hashing, hash_used);
160
161 let mut evaluation = result_to_layer_eval(&hashed_name, &mut context.result);
162
163 if let Some(allocated_experiment_name) = evaluation.allocated_experiment_name {
164 evaluation.allocated_experiment_name =
165 Some(context.hashing.hash(&allocated_experiment_name, hash_used));
166 }
167
168 layer_configs.insert(hashed_name, evaluation);
169 }
170
171 let mut param_stores = HashMap::new();
172 let default_store = HashMap::new();
173 let stores = match &data.values.param_stores {
174 Some(stores) => stores,
175 None => &default_store,
176 };
177 for (name, store) in stores {
178 if should_filter_config_for_app(&store.target_app_ids, &app_id, &options.client_sdk_key)
179 {
180 continue;
181 }
182
183 let hashed_name = context.hashing.hash(name, hash_used);
184 let parameters = get_parameters_from_store(store, hash_used, &context);
185 param_stores.insert(hashed_name, parameters);
186 }
187
188 let evaluated_keys = get_evaluated_keys(&user_internal);
189 let metadata = StatsigMetadata::get_metadata();
190 InitializeResponse {
191 feature_gates,
192 dynamic_configs,
193 layer_configs,
194 time: data.values.time,
195 has_updates: true,
196 hash_used: hash_used.to_string(),
197 user: StatsigUserLoggable::new(user_internal),
198 sdk_params: HashMap::new(),
199 evaluated_keys,
200 sdk_info: HashMap::from([
201 ("sdkType".to_string(), metadata.sdk_type),
202 ("sdkVersion".to_string(), metadata.sdk_version),
203 ]),
204 param_stores,
205 }
206 }
207}
208
209fn get_parameters_from_store(
210 store: &ParameterStore,
211 hash_used: &HashAlgorithm,
212 context: &EvaluatorContext,
213) -> HashMap<String, Parameter> {
214 let mut parameters = HashMap::new();
215 for (param_name, param) in &store.parameters {
216 match param {
217 Parameter::StaticValue(_static_value) => {
218 parameters.insert(param_name.clone(), param.clone());
219 }
220 Parameter::Gate(gate) => {
221 let new_param = GateParameter {
222 ref_type: gate.ref_type.clone(),
223 param_type: gate.param_type.clone(),
224 gate_name: context.hashing.hash(&gate.gate_name, hash_used),
225 pass_value: gate.pass_value.clone(),
226 fail_value: gate.fail_value.clone(),
227 };
228 parameters.insert(param_name.clone(), Parameter::Gate(new_param));
229 }
230 Parameter::DynamicConfig(dynamic_config) => {
231 let new_param = DynamicConfigParameter {
232 ref_type: dynamic_config.ref_type.clone(),
233 param_type: dynamic_config.param_type.clone(),
234 config_name: context.hashing.hash(&dynamic_config.config_name, hash_used),
235 param_name: dynamic_config.param_name.clone(),
236 };
237 parameters.insert(param_name.clone(), Parameter::DynamicConfig(new_param));
238 }
239 Parameter::Experiment(experiment) => {
240 let new_param = ExperimentParameter {
241 ref_type: experiment.ref_type.clone(),
242 param_type: experiment.param_type.clone(),
243 experiment_name: context.hashing.hash(&experiment.experiment_name, hash_used),
244 param_name: experiment.param_name.clone(),
245 };
246 parameters.insert(param_name.clone(), Parameter::Experiment(new_param));
247 }
248 Parameter::Layer(layer) => {
249 let new_param = LayerParameter {
250 ref_type: layer.ref_type.clone(),
251 param_type: layer.param_type.clone(),
252 layer_name: context.hashing.hash(&layer.layer_name, hash_used),
253 param_name: layer.param_name.clone(),
254 };
255 parameters.insert(param_name.clone(), Parameter::Layer(new_param));
256 }
257 }
258 }
259 parameters
260}
261
262fn should_filter_spec_for_app(
263 spec: &Spec,
264 app_id: &Option<&DynamicValue>,
265 client_sdk_key: &Option<String>,
266) -> bool {
267 should_filter_config_for_app(&spec.target_app_ids, app_id, client_sdk_key)
268}
269
270fn should_filter_config_for_app(
271 target_app_ids: &Option<Vec<String>>,
272 app_id: &Option<&DynamicValue>,
273 client_sdk_key: &Option<String>,
274) -> bool {
275 let _client_sdk_key = match client_sdk_key {
276 Some(client_sdk_key) => client_sdk_key,
277 None => return false,
278 };
279
280 let app_id = match app_id {
281 Some(app_id) => app_id,
282 None => return false,
283 };
284
285 let string_app_id = match app_id.string_value.as_ref() {
286 Some(string_app_id) => string_app_id,
287 None => return false,
288 };
289
290 let target_app_ids = match target_app_ids {
291 Some(target_app_ids) => target_app_ids,
292 None => return true,
293 };
294
295 if !target_app_ids.contains(string_app_id) {
296 return true;
297 }
298 false
299}
300
301fn get_evaluated_keys(user_internal: &StatsigUserInternal) -> HashMap<String, String> {
302 let mut evaluated_keys = HashMap::new();
303
304 if let Some(user_id) = user_internal.user_data.user_id.as_ref() {
305 evaluated_keys.insert(
306 "userID".to_string(),
307 user_id.string_value.clone().unwrap_or_default(),
308 );
309 }
310
311 if let Some(custom_ids) = user_internal.user_data.custom_ids.as_ref() {
312 for (key, value) in custom_ids {
313 evaluated_keys.insert(key.clone(), value.string_value.clone().unwrap_or_default());
314 }
315 }
316
317 evaluated_keys
318}
319
320fn hash_secondary_exposures(
321 result: &mut EvaluatorResult,
322 hashing: &HashUtil,
323 hash_algorithm: &HashAlgorithm,
324) {
325 fn loop_and_hash(
326 exposures: &[SecondaryExposure],
327 hashing: &HashUtil,
328 hash_algorithm: &HashAlgorithm,
329 ) -> Vec<SecondaryExposure> {
330 exposures
331 .iter()
332 .map(|exposure| {
333 let hashed_gate = hashing.hash(&exposure.gate, hash_algorithm);
334 SecondaryExposure {
335 gate: hashed_gate,
336 ..exposure.clone()
337 }
338 })
339 .collect()
340 }
341
342 if !result.secondary_exposures.is_empty() {
343 result.secondary_exposures =
344 loop_and_hash(&result.secondary_exposures, hashing, hash_algorithm);
345 }
346
347 if let Some(undelegated_secondary_exposures) = result.undelegated_secondary_exposures.as_ref() {
348 result.undelegated_secondary_exposures = Some(loop_and_hash(
349 undelegated_secondary_exposures,
350 hashing,
351 hash_algorithm,
352 ));
353 }
354}