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