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 InitializeV1,
37 Evaluations, }
39
40const TAG: &str = "GCIRFormatter";
41
42impl GCIRFormatter {
43 pub fn new(
44 spec_store: &Arc<SpecStore>,
45 override_adapter: &Option<Arc<dyn OverrideAdapter>>,
46 ) -> Self {
47 Self {
48 spec_store: spec_store.clone(),
49 override_adapter: override_adapter.as_ref().map(Arc::clone),
50 default_options: ClientInitResponseOptions {
51 hash_algorithm: Some(HashAlgorithm::Djb2),
52 client_sdk_key: None,
53 include_local_overrides: Some(false),
54 response_format: None,
55 },
56 }
57 }
58
59 pub fn get_default_options(&self) -> &ClientInitResponseOptions {
60 &self.default_options
61 }
62
63 pub fn get_as_v1_format(
64 &self,
65 user_internal: StatsigUserInternal,
66 hashing: &HashUtil,
67 options: &ClientInitResponseOptions,
68 ) -> InitializeResponse {
69 self.get_v1_impl(&user_internal, hashing, options)
70 .unwrap_or_else(|e| {
71 log_e!(TAG, "Error getting client init response: {}", e);
72 InitializeResponse::blank(user_internal)
73 })
74 }
75
76 pub fn get_as_v2_format(
77 &self,
78 user_internal: StatsigUserInternal,
79 hashing: &HashUtil,
80 options: &ClientInitResponseOptions,
81 ) -> InitializeEvaluationsResponse {
82 self.get_v2_impl(&user_internal, hashing, options)
83 .unwrap_or_else(|e| {
84 log_e!(TAG, "Error getting client init evaluations response: {}", e);
85 InitializeEvaluationsResponse::blank(user_internal)
86 })
87 }
88
89 fn get_v2_impl(
90 &self,
91 user_internal: &StatsigUserInternal,
92 hashing: &HashUtil,
93 options: &ClientInitResponseOptions,
94 ) -> Result<InitializeEvaluationsResponse, StatsigErr> {
95 let data = read_lock_or_else!(self.spec_store.data, {
96 return Err(StatsigErr::LockFailure(
97 "Failed to acquire read lock for spec store data".to_string(),
98 ));
99 });
100
101 let mut sec_expo_hash_memo = HashMap::new();
102 let mut context = self.setup_evaluator_context(user_internal, &data, options, hashing);
103 let mut exposures = HashMap::new();
104
105 let param_stores = get_serializeable_param_stores(&mut context, options);
106 let evaluated_keys = get_evaluated_keys(user_internal);
107
108 Ok(InitializeEvaluationsResponse {
109 feature_gates: get_gate_evaluations_v2(
110 &mut context,
111 options,
112 &mut sec_expo_hash_memo,
113 &mut exposures,
114 )?,
115 dynamic_configs: get_dynamic_config_evaluations_v2(
116 &mut context,
117 options,
118 &mut sec_expo_hash_memo,
119 &mut exposures,
120 )?,
121 layer_configs: get_layer_evaluations_v2(
122 &mut context,
123 options,
124 &mut sec_expo_hash_memo,
125 &mut exposures,
126 )?,
127 time: data.values.time,
128 has_updates: true,
129 hash_used: options.get_hash_algorithm().to_string(),
130 user: user_internal.to_loggable(),
131 sdk_params: HashMap::new(),
132 evaluated_keys,
133 sdk_info: get_sdk_info(),
134 param_stores,
135 exposures,
136 })
137 }
138
139 fn get_v1_impl(
140 &self,
141 user_internal: &StatsigUserInternal,
142 hashing: &HashUtil,
143 options: &ClientInitResponseOptions,
144 ) -> Result<InitializeResponse, StatsigErr> {
145 let data = read_lock_or_else!(self.spec_store.data, {
146 return Err(StatsigErr::LockFailure(
147 "Failed to acquire read lock for spec store data".to_string(),
148 ));
149 });
150
151 let mut sec_expo_hash_memo = HashMap::new();
152 let mut context = self.setup_evaluator_context(user_internal, &data, options, hashing);
153
154 let param_stores = get_serializeable_param_stores(&mut context, options);
155 let evaluated_keys = get_evaluated_keys(user_internal);
156
157 Ok(InitializeResponse {
158 feature_gates: get_gate_evaluations(&mut context, options, &mut sec_expo_hash_memo)?,
159 dynamic_configs: get_dynamic_config_evaluations(
160 &mut context,
161 options,
162 &mut sec_expo_hash_memo,
163 )?,
164 layer_configs: get_layer_evaluations(&mut context, options, &mut sec_expo_hash_memo)?,
165 time: data.values.time,
166 has_updates: true,
167 hash_used: options.get_hash_algorithm().to_string(),
168 user: user_internal.to_loggable(),
169 sdk_params: HashMap::new(),
170 evaluated_keys,
171 sdk_info: get_sdk_info(),
172 param_stores,
173 })
174 }
175
176 fn setup_evaluator_context<'a>(
177 &'a self,
178 user_internal: &'a StatsigUserInternal,
179 data: &'a SpecStoreData,
180 options: &'a ClientInitResponseOptions,
181 hashing: &'a HashUtil,
182 ) -> EvaluatorContext<'a> {
183 let app_id = select_app_id(options, &data.values, hashing);
184
185 let override_adapter = match options.include_local_overrides {
186 Some(true) => self.override_adapter.as_ref(),
187 _ => None,
188 };
189
190 EvaluatorContext::new(user_internal, data, hashing, app_id, override_adapter)
191 }
192}
193
194fn get_evaluated_keys(user_internal: &StatsigUserInternal) -> HashMap<String, String> {
195 let mut evaluated_keys = HashMap::new();
196
197 if let Some(user_id) = user_internal.user_data.user_id.as_ref() {
198 evaluated_keys.insert(
199 "userID".to_string(),
200 user_id
201 .string_value
202 .as_ref()
203 .map(|s| s.value.clone())
204 .unwrap_or_default(),
205 );
206 }
207
208 if let Some(custom_ids) = user_internal.user_data.custom_ids.as_ref() {
209 for (key, value) in custom_ids {
210 evaluated_keys.insert(
211 key.clone(),
212 value
213 .string_value
214 .as_ref()
215 .map(|s| s.value.clone())
216 .unwrap_or_default(),
217 );
218 }
219 }
220
221 evaluated_keys
222}
223
224fn get_sdk_info() -> HashMap<String, String> {
225 let metadata = StatsigMetadata::get_metadata();
226 HashMap::from([
227 ("sdkType".to_string(), metadata.sdk_type),
228 ("sdkVersion".to_string(), metadata.sdk_version),
229 ])
230}