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    InitializeV1,
37    Evaluations, // v2
38}
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}