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