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