statsig_rust/gcir/
gcir_formatter.rs

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,                             // v1
37    InitializeWithSecondaryExposureMapping, // v2
38}
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}