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, ParameterStoreEvaluationOptions,
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.get_parameter_store_with_options(
642            parameter_store_name,
643            ParameterStoreEvaluationOptions::default(),
644        )
645    }
646
647    pub fn get_parameter_store_with_options(
648        &self,
649        parameter_store_name: &str,
650        options: ParameterStoreEvaluationOptions,
651    ) -> ParameterStore {
652        self.event_logger
653            .increment_non_exposure_checks_count(parameter_store_name.to_string());
654        let data = read_lock_or_else!(self.spec_store.data, {
655            log_error_to_statsig_and_console!(
656                self.ops_stats.clone(),
657                TAG,
658                "Failed to acquire read lock for spec store data"
659            );
660            return ParameterStore {
661                name: parameter_store_name.to_string(),
662                parameters: HashMap::new(),
663                details: EvaluationDetails::unrecognized_no_data(),
664                options,
665                _statsig_ref: self,
666            };
667        });
668
669        let stores = &data.values.param_stores;
670        let store = match stores {
671            Some(stores) => stores.get(parameter_store_name),
672            None => {
673                return ParameterStore {
674                    name: parameter_store_name.to_string(),
675                    parameters: HashMap::new(),
676                    details: EvaluationDetails::unrecognized(&data),
677                    options,
678                    _statsig_ref: self,
679                };
680            }
681        };
682        match store {
683            Some(store) => ParameterStore {
684                name: parameter_store_name.to_string(),
685                parameters: store.parameters.clone(),
686                details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
687                options,
688                _statsig_ref: self,
689            },
690            None => ParameterStore {
691                name: parameter_store_name.to_string(),
692                parameters: HashMap::new(),
693                details: EvaluationDetails::unrecognized(&data),
694                options,
695                _statsig_ref: self,
696            },
697        }
698    }
699}
700
701// -------------------------
702//   CMAB Functions
703// -------------------------
704
705impl Statsig {
706    pub fn get_cmab_ranked_groups(
707        &self,
708        user: &StatsigUser,
709        cmab_name: &str,
710    ) -> Vec<CMABRankedGroup> {
711        self.event_logger
712            .increment_non_exposure_checks_count(cmab_name.to_string());
713        let data = read_lock_or_else!(self.spec_store.data, {
714            log_error_to_statsig_and_console!(
715                self.ops_stats.clone(),
716                TAG,
717                "Failed to acquire read lock for spec store data"
718            );
719            return vec![];
720        });
721        let user_internal = self.internalize_user(user);
722        get_cmab_ranked_list(
723            &mut EvaluatorContext::new(
724                &user_internal,
725                &data,
726                &self.hashing,
727                &data.values.app_id.as_ref(),
728                &self.override_adapter,
729            ),
730            cmab_name,
731        )
732    }
733
734    pub fn log_cmab_exposure_for_group(
735        &self,
736        user: &StatsigUser,
737        cmab_name: &str,
738        group_id: String,
739    ) {
740        let user_internal = self.internalize_user(user);
741        let experiment = self.get_experiment_impl(&user_internal, cmab_name);
742        let sampling_info = match experiment.__evaluation {
743            Some(ref eval) => eval.base.sampling_info.clone(),
744            None => None,
745        };
746        let base_eval = BaseEvaluation {
747            name: cmab_name.to_string(),
748            rule_id: group_id.clone(),
749            secondary_exposures: match experiment.__evaluation {
750                Some(ref eval) => eval.base.secondary_exposures.clone(),
751                None => vec![],
752            },
753            sampling_info,
754        };
755        let experiment_eval = ExperimentEvaluation {
756            base: base_eval.clone(),
757            id_type: experiment.id_type.clone(),
758            value: HashMap::new(),
759            group: group_id,
760            is_device_based: false,
761            is_in_layer: false,
762            explicit_parameters: None,
763            group_name: None,
764            is_experiment_active: Some(true),
765            is_user_in_experiment: Some(true),
766        };
767
768        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
769            &user_internal,
770            Some(&AnyEvaluation::from(&experiment_eval)),
771            None,
772        );
773
774        if !sampling_details.should_send_exposure {
775            return;
776        }
777
778        self.event_logger
779            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
780                user: user_internal,
781                evaluation: Some(base_eval),
782                evaluation_details: experiment.details.clone(),
783                config_name: cmab_name.to_string(),
784                rule_passed: None,
785                version: experiment.__version,
786                is_manual_exposure: true,
787                sampling_details,
788                override_config_name: experiment.__override_config_name.clone(),
789            }));
790    }
791}
792
793// -------------------------
794//   Shared Instance Functions
795// -------------------------
796
797impl Statsig {
798    pub fn shared() -> Arc<Statsig> {
799        let lock = match SHARED_INSTANCE.lock() {
800            Ok(lock) => lock,
801            Err(e) => {
802                log_e!(TAG, "Statsig::shared() mutex error: {}", e);
803                return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
804            }
805        };
806
807        match lock.as_ref() {
808            Some(statsig) => statsig.clone(),
809            None => {
810                log_e!(
811                    TAG,
812                    "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
813                );
814                Arc::new(Statsig::new(ERROR_SDK_KEY, None))
815            }
816        }
817    }
818
819    pub fn new_shared(
820        sdk_key: &str,
821        options: Option<Arc<StatsigOptions>>,
822    ) -> Result<Arc<Statsig>, StatsigErr> {
823        match SHARED_INSTANCE.lock() {
824            Ok(mut lock) => {
825                if lock.is_some() {
826                    let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
827                    log_e!(TAG, "{}", message);
828                    return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
829                }
830
831                let statsig = Arc::new(Statsig::new(sdk_key, options));
832                *lock = Some(statsig.clone());
833                Ok(statsig)
834            }
835            Err(e) => {
836                let message = format!("Statsig::new_shared() mutex error: {}", e);
837                log_e!(TAG, "{}", message);
838                Err(StatsigErr::SharedInstanceFailure(message))
839            }
840        }
841    }
842
843    pub fn remove_shared() {
844        match SHARED_INSTANCE.lock() {
845            Ok(mut lock) => {
846                *lock = None;
847            }
848            Err(e) => {
849                log_e!(TAG, "Statsig::remove_shared() mutex error: {}", e);
850            }
851        }
852    }
853
854    pub fn has_shared_instance() -> bool {
855        match SHARED_INSTANCE.lock() {
856            Ok(lock) => lock.is_some(),
857            Err(_) => false,
858        }
859    }
860}
861
862// -------------------------
863//   Feature Gate Functions
864// -------------------------
865
866impl Statsig {
867    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
868        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
869    }
870
871    pub fn check_gate_with_options(
872        &self,
873        user: &StatsigUser,
874        gate_name: &str,
875        options: FeatureGateEvaluationOptions,
876    ) -> bool {
877        self.get_feature_gate_with_options(user, gate_name, options)
878            .value
879    }
880
881    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
882        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
883    }
884
885    pub fn get_feature_gate_with_options(
886        &self,
887        user: &StatsigUser,
888        gate_name: &str,
889        options: FeatureGateEvaluationOptions,
890    ) -> FeatureGate {
891        log_d!(TAG, "Get Feature Gate {}", gate_name);
892
893        let user_internal = self.internalize_user(user);
894
895        let disable_exposure_logging = options.disable_exposure_logging;
896        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
897
898        if disable_exposure_logging {
899            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
900            self.event_logger
901                .increment_non_exposure_checks_count(gate_name.to_string());
902        } else {
903            self.log_gate_exposure(user_internal, gate_name, &gate, false);
904        }
905
906        gate
907    }
908
909    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
910        let user_internal = self.internalize_user(user);
911        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
912        self.log_gate_exposure(user_internal, gate_name, &gate, true);
913    }
914
915    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
916        let data = read_lock_or_else!(self.spec_store.data, {
917            log_error_to_statsig_and_console!(
918                self.ops_stats.clone(),
919                TAG,
920                "Failed to acquire read lock for spec store data"
921            );
922            return vec![];
923        });
924
925        let gate = data.values.feature_gates.get(gate_name);
926        match gate {
927            Some(gate) => match &gate.fields_used {
928                Some(fields) => fields.clone(),
929                None => vec![],
930            },
931            None => vec![],
932        }
933    }
934
935    pub fn override_gate(
936        &self,
937        gate_name: &str,
938        value: bool,
939        _adapter: Option<&OverrideAdapterType>,
940    ) {
941        if let Some(adapter) = &self.override_adapter {
942            adapter.override_gate(gate_name, value);
943        }
944    }
945
946    pub fn override_dynamic_config(
947        &self,
948        config_name: &str,
949        value: HashMap<String, serde_json::Value>,
950        _adapter: Option<&OverrideAdapterType>,
951    ) {
952        if let Some(adapter) = &self.override_adapter {
953            adapter.override_dynamic_config(config_name, value);
954        }
955    }
956
957    pub fn override_layer(
958        &self,
959        layer_name: &str,
960        value: HashMap<String, serde_json::Value>,
961        _adapter: Option<&OverrideAdapterType>,
962    ) {
963        if let Some(adapter) = &self.override_adapter {
964            adapter.override_layer(layer_name, value);
965        }
966    }
967
968    pub fn override_experiment(
969        &self,
970        experiment_name: &str,
971        value: HashMap<String, serde_json::Value>,
972        _adapter: Option<&OverrideAdapterType>,
973    ) {
974        if let Some(adapter) = &self.override_adapter {
975            adapter.override_experiment(experiment_name, value);
976        }
977    }
978
979    pub fn override_experiment_by_group_name(
980        &self,
981        experiment_name: &str,
982        group_name: &str,
983        _adapter: Option<&OverrideAdapterType>,
984    ) {
985        if let Some(adapter) = &self.override_adapter {
986            adapter.override_experiment_by_group_name(experiment_name, group_name);
987        }
988    }
989}
990
991// -------------------------
992//   Dynamic Config Functions
993// -------------------------
994
995impl Statsig {
996    pub fn get_dynamic_config(
997        &self,
998        user: &StatsigUser,
999        dynamic_config_name: &str,
1000    ) -> DynamicConfig {
1001        self.get_dynamic_config_with_options(
1002            user,
1003            dynamic_config_name,
1004            DynamicConfigEvaluationOptions::default(),
1005        )
1006    }
1007
1008    pub fn get_dynamic_config_with_options(
1009        &self,
1010        user: &StatsigUser,
1011        dynamic_config_name: &str,
1012        options: DynamicConfigEvaluationOptions,
1013    ) -> DynamicConfig {
1014        let user_internal = self.internalize_user(user);
1015        let disable_exposure_logging = options.disable_exposure_logging;
1016        let config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1017
1018        if disable_exposure_logging {
1019            log_d!(
1020                TAG,
1021                "Exposure logging is disabled for Dynamic Config {}",
1022                dynamic_config_name
1023            );
1024            self.event_logger
1025                .increment_non_exposure_checks_count(dynamic_config_name.to_string());
1026        } else {
1027            self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &config, false);
1028        }
1029
1030        config
1031    }
1032
1033    pub fn manually_log_dynamic_config_exposure(
1034        &self,
1035        user: &StatsigUser,
1036        dynamic_config_name: &str,
1037    ) {
1038        let user_internal = self.internalize_user(user);
1039        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1040        self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &dynamic_config, true);
1041    }
1042
1043    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1044        let data = read_lock_or_else!(self.spec_store.data, {
1045            log_error_to_statsig_and_console!(
1046                self.ops_stats.clone(),
1047                TAG,
1048                "Failed to acquire read lock for spec store data"
1049            );
1050            return vec![];
1051        });
1052
1053        let config = data.values.dynamic_configs.get(config_name);
1054        match config {
1055            Some(config) => match &config.fields_used {
1056                Some(fields) => fields.clone(),
1057                None => vec![],
1058            },
1059            None => vec![],
1060        }
1061    }
1062}
1063
1064// -------------------------
1065//   Experiment Functions
1066// -------------------------
1067
1068impl Statsig {
1069    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1070        self.get_experiment_with_options(
1071            user,
1072            experiment_name,
1073            ExperimentEvaluationOptions::default(),
1074        )
1075    }
1076
1077    pub fn get_experiment_with_options(
1078        &self,
1079        user: &StatsigUser,
1080        experiment_name: &str,
1081        options: ExperimentEvaluationOptions,
1082    ) -> Experiment {
1083        let user_internal = self.internalize_user(user);
1084        let disable_exposure_logging = options.disable_exposure_logging;
1085        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1086
1087        if disable_exposure_logging {
1088            log_d!(
1089                TAG,
1090                "Exposure logging is disabled for experiment {}",
1091                experiment_name
1092            );
1093            self.event_logger
1094                .increment_non_exposure_checks_count(experiment_name.to_string());
1095        } else {
1096            self.log_experiment_exposure(user_internal, experiment_name, &experiment, false);
1097        }
1098
1099        experiment
1100    }
1101
1102    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1103        let user_internal = self.internalize_user(user);
1104        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1105        self.log_experiment_exposure(user_internal, experiment_name, &experiment, true);
1106    }
1107
1108    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1109        let data = read_lock_or_else!(self.spec_store.data, {
1110            log_error_to_statsig_and_console!(
1111                self.ops_stats.clone(),
1112                TAG,
1113                "Failed to acquire read lock for spec store data"
1114            );
1115            return vec![];
1116        });
1117
1118        let config = data.values.dynamic_configs.get(experiment_name);
1119        match config {
1120            Some(config) => match &config.fields_used {
1121                Some(fields) => fields.clone(),
1122                None => vec![],
1123            },
1124            None => vec![],
1125        }
1126    }
1127}
1128
1129// -------------------------
1130//   Layer Functions
1131// -------------------------
1132
1133impl Statsig {
1134    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1135        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1136    }
1137
1138    pub fn get_layer_with_options(
1139        &self,
1140        user: &StatsigUser,
1141        layer_name: &str,
1142        options: LayerEvaluationOptions,
1143    ) -> Layer {
1144        let user_internal = self.internalize_user(user);
1145        self.get_layer_impl(&user_internal, layer_name, options)
1146    }
1147
1148    pub fn manually_log_layer_parameter_exposure(
1149        &self,
1150        user: &StatsigUser,
1151        layer_name: &str,
1152        parameter_name: String,
1153    ) {
1154        let user_internal = self.internalize_user(user);
1155        let layer = self.get_layer_impl(
1156            &user_internal,
1157            layer_name,
1158            LayerEvaluationOptions::default(),
1159        );
1160
1161        let layer_eval = layer.__evaluation.as_ref();
1162
1163        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1164            &layer.__user,
1165            layer_eval.map(AnyEvaluation::from).as_ref(),
1166            Some(parameter_name.as_str()),
1167        );
1168
1169        if !sampling_details.should_send_exposure {
1170            return;
1171        }
1172
1173        self.event_logger
1174            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
1175                user: layer.__user,
1176                parameter_name,
1177                evaluation: layer.__evaluation,
1178                layer_name: layer.name,
1179                evaluation_details: layer.details,
1180                version: layer.__version,
1181                is_manual_exposure: true,
1182                sampling_details,
1183                override_config_name: layer.__override_config_name.clone(),
1184            }));
1185    }
1186
1187    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1188        let data = read_lock_or_else!(self.spec_store.data, {
1189            log_error_to_statsig_and_console!(
1190                self.ops_stats.clone(),
1191                TAG,
1192                "Failed to acquire read lock for spec store data"
1193            );
1194            return vec![];
1195        });
1196
1197        let layer = data.values.layer_configs.get(layer_name);
1198        match layer {
1199            Some(layer) => match &layer.fields_used {
1200                Some(fields) => fields.clone(),
1201                None => vec![],
1202            },
1203            None => vec![],
1204        }
1205    }
1206}
1207
1208// -------------------------
1209//   Private Functions
1210// -------------------------
1211
1212impl Statsig {
1213    fn evaluate_spec<T>(
1214        &self,
1215        user_internal: &StatsigUserInternal,
1216        spec_name: &str,
1217        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1218        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1219        spec_type: &SpecType,
1220    ) -> T {
1221        let data = read_lock_or_else!(self.spec_store.data, {
1222            log_error_to_statsig_and_console!(
1223                &self.ops_stats,
1224                TAG,
1225                "Failed to acquire read lock for spec store data"
1226            );
1227            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1228        });
1229        let app_id = data.values.app_id.as_ref();
1230        let mut context = EvaluatorContext::new(
1231            user_internal,
1232            &data,
1233            &self.hashing,
1234            &app_id,
1235            &self.override_adapter,
1236        );
1237
1238        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1239            Ok(eval_details) => make_result(context.result, eval_details),
1240            Err(e) => {
1241                log_error_to_statsig_and_console!(&self.ops_stats, TAG, "Error evaluating: {}", e);
1242                make_empty_result(EvaluationDetails::error(&e.to_string()))
1243            }
1244        }
1245    }
1246
1247    fn get_feature_gate_impl(
1248        &self,
1249        user_internal: &StatsigUserInternal,
1250        gate_name: &str,
1251    ) -> FeatureGate {
1252        self.evaluate_spec(
1253            user_internal,
1254            gate_name,
1255            |eval_details| make_feature_gate(gate_name, None, eval_details, None, None),
1256            |mut result, eval_details| {
1257                let evaluation = result_to_gate_eval(gate_name, &mut result);
1258                make_feature_gate(
1259                    gate_name,
1260                    Some(evaluation),
1261                    eval_details,
1262                    result.version,
1263                    result.override_config_name,
1264                )
1265            },
1266            &SpecType::Gate,
1267        )
1268    }
1269
1270    fn get_dynamic_config_impl(
1271        &self,
1272        user_internal: &StatsigUserInternal,
1273        config_name: &str,
1274    ) -> DynamicConfig {
1275        self.evaluate_spec(
1276            user_internal,
1277            config_name,
1278            |eval_details| make_dynamic_config(config_name, None, eval_details, None, None),
1279            |mut result, eval_details| {
1280                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1281                make_dynamic_config(
1282                    config_name,
1283                    Some(evaluation),
1284                    eval_details,
1285                    result.version,
1286                    result.override_config_name,
1287                )
1288            },
1289            &SpecType::DynamicConfig,
1290        )
1291    }
1292
1293    fn get_experiment_impl(
1294        &self,
1295        user_internal: &StatsigUserInternal,
1296        experiment_name: &str,
1297    ) -> Experiment {
1298        self.evaluate_spec(
1299            user_internal,
1300            experiment_name,
1301            |eval_details| make_experiment(experiment_name, None, eval_details, None, None),
1302            |mut result, eval_details| {
1303                let data = read_lock_or_else!(self.spec_store.data, {
1304                    let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1305                    return make_experiment(
1306                        experiment_name,
1307                        Some(evaluation),
1308                        eval_details,
1309                        result.version,
1310                        result.override_config_name,
1311                    );
1312                });
1313                let evaluation = result_to_experiment_eval(
1314                    experiment_name,
1315                    data.values.dynamic_configs.get(experiment_name),
1316                    &mut result,
1317                );
1318                make_experiment(
1319                    experiment_name,
1320                    Some(evaluation),
1321                    eval_details,
1322                    result.version,
1323                    result.override_config_name,
1324                )
1325            },
1326            &SpecType::Experiment,
1327        )
1328    }
1329
1330    fn get_layer_impl(
1331        &self,
1332        user_internal: &StatsigUserInternal,
1333        layer_name: &str,
1334        evaluation_options: LayerEvaluationOptions,
1335    ) -> Layer {
1336        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1337        if disable_exposure_logging {
1338            self.event_logger
1339                .increment_non_exposure_checks_count(layer_name.to_string());
1340        }
1341
1342        self.evaluate_spec(
1343            user_internal,
1344            layer_name,
1345            |eval_details| {
1346                make_layer(
1347                    user_internal,
1348                    layer_name,
1349                    None,
1350                    eval_details,
1351                    None,
1352                    None,
1353                    disable_exposure_logging,
1354                    None,
1355                    None,
1356                )
1357            },
1358            |mut result, eval_details| {
1359                let evaluation = result_to_layer_eval(layer_name, &mut result);
1360                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1361                let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1362
1363                make_layer(
1364                    user_internal,
1365                    layer_name,
1366                    Some(evaluation),
1367                    eval_details,
1368                    Some(event_logger_ptr),
1369                    result.version,
1370                    disable_exposure_logging,
1371                    Some(sampling_processor_ptr),
1372                    result.override_config_name,
1373                )
1374            },
1375            &SpecType::Layer,
1376        )
1377    }
1378
1379    fn log_gate_exposure(
1380        &self,
1381        user_internal: StatsigUserInternal,
1382        gate_name: &str,
1383        gate: &FeatureGate,
1384        is_manual: bool,
1385    ) {
1386        let gate_eval = gate.__evaluation.as_ref();
1387        let secondary_exposures = gate_eval.map(|eval| &eval.base.secondary_exposures);
1388
1389        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1390            &user_internal,
1391            gate_eval.map(AnyEvaluation::from).as_ref(),
1392            None,
1393        );
1394
1395        if !sampling_details.should_send_exposure {
1396            return;
1397        }
1398
1399        self.event_logger
1400            .enqueue(QueuedEventPayload::GateExposure(GateExposure {
1401                user: user_internal,
1402                gate_name: gate_name.to_string(),
1403                value: gate.value,
1404                rule_id: Some(gate.rule_id.clone()),
1405                secondary_exposures: secondary_exposures.cloned(),
1406                evaluation_details: gate.details.clone(),
1407                version: gate.__version,
1408                is_manual_exposure: is_manual,
1409                sampling_details,
1410                override_config_name: gate.__override_config_name.clone(),
1411            }));
1412    }
1413
1414    fn log_dynamic_config_exposure(
1415        &self,
1416        user_internal: StatsigUserInternal,
1417        dynamic_config_name: &str,
1418        dynamic_config: &DynamicConfig,
1419        is_manual: bool,
1420    ) {
1421        let config_eval = dynamic_config.__evaluation.as_ref();
1422        let base_eval = config_eval.map(|eval| eval.base.clone());
1423
1424        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1425            &user_internal,
1426            config_eval.map(AnyEvaluation::from).as_ref(),
1427            None,
1428        );
1429
1430        if !sampling_details.should_send_exposure {
1431            return;
1432        }
1433
1434        self.event_logger
1435            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1436                user: user_internal,
1437                evaluation: base_eval,
1438                evaluation_details: dynamic_config.details.clone(),
1439                config_name: dynamic_config_name.to_string(),
1440                rule_passed: dynamic_config.__evaluation.as_ref().map(|eval| eval.passed),
1441                version: dynamic_config.__version,
1442                is_manual_exposure: is_manual,
1443                sampling_details,
1444                override_config_name: dynamic_config.__override_config_name.clone(),
1445            }));
1446    }
1447
1448    fn log_experiment_exposure(
1449        &self,
1450        user_internal: StatsigUserInternal,
1451        experiment_name: &str,
1452        experiment: &Experiment,
1453        is_manual: bool,
1454    ) {
1455        let experiment_eval = experiment.__evaluation.as_ref();
1456        let base_eval = experiment_eval.map(|eval| eval.base.clone());
1457
1458        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1459            &user_internal,
1460            experiment_eval.map(AnyEvaluation::from).as_ref(),
1461            None,
1462        );
1463
1464        if !sampling_details.should_send_exposure {
1465            return;
1466        }
1467
1468        self.event_logger
1469            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1470                user: user_internal,
1471                evaluation: base_eval,
1472                evaluation_details: experiment.details.clone(),
1473                config_name: experiment_name.to_string(),
1474                rule_passed: None,
1475                version: experiment.__version,
1476                is_manual_exposure: is_manual,
1477                sampling_details,
1478                override_config_name: experiment.__override_config_name.clone(),
1479            }));
1480    }
1481
1482    fn internalize_user(&self, user: &StatsigUser) -> StatsigUserInternal {
1483        StatsigUserInternal::new(
1484            user,
1485            self.get_statsig_env(),
1486            self.options.global_custom_fields.clone(),
1487        )
1488    }
1489
1490    fn get_statsig_env(&self) -> Option<HashMap<String, DynamicValue>> {
1491        if let Some(env) = &self.statsig_environment {
1492            return Some(env.clone());
1493        }
1494
1495        if let Ok(fallback_env) = self.fallback_environment.lock() {
1496            if let Some(env) = &*fallback_env {
1497                return Some(env.clone());
1498            }
1499        }
1500
1501        None
1502    }
1503
1504    fn set_default_environment_from_server(&self) {
1505        let data = read_lock_or_else!(self.spec_store.data, {
1506            return;
1507        });
1508
1509        if let Some(default_env) = data.values.default_environment.as_ref() {
1510            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1511
1512            if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1513                *fallback_env = Some(env_map);
1514            }
1515        }
1516    }
1517
1518    fn log_init_finish(
1519        &self,
1520        success: bool,
1521        error_message: &Option<String>,
1522        duration: &f64,
1523        specs_info: &SpecsInfo,
1524    ) {
1525        let is_store_populated = specs_info.source != SpecsSource::NoValues;
1526        let source_str = specs_info.source.to_string();
1527        self.ops_stats.log(ObservabilityEvent::new_event(
1528            MetricType::Dist,
1529            "initialization".to_string(),
1530            *duration,
1531            Some(HashMap::from([
1532                ("success".to_owned(), success.to_string()),
1533                ("source".to_owned(), source_str.clone()),
1534                ("store_populated".to_owned(), is_store_populated.to_string()),
1535            ])),
1536        ));
1537        self.ops_stats.add_marker(
1538            {
1539                let marker =
1540                    Marker::new(KeyType::Overall, ActionType::End, Some(StepType::Process))
1541                        .with_is_success(success)
1542                        .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1543                        .with_source(source_str);
1544
1545                if let Some(msg) = &error_message {
1546                    marker.with_message(msg.to_string())
1547                } else {
1548                    marker
1549                }
1550            },
1551            Some(ContextType::Initialize),
1552        );
1553        self.ops_stats
1554            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1555    }
1556}
1557
1558fn initialize_event_logging_adapter(
1559    sdk_key: &str,
1560    options: &StatsigOptions,
1561) -> Arc<dyn EventLoggingAdapter> {
1562    let adapter = options.event_logging_adapter.clone().unwrap_or_else(|| {
1563        Arc::new(StatsigHttpEventLoggingAdapter::new(
1564            sdk_key,
1565            options.log_event_url.as_ref(),
1566            options.disable_network,
1567        ))
1568    });
1569    adapter
1570}
1571
1572fn initialize_specs_adapter(
1573    sdk_key: &str,
1574    options: &StatsigOptions,
1575    hashing: &HashUtil,
1576) -> Arc<dyn SpecsAdapter> {
1577    if let Some(adapter) = options.specs_adapter.clone() {
1578        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1579        return adapter;
1580    }
1581
1582    if let Some(adapter_config) = options.spec_adapters_config.clone() {
1583        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1584            sdk_key,
1585            adapter_config,
1586            options,
1587            hashing,
1588        ));
1589    }
1590
1591    if let Some(data_adapter) = options.data_store.clone() {
1592        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1593            sdk_key,
1594            data_adapter,
1595            options,
1596            hashing,
1597        ));
1598    }
1599
1600    Arc::new(StatsigHttpSpecsAdapter::new(
1601        sdk_key,
1602        options.specs_url.as_ref(),
1603        options.fallback_to_statsig_api.unwrap_or(false),
1604        options.specs_sync_interval_ms,
1605        options.disable_network,
1606    ))
1607}
1608
1609fn initialize_id_lists_adapter(
1610    sdk_key: &str,
1611    options: &StatsigOptions,
1612) -> Option<Arc<dyn IdListsAdapter>> {
1613    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1614        return Some(id_lists_adapter);
1615    }
1616
1617    if options.enable_id_lists.unwrap_or(false) {
1618        return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1619    }
1620
1621    None
1622}
1623
1624fn setup_ops_stats(
1625    sdk_key: &str,
1626    options: &StatsigOptions,
1627    statsig_runtime: Arc<StatsigRuntime>,
1628    error_observer: &Arc<dyn OpsStatsEventObserver>,
1629    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1630    external_observer: &Option<Weak<dyn ObservabilityClient>>,
1631) -> Arc<OpsStatsForInstance> {
1632    // TODO migrate output logger to use ops_stats
1633    initialize_simple_output_logger(&options.output_log_level);
1634
1635    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1636    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1637    ops_stat.subscribe(
1638        statsig_runtime.clone(),
1639        Arc::downgrade(diagnostics_observer),
1640    );
1641
1642    if let Some(ob_client) = external_observer {
1643        if let Some(client) = ob_client.upgrade() {
1644            client.init();
1645            let as_observer = client.to_ops_stats_event_observer();
1646            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1647        }
1648    }
1649
1650    ops_stat
1651}
1652
1653#[cfg(test)]
1654mod tests {
1655    use super::*;
1656    use crate::hashing::djb2;
1657    use crate::{
1658        evaluation::evaluation_types::AnyConfigEvaluation, output_logger::LogLevel,
1659        StatsigHttpIdListsAdapter,
1660    };
1661    use std::env;
1662
1663    impl Statsig {}
1664
1665    fn get_sdk_key() -> String {
1666        let key = env::var("test_api_key").expect("test_api_key environment variable not set");
1667        assert!(key.starts_with("secret-9IWf"));
1668        key
1669    }
1670
1671    #[tokio::test]
1672    async fn test_check_gate() {
1673        let user = StatsigUser {
1674            email: Some(dyn_value!("daniel@statsig.com")),
1675            ..StatsigUser::with_user_id("a-user".to_string())
1676        };
1677
1678        let statsig = Statsig::new(&get_sdk_key(), None);
1679        statsig.initialize().await.unwrap();
1680
1681        let gate_result = statsig.check_gate(&user, "test_50_50");
1682
1683        assert!(gate_result);
1684    }
1685
1686    #[tokio::test]
1687    async fn test_check_gate_id_list() {
1688        let user = StatsigUser {
1689            custom_ids: Some(HashMap::from([(
1690                "companyID".to_string(),
1691                dyn_value!("marcos_1"),
1692            )])),
1693            ..StatsigUser::with_user_id("marcos_1".to_string())
1694        };
1695
1696        let mut opts = StatsigOptions::new();
1697
1698        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(&get_sdk_key(), &opts));
1699        opts.id_lists_adapter = Some(adapter);
1700
1701        let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1702        statsig.initialize().await.unwrap();
1703
1704        let gate_result = statsig.check_gate(&user, "test_id_list");
1705
1706        assert!(gate_result);
1707    }
1708
1709    #[tokio::test]
1710    async fn test_get_experiment() {
1711        let user = StatsigUser {
1712            email: Some(dyn_value!("daniel@statsig.com")),
1713            ..StatsigUser::with_user_id("a-user".to_string())
1714        };
1715
1716        let statsig = Statsig::new(&get_sdk_key(), None);
1717        statsig.initialize().await.unwrap();
1718
1719        let experiment = statsig.get_experiment(&user, "running_exp_in_unlayered_with_holdout");
1720        let _ = statsig.shutdown().await;
1721
1722        assert_ne!(experiment.value.len(), 0);
1723    }
1724
1725    #[tokio::test]
1726    async fn test_gcir() {
1727        let user = StatsigUser {
1728            email: Some(dyn_value!("daniel@statsig.com")),
1729            ..StatsigUser::with_user_id("a-user".to_string())
1730        };
1731        let opts = StatsigOptions {
1732            output_log_level: Some(LogLevel::Debug),
1733            ..StatsigOptions::new()
1734        };
1735
1736        let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1737        statsig.initialize().await.unwrap();
1738
1739        let response = statsig.get_client_init_response(&user);
1740        let _ = statsig.shutdown().await;
1741
1742        let gates = response.feature_gates;
1743        assert_eq!(gates.len(), 69);
1744
1745        let configs = response.dynamic_configs.len();
1746        assert_eq!(configs, 62);
1747
1748        let a_config_opt = response.dynamic_configs.get(&djb2("big_number"));
1749        let a_config = match a_config_opt {
1750            Some(v) => match v {
1751                AnyConfigEvaluation::DynamicConfig(config) => &config.value,
1752                AnyConfigEvaluation::Experiment(exp) => &exp.value,
1753            },
1754            None => panic!("Should have values"),
1755        };
1756
1757        assert!(!a_config.is_empty());
1758    }
1759}