statsig_rust/
statsig.rs

1use crate::evaluation::cmab_evaluator::{get_cmab_ranked_list, CMABRankedGroup};
2use crate::evaluation::country_lookup::CountryLookup;
3use crate::evaluation::dynamic_value::DynamicValue;
4use crate::evaluation::evaluation_details::EvaluationDetails;
5use crate::evaluation::evaluation_types::{AnyEvaluation, BaseEvaluation, ExperimentEvaluation};
6use crate::evaluation::evaluator::{Evaluator, SpecType};
7use crate::evaluation::evaluator_context::EvaluatorContext;
8use crate::evaluation::evaluator_result::{
9    result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
10    result_to_layer_eval, EvaluatorResult,
11};
12use crate::evaluation::ua_parser::UserAgentParser;
13use crate::event_logging::config_exposure::ConfigExposure;
14use crate::event_logging::event_logger::{EventLogger, QueuedEventPayload};
15use crate::event_logging::gate_exposure::GateExposure;
16use crate::event_logging::layer_exposure::LayerExposure;
17use crate::event_logging::statsig_event::StatsigEvent;
18use crate::event_logging::statsig_event_internal::make_custom_event;
19use crate::event_logging_adapter::EventLoggingAdapter;
20use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
21use crate::gcir::gcir_formatter::GCIRFormatter;
22use crate::hashing::HashUtil;
23use crate::initialize_response::InitializeResponse;
24use crate::observability::diagnostics_observer::DiagnosticsObserver;
25use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
26use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
27use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
28use crate::output_logger::initialize_simple_output_logger;
29use crate::persistent_storage::persistent_values_manager::PersistentValuesManager;
30use crate::sdk_diagnostics::diagnostics::{ContextType, Diagnostics};
31use crate::sdk_diagnostics::marker::{ActionType, KeyType, Marker};
32use crate::spec_store::SpecStore;
33use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
34use crate::statsig_err::StatsigErr;
35use crate::statsig_metadata::StatsigMetadata;
36use crate::statsig_options::StatsigOptions;
37use crate::statsig_runtime::StatsigRuntime;
38use crate::statsig_type_factories::{
39    make_dynamic_config, make_experiment, make_feature_gate, make_layer,
40};
41use crate::statsig_types::{DynamicConfig, Experiment, FeatureGate, Layer, ParameterStore};
42use crate::user::StatsigUserInternal;
43use crate::{
44    dyn_value, log_d, log_e, log_w, read_lock_or_else, ClientInitResponseOptions,
45    GCIRResponseFormat, IdListsAdapter, ObservabilityClient, OpsStatsEventObserver,
46    OverrideAdapter, SamplingProcessor, SpecsAdapter, SpecsInfo, SpecsSource, SpecsUpdateListener,
47    StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter, StatsigUser,
48};
49use crate::{
50    log_error_to_statsig_and_console,
51    statsig_core_api_options::{
52        DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
53        LayerEvaluationOptions, ParameterStoreEvaluationOptions,
54    },
55};
56use serde::de::DeserializeOwned;
57use serde_json::json;
58use serde_json::Value;
59use std::collections::HashMap;
60use std::sync::atomic::{AtomicBool, Ordering};
61use std::sync::Mutex;
62use std::sync::{Arc, Weak};
63use std::time::{Duration, Instant};
64use tokio::time::sleep;
65use tokio::try_join;
66
67const TAG: &str = stringify!(Statsig);
68const ERROR_SDK_KEY: &str = "__STATSIG_ERROR_SDK_KEY__";
69const INIT_IP_TAG: &str = "INIT_COUNTRY_LOOKUP";
70const INIT_UA_TAG: &str = "INIT_UA";
71
72lazy_static::lazy_static! {
73    static ref SHARED_INSTANCE: Mutex<Option<Arc<Statsig>>> = Mutex::new(None);
74}
75
76pub struct Statsig {
77    pub statsig_runtime: Arc<StatsigRuntime>,
78
79    sdk_key: String,
80    options: Arc<StatsigOptions>,
81    event_logger: Arc<EventLogger>,
82    specs_adapter: Arc<dyn SpecsAdapter>,
83    event_logging_adapter: Arc<dyn EventLoggingAdapter>,
84    id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
85    override_adapter: Option<Arc<dyn OverrideAdapter>>,
86    spec_store: Arc<SpecStore>,
87    hashing: Arc<HashUtil>,
88    gcir_formatter: Arc<GCIRFormatter>,
89    statsig_environment: Option<HashMap<String, DynamicValue>>,
90    fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
91    ops_stats: Arc<OpsStatsForInstance>,
92    error_observer: Arc<dyn OpsStatsEventObserver>,
93    diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
94    sampling_processor: Arc<SamplingProcessor>,
95    background_tasks_started: Arc<AtomicBool>,
96    persistent_values_manager: Option<Arc<PersistentValuesManager>>,
97    initialize_details: Mutex<InitializeDetails>,
98}
99
100pub struct StatsigContext {
101    pub sdk_key: String,
102    pub options: Arc<StatsigOptions>,
103    pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
104    pub error_observer: Arc<dyn OpsStatsEventObserver>,
105    pub diagnostics_observer: Arc<dyn OpsStatsEventObserver>,
106    pub spec_store: Arc<SpecStore>,
107}
108
109#[derive(Debug, Clone)]
110pub struct FailureDetails {
111    pub reason: String,
112    pub error: Option<StatsigErr>,
113}
114
115#[derive(Debug, Clone)]
116pub struct InitializeDetails {
117    pub duration: f64,
118    pub init_success: bool,
119    pub is_config_spec_ready: bool,
120    pub is_id_list_ready: Option<bool>,
121    pub source: SpecsSource,
122    pub failure_details: Option<FailureDetails>,
123}
124
125impl Default for InitializeDetails {
126    fn default() -> Self {
127        InitializeDetails {
128            duration: 0.0,
129            init_success: false,
130            is_config_spec_ready: false,
131            is_id_list_ready: None,
132            source: SpecsSource::Uninitialized,
133            failure_details: None,
134        }
135    }
136}
137
138impl InitializeDetails {
139    pub fn from_error(reason: &str, error: Option<StatsigErr>) -> Self {
140        InitializeDetails {
141            duration: 0.0,
142            init_success: false,
143            is_config_spec_ready: false,
144            is_id_list_ready: None,
145            source: SpecsSource::Uninitialized,
146            failure_details: Some(FailureDetails {
147                reason: reason.to_string(),
148                error,
149            }),
150        }
151    }
152}
153
154impl Statsig {
155    pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
156        let statsig_runtime = StatsigRuntime::get_runtime();
157        let options = options.unwrap_or_default();
158
159        let hashing = Arc::new(HashUtil::new());
160
161        let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
162        let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
163        let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
164        let override_adapter = match options.override_adapter.as_ref() {
165            Some(adapter) => Some(Arc::clone(adapter)),
166            None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
167        };
168
169        let event_logger = Arc::new(EventLogger::new(
170            sdk_key,
171            event_logging_adapter.clone(),
172            &options,
173            &statsig_runtime,
174        ));
175
176        let diagnostics = Arc::new(Diagnostics::new(event_logger.clone(), sdk_key));
177        let diagnostics_observer: Arc<dyn OpsStatsEventObserver> =
178            Arc::new(DiagnosticsObserver::new(diagnostics));
179        let error_observer: Arc<dyn OpsStatsEventObserver> =
180            Arc::new(SDKErrorsObserver::new(sdk_key, &options));
181
182        let ops_stats = setup_ops_stats(
183            sdk_key,
184            &options,
185            statsig_runtime.clone(),
186            &error_observer,
187            &diagnostics_observer,
188            &options.observability_client,
189        );
190
191        let spec_store = Arc::new(SpecStore::new(
192            sdk_key,
193            hashing.sha256(sdk_key),
194            statsig_runtime.clone(),
195            options.data_store.clone(),
196        ));
197
198        let environment = options
199            .environment
200            .as_ref()
201            .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
202
203        let sampling_processor = Arc::new(SamplingProcessor::new(
204            &statsig_runtime,
205            hashing.clone(),
206            sdk_key,
207        ));
208
209        let persistent_values_manager = options.persistent_storage.clone().map(|storage| {
210            Arc::new(PersistentValuesManager {
211                persistent_storage: storage,
212            })
213        });
214
215        StatsigMetadata::update_service_name(options.service_name.clone());
216
217        Statsig {
218            sdk_key: sdk_key.to_string(),
219            options,
220            gcir_formatter: Arc::new(GCIRFormatter::new(&spec_store, &override_adapter)),
221            event_logger,
222            hashing,
223            statsig_environment: environment,
224            fallback_environment: Mutex::new(None),
225            override_adapter,
226            spec_store,
227            specs_adapter,
228            event_logging_adapter,
229            id_lists_adapter,
230            statsig_runtime,
231            ops_stats,
232            error_observer,
233            sampling_processor,
234            diagnostics_observer,
235            background_tasks_started: Arc::new(AtomicBool::new(false)),
236            persistent_values_manager,
237            initialize_details: Mutex::new(InitializeDetails::default()),
238        }
239    }
240
241    /***
242     *  Initializes the Statsig client and returns an error if initialization fails.
243     *
244     *  This method performs the client initialization and returns `Ok(())` if successful.
245     *  If the initialization completes with failure details, it returns a [`StatsigErr`]
246     *  describing the failure.
247     *
248     *  For detailed information about the initialization process—regardless of success or failure—
249     *  use [`initialize_with_details`] instead.
250     *
251     *  # Errors
252     *
253     *  Returns a [`StatsigErr`] if the client fails to initialize successfully.
254     */
255    pub async fn initialize(&self) -> Result<(), StatsigErr> {
256        let details = self.initialize_with_details().await?;
257
258        if let Some(failure_details) = details.failure_details {
259            Err(failure_details
260                .error
261                .unwrap_or(StatsigErr::InitializationError(failure_details.reason)))
262        } else {
263            Ok(())
264        }
265    }
266
267    /***
268     *  Initializes the Statsig client and returns detailed information about the process.
269     *
270     *  This method returns a [`StatsigInitializeDetails`] struct, which includes metadata such as
271     *  the success status, initialization source, and any failure details. Even if initialization
272     *  fails, this method does not return an error; instead, the `init_success` field will be `false`
273     *  and `failure_details` may be populated.
274     ***/
275    pub async fn initialize_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
276        self.ops_stats.add_marker(
277            Marker::new(KeyType::Overall, ActionType::Start, None),
278            Some(ContextType::Initialize),
279        );
280
281        let init_details = if let Some(timeout_ms) = self.options.init_timeout_ms {
282            self.apply_timeout_to_init(timeout_ms).await
283        } else {
284            self.initialize_impl_with_details().await
285        };
286        self.log_init_details(&init_details);
287        if let Ok(details) = &init_details {
288            if let Ok(mut curr_init_details) = self.initialize_details.try_lock() {
289                *curr_init_details = details.clone();
290            }
291        }
292        init_details
293    }
294
295    pub fn get_initialize_details(&self) -> InitializeDetails {
296        match self.initialize_details.lock() {
297            Ok(details) => details.clone(),
298            Err(poison_error) => InitializeDetails::from_error(
299                "Failed to lock initialize_details",
300                Some(StatsigErr::LockFailure(poison_error.to_string())),
301            ),
302        }
303    }
304
305    pub fn is_initialized(&self) -> bool {
306        match self.initialize_details.lock() {
307            Ok(details) => details.init_success,
308            Err(_) => false,
309        }
310    }
311
312    pub async fn shutdown(&self) -> Result<(), StatsigErr> {
313        self.shutdown_with_timeout(Duration::from_secs(3)).await
314    }
315
316    pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
317        log_d!(
318            TAG,
319            "Shutting down Statsig with timeout {}ms",
320            timeout.as_millis()
321        );
322
323        let start = Instant::now();
324        let shutdown_result = tokio::select! {
325            () = tokio::time::sleep(timeout) => {
326                log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
327                Err(StatsigErr::ShutdownFailure(
328                    "Shutdown timed out".to_string()
329                ))
330            }
331            sub_result = async {
332                let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter {
333                    adapter.shutdown(timeout)
334                } else {
335                    Box::pin(async { Ok(()) })
336                };
337
338                try_join!(
339                    id_list_shutdown,
340                    self.event_logger.shutdown(timeout),
341                    self.specs_adapter.shutdown(timeout, &self.statsig_runtime),
342                )
343            } => {
344                match sub_result {
345                    Ok(_) => {
346                        log_d!(TAG, "All Statsig tasks shutdown successfully");
347                        Ok(())
348                    }
349                    Err(e) => {
350                        log_w!(TAG, "Error during shutdown: {:?}", e);
351                        Err(e)
352                    }
353                }
354            }
355        };
356
357        self.statsig_runtime.shutdown();
358        shutdown_result
359    }
360
361    async fn start_background_tasks(
362        event_logger: Arc<EventLogger>,
363        statsig_runtime: Arc<StatsigRuntime>,
364        id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
365        specs_adapter: Arc<dyn SpecsAdapter>,
366        ops_stats: Arc<OpsStatsForInstance>,
367        bg_tasks_started: Arc<AtomicBool>,
368    ) -> bool {
369        if bg_tasks_started.load(Ordering::SeqCst) {
370            return true;
371        }
372
373        let mut success = true;
374        event_logger.clone().start_background_task(&statsig_runtime);
375
376        if let Some(adapter) = &id_lists_adapter {
377            if let Err(e) = adapter
378                .clone()
379                .schedule_background_sync(&statsig_runtime)
380                .await
381            {
382                success = false;
383                log_w!(TAG, "Failed to schedule idlist background job {}", e);
384            }
385        }
386
387        if let Err(e) = specs_adapter
388            .clone()
389            .schedule_background_sync(&statsig_runtime)
390            .await
391        {
392            success = false;
393            log_error_to_statsig_and_console!(
394                ops_stats,
395                TAG,
396                StatsigErr::SpecsAdapterSkipPoll(format!(
397                    "Failed to schedule specs adapter background job: {}",
398                    e
399                ))
400            );
401        }
402
403        bg_tasks_started.store(true, Ordering::SeqCst);
404
405        success
406    }
407
408    async fn apply_timeout_to_init(
409        &self,
410        timeout_ms: u64,
411    ) -> Result<InitializeDetails, StatsigErr> {
412        let timeout = Duration::from_millis(timeout_ms);
413
414        let init_future = self.initialize_impl_with_details();
415        let timeout_future = sleep(timeout);
416
417        let event_logger = self.event_logger.clone();
418        let statsig_runtime = self.statsig_runtime.clone();
419        let id_lists_adapter = self.id_lists_adapter.clone();
420        let specs_adapter = self.specs_adapter.clone();
421        let ops_stats = self.ops_stats.clone();
422        let background_tasks_started = self.background_tasks_started.clone();
423        // Create another clone specifically for the closure
424        let statsig_runtime_for_closure = statsig_runtime.clone();
425
426        tokio::select! {
427            result = init_future => {
428                result
429            },
430            _ = timeout_future => {
431                statsig_runtime.spawn(
432                    "start_background_tasks",
433                    |_shutdown_notify| async move {
434                        Self::start_background_tasks(
435                            event_logger,
436                            statsig_runtime_for_closure,
437                            id_lists_adapter,
438                            specs_adapter,
439                            ops_stats,
440                            background_tasks_started,
441                        ).await;
442                    }
443                );
444                Ok(self.timeout_failure(timeout_ms))
445            },
446        }
447    }
448
449    async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
450        let start_time = Instant::now();
451        self.spec_store.set_source(SpecsSource::Loading);
452        self.specs_adapter.initialize(self.spec_store.clone());
453
454        let mut error_message = None;
455        let mut id_list_ready = None;
456
457        let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
458            Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
459                CountryLookup::load_country_lookup();
460            }))
461        } else {
462            None
463        };
464
465        let init_ua = if !self.options.disable_user_agent_parsing.unwrap_or_default() {
466            Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
467                UserAgentParser::load_parser();
468            }))
469        } else {
470            None
471        };
472
473        let init_res = match self
474            .specs_adapter
475            .clone()
476            .start(&self.statsig_runtime)
477            .await
478        {
479            Ok(()) => Ok(()),
480            Err(e) => {
481                self.spec_store.set_source(SpecsSource::NoValues);
482                error_message = Some(format!("Failed to start specs adapter: {e}"));
483                Err(e)
484            }
485        };
486
487        if let Some(adapter) = &self.id_lists_adapter {
488            match adapter
489                .clone()
490                .start(&self.statsig_runtime, self.spec_store.clone())
491                .await
492            {
493                Ok(()) => {
494                    id_list_ready = Some(true);
495                }
496                Err(e) => {
497                    id_list_ready = Some(false);
498                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
499                }
500            }
501            if let Err(e) = adapter
502                .clone()
503                .schedule_background_sync(&self.statsig_runtime)
504                .await
505            {
506                log_w!(TAG, "Failed to schedule id_list background job {}", e);
507            }
508        }
509
510        if let Err(e) = self
511            .event_logging_adapter
512            .clone()
513            .start(&self.statsig_runtime)
514            .await
515        {
516            log_error_to_statsig_and_console!(
517                self.ops_stats.clone(),
518                TAG,
519                StatsigErr::UnstartedAdapter(format!(
520                    "Failed to start event logging adapter: {}",
521                    e
522                ))
523            );
524        }
525
526        let spec_info = self.spec_store.get_current_specs_info();
527        let duration = start_time.elapsed().as_millis() as f64;
528
529        self.set_default_environment_from_server();
530
531        if self.options.wait_for_country_lookup_init.unwrap_or(false) {
532            if let Some(task_id) = init_country_lookup {
533                let _ = self
534                    .statsig_runtime
535                    .await_join_handle(INIT_IP_TAG, &task_id)
536                    .await;
537            }
538        }
539        if self.options.wait_for_user_agent_init.unwrap_or(false) {
540            if let Some(task_id) = init_ua {
541                let _ = self
542                    .statsig_runtime
543                    .await_join_handle(INIT_UA_TAG, &task_id)
544                    .await;
545            };
546        }
547
548        let error = init_res.clone().err();
549
550        let success = Self::start_background_tasks(
551            self.event_logger.clone(),
552            self.statsig_runtime.clone(),
553            self.id_lists_adapter.clone(),
554            self.specs_adapter.clone(),
555            self.ops_stats.clone(),
556            self.background_tasks_started.clone(),
557        )
558        .await;
559
560        Ok(InitializeDetails {
561            init_success: success,
562            is_config_spec_ready: matches!(spec_info.lcut, Some(v) if v != 0),
563            is_id_list_ready: id_list_ready,
564            source: spec_info.source,
565            failure_details: error.as_ref().map(|e| FailureDetails {
566                reason: e.to_string(),
567                error: Some(e.clone()),
568            }),
569            duration,
570        })
571    }
572
573    fn timeout_failure(&self, timeout_ms: u64) -> InitializeDetails {
574        InitializeDetails {
575            init_success: false,
576            is_config_spec_ready: false,
577            is_id_list_ready: None,
578            source: SpecsSource::Uninitialized,
579            failure_details: Some(FailureDetails {
580                reason: "Initialization timed out".to_string(),
581                error: None,
582            }),
583            duration: timeout_ms as f64,
584        }
585    }
586
587    fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
588        match init_details {
589            Ok(details) => {
590                self.log_init_finish(
591                    details.init_success,
592                    &None,
593                    &details.duration,
594                    &self.spec_store.get_current_specs_info(),
595                );
596                if let Some(failure) = &details.failure_details {
597                    log_error_to_statsig_and_console!(
598                        self.ops_stats,
599                        TAG,
600                        StatsigErr::InitializationError(failure.reason.clone())
601                    );
602                }
603            }
604            Err(err) => {
605                log_w!(TAG, "Initialization error: {:?}", err);
606            }
607        }
608    }
609
610    pub fn get_context(&self) -> StatsigContext {
611        StatsigContext {
612            sdk_key: self.sdk_key.clone(),
613            options: self.options.clone(),
614            local_override_adapter: self.override_adapter.clone(),
615            error_observer: self.error_observer.clone(),
616            diagnostics_observer: self.diagnostics_observer.clone(),
617            spec_store: self.spec_store.clone(),
618        }
619    }
620
621    pub fn log_event(
622        &self,
623        user: &StatsigUser,
624        event_name: &str,
625        value: Option<String>,
626        metadata: Option<HashMap<String, String>>,
627    ) {
628        let user_internal = self.internalize_user(user);
629
630        self.event_logger
631            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
632                user_internal.to_loggable(),
633                StatsigEvent {
634                    event_name: event_name.to_string(),
635                    value: value.map(|v| json!(v)),
636                    metadata,
637                    statsig_metadata: None,
638                },
639            )));
640    }
641
642    pub fn log_event_with_number(
643        &self,
644        user: &StatsigUser,
645        event_name: &str,
646        value: Option<f64>,
647        metadata: Option<HashMap<String, String>>,
648    ) {
649        let user_internal = self.internalize_user(user);
650
651        self.event_logger
652            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
653                user_internal.to_loggable(),
654                StatsigEvent {
655                    event_name: event_name.to_string(),
656                    value: value.map(|v| json!(v)),
657                    metadata,
658                    statsig_metadata: None,
659                },
660            )));
661    }
662
663    pub fn log_layer_param_exposure_with_layer_json(
664        &self,
665        layer_json: String,
666        parameter_name: String,
667    ) {
668        let layer = match serde_json::from_str::<Layer>(&layer_json) {
669            Ok(layer) => layer,
670            Err(e) => {
671                log_error_to_statsig_and_console!(
672                    self.ops_stats.clone(),
673                    TAG,
674                    StatsigErr::ShutdownFailure(e.to_string())
675                );
676                return;
677            }
678        };
679
680        self.log_layer_param_exposure_with_layer(layer, parameter_name);
681    }
682
683    pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
684        if layer.__disable_exposure {
685            self.event_logger
686                .increment_non_exposure_checks_count(layer.name.clone());
687            return;
688        }
689
690        let layer_eval = layer.__evaluation.as_ref();
691
692        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
693            &layer.__user.get_sampling_key(),
694            layer_eval.map(AnyEvaluation::from).as_ref(),
695            Some(&parameter_name),
696        );
697
698        if !sampling_details.should_send_exposure {
699            return;
700        }
701
702        self.event_logger
703            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
704                user: layer.__user,
705                parameter_name,
706                evaluation: layer.__evaluation,
707                layer_name: layer.name,
708                evaluation_details: layer.details,
709                version: layer.__version,
710                is_manual_exposure: false,
711                sampling_details,
712                override_config_name: layer.__override_config_name.clone(),
713            }));
714    }
715
716    pub async fn flush_events(&self) {
717        self.event_logger.flush_all_blocking().await;
718    }
719
720    pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
721        self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
722    }
723
724    pub fn get_client_init_response_with_options(
725        &self,
726        user: &StatsigUser,
727        options: &ClientInitResponseOptions,
728    ) -> InitializeResponse {
729        let user_internal = self.internalize_user(user);
730        self.gcir_formatter
731            .get_as_v1_format(user_internal, &self.hashing, options)
732    }
733
734    pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
735        serde_json::to_string(&self.get_client_init_response(user)).unwrap_or_default()
736    }
737
738    pub fn get_client_init_response_with_options_as_string(
739        &self,
740        user: &StatsigUser,
741        options: &ClientInitResponseOptions,
742    ) -> String {
743        let user_internal = self.internalize_user(user);
744        let response = match options.response_format {
745            Some(GCIRResponseFormat::InitializeWithSecondaryExposureMapping) => {
746                json!(self
747                    .gcir_formatter
748                    .get_as_v2_format(user_internal, &self.hashing, options))
749            }
750            _ => json!(self
751                .gcir_formatter
752                .get_as_v1_format(user_internal, &self.hashing, options)),
753        };
754
755        json!(response).to_string()
756    }
757
758    pub fn get_string_parameter_from_store(
759        &self,
760        user: &StatsigUser,
761        parameter_store_name: &str,
762        parameter_name: &str,
763        fallback: Option<String>,
764    ) -> Option<String> {
765        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
766    }
767
768    pub fn get_boolean_parameter_from_store(
769        &self,
770        user: &StatsigUser,
771        parameter_store_name: &str,
772        parameter_name: &str,
773        fallback: Option<bool>,
774    ) -> Option<bool> {
775        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
776    }
777
778    pub fn get_float_parameter_from_store(
779        &self,
780        user: &StatsigUser,
781        parameter_store_name: &str,
782        parameter_name: &str,
783        fallback: Option<f64>,
784    ) -> Option<f64> {
785        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
786    }
787
788    pub fn get_integer_parameter_from_store(
789        &self,
790        user: &StatsigUser,
791        parameter_store_name: &str,
792        parameter_name: &str,
793        fallback: Option<i64>,
794    ) -> Option<i64> {
795        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
796    }
797
798    pub fn get_array_parameter_from_store(
799        &self,
800        user: &StatsigUser,
801        parameter_store_name: &str,
802        parameter_name: &str,
803        fallback: Option<Vec<Value>>,
804    ) -> Option<Vec<Value>> {
805        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
806    }
807
808    pub fn get_object_parameter_from_store(
809        &self,
810        user: &StatsigUser,
811        parameter_store_name: &str,
812        parameter_name: &str,
813        fallback: Option<HashMap<String, Value>>,
814    ) -> Option<HashMap<String, Value>> {
815        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
816    }
817
818    pub fn get_parameter_from_store<T: DeserializeOwned>(
819        &self,
820        user: &StatsigUser,
821        parameter_store_name: &str,
822        parameter_name: &str,
823        fallback: Option<T>,
824    ) -> Option<T> {
825        let store = self.get_parameter_store(parameter_store_name);
826        match fallback {
827            Some(fallback) => Some(store.get(user, parameter_name, fallback)),
828            None => store.get_opt(user, parameter_name),
829        }
830    }
831
832    pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore {
833        self.get_parameter_store_with_options(
834            parameter_store_name,
835            ParameterStoreEvaluationOptions::default(),
836        )
837    }
838
839    pub fn get_parameter_store_with_options(
840        &self,
841        parameter_store_name: &str,
842        options: ParameterStoreEvaluationOptions,
843    ) -> ParameterStore {
844        self.event_logger
845            .increment_non_exposure_checks_count(parameter_store_name.to_string());
846        let data = read_lock_or_else!(self.spec_store.data, {
847            log_error_to_statsig_and_console!(
848                self.ops_stats.clone(),
849                TAG,
850                StatsigErr::LockFailure(
851                    "Failed to acquire read lock for spec store data".to_string()
852                )
853            );
854            return ParameterStore {
855                name: parameter_store_name.to_string(),
856                parameters: HashMap::new(),
857                details: EvaluationDetails::unrecognized_no_data(),
858                options,
859                _statsig_ref: self,
860            };
861        });
862
863        let stores = &data.values.param_stores;
864        let store = match stores {
865            Some(stores) => stores.get(parameter_store_name),
866            None => {
867                return ParameterStore {
868                    name: parameter_store_name.to_string(),
869                    parameters: HashMap::new(),
870                    details: EvaluationDetails::unrecognized(&data),
871                    options,
872                    _statsig_ref: self,
873                };
874            }
875        };
876        match store {
877            Some(store) => ParameterStore {
878                name: parameter_store_name.to_string(),
879                parameters: store.parameters.clone(),
880                details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
881                options,
882                _statsig_ref: self,
883            },
884            None => ParameterStore {
885                name: parameter_store_name.to_string(),
886                parameters: HashMap::new(),
887                details: EvaluationDetails::unrecognized(&data),
888                options,
889                _statsig_ref: self,
890            },
891        }
892    }
893}
894
895// -------------------------
896//   User Store Functions
897// -------------------------
898impl Statsig {
899    pub fn identify(&self, user: &StatsigUser) {
900        let user_internal = self.internalize_user(user);
901
902        self.event_logger
903            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
904                user_internal.to_loggable(),
905                StatsigEvent {
906                    event_name: "statsig::identify".to_string(),
907                    value: None,
908                    metadata: None,
909                    statsig_metadata: None,
910                },
911            )));
912    }
913}
914
915// -------------------------
916//   CMAB Functions
917// -------------------------
918
919impl Statsig {
920    pub fn get_cmab_ranked_groups(
921        &self,
922        user: &StatsigUser,
923        cmab_name: &str,
924    ) -> Vec<CMABRankedGroup> {
925        self.event_logger
926            .increment_non_exposure_checks_count(cmab_name.to_string());
927        let data = read_lock_or_else!(self.spec_store.data, {
928            log_error_to_statsig_and_console!(
929                self.ops_stats.clone(),
930                TAG,
931                StatsigErr::LockFailure(
932                    "Failed to acquire read lock for spec store data".to_string()
933                )
934            );
935            return vec![];
936        });
937        let user_internal = self.internalize_user(user);
938        get_cmab_ranked_list(
939            &mut EvaluatorContext::new(
940                &user_internal,
941                &data,
942                &self.hashing,
943                data.values.app_id.as_ref(),
944                self.override_adapter.as_ref(),
945            ),
946            cmab_name,
947        )
948    }
949
950    pub fn log_cmab_exposure_for_group(
951        &self,
952        user: &StatsigUser,
953        cmab_name: &str,
954        group_id: String,
955    ) {
956        let user_internal = self.internalize_user(user);
957        let experiment = self.get_experiment_impl(&user_internal, cmab_name);
958        let sampling_info = match experiment.__evaluation {
959            Some(ref eval) => eval.base.sampling_info.clone(),
960            None => None,
961        };
962        let base_eval = BaseEvaluation {
963            name: cmab_name.to_string(),
964            rule_id: group_id.clone(),
965            secondary_exposures: match experiment.__evaluation {
966                Some(ref eval) => eval.base.secondary_exposures.clone(),
967                None => vec![],
968            },
969            sampling_info,
970        };
971        let experiment_eval = ExperimentEvaluation {
972            base: base_eval.clone(),
973            id_type: experiment.id_type.clone(),
974            value: HashMap::new(),
975            group: group_id,
976            is_device_based: false,
977            is_in_layer: false,
978            explicit_parameters: None,
979            group_name: None,
980            is_experiment_active: Some(true),
981            is_user_in_experiment: Some(true),
982            undelegated_secondary_exposures: None,
983        };
984
985        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
986            &user_internal.get_sampling_key(),
987            Some(&AnyEvaluation::from(&experiment_eval)),
988            None,
989        );
990
991        if !sampling_details.should_send_exposure {
992            return;
993        }
994
995        self.event_logger
996            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
997                user: user_internal.to_loggable(),
998                evaluation: Some(base_eval),
999                evaluation_details: experiment.details.clone(),
1000                config_name: cmab_name.to_string(),
1001                rule_passed: None,
1002                version: experiment.__version,
1003                is_manual_exposure: true,
1004                sampling_details,
1005                override_config_name: experiment.__override_config_name.clone(),
1006            }));
1007    }
1008}
1009
1010// -------------------------
1011//   Shared Instance Functions
1012// -------------------------
1013
1014impl Statsig {
1015    pub fn shared() -> Arc<Statsig> {
1016        let lock = match SHARED_INSTANCE.lock() {
1017            Ok(lock) => lock,
1018            Err(e) => {
1019                log_e!(TAG, "Statsig::shared() mutex error: {}", e);
1020                return Arc::new(Statsig::new(ERROR_SDK_KEY, None));
1021            }
1022        };
1023
1024        match lock.as_ref() {
1025            Some(statsig) => statsig.clone(),
1026            None => {
1027                log_e!(
1028                    TAG,
1029                    "Statsig::shared() called, but no instance has been set with Statsig::new_shared(...)"
1030                );
1031                Arc::new(Statsig::new(ERROR_SDK_KEY, None))
1032            }
1033        }
1034    }
1035
1036    pub fn new_shared(
1037        sdk_key: &str,
1038        options: Option<Arc<StatsigOptions>>,
1039    ) -> Result<Arc<Statsig>, StatsigErr> {
1040        match SHARED_INSTANCE.lock() {
1041            Ok(mut lock) => {
1042                if lock.is_some() {
1043                    let message = "Statsig shared instance already exists. Call Statsig::remove_shared() before creating a new instance.";
1044                    log_e!(TAG, "{}", message);
1045                    return Err(StatsigErr::SharedInstanceFailure(message.to_string()));
1046                }
1047
1048                let statsig = Arc::new(Statsig::new(sdk_key, options));
1049                *lock = Some(statsig.clone());
1050                Ok(statsig)
1051            }
1052            Err(e) => {
1053                let message = format!("Statsig::new_shared() mutex error: {}", e);
1054                log_e!(TAG, "{}", message);
1055                Err(StatsigErr::SharedInstanceFailure(message))
1056            }
1057        }
1058    }
1059
1060    pub fn remove_shared() {
1061        match SHARED_INSTANCE.lock() {
1062            Ok(mut lock) => {
1063                *lock = None;
1064            }
1065            Err(e) => {
1066                log_e!(TAG, "Statsig::remove_shared() mutex error: {}", e);
1067            }
1068        }
1069    }
1070
1071    pub fn has_shared_instance() -> bool {
1072        match SHARED_INSTANCE.lock() {
1073            Ok(lock) => lock.is_some(),
1074            Err(_) => false,
1075        }
1076    }
1077}
1078
1079// -------------------------
1080//   Feature Gate Functions
1081// -------------------------
1082
1083impl Statsig {
1084    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1085        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1086    }
1087
1088    pub fn check_gate_with_options(
1089        &self,
1090        user: &StatsigUser,
1091        gate_name: &str,
1092        options: FeatureGateEvaluationOptions,
1093    ) -> bool {
1094        self.get_feature_gate_with_options(user, gate_name, options)
1095            .value
1096    }
1097
1098    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1099        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1100    }
1101
1102    pub fn get_feature_gate_with_options(
1103        &self,
1104        user: &StatsigUser,
1105        gate_name: &str,
1106        options: FeatureGateEvaluationOptions,
1107    ) -> FeatureGate {
1108        let user_internal = self.internalize_user(user);
1109        let disable_exposure_logging = options.disable_exposure_logging;
1110        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
1111
1112        if disable_exposure_logging {
1113            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1114            self.event_logger
1115                .increment_non_exposure_checks_count(gate_name.to_string());
1116        } else {
1117            self.log_gate_exposure(user_internal, gate_name, &gate, false);
1118        }
1119
1120        gate
1121    }
1122
1123    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1124        let user_internal = self.internalize_user(user);
1125        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
1126        self.log_gate_exposure(user_internal, gate_name, &gate, true);
1127    }
1128
1129    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1130        let data = read_lock_or_else!(self.spec_store.data, {
1131            log_error_to_statsig_and_console!(
1132                self.ops_stats.clone(),
1133                TAG,
1134                StatsigErr::LockFailure(
1135                    "Failed to acquire read lock for spec store data".to_string()
1136                )
1137            );
1138            return vec![];
1139        });
1140
1141        let gate = data.values.feature_gates.get(gate_name);
1142        match gate {
1143            Some(gate) => match &gate.fields_used {
1144                Some(fields) => fields.clone(),
1145                None => vec![],
1146            },
1147            None => vec![],
1148        }
1149    }
1150}
1151
1152// -------------------------
1153//   Override Functions
1154// -------------------------
1155
1156impl Statsig {
1157    pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1158        if let Some(adapter) = &self.override_adapter {
1159            adapter.override_gate(gate_name, value, id);
1160        }
1161    }
1162
1163    pub fn override_dynamic_config(
1164        &self,
1165        config_name: &str,
1166        value: HashMap<String, serde_json::Value>,
1167        id: Option<&str>,
1168    ) {
1169        if let Some(adapter) = &self.override_adapter {
1170            adapter.override_dynamic_config(config_name, value, id);
1171        }
1172    }
1173
1174    pub fn override_layer(
1175        &self,
1176        layer_name: &str,
1177        value: HashMap<String, serde_json::Value>,
1178        id: Option<&str>,
1179    ) {
1180        if let Some(adapter) = &self.override_adapter {
1181            adapter.override_layer(layer_name, value, id);
1182        }
1183    }
1184
1185    pub fn override_experiment(
1186        &self,
1187        experiment_name: &str,
1188        value: HashMap<String, serde_json::Value>,
1189        id: Option<&str>,
1190    ) {
1191        if let Some(adapter) = &self.override_adapter {
1192            adapter.override_experiment(experiment_name, value, id);
1193        }
1194    }
1195
1196    pub fn override_experiment_by_group_name(
1197        &self,
1198        experiment_name: &str,
1199        group_name: &str,
1200        id: Option<&str>,
1201    ) {
1202        if let Some(adapter) = &self.override_adapter {
1203            adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1204        }
1205    }
1206
1207    pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1208        if let Some(adapter) = &self.override_adapter {
1209            adapter.remove_gate_override(gate_name, id);
1210        }
1211    }
1212
1213    pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1214        if let Some(adapter) = &self.override_adapter {
1215            adapter.remove_dynamic_config_override(config_name, id);
1216        }
1217    }
1218
1219    pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1220        if let Some(adapter) = &self.override_adapter {
1221            adapter.remove_experiment_override(experiment_name, id);
1222        }
1223    }
1224
1225    pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1226        if let Some(adapter) = &self.override_adapter {
1227            adapter.remove_layer_override(layer_name, id);
1228        }
1229    }
1230
1231    pub fn remove_all_overrides(&self) {
1232        if let Some(adapter) = &self.override_adapter {
1233            adapter.remove_all_overrides();
1234        }
1235    }
1236}
1237
1238// -------------------------
1239//   Debugging Functions
1240// -------------------------
1241
1242impl Statsig {
1243    pub fn get_feature_gate_list(&self) -> Vec<String> {
1244        let data = read_lock_or_else!(self.spec_store.data, {
1245            log_error_to_statsig_and_console!(
1246                self.ops_stats.clone(),
1247                TAG,
1248                StatsigErr::LockFailure(
1249                    "Failed to acquire read lock for spec store data".to_string()
1250                )
1251            );
1252            return vec![];
1253        });
1254
1255        data.values.feature_gates.keys().cloned().collect()
1256    }
1257
1258    pub fn get_dynamic_config_list(&self) -> Vec<String> {
1259        let data = read_lock_or_else!(self.spec_store.data, {
1260            log_error_to_statsig_and_console!(
1261                self.ops_stats.clone(),
1262                TAG,
1263                StatsigErr::LockFailure(
1264                    "Failed to acquire read lock for spec store data".to_string()
1265                )
1266            );
1267            return vec![];
1268        });
1269
1270        data.values
1271            .dynamic_configs
1272            .iter()
1273            .filter(|(_, config)| config.entity == "dynamic_config")
1274            .map(|(name, _)| name.clone())
1275            .collect()
1276    }
1277
1278    pub fn get_experiment_list(&self) -> Vec<String> {
1279        let data = read_lock_or_else!(self.spec_store.data, {
1280            log_error_to_statsig_and_console!(
1281                self.ops_stats.clone(),
1282                TAG,
1283                StatsigErr::LockFailure(
1284                    "Failed to acquire read lock for spec store data".to_string()
1285                )
1286            );
1287            return vec![];
1288        });
1289
1290        data.values
1291            .dynamic_configs
1292            .iter()
1293            .filter(|(_, config)| config.entity == "experiment")
1294            .map(|(name, _)| name.clone())
1295            .collect()
1296    }
1297
1298    pub fn get_parameter_store_list(&self) -> Vec<String> {
1299        let data = read_lock_or_else!(self.spec_store.data, {
1300            log_error_to_statsig_and_console!(
1301                self.ops_stats.clone(),
1302                TAG,
1303                StatsigErr::LockFailure(
1304                    "Failed to acquire read lock for spec store data".to_string()
1305                )
1306            );
1307            return vec![];
1308        });
1309
1310        match &data.values.param_stores {
1311            Some(param_stores) => param_stores.keys().cloned().collect(),
1312            None => vec![],
1313        }
1314    }
1315}
1316
1317// -------------------------
1318//   Dynamic Config Functions
1319// -------------------------
1320
1321impl Statsig {
1322    pub fn get_dynamic_config(
1323        &self,
1324        user: &StatsigUser,
1325        dynamic_config_name: &str,
1326    ) -> DynamicConfig {
1327        self.get_dynamic_config_with_options(
1328            user,
1329            dynamic_config_name,
1330            DynamicConfigEvaluationOptions::default(),
1331        )
1332    }
1333
1334    pub fn get_dynamic_config_with_options(
1335        &self,
1336        user: &StatsigUser,
1337        dynamic_config_name: &str,
1338        options: DynamicConfigEvaluationOptions,
1339    ) -> DynamicConfig {
1340        let user_internal = self.internalize_user(user);
1341        let disable_exposure_logging = options.disable_exposure_logging;
1342        let config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1343
1344        if disable_exposure_logging {
1345            log_d!(
1346                TAG,
1347                "Exposure logging is disabled for Dynamic Config {}",
1348                dynamic_config_name
1349            );
1350            self.event_logger
1351                .increment_non_exposure_checks_count(dynamic_config_name.to_string());
1352        } else {
1353            self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &config, false);
1354        }
1355
1356        config
1357    }
1358
1359    pub fn manually_log_dynamic_config_exposure(
1360        &self,
1361        user: &StatsigUser,
1362        dynamic_config_name: &str,
1363    ) {
1364        let user_internal = self.internalize_user(user);
1365        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1366        self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &dynamic_config, true);
1367    }
1368
1369    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1370        let data = read_lock_or_else!(self.spec_store.data, {
1371            log_error_to_statsig_and_console!(
1372                self.ops_stats.clone(),
1373                TAG,
1374                StatsigErr::LockFailure(
1375                    "Failed to acquire read lock for spec store data".to_string()
1376                )
1377            );
1378            return vec![];
1379        });
1380
1381        let config = data.values.dynamic_configs.get(config_name);
1382        match config {
1383            Some(config) => match &config.fields_used {
1384                Some(fields) => fields.clone(),
1385                None => vec![],
1386            },
1387            None => vec![],
1388        }
1389    }
1390}
1391
1392// -------------------------
1393//   Experiment Functions
1394// -------------------------
1395
1396impl Statsig {
1397    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1398        self.get_experiment_with_options(
1399            user,
1400            experiment_name,
1401            ExperimentEvaluationOptions::default(),
1402        )
1403    }
1404
1405    pub fn get_experiment_with_options(
1406        &self,
1407        user: &StatsigUser,
1408        experiment_name: &str,
1409        options: ExperimentEvaluationOptions,
1410    ) -> Experiment {
1411        let user_internal = self.internalize_user(user);
1412        let disable_exposure_logging = options.disable_exposure_logging;
1413        let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1414        if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1415            m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1416        }) {
1417            experiment = persisted_experiment
1418        }
1419
1420        if disable_exposure_logging {
1421            log_d!(
1422                TAG,
1423                "Exposure logging is disabled for experiment {}",
1424                experiment_name
1425            );
1426            self.event_logger
1427                .increment_non_exposure_checks_count(experiment_name.to_string());
1428        } else {
1429            self.log_experiment_exposure(user_internal, experiment_name, &experiment, false);
1430        }
1431
1432        experiment
1433    }
1434
1435    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1436        let user_internal = self.internalize_user(user);
1437        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1438        self.log_experiment_exposure(user_internal, experiment_name, &experiment, true);
1439    }
1440
1441    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1442        let data = read_lock_or_else!(self.spec_store.data, {
1443            log_error_to_statsig_and_console!(
1444                self.ops_stats.clone(),
1445                TAG,
1446                StatsigErr::LockFailure(
1447                    "Failed to acquire read lock for spec store data".to_string()
1448                )
1449            );
1450            return vec![];
1451        });
1452
1453        let config = data.values.dynamic_configs.get(experiment_name);
1454        match config {
1455            Some(config) => match &config.fields_used {
1456                Some(fields) => fields.clone(),
1457                None => vec![],
1458            },
1459            None => vec![],
1460        }
1461    }
1462}
1463
1464// -------------------------
1465//   Layer Functions
1466// -------------------------
1467
1468impl Statsig {
1469    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1470        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1471    }
1472
1473    pub fn get_layer_with_options(
1474        &self,
1475        user: &StatsigUser,
1476        layer_name: &str,
1477        options: LayerEvaluationOptions,
1478    ) -> Layer {
1479        let user_internal = self.internalize_user(user);
1480        self.get_layer_impl(user_internal, layer_name, options)
1481    }
1482
1483    pub fn manually_log_layer_parameter_exposure(
1484        &self,
1485        user: &StatsigUser,
1486        layer_name: &str,
1487        parameter_name: String,
1488    ) {
1489        let user_internal = self.internalize_user(user);
1490        let layer =
1491            self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1492
1493        let layer_eval = layer.__evaluation.as_ref();
1494
1495        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1496            &layer.__user.get_sampling_key(),
1497            layer_eval.map(AnyEvaluation::from).as_ref(),
1498            Some(parameter_name.as_str()),
1499        );
1500
1501        if !sampling_details.should_send_exposure {
1502            return;
1503        }
1504
1505        self.event_logger
1506            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
1507                user: layer.__user,
1508                parameter_name,
1509                evaluation: layer.__evaluation,
1510                layer_name: layer.name,
1511                evaluation_details: layer.details,
1512                version: layer.__version,
1513                is_manual_exposure: true,
1514                sampling_details,
1515                override_config_name: layer.__override_config_name.clone(),
1516            }));
1517    }
1518
1519    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1520        let data = read_lock_or_else!(self.spec_store.data, {
1521            log_error_to_statsig_and_console!(
1522                self.ops_stats.clone(),
1523                TAG,
1524                StatsigErr::LockFailure(
1525                    "Failed to acquire read lock for spec store data".to_string()
1526                )
1527            );
1528            return vec![];
1529        });
1530
1531        let layer = data.values.layer_configs.get(layer_name);
1532        match layer {
1533            Some(layer) => match &layer.fields_used {
1534                Some(fields) => fields.clone(),
1535                None => vec![],
1536            },
1537            None => vec![],
1538        }
1539    }
1540}
1541
1542// -------------------------
1543//   Internal Functions
1544// -------------------------
1545
1546impl Statsig {
1547    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1548        if let Some(env) = &self.statsig_environment {
1549            return env.get(key).cloned();
1550        }
1551
1552        if let Ok(fallback_env) = self.fallback_environment.lock() {
1553            if let Some(env) = &*fallback_env {
1554                return env.get(key).cloned();
1555            }
1556        }
1557
1558        None
1559    }
1560
1561    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1562        if let Some(env) = &self.options.global_custom_fields {
1563            return env.get(key);
1564        }
1565
1566        None
1567    }
1568
1569    pub(crate) fn use_global_custom_fields<T>(
1570        &self,
1571        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> Result<(), T>,
1572    ) -> Result<(), T> {
1573        f(self.options.global_custom_fields.as_ref())
1574    }
1575
1576    pub(crate) fn use_statsig_env<T>(
1577        &self,
1578        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1579    ) -> T {
1580        if let Some(env) = &self.statsig_environment {
1581            return f(Some(env));
1582        }
1583
1584        if let Ok(fallback_env) = self.fallback_environment.lock() {
1585            if let Some(env) = &*fallback_env {
1586                return f(Some(env));
1587            }
1588        }
1589
1590        f(None)
1591    }
1592}
1593
1594// -------------------------
1595//   Private Functions
1596// -------------------------
1597
1598impl Statsig {
1599    fn evaluate_spec<T>(
1600        &self,
1601        user_internal: &StatsigUserInternal,
1602        spec_name: &str,
1603        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1604        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1605        spec_type: &SpecType,
1606    ) -> T {
1607        let data = read_lock_or_else!(self.spec_store.data, {
1608            log_error_to_statsig_and_console!(
1609                &self.ops_stats,
1610                TAG,
1611                StatsigErr::LockFailure(
1612                    "Failed to acquire read lock for spec store data".to_string()
1613                )
1614            );
1615            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1616        });
1617        let app_id = data.values.app_id.as_ref();
1618        let mut context = EvaluatorContext::new(
1619            user_internal,
1620            &data,
1621            &self.hashing,
1622            app_id,
1623            self.override_adapter.as_ref(),
1624        );
1625
1626        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1627            Ok(eval_details) => make_result(context.result, eval_details),
1628            Err(e) => {
1629                log_error_to_statsig_and_console!(
1630                    &self.ops_stats,
1631                    TAG,
1632                    StatsigErr::EvaluationError(e.to_string())
1633                );
1634                make_empty_result(EvaluationDetails::error(&e.to_string()))
1635            }
1636        }
1637    }
1638
1639    fn get_feature_gate_impl(
1640        &self,
1641        user_internal: &StatsigUserInternal,
1642        gate_name: &str,
1643    ) -> FeatureGate {
1644        self.evaluate_spec(
1645            user_internal,
1646            gate_name,
1647            |eval_details| make_feature_gate(gate_name, None, eval_details, None, None),
1648            |mut result, eval_details| {
1649                let evaluation = result_to_gate_eval(gate_name, &mut result);
1650                make_feature_gate(
1651                    gate_name,
1652                    Some(evaluation),
1653                    eval_details,
1654                    result.version,
1655                    result.override_config_name,
1656                )
1657            },
1658            &SpecType::Gate,
1659        )
1660    }
1661
1662    fn get_dynamic_config_impl(
1663        &self,
1664        user_internal: &StatsigUserInternal,
1665        config_name: &str,
1666    ) -> DynamicConfig {
1667        self.evaluate_spec(
1668            user_internal,
1669            config_name,
1670            |eval_details| make_dynamic_config(config_name, None, eval_details, None, None),
1671            |mut result, eval_details| {
1672                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1673                make_dynamic_config(
1674                    config_name,
1675                    Some(evaluation),
1676                    eval_details,
1677                    result.version,
1678                    result.override_config_name,
1679                )
1680            },
1681            &SpecType::DynamicConfig,
1682        )
1683    }
1684
1685    fn get_experiment_impl(
1686        &self,
1687        user_internal: &StatsigUserInternal,
1688        experiment_name: &str,
1689    ) -> Experiment {
1690        self.evaluate_spec(
1691            user_internal,
1692            experiment_name,
1693            |eval_details| make_experiment(experiment_name, None, eval_details, None, None),
1694            |mut result, eval_details| {
1695                let data = read_lock_or_else!(self.spec_store.data, {
1696                    let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1697                    return make_experiment(
1698                        experiment_name,
1699                        Some(evaluation),
1700                        eval_details,
1701                        result.version,
1702                        result.override_config_name,
1703                    );
1704                });
1705                let evaluation = result_to_experiment_eval(
1706                    experiment_name,
1707                    data.values.dynamic_configs.get(experiment_name),
1708                    &mut result,
1709                );
1710                make_experiment(
1711                    experiment_name,
1712                    Some(evaluation),
1713                    eval_details,
1714                    result.version,
1715                    result.override_config_name,
1716                )
1717            },
1718            &SpecType::Experiment,
1719        )
1720    }
1721
1722    fn get_layer_impl(
1723        &self,
1724        user_internal: StatsigUserInternal,
1725        layer_name: &str,
1726        evaluation_options: LayerEvaluationOptions,
1727    ) -> Layer {
1728        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1729        let event_logger_ptr = Arc::downgrade(&self.event_logger);
1730        let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1731
1732        if disable_exposure_logging {
1733            self.event_logger
1734                .increment_non_exposure_checks_count(layer_name.to_string());
1735        }
1736
1737        let mut layer = self.evaluate_spec(
1738            &user_internal,
1739            layer_name,
1740            |eval_details| {
1741                make_layer(
1742                    user_internal.to_loggable(),
1743                    layer_name,
1744                    None,
1745                    eval_details,
1746                    None,
1747                    None,
1748                    disable_exposure_logging,
1749                    None,
1750                    None,
1751                )
1752            },
1753            |mut result, eval_details| {
1754                let evaluation = result_to_layer_eval(layer_name, &mut result);
1755                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1756                let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1757
1758                make_layer(
1759                    user_internal.to_loggable(),
1760                    layer_name,
1761                    Some(evaluation),
1762                    eval_details,
1763                    Some(event_logger_ptr),
1764                    result.version,
1765                    disable_exposure_logging,
1766                    Some(sampling_processor_ptr),
1767                    result.override_config_name,
1768                )
1769            },
1770            &SpecType::Layer,
1771        );
1772        if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1773            p.try_apply_sticky_value_to_layer(
1774                &user_internal,
1775                &evaluation_options,
1776                &layer,
1777                Some(event_logger_ptr),
1778                Some(sampling_processor_ptr),
1779                disable_exposure_logging,
1780            )
1781        }) {
1782            layer = persisted_layer
1783        }
1784        layer
1785    }
1786
1787    fn log_gate_exposure(
1788        &self,
1789        user: StatsigUserInternal,
1790        gate_name: &str,
1791        gate: &FeatureGate,
1792        is_manual: bool,
1793    ) {
1794        let gate_eval = gate.__evaluation.as_ref();
1795        let secondary_exposures = gate_eval.map(|eval| &eval.base.secondary_exposures);
1796        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1797            &user.get_sampling_key(),
1798            gate_eval.map(AnyEvaluation::from).as_ref(),
1799            None,
1800        );
1801
1802        if !sampling_details.should_send_exposure {
1803            return;
1804        }
1805
1806        self.event_logger
1807            .enqueue(QueuedEventPayload::GateExposure(GateExposure {
1808                user: user.to_loggable(),
1809                gate_name: gate_name.to_string(),
1810                value: gate.value,
1811                rule_id: Some(gate.rule_id.clone()),
1812                secondary_exposures: secondary_exposures.cloned(),
1813                evaluation_details: gate.details.clone(),
1814                version: gate.__version,
1815                is_manual_exposure: is_manual,
1816                sampling_details,
1817                override_config_name: gate.__override_config_name.clone(),
1818            }));
1819    }
1820
1821    fn log_dynamic_config_exposure(
1822        &self,
1823        user_internal: StatsigUserInternal,
1824        dynamic_config_name: &str,
1825        dynamic_config: &DynamicConfig,
1826        is_manual: bool,
1827    ) {
1828        let config_eval = dynamic_config.__evaluation.as_ref();
1829        let base_eval = config_eval.map(|eval| eval.base.clone());
1830        let loggable_user = user_internal.to_loggable();
1831
1832        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1833            &loggable_user.get_sampling_key(),
1834            config_eval.map(AnyEvaluation::from).as_ref(),
1835            None,
1836        );
1837
1838        if !sampling_details.should_send_exposure {
1839            return;
1840        }
1841
1842        self.event_logger
1843            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1844                user: loggable_user,
1845                evaluation: base_eval,
1846                evaluation_details: dynamic_config.details.clone(),
1847                config_name: dynamic_config_name.to_string(),
1848                rule_passed: dynamic_config.__evaluation.as_ref().map(|eval| eval.passed),
1849                version: dynamic_config.__version,
1850                is_manual_exposure: is_manual,
1851                sampling_details,
1852                override_config_name: dynamic_config.__override_config_name.clone(),
1853            }));
1854    }
1855
1856    fn log_experiment_exposure(
1857        &self,
1858        user_internal: StatsigUserInternal,
1859        experiment_name: &str,
1860        experiment: &Experiment,
1861        is_manual: bool,
1862    ) {
1863        let experiment_eval = experiment.__evaluation.as_ref();
1864        let base_eval = experiment_eval.map(|eval| eval.base.clone());
1865
1866        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1867            &user_internal.get_sampling_key(),
1868            experiment_eval.map(AnyEvaluation::from).as_ref(),
1869            None,
1870        );
1871
1872        if !sampling_details.should_send_exposure {
1873            return;
1874        }
1875
1876        let loggable_user = user_internal.to_loggable();
1877
1878        self.event_logger
1879            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1880                user: loggable_user,
1881                evaluation: base_eval,
1882                evaluation_details: experiment.details.clone(),
1883                config_name: experiment_name.to_string(),
1884                rule_passed: None,
1885                version: experiment.__version,
1886                is_manual_exposure: is_manual,
1887                sampling_details,
1888                override_config_name: experiment.__override_config_name.clone(),
1889            }));
1890    }
1891
1892    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
1893        StatsigUserInternal::new(user, Some(self))
1894    }
1895
1896    fn set_default_environment_from_server(&self) {
1897        let data = read_lock_or_else!(self.spec_store.data, {
1898            return;
1899        });
1900
1901        if let Some(default_env) = data.values.default_environment.as_ref() {
1902            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1903
1904            if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1905                *fallback_env = Some(env_map);
1906            }
1907        }
1908    }
1909
1910    fn log_init_finish(
1911        &self,
1912        success: bool,
1913        error_message: &Option<String>,
1914        duration: &f64,
1915        specs_info: &SpecsInfo,
1916    ) {
1917        let is_store_populated = specs_info.source != SpecsSource::NoValues;
1918        let source_str = specs_info.source.to_string();
1919        self.ops_stats.log(ObservabilityEvent::new_event(
1920            MetricType::Dist,
1921            "initialization".to_string(),
1922            *duration,
1923            Some(HashMap::from([
1924                ("success".to_owned(), success.to_string()),
1925                ("source".to_owned(), source_str.clone()),
1926                ("store_populated".to_owned(), is_store_populated.to_string()),
1927            ])),
1928        ));
1929        self.ops_stats.add_marker(
1930            {
1931                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
1932                    .with_is_success(success)
1933                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1934                    .with_source(source_str);
1935
1936                if let Some(msg) = &error_message {
1937                    marker.with_message(msg.to_string())
1938                } else {
1939                    marker
1940                }
1941            },
1942            Some(ContextType::Initialize),
1943        );
1944        self.ops_stats
1945            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1946    }
1947}
1948
1949fn initialize_event_logging_adapter(
1950    sdk_key: &str,
1951    options: &StatsigOptions,
1952) -> Arc<dyn EventLoggingAdapter> {
1953    options
1954        .event_logging_adapter
1955        .clone()
1956        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
1957}
1958
1959fn initialize_specs_adapter(
1960    sdk_key: &str,
1961    options: &StatsigOptions,
1962    hashing: &HashUtil,
1963) -> Arc<dyn SpecsAdapter> {
1964    if let Some(adapter) = options.specs_adapter.clone() {
1965        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1966        return adapter;
1967    }
1968
1969    if let Some(adapter_config) = options.spec_adapters_config.clone() {
1970        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1971            sdk_key,
1972            adapter_config,
1973            options,
1974            hashing,
1975        ));
1976    }
1977
1978    if let Some(data_adapter) = options.data_store.clone() {
1979        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1980            sdk_key,
1981            data_adapter,
1982            options,
1983            hashing,
1984        ));
1985    }
1986
1987    Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options)))
1988}
1989
1990fn initialize_id_lists_adapter(
1991    sdk_key: &str,
1992    options: &StatsigOptions,
1993) -> Option<Arc<dyn IdListsAdapter>> {
1994    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1995        return Some(id_lists_adapter);
1996    }
1997
1998    if options.enable_id_lists.unwrap_or(false) {
1999        return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
2000    }
2001
2002    None
2003}
2004
2005fn setup_ops_stats(
2006    sdk_key: &str,
2007    options: &StatsigOptions,
2008    statsig_runtime: Arc<StatsigRuntime>,
2009    error_observer: &Arc<dyn OpsStatsEventObserver>,
2010    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2011    external_observer: &Option<Weak<dyn ObservabilityClient>>,
2012) -> Arc<OpsStatsForInstance> {
2013    // TODO migrate output logger to use ops_stats
2014    initialize_simple_output_logger(&options.output_log_level);
2015
2016    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2017    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2018    ops_stat.subscribe(
2019        statsig_runtime.clone(),
2020        Arc::downgrade(diagnostics_observer),
2021    );
2022
2023    if let Some(ob_client) = external_observer {
2024        if let Some(client) = ob_client.upgrade() {
2025            client.init();
2026            let as_observer = client.to_ops_stats_event_observer();
2027            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2028        }
2029    }
2030
2031    ops_stat
2032}