statsig_rust/
statsig.rs

1use crate::client_init_response_formatter::{
2    ClientInitResponseFormatter, ClientInitResponseOptions,
3};
4use crate::evaluation::cmab_evaluator::{get_cmab_ranked_list, CMABRankedGroup};
5use crate::evaluation::country_lookup::CountryLookup;
6use crate::evaluation::dynamic_value::DynamicValue;
7use crate::evaluation::evaluation_details::EvaluationDetails;
8use crate::evaluation::evaluation_types::{AnyEvaluation, BaseEvaluation, ExperimentEvaluation};
9use crate::evaluation::evaluator::{Evaluator, SpecType};
10use crate::evaluation::evaluator_context::EvaluatorContext;
11use crate::evaluation::evaluator_result::{
12    result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
13    result_to_layer_eval, EvaluatorResult,
14};
15use crate::evaluation::ua_parser::UserAgentParser;
16use crate::event_logging::config_exposure::ConfigExposure;
17use crate::event_logging::event_logger::{EventLogger, QueuedEventPayload};
18use crate::event_logging::gate_exposure::GateExposure;
19use crate::event_logging::layer_exposure::LayerExposure;
20use crate::event_logging::statsig_event::StatsigEvent;
21use crate::event_logging::statsig_event_internal::make_custom_event;
22use crate::event_logging_adapter::EventLoggingAdapter;
23use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
24use crate::hashing::HashUtil;
25use crate::initialize_response::InitializeResponse;
26use crate::observability::diagnostics_observer::DiagnosticsObserver;
27use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
28use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
29use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
30use crate::output_logger::initialize_simple_output_logger;
31use crate::sdk_diagnostics::diagnostics::Diagnostics;
32use crate::spec_store::{SpecStore, SpecStoreData};
33use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
34use crate::statsig_err::StatsigErr;
35use crate::statsig_metadata::StatsigMetadata;
36use crate::statsig_options::StatsigOptions;
37use crate::statsig_runtime::StatsigRuntime;
38use crate::statsig_type_factories::{
39    make_dynamic_config, make_experiment, make_feature_gate, make_layer,
40};
41use crate::statsig_types::{
42    DynamicConfig, Experiment, FeatureGate, Layer, OverrideAdapterType, ParameterStore,
43};
44use crate::statsig_user_internal::StatsigUserInternal;
45use crate::{
46    dyn_value, log_d, log_e, log_w, read_lock_or_else, IdListsAdapter, ObservabilityClient,
47    OpsStatsEventObserver, OverrideAdapter, SamplingProcessor, SpecsAdapter, SpecsInfo,
48    SpecsSource, SpecsUpdateListener, StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter,
49    StatsigUser,
50};
51use crate::{
52    log_error_to_statsig_and_console,
53    statsig_core_api_options::{
54        DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
55        LayerEvaluationOptions,
56    },
57};
58use serde::de::DeserializeOwned;
59use serde_json::json;
60use serde_json::Value;
61use std::collections::HashMap;
62use std::sync::Mutex;
63use std::sync::{Arc, Weak};
64use std::time::{Duration, Instant};
65use tokio::try_join;
66
67const TAG: &str = stringify!(Statsig);
68
69pub struct Statsig {
70    pub statsig_runtime: Arc<StatsigRuntime>,
71
72    sdk_key: String,
73    options: Arc<StatsigOptions>,
74    event_logger: Arc<EventLogger>,
75    specs_adapter: Arc<dyn SpecsAdapter>,
76    event_logging_adapter: Arc<dyn EventLoggingAdapter>,
77    id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
78    override_adapter: Option<Arc<dyn OverrideAdapter>>,
79    spec_store: Arc<SpecStore>,
80    hashing: Arc<HashUtil>,
81    gcir_formatter: Arc<ClientInitResponseFormatter>,
82    statsig_environment: Option<HashMap<String, DynamicValue>>,
83    fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
84    diagnostics: Arc<Diagnostics>,
85    ops_stats: Arc<OpsStatsForInstance>,
86    error_observer: Arc<dyn OpsStatsEventObserver>,
87    sampling_processor: Arc<SamplingProcessor>,
88}
89
90pub struct StatsigContext {
91    pub sdk_key: String,
92    pub options: Arc<StatsigOptions>,
93    pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
94    pub spec_store_data: Option<SpecStoreData>,
95    pub error_observer: Arc<dyn OpsStatsEventObserver>,
96}
97#[derive(Debug)]
98pub struct FailureDetails {
99    pub reason: String,
100    pub error: Option<StatsigErr>,
101}
102#[derive(Debug)]
103pub struct StatsigInitializeDetails {
104    pub duration: f64,
105    pub init_success: bool,
106    pub is_config_spec_ready: bool,
107    pub source: SpecsSource,
108    pub failure_details: Option<FailureDetails>,
109}
110
111impl Statsig {
112    pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
113        let statsig_runtime = StatsigRuntime::get_runtime();
114        let options = options.unwrap_or_default();
115
116        let hashing = Arc::new(HashUtil::new());
117
118        let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
119        let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
120        let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
121        let override_adapter = match options.override_adapter.as_ref() {
122            Some(adapter) => Some(Arc::clone(adapter)),
123            None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
124        };
125
126        let event_logger = Arc::new(EventLogger::new(
127            sdk_key,
128            event_logging_adapter.clone(),
129            &options,
130            &statsig_runtime,
131        ));
132
133        let diagnostics = Arc::new(Diagnostics::new(event_logger.clone()));
134        let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
135            Arc::new(DiagnosticsObserver::new(diagnostics.clone()));
136        let error_observer: Arc<dyn OpsStatsEventObserver> = Arc::new(SDKErrorsObserver::new(
137            sdk_key,
138            serde_json::to_string(options.as_ref()).unwrap_or_default(),
139        ));
140
141        let ops_stats = setup_ops_stats(
142            sdk_key,
143            &options,
144            statsig_runtime.clone(),
145            &error_observer,
146            &diagnostics_observer,
147            &options.observability_client,
148        );
149
150        let spec_store = Arc::new(SpecStore::new(
151            sdk_key,
152            hashing.sha256(&sdk_key.to_string()),
153            options.data_store.clone(),
154            Some(statsig_runtime.clone()),
155            Some(diagnostics.clone()),
156        ));
157
158        if options.enable_user_agent_parsing.unwrap_or(false) {
159            UserAgentParser::load_parser();
160        }
161
162        if options.enable_country_lookup.unwrap_or(false) {
163            CountryLookup::load_country_lookup();
164        }
165
166        let environment = options
167            .environment
168            .as_ref()
169            .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
170
171        let sampling_processor = Arc::new(SamplingProcessor::new(
172            &statsig_runtime,
173            &spec_store,
174            hashing.clone(),
175        ));
176
177        StatsigMetadata::update_service_name(options.service_name.clone());
178
179        Statsig {
180            sdk_key: sdk_key.to_string(),
181            options,
182            gcir_formatter: Arc::new(ClientInitResponseFormatter::new(
183                &spec_store,
184                &override_adapter,
185            )),
186            event_logger,
187            hashing,
188            statsig_environment: environment,
189            fallback_environment: Mutex::new(None),
190            override_adapter,
191            spec_store,
192            specs_adapter,
193            event_logging_adapter,
194            id_lists_adapter,
195            diagnostics,
196            statsig_runtime,
197            ops_stats,
198            error_observer,
199            sampling_processor,
200        }
201    }
202
203    pub async fn initialize(&self) -> Result<(), StatsigErr> {
204        self.initialize_with_details().await.map(|_| ())
205    }
206
207    pub async fn initialize_with_details(&self) -> Result<StatsigInitializeDetails, StatsigErr> {
208        let start_time = Instant::now();
209        self.diagnostics.mark_init_overall_start();
210        self.spec_store.set_source(SpecsSource::Loading);
211        self.event_logger
212            .clone()
213            .start_background_task(&self.statsig_runtime);
214
215        self.specs_adapter.initialize(self.spec_store.clone());
216
217        let mut success = true;
218        let mut error_message = None;
219
220        let init_res = match self
221            .specs_adapter
222            .clone()
223            .start(&self.statsig_runtime)
224            .await
225        {
226            Ok(()) => Ok(()),
227            Err(e) => {
228                success = false;
229                self.spec_store.set_source(SpecsSource::NoValues);
230                error_message = Some(format!("Failed to start specs adapter: {e}"));
231                Err(e)
232            }
233        };
234
235        if let Some(adapter) = &self.id_lists_adapter {
236            match adapter
237                .clone()
238                .start(&self.statsig_runtime, self.spec_store.clone())
239                .await
240            {
241                Ok(()) => {}
242                Err(e) => {
243                    success = false;
244                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
245                }
246            }
247            if let Err(e) = adapter
248                .clone()
249                .schedule_background_sync(&self.statsig_runtime)
250                .await
251            {
252                log_w!(TAG, "Failed to schedule idlist background job {}", e);
253            }
254        }
255
256        self.event_logging_adapter
257            .clone()
258            .start(&self.statsig_runtime)
259            .await
260            .map_err(|e| {
261                success = false;
262                log_error_to_statsig_and_console!(
263                    self.ops_stats.clone(),
264                    TAG,
265                    "Failed to start event logging adaper {}",
266                    e
267                );
268                e
269            })?;
270
271        if let Err(e) = self
272            .specs_adapter
273            .clone()
274            .schedule_background_sync(&self.statsig_runtime)
275            .await
276        {
277            log_error_to_statsig_and_console!(
278                self.ops_stats,
279                TAG,
280                "Failed to schedule SpecAdapter({}) background job. Error: {}",
281                self.specs_adapter.get_type_name(),
282                e,
283            );
284        }
285
286        let spec_info = self.spec_store.get_current_specs_info();
287        let duration = start_time.elapsed().as_millis() as f64;
288
289        self.set_default_environment_from_server();
290        self.log_init_finish(success, &error_message, &duration, &spec_info);
291        let error = init_res.clone().err();
292        if let Some(ref e) = error {
293            log_error_to_statsig_and_console!(
294                self.ops_stats,
295                TAG,
296                "{}",
297                error_message.clone().unwrap_or(e.to_string())
298            );
299        }
300
301        Ok(StatsigInitializeDetails {
302            init_success: success,
303            is_config_spec_ready: spec_info.lcut.is_some(),
304            source: spec_info.source,
305            failure_details: error.as_ref().map(|e| FailureDetails {
306                reason: e.to_string(),
307                error: Some(e.clone()),
308            }),
309            duration,
310        })
311    }
312
313    pub async fn shutdown(&self) -> Result<(), StatsigErr> {
314        self.shutdown_with_timeout(Duration::from_secs(3)).await
315    }
316
317    pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
318        log_d!(
319            TAG,
320            "Shutting down Statsig with timeout {}ms",
321            timeout.as_millis()
322        );
323
324        let start = Instant::now();
325        let final_result = self.__shutdown_internal(timeout).await;
326        self.finalize_shutdown(timeout.saturating_sub(start.elapsed()));
327        final_result
328    }
329
330    pub async fn __shutdown_internal(&self, timeout: Duration) -> Result<(), StatsigErr> {
331        log_d!(
332            TAG,
333            "Shutting down Statsig with timeout {}ms",
334            timeout.as_millis()
335        );
336
337        let start = Instant::now();
338        let shutdown_result = tokio::select! {
339            () = tokio::time::sleep(timeout) => {
340                log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
341                Err(StatsigErr::ShutdownTimeout)
342            }
343            sub_result = async {
344                let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter {
345                    adapter.shutdown(timeout)
346                } else {
347                    Box::pin(async { Ok(()) })
348                };
349
350                try_join!(
351                    id_list_shutdown,
352                    self.event_logger.shutdown(timeout),
353                    self.specs_adapter.shutdown(timeout, &self.statsig_runtime),
354                )
355            } => {
356                match sub_result {
357                    Ok(_) => {
358                        log_d!(TAG, "All Statsig tasks shutdown successfully");
359                        Ok(())
360                    }
361                    Err(e) => {
362                        log_w!(TAG, "Error during shutdown: {:?}", e);
363                        Err(e)
364                    }
365                }
366            }
367        };
368
369        shutdown_result
370    }
371
372    pub fn sequenced_shutdown_prepare<F>(&self, callback: F)
373    where
374        F: FnOnce() + Send + 'static,
375    {
376        let event_logger = self.event_logger.clone();
377        let specs_adapter = self.specs_adapter.clone();
378        let runtime: Arc<StatsigRuntime> = self.statsig_runtime.clone();
379
380        self.statsig_runtime
381            .spawn("sequenced_shutdown_prep", |_shutdown_notify| async move {
382                let timeout = Duration::from_millis(1000);
383
384                let result = try_join!(
385                    event_logger.shutdown(timeout),
386                    specs_adapter.shutdown(timeout, &runtime)
387                );
388
389                match result {
390                    Ok(_) => {
391                        log_d!(TAG, "Shutdown successfully");
392                        callback();
393                    }
394                    Err(e) => {
395                        log_e!(TAG, "Shutdown failed: {:?}", e);
396                        callback();
397                    }
398                }
399            });
400    }
401
402    pub fn finalize_shutdown(&self, timeout: Duration) {
403        self.statsig_runtime.shutdown(timeout);
404    }
405
406    pub fn get_context(&self) -> StatsigContext {
407        StatsigContext {
408            sdk_key: self.sdk_key.clone(),
409            options: self.options.clone(),
410            local_override_adapter: self.override_adapter.clone(),
411            spec_store_data: self.get_current_values(),
412            error_observer: self.error_observer.clone(),
413        }
414    }
415
416    // todo: merge into get_context
417    pub fn get_current_values(&self) -> Option<SpecStoreData> {
418        // TODO better error handling here
419        Some(self.spec_store.data.read().ok()?.clone())
420    }
421
422    pub fn log_event(
423        &self,
424        user: &StatsigUser,
425        event_name: &str,
426        value: Option<String>,
427        metadata: Option<HashMap<String, String>>,
428    ) {
429        let user_internal = self.internalize_user(user);
430
431        self.event_logger
432            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
433                user_internal,
434                StatsigEvent {
435                    event_name: event_name.to_string(),
436                    value: value.map(|v| json!(v)),
437                    metadata,
438                    statsig_metadata: None,
439                },
440            )));
441    }
442
443    pub fn log_event_with_number(
444        &self,
445        user: &StatsigUser,
446        event_name: &str,
447        value: Option<f64>,
448        metadata: Option<HashMap<String, String>>,
449    ) {
450        let user_internal = self.internalize_user(user);
451
452        self.event_logger
453            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
454                user_internal,
455                StatsigEvent {
456                    event_name: event_name.to_string(),
457                    value: value.map(|v| json!(v)),
458                    metadata,
459                    statsig_metadata: None,
460                },
461            )));
462    }
463
464    pub fn log_layer_param_exposure_with_layer_json(
465        &self,
466        layer_json: String,
467        parameter_name: String,
468    ) {
469        let layer = match serde_json::from_str::<Layer>(&layer_json) {
470            Ok(layer) => layer,
471            Err(e) => {
472                log_error_to_statsig_and_console!(
473                    self.ops_stats.clone(),
474                    TAG,
475                    "Shutdown failed: {:?}",
476                    e
477                );
478                return;
479            }
480        };
481
482        self.log_layer_param_exposure_with_layer(layer, parameter_name);
483    }
484
485    pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
486        if layer.__disable_exposure {
487            self.event_logger
488                .increment_non_exposure_checks_count(layer.name.clone());
489            return;
490        }
491
492        let layer_eval = layer.__evaluation.as_ref();
493
494        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
495            &layer.__user,
496            layer_eval.map(AnyEvaluation::from).as_ref(),
497            Some(&parameter_name),
498        );
499
500        if !sampling_details.should_send_exposure {
501            return;
502        }
503
504        self.event_logger
505            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
506                user: layer.__user,
507                parameter_name,
508                evaluation: layer.__evaluation,
509                layer_name: layer.name,
510                evaluation_details: layer.details,
511                version: layer.__version,
512                is_manual_exposure: false,
513                sampling_details,
514                override_config_name: layer.__override_config_name.clone(),
515            }));
516    }
517
518    pub async fn flush_events(&self) {
519        self.event_logger.flush_blocking().await;
520    }
521
522    pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
523        self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
524    }
525
526    pub fn get_client_init_response_with_options(
527        &self,
528        user: &StatsigUser,
529        options: &ClientInitResponseOptions,
530    ) -> InitializeResponse {
531        let user_internal = self.internalize_user(user);
532        self.gcir_formatter
533            .get(user_internal, &self.hashing, options)
534    }
535
536    pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
537        json!(self.get_client_init_response(user)).to_string()
538    }
539
540    pub fn get_client_init_response_with_options_as_string(
541        &self,
542        user: &StatsigUser,
543        options: &ClientInitResponseOptions,
544    ) -> String {
545        json!(self.get_client_init_response_with_options(user, options)).to_string()
546    }
547
548    pub fn get_string_parameter_from_store(
549        &self,
550        user: &StatsigUser,
551        parameter_store_name: &str,
552        parameter_name: &str,
553        fallback: Option<String>,
554    ) -> Option<String> {
555        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
556    }
557
558    pub fn get_boolean_parameter_from_store(
559        &self,
560        user: &StatsigUser,
561        parameter_store_name: &str,
562        parameter_name: &str,
563        fallback: Option<bool>,
564    ) -> Option<bool> {
565        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
566    }
567
568    pub fn get_float_parameter_from_store(
569        &self,
570        user: &StatsigUser,
571        parameter_store_name: &str,
572        parameter_name: &str,
573        fallback: Option<f64>,
574    ) -> Option<f64> {
575        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
576    }
577
578    pub fn get_integer_parameter_from_store(
579        &self,
580        user: &StatsigUser,
581        parameter_store_name: &str,
582        parameter_name: &str,
583        fallback: Option<i64>,
584    ) -> Option<i64> {
585        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
586    }
587
588    pub fn get_array_parameter_from_store(
589        &self,
590        user: &StatsigUser,
591        parameter_store_name: &str,
592        parameter_name: &str,
593        fallback: Option<Vec<Value>>,
594    ) -> Option<Vec<Value>> {
595        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
596    }
597
598    pub fn get_object_parameter_from_store(
599        &self,
600        user: &StatsigUser,
601        parameter_store_name: &str,
602        parameter_name: &str,
603        fallback: Option<HashMap<String, Value>>,
604    ) -> Option<HashMap<String, Value>> {
605        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
606    }
607
608    pub fn get_parameter_from_store<T: DeserializeOwned>(
609        &self,
610        user: &StatsigUser,
611        parameter_store_name: &str,
612        parameter_name: &str,
613        fallback: Option<T>,
614    ) -> Option<T> {
615        let store = self.get_parameter_store(parameter_store_name);
616        match fallback {
617            Some(fallback) => Some(store.get(user, parameter_name, fallback)),
618            None => store.get_opt(user, parameter_name),
619        }
620    }
621
622    pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore {
623        self.event_logger
624            .increment_non_exposure_checks_count(parameter_store_name.to_string());
625        let data = read_lock_or_else!(self.spec_store.data, {
626            log_error_to_statsig_and_console!(
627                self.ops_stats.clone(),
628                TAG,
629                "Failed to acquire read lock for spec store data"
630            );
631            return ParameterStore {
632                name: parameter_store_name.to_string(),
633                parameters: HashMap::new(),
634                details: EvaluationDetails::unrecognized_no_data(),
635                _statsig_ref: self,
636            };
637        });
638
639        let stores = &data.values.param_stores;
640        let store = match stores {
641            Some(stores) => stores.get(parameter_store_name),
642            None => {
643                return ParameterStore {
644                    name: parameter_store_name.to_string(),
645                    parameters: HashMap::new(),
646                    details: EvaluationDetails::unrecognized(&data),
647                    _statsig_ref: self,
648                };
649            }
650        };
651        match store {
652            Some(store) => ParameterStore {
653                name: parameter_store_name.to_string(),
654                parameters: store.parameters.clone(),
655                details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
656                _statsig_ref: self,
657            },
658            None => ParameterStore {
659                name: parameter_store_name.to_string(),
660                parameters: HashMap::new(),
661                details: EvaluationDetails::unrecognized(&data),
662                _statsig_ref: self,
663            },
664        }
665    }
666}
667
668// -------------------------
669//   CMAB Functions
670// -------------------------
671
672impl Statsig {
673    pub fn get_cmab_ranked_groups(
674        &self,
675        user: &StatsigUser,
676        cmab_name: &str,
677    ) -> Vec<CMABRankedGroup> {
678        self.event_logger
679            .increment_non_exposure_checks_count(cmab_name.to_string());
680        let data = read_lock_or_else!(self.spec_store.data, {
681            log_error_to_statsig_and_console!(
682                self.ops_stats.clone(),
683                TAG,
684                "Failed to acquire read lock for spec store data"
685            );
686            return vec![];
687        });
688        let user_internal = self.internalize_user(user);
689        get_cmab_ranked_list(
690            &mut EvaluatorContext::new(
691                &user_internal,
692                &data,
693                &self.hashing,
694                &data.values.app_id.as_ref(),
695                &self.override_adapter,
696            ),
697            cmab_name,
698        )
699    }
700
701    pub fn log_cmab_exposure_for_group(
702        &self,
703        user: &StatsigUser,
704        cmab_name: &str,
705        group_id: String,
706    ) {
707        let user_internal = self.internalize_user(user);
708        let experiment = self.get_experiment_impl(&user_internal, cmab_name);
709        let base_eval = BaseEvaluation {
710            name: cmab_name.to_string(),
711            rule_id: group_id.clone(),
712            secondary_exposures: match experiment.__evaluation {
713                Some(ref eval) => eval.base.secondary_exposures.clone(),
714                None => vec![],
715            },
716            sampling_rate: match experiment.__evaluation {
717                Some(ref eval) => eval.base.sampling_rate,
718                None => Some(1),
719            },
720            forward_all_exposures: match experiment.__evaluation {
721                Some(ref eval) => eval.base.forward_all_exposures,
722                None => Some(true),
723            },
724        };
725        let experiment_eval = ExperimentEvaluation {
726            base: base_eval.clone(),
727            id_type: experiment.id_type.clone(),
728            value: HashMap::new(),
729            group: group_id,
730            is_device_based: false,
731            is_in_layer: false,
732            explicit_parameters: None,
733            group_name: None,
734            is_experiment_active: Some(true),
735            is_user_in_experiment: Some(true),
736        };
737
738        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
739            &user_internal,
740            Some(&AnyEvaluation::from(&experiment_eval)),
741            None,
742        );
743
744        if !sampling_details.should_send_exposure {
745            return;
746        }
747
748        self.event_logger
749            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
750                user: user_internal,
751                evaluation: Some(base_eval),
752                evaluation_details: experiment.details.clone(),
753                config_name: cmab_name.to_string(),
754                rule_passed: None,
755                version: experiment.__version,
756                is_manual_exposure: true,
757                sampling_details,
758                override_config_name: experiment.__override_config_name.clone(),
759            }));
760    }
761}
762
763// -------------------------
764//   Feature Gate Functions
765// -------------------------
766
767impl Statsig {
768    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
769        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
770    }
771
772    pub fn check_gate_with_options(
773        &self,
774        user: &StatsigUser,
775        gate_name: &str,
776        options: FeatureGateEvaluationOptions,
777    ) -> bool {
778        self.get_feature_gate_with_options(user, gate_name, options)
779            .value
780    }
781
782    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
783        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
784    }
785
786    pub fn get_feature_gate_with_options(
787        &self,
788        user: &StatsigUser,
789        gate_name: &str,
790        options: FeatureGateEvaluationOptions,
791    ) -> FeatureGate {
792        log_d!(TAG, "Get Feature Gate {}", gate_name);
793
794        let user_internal = self.internalize_user(user);
795
796        let disable_exposure_logging = options.disable_exposure_logging;
797        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
798
799        if disable_exposure_logging {
800            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
801            self.event_logger
802                .increment_non_exposure_checks_count(gate_name.to_string());
803        } else {
804            self.log_gate_exposure(user_internal, gate_name, &gate, false);
805        }
806
807        gate
808    }
809
810    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
811        let user_internal = self.internalize_user(user);
812        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
813        self.log_gate_exposure(user_internal, gate_name, &gate, true);
814    }
815
816    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
817        let data = read_lock_or_else!(self.spec_store.data, {
818            log_error_to_statsig_and_console!(
819                self.ops_stats.clone(),
820                TAG,
821                "Failed to acquire read lock for spec store data"
822            );
823            return vec![];
824        });
825
826        let gate = data.values.feature_gates.get(gate_name);
827        match gate {
828            Some(gate) => match &gate.fields_used {
829                Some(fields) => fields.clone(),
830                None => vec![],
831            },
832            None => vec![],
833        }
834    }
835
836    pub fn override_gate(
837        &self,
838        gate_name: &str,
839        value: bool,
840        _adapter: Option<&OverrideAdapterType>,
841    ) {
842        if let Some(adapter) = &self.override_adapter {
843            adapter.override_gate(gate_name, value);
844        }
845    }
846
847    pub fn override_dynamic_config(
848        &self,
849        config_name: &str,
850        value: HashMap<String, serde_json::Value>,
851        _adapter: Option<&OverrideAdapterType>,
852    ) {
853        if let Some(adapter) = &self.override_adapter {
854            adapter.override_dynamic_config(config_name, value);
855        }
856    }
857
858    pub fn override_layer(
859        &self,
860        layer_name: &str,
861        value: HashMap<String, serde_json::Value>,
862        _adapter: Option<&OverrideAdapterType>,
863    ) {
864        if let Some(adapter) = &self.override_adapter {
865            adapter.override_layer(layer_name, value);
866        }
867    }
868
869    pub fn override_experiment(
870        &self,
871        experiment_name: &str,
872        value: HashMap<String, serde_json::Value>,
873        _adapter: Option<&OverrideAdapterType>,
874    ) {
875        if let Some(adapter) = &self.override_adapter {
876            adapter.override_experiment(experiment_name, value);
877        }
878    }
879
880    pub fn override_experiment_by_group_name(
881        &self,
882        experiment_name: &str,
883        group_name: &str,
884        _adapter: Option<&OverrideAdapterType>,
885    ) {
886        if let Some(adapter) = &self.override_adapter {
887            adapter.override_experiment_by_group_name(experiment_name, group_name);
888        }
889    }
890}
891
892// -------------------------
893//   Dynamic Config Functions
894// -------------------------
895
896impl Statsig {
897    pub fn get_dynamic_config(
898        &self,
899        user: &StatsigUser,
900        dynamic_config_name: &str,
901    ) -> DynamicConfig {
902        self.get_dynamic_config_with_options(
903            user,
904            dynamic_config_name,
905            DynamicConfigEvaluationOptions::default(),
906        )
907    }
908
909    pub fn get_dynamic_config_with_options(
910        &self,
911        user: &StatsigUser,
912        dynamic_config_name: &str,
913        options: DynamicConfigEvaluationOptions,
914    ) -> DynamicConfig {
915        let user_internal = self.internalize_user(user);
916        let disable_exposure_logging = options.disable_exposure_logging;
917        let config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
918
919        if disable_exposure_logging {
920            log_d!(
921                TAG,
922                "Exposure logging is disabled for Dynamic Config {}",
923                dynamic_config_name
924            );
925            self.event_logger
926                .increment_non_exposure_checks_count(dynamic_config_name.to_string());
927        } else {
928            self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &config, false);
929        }
930
931        config
932    }
933
934    pub fn manually_log_dynamic_config_exposure(
935        &self,
936        user: &StatsigUser,
937        dynamic_config_name: &str,
938    ) {
939        let user_internal = self.internalize_user(user);
940        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
941        self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &dynamic_config, true);
942    }
943
944    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
945        let data = read_lock_or_else!(self.spec_store.data, {
946            log_error_to_statsig_and_console!(
947                self.ops_stats.clone(),
948                TAG,
949                "Failed to acquire read lock for spec store data"
950            );
951            return vec![];
952        });
953
954        let config = data.values.dynamic_configs.get(config_name);
955        match config {
956            Some(config) => match &config.fields_used {
957                Some(fields) => fields.clone(),
958                None => vec![],
959            },
960            None => vec![],
961        }
962    }
963}
964
965// -------------------------
966//   Experiment Functions
967// -------------------------
968
969impl Statsig {
970    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
971        self.get_experiment_with_options(
972            user,
973            experiment_name,
974            ExperimentEvaluationOptions::default(),
975        )
976    }
977
978    pub fn get_experiment_with_options(
979        &self,
980        user: &StatsigUser,
981        experiment_name: &str,
982        options: ExperimentEvaluationOptions,
983    ) -> Experiment {
984        let user_internal = self.internalize_user(user);
985        let disable_exposure_logging = options.disable_exposure_logging;
986        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
987
988        if disable_exposure_logging {
989            log_d!(
990                TAG,
991                "Exposure logging is disabled for experiment {}",
992                experiment_name
993            );
994            self.event_logger
995                .increment_non_exposure_checks_count(experiment_name.to_string());
996        } else {
997            self.log_experiment_exposure(user_internal, experiment_name, &experiment, false);
998        }
999
1000        experiment
1001    }
1002
1003    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1004        let user_internal = self.internalize_user(user);
1005        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1006        self.log_experiment_exposure(user_internal, experiment_name, &experiment, true);
1007    }
1008
1009    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1010        let data = read_lock_or_else!(self.spec_store.data, {
1011            log_error_to_statsig_and_console!(
1012                self.ops_stats.clone(),
1013                TAG,
1014                "Failed to acquire read lock for spec store data"
1015            );
1016            return vec![];
1017        });
1018
1019        let config = data.values.dynamic_configs.get(experiment_name);
1020        match config {
1021            Some(config) => match &config.fields_used {
1022                Some(fields) => fields.clone(),
1023                None => vec![],
1024            },
1025            None => vec![],
1026        }
1027    }
1028}
1029
1030// -------------------------
1031//   Layer Functions
1032// -------------------------
1033
1034impl Statsig {
1035    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1036        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1037    }
1038
1039    pub fn get_layer_with_options(
1040        &self,
1041        user: &StatsigUser,
1042        layer_name: &str,
1043        options: LayerEvaluationOptions,
1044    ) -> Layer {
1045        let user_internal = self.internalize_user(user);
1046        self.get_layer_impl(&user_internal, layer_name, options)
1047    }
1048
1049    pub fn manually_log_layer_parameter_exposure(
1050        &self,
1051        user: &StatsigUser,
1052        layer_name: &str,
1053        parameter_name: String,
1054    ) {
1055        let user_internal = self.internalize_user(user);
1056        let layer = self.get_layer_impl(
1057            &user_internal,
1058            layer_name,
1059            LayerEvaluationOptions::default(),
1060        );
1061
1062        let layer_eval = layer.__evaluation.as_ref();
1063
1064        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1065            &layer.__user,
1066            layer_eval.map(AnyEvaluation::from).as_ref(),
1067            Some(parameter_name.as_str()),
1068        );
1069
1070        if !sampling_details.should_send_exposure {
1071            return;
1072        }
1073
1074        self.event_logger
1075            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
1076                user: layer.__user,
1077                parameter_name,
1078                evaluation: layer.__evaluation,
1079                layer_name: layer.name,
1080                evaluation_details: layer.details,
1081                version: layer.__version,
1082                is_manual_exposure: true,
1083                sampling_details,
1084                override_config_name: layer.__override_config_name.clone(),
1085            }));
1086    }
1087
1088    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1089        let data = read_lock_or_else!(self.spec_store.data, {
1090            log_error_to_statsig_and_console!(
1091                self.ops_stats.clone(),
1092                TAG,
1093                "Failed to acquire read lock for spec store data"
1094            );
1095            return vec![];
1096        });
1097
1098        let layer = data.values.layer_configs.get(layer_name);
1099        match layer {
1100            Some(layer) => match &layer.fields_used {
1101                Some(fields) => fields.clone(),
1102                None => vec![],
1103            },
1104            None => vec![],
1105        }
1106    }
1107}
1108
1109// -------------------------
1110//   Private Functions
1111// -------------------------
1112
1113impl Statsig {
1114    fn evaluate_spec<T>(
1115        &self,
1116        user_internal: &StatsigUserInternal,
1117        spec_name: &str,
1118        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1119        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1120        spec_type: &SpecType,
1121    ) -> T {
1122        let data = read_lock_or_else!(self.spec_store.data, {
1123            log_error_to_statsig_and_console!(
1124                &self.ops_stats,
1125                TAG,
1126                "Failed to acquire read lock for spec store data"
1127            );
1128            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1129        });
1130        let app_id = data.values.app_id.as_ref();
1131        let mut context = EvaluatorContext::new(
1132            user_internal,
1133            &data,
1134            &self.hashing,
1135            &app_id,
1136            &self.override_adapter,
1137        );
1138
1139        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1140            Ok(eval_details) => make_result(context.result, eval_details),
1141            Err(e) => {
1142                log_error_to_statsig_and_console!(&self.ops_stats, TAG, "Error evaluating: {}", e);
1143                make_empty_result(EvaluationDetails::error(&e.to_string()))
1144            }
1145        }
1146    }
1147
1148    fn get_feature_gate_impl(
1149        &self,
1150        user_internal: &StatsigUserInternal,
1151        gate_name: &str,
1152    ) -> FeatureGate {
1153        self.evaluate_spec(
1154            user_internal,
1155            gate_name,
1156            |eval_details| make_feature_gate(gate_name, None, eval_details, None, None),
1157            |mut result, eval_details| {
1158                let evaluation = result_to_gate_eval(gate_name, &mut result);
1159                make_feature_gate(
1160                    gate_name,
1161                    Some(evaluation),
1162                    eval_details,
1163                    result.version,
1164                    result.override_config_name,
1165                )
1166            },
1167            &SpecType::Gate,
1168        )
1169    }
1170
1171    fn get_dynamic_config_impl(
1172        &self,
1173        user_internal: &StatsigUserInternal,
1174        config_name: &str,
1175    ) -> DynamicConfig {
1176        self.evaluate_spec(
1177            user_internal,
1178            config_name,
1179            |eval_details| make_dynamic_config(config_name, None, eval_details, None, None),
1180            |mut result, eval_details| {
1181                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1182                make_dynamic_config(
1183                    config_name,
1184                    Some(evaluation),
1185                    eval_details,
1186                    result.version,
1187                    result.override_config_name,
1188                )
1189            },
1190            &SpecType::DynamicConfig,
1191        )
1192    }
1193
1194    fn get_experiment_impl(
1195        &self,
1196        user_internal: &StatsigUserInternal,
1197        experiment_name: &str,
1198    ) -> Experiment {
1199        self.evaluate_spec(
1200            user_internal,
1201            experiment_name,
1202            |eval_details| make_experiment(experiment_name, None, eval_details, None, None),
1203            |mut result, eval_details| {
1204                let data = read_lock_or_else!(self.spec_store.data, {
1205                    let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1206                    return make_experiment(
1207                        experiment_name,
1208                        Some(evaluation),
1209                        eval_details,
1210                        result.version,
1211                        result.override_config_name,
1212                    );
1213                });
1214                let evaluation = result_to_experiment_eval(
1215                    experiment_name,
1216                    data.values.dynamic_configs.get(experiment_name),
1217                    &mut result,
1218                );
1219                make_experiment(
1220                    experiment_name,
1221                    Some(evaluation),
1222                    eval_details,
1223                    result.version,
1224                    result.override_config_name,
1225                )
1226            },
1227            &SpecType::Experiment,
1228        )
1229    }
1230
1231    fn get_layer_impl(
1232        &self,
1233        user_internal: &StatsigUserInternal,
1234        layer_name: &str,
1235        evaluation_options: LayerEvaluationOptions,
1236    ) -> Layer {
1237        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1238        if disable_exposure_logging {
1239            self.event_logger
1240                .increment_non_exposure_checks_count(layer_name.to_string());
1241        }
1242
1243        self.evaluate_spec(
1244            user_internal,
1245            layer_name,
1246            |eval_details| {
1247                make_layer(
1248                    user_internal,
1249                    layer_name,
1250                    None,
1251                    eval_details,
1252                    None,
1253                    None,
1254                    disable_exposure_logging,
1255                    None,
1256                    None,
1257                )
1258            },
1259            |mut result, eval_details| {
1260                let evaluation = result_to_layer_eval(layer_name, &mut result);
1261                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1262                let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1263
1264                make_layer(
1265                    user_internal,
1266                    layer_name,
1267                    Some(evaluation),
1268                    eval_details,
1269                    Some(event_logger_ptr),
1270                    result.version,
1271                    disable_exposure_logging,
1272                    Some(sampling_processor_ptr),
1273                    result.override_config_name,
1274                )
1275            },
1276            &SpecType::Layer,
1277        )
1278    }
1279
1280    fn log_gate_exposure(
1281        &self,
1282        user_internal: StatsigUserInternal,
1283        gate_name: &str,
1284        gate: &FeatureGate,
1285        is_manual: bool,
1286    ) {
1287        let gate_eval = gate.__evaluation.as_ref();
1288        let secondary_exposures = gate_eval.map(|eval| &eval.base.secondary_exposures);
1289
1290        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1291            &user_internal,
1292            gate_eval.map(AnyEvaluation::from).as_ref(),
1293            None,
1294        );
1295
1296        if !sampling_details.should_send_exposure {
1297            return;
1298        }
1299
1300        self.event_logger
1301            .enqueue(QueuedEventPayload::GateExposure(GateExposure {
1302                user: user_internal,
1303                gate_name: gate_name.to_string(),
1304                value: gate.value,
1305                rule_id: Some(gate.rule_id.clone()),
1306                secondary_exposures: secondary_exposures.cloned(),
1307                evaluation_details: gate.details.clone(),
1308                version: gate.__version,
1309                is_manual_exposure: is_manual,
1310                sampling_details,
1311                override_config_name: gate.__override_config_name.clone(),
1312            }));
1313    }
1314
1315    fn log_dynamic_config_exposure(
1316        &self,
1317        user_internal: StatsigUserInternal,
1318        dynamic_config_name: &str,
1319        dynamic_config: &DynamicConfig,
1320        is_manual: bool,
1321    ) {
1322        let config_eval = dynamic_config.__evaluation.as_ref();
1323        let base_eval = config_eval.map(|eval| eval.base.clone());
1324
1325        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1326            &user_internal,
1327            config_eval.map(AnyEvaluation::from).as_ref(),
1328            None,
1329        );
1330
1331        if !sampling_details.should_send_exposure {
1332            return;
1333        }
1334
1335        self.event_logger
1336            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1337                user: user_internal,
1338                evaluation: base_eval,
1339                evaluation_details: dynamic_config.details.clone(),
1340                config_name: dynamic_config_name.to_string(),
1341                rule_passed: dynamic_config.__evaluation.as_ref().map(|eval| eval.passed),
1342                version: dynamic_config.__version,
1343                is_manual_exposure: is_manual,
1344                sampling_details,
1345                override_config_name: dynamic_config.__override_config_name.clone(),
1346            }));
1347    }
1348
1349    fn log_experiment_exposure(
1350        &self,
1351        user_internal: StatsigUserInternal,
1352        experiment_name: &str,
1353        experiment: &Experiment,
1354        is_manual: bool,
1355    ) {
1356        let experiment_eval = experiment.__evaluation.as_ref();
1357        let base_eval = experiment_eval.map(|eval| eval.base.clone());
1358
1359        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1360            &user_internal,
1361            experiment_eval.map(AnyEvaluation::from).as_ref(),
1362            None,
1363        );
1364
1365        if !sampling_details.should_send_exposure {
1366            return;
1367        }
1368
1369        self.event_logger
1370            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1371                user: user_internal,
1372                evaluation: base_eval,
1373                evaluation_details: experiment.details.clone(),
1374                config_name: experiment_name.to_string(),
1375                rule_passed: None,
1376                version: experiment.__version,
1377                is_manual_exposure: is_manual,
1378                sampling_details,
1379                override_config_name: experiment.__override_config_name.clone(),
1380            }));
1381    }
1382
1383    fn internalize_user(&self, user: &StatsigUser) -> StatsigUserInternal {
1384        StatsigUserInternal::new(
1385            user,
1386            self.get_statsig_env(),
1387            self.options.global_custom_fields.clone(),
1388        )
1389    }
1390
1391    fn get_statsig_env(&self) -> Option<HashMap<String, DynamicValue>> {
1392        if let Some(env) = &self.statsig_environment {
1393            return Some(env.clone());
1394        }
1395
1396        if let Ok(fallback_env) = self.fallback_environment.lock() {
1397            if let Some(env) = &*fallback_env {
1398                return Some(env.clone());
1399            }
1400        }
1401
1402        None
1403    }
1404
1405    fn set_default_environment_from_server(&self) {
1406        let data = read_lock_or_else!(self.spec_store.data, {
1407            return;
1408        });
1409
1410        if let Some(default_env) = data.values.default_environment.as_ref() {
1411            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1412
1413            if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1414                *fallback_env = Some(env_map);
1415            }
1416        }
1417    }
1418
1419    fn log_init_finish(
1420        &self,
1421        success: bool,
1422        error_message: &Option<String>,
1423        duration: &f64,
1424        specs_info: &SpecsInfo,
1425    ) {
1426        self.ops_stats.log(ObservabilityEvent::new_event(
1427            MetricType::Dist,
1428            "initialization".to_string(),
1429            *duration,
1430            Some(HashMap::from([
1431                ("success".to_owned(), success.to_string()),
1432                ("source".to_owned(), specs_info.source.to_string()),
1433                (
1434                    "store_populated".to_owned(),
1435                    (specs_info.source != SpecsSource::NoValues).to_string(),
1436                ),
1437            ])),
1438        ));
1439        self.diagnostics.mark_init_overall_end(
1440            success,
1441            error_message.clone(),
1442            EvaluationDetails {
1443                reason: format!("{}", specs_info.source),
1444                lcut: specs_info.lcut,
1445                received_at: None,
1446            },
1447        );
1448    }
1449}
1450
1451fn initialize_event_logging_adapter(
1452    sdk_key: &str,
1453    options: &StatsigOptions,
1454) -> Arc<dyn EventLoggingAdapter> {
1455    let adapter = options.event_logging_adapter.clone().unwrap_or_else(|| {
1456        Arc::new(StatsigHttpEventLoggingAdapter::new(
1457            sdk_key,
1458            options.log_event_url.as_ref(),
1459        ))
1460    });
1461    adapter
1462}
1463
1464fn initialize_specs_adapter(
1465    sdk_key: &str,
1466    options: &StatsigOptions,
1467    hashing: &HashUtil,
1468) -> Arc<dyn SpecsAdapter> {
1469    if let Some(adapter) = options.specs_adapter.clone() {
1470        return adapter;
1471    }
1472
1473    if let Some(adapter_config) = options.spec_adapters_config.clone() {
1474        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1475            sdk_key,
1476            adapter_config,
1477            options,
1478            hashing,
1479        ));
1480    }
1481
1482    if let Some(data_adapter) = options.data_store.clone() {
1483        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1484            sdk_key,
1485            data_adapter,
1486            options,
1487            hashing,
1488        ));
1489    }
1490
1491    Arc::new(StatsigHttpSpecsAdapter::new(
1492        sdk_key,
1493        options.specs_url.as_ref(),
1494        options.fallback_to_statsig_api.unwrap_or(false),
1495        options.specs_sync_interval_ms,
1496    ))
1497}
1498
1499fn initialize_id_lists_adapter(
1500    sdk_key: &str,
1501    options: &StatsigOptions,
1502) -> Option<Arc<dyn IdListsAdapter>> {
1503    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1504        return Some(id_lists_adapter);
1505    }
1506
1507    if options.enable_id_lists.unwrap_or(false) {
1508        return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1509    }
1510
1511    None
1512}
1513
1514fn setup_ops_stats(
1515    sdk_key: &str,
1516    options: &StatsigOptions,
1517    statsig_runtime: Arc<StatsigRuntime>,
1518    error_observer: &Arc<dyn OpsStatsEventObserver>,
1519    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1520    external_observer: &Option<Weak<dyn ObservabilityClient>>,
1521) -> Arc<OpsStatsForInstance> {
1522    // TODO migrate output logger to use ops_stats
1523    initialize_simple_output_logger(&options.output_log_level);
1524
1525    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1526    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1527    ops_stat.subscribe(
1528        statsig_runtime.clone(),
1529        Arc::downgrade(diagnostics_observer),
1530    );
1531
1532    if let Some(ob_client) = external_observer {
1533        if let Some(client) = ob_client.upgrade() {
1534            client.init();
1535            let as_observer = client.to_ops_stats_event_observer();
1536            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1537        }
1538    }
1539
1540    ops_stat
1541}
1542
1543#[cfg(test)]
1544mod tests {
1545    use super::*;
1546    use crate::hashing::djb2;
1547    use crate::{
1548        evaluation::evaluation_types::AnyConfigEvaluation, output_logger::LogLevel,
1549        StatsigHttpIdListsAdapter,
1550    };
1551    use std::env;
1552
1553    fn get_sdk_key() -> String {
1554        let key = env::var("test_api_key").expect("test_api_key environment variable not set");
1555        assert!(key.starts_with("secret-9IWf"));
1556        key
1557    }
1558
1559    #[tokio::test]
1560    async fn test_check_gate() {
1561        let user = StatsigUser {
1562            email: Some(dyn_value!("daniel@statsig.com")),
1563            ..StatsigUser::with_user_id("a-user".to_string())
1564        };
1565
1566        let statsig = Statsig::new(&get_sdk_key(), None);
1567        statsig.initialize().await.unwrap();
1568
1569        let gate_result = statsig.check_gate(&user, "test_50_50");
1570
1571        assert!(gate_result);
1572    }
1573
1574    #[tokio::test]
1575    async fn test_check_gate_id_list() {
1576        let user = StatsigUser {
1577            custom_ids: Some(HashMap::from([(
1578                "companyID".to_string(),
1579                dyn_value!("marcos_1"),
1580            )])),
1581            ..StatsigUser::with_user_id("marcos_1".to_string())
1582        };
1583
1584        let mut opts = StatsigOptions::new();
1585
1586        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(&get_sdk_key(), &opts));
1587        opts.id_lists_adapter = Some(adapter);
1588
1589        let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1590        statsig.initialize().await.unwrap();
1591
1592        let gate_result = statsig.check_gate(&user, "test_id_list");
1593
1594        assert!(gate_result);
1595    }
1596
1597    #[tokio::test]
1598    async fn test_get_experiment() {
1599        let user = StatsigUser {
1600            email: Some(dyn_value!("daniel@statsig.com")),
1601            ..StatsigUser::with_user_id("a-user".to_string())
1602        };
1603
1604        let statsig = Statsig::new(&get_sdk_key(), None);
1605        statsig.initialize().await.unwrap();
1606
1607        let experiment = statsig.get_experiment(&user, "running_exp_in_unlayered_with_holdout");
1608        let _ = statsig.shutdown().await;
1609
1610        assert_ne!(experiment.value.len(), 0);
1611    }
1612
1613    #[tokio::test]
1614    async fn test_gcir() {
1615        let user = StatsigUser {
1616            email: Some(dyn_value!("daniel@statsig.com")),
1617            ..StatsigUser::with_user_id("a-user".to_string())
1618        };
1619        let opts = StatsigOptions {
1620            output_log_level: Some(LogLevel::Debug),
1621            ..StatsigOptions::new()
1622        };
1623
1624        let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1625        statsig.initialize().await.unwrap();
1626
1627        let response = statsig.get_client_init_response(&user);
1628        let _ = statsig.shutdown().await;
1629
1630        let gates = response.feature_gates;
1631        assert_eq!(gates.len(), 69);
1632
1633        let configs = response.dynamic_configs.len();
1634        assert_eq!(configs, 62);
1635
1636        let a_config_opt = response.dynamic_configs.get(&djb2("big_number"));
1637        let a_config = match a_config_opt {
1638            Some(v) => match v {
1639                AnyConfigEvaluation::DynamicConfig(config) => &config.value,
1640                AnyConfigEvaluation::Experiment(exp) => &exp.value,
1641            },
1642            None => panic!("Should have values"),
1643        };
1644
1645        assert!(!a_config.is_empty());
1646    }
1647}