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    pub fn get_experiment_by_group_name(
1536        &self,
1537        experiment_name: &str,
1538        group_name: &str,
1539    ) -> Experiment {
1540        let data = read_lock_or_else!(self.spec_store.data, {
1541            log_error_to_statsig_and_console!(
1542                self.ops_stats.clone(),
1543                TAG,
1544                StatsigErr::LockFailure(
1545                    "Failed to acquire read lock for spec store data".to_string()
1546                )
1547            );
1548            return make_experiment(
1549                experiment_name,
1550                None,
1551                EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1552            );
1553        });
1554
1555        let Some(exp) = data.values.dynamic_configs.get(experiment_name) else {
1556            return make_experiment(
1557                experiment_name,
1558                None,
1559                EvaluationDetails::unrecognized(&data),
1560            );
1561        };
1562
1563        if let Some(rule) = exp
1564            .spec
1565            .rules
1566            .iter()
1567            .find(|rule| rule.group_name.as_deref() == Some(group_name))
1568        {
1569            let value = rule.return_value.get_json().unwrap_or_default();
1570            let rule_id = String::from(rule.id.as_str());
1571            let id_type = rule.id_type.value.clone();
1572            let group_name = rule.group_name.clone();
1573
1574            return Experiment {
1575                name: experiment_name.to_string(),
1576                value,
1577                rule_id,
1578                id_type,
1579                group_name,
1580                details: EvaluationDetails::recognized_without_eval_result(&data),
1581                __evaluation: None,
1582            };
1583        }
1584
1585        make_experiment(
1586            experiment_name,
1587            None,
1588            EvaluationDetails::unrecognized(&data),
1589        )
1590    }
1591}
1592
1593// -------------------------
1594//   Layer Functions
1595// -------------------------
1596
1597impl Statsig {
1598    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1599        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1600    }
1601
1602    pub fn get_layer_with_options(
1603        &self,
1604        user: &StatsigUser,
1605        layer_name: &str,
1606        options: LayerEvaluationOptions,
1607    ) -> Layer {
1608        let user_internal = self.internalize_user(user);
1609        self.get_layer_impl(user_internal, layer_name, options)
1610    }
1611
1612    pub fn manually_log_layer_parameter_exposure(
1613        &self,
1614        user: &StatsigUser,
1615        layer_name: &str,
1616        parameter_name: String,
1617    ) {
1618        let user_internal = self.internalize_user(user);
1619        let layer =
1620            self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1621
1622        self.event_logger
1623            .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1624                Box::new(layer),
1625                parameter_name,
1626                ExposureTrigger::Manual,
1627            ));
1628    }
1629
1630    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1631        let data = read_lock_or_else!(self.spec_store.data, {
1632            log_error_to_statsig_and_console!(
1633                self.ops_stats.clone(),
1634                TAG,
1635                StatsigErr::LockFailure(
1636                    "Failed to acquire read lock for spec store data".to_string()
1637                )
1638            );
1639            return vec![];
1640        });
1641
1642        let layer = data.values.layer_configs.get(layer_name);
1643        match layer {
1644            Some(layer) => match &layer.spec.fields_used {
1645                Some(fields) => fields.clone(),
1646                None => vec![],
1647            },
1648            None => vec![],
1649        }
1650    }
1651}
1652
1653// -------------------------
1654//   Internal Functions
1655// -------------------------
1656
1657impl Statsig {
1658    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1659        if let Some(env) = &self.statsig_environment {
1660            return env.get(key).cloned();
1661        }
1662
1663        if let Ok(fallback_env) = self.fallback_environment.lock() {
1664            if let Some(env) = &*fallback_env {
1665                return env.get(key).cloned();
1666            }
1667        }
1668
1669        None
1670    }
1671
1672    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1673        if let Some(env) = &self.options.global_custom_fields {
1674            return env.get(key);
1675        }
1676
1677        None
1678    }
1679
1680    pub(crate) fn use_global_custom_fields<T>(
1681        &self,
1682        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1683    ) -> T {
1684        f(self.options.global_custom_fields.as_ref())
1685    }
1686
1687    pub(crate) fn use_statsig_env<T>(
1688        &self,
1689        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1690    ) -> T {
1691        if let Some(env) = &self.statsig_environment {
1692            return f(Some(env));
1693        }
1694
1695        if let Ok(fallback_env) = self.fallback_environment.lock() {
1696            if let Some(env) = &*fallback_env {
1697                return f(Some(env));
1698            }
1699        }
1700
1701        f(None)
1702    }
1703}
1704
1705// -------------------------
1706//   Private Functions
1707// -------------------------
1708
1709impl Statsig {
1710    fn evaluate_spec<T>(
1711        &self,
1712        user_internal: &StatsigUserInternal,
1713        spec_name: &str,
1714        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1715        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1716        spec_type: &SpecType,
1717    ) -> T {
1718        let data = read_lock_or_else!(self.spec_store.data, {
1719            log_error_to_statsig_and_console!(
1720                &self.ops_stats,
1721                TAG,
1722                StatsigErr::LockFailure(
1723                    "Failed to acquire read lock for spec store data".to_string()
1724                )
1725            );
1726            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1727        });
1728        let app_id = data.values.app_id.as_ref();
1729        let mut context = EvaluatorContext::new(
1730            user_internal,
1731            &data,
1732            &self.hashing,
1733            app_id,
1734            self.override_adapter.as_ref(),
1735        );
1736
1737        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1738            Ok(eval_details) => make_result(context.result, eval_details),
1739            Err(e) => {
1740                log_error_to_statsig_and_console!(
1741                    &self.ops_stats,
1742                    TAG,
1743                    StatsigErr::EvaluationError(e.to_string())
1744                );
1745                make_empty_result(EvaluationDetails::error(&e.to_string()))
1746            }
1747        }
1748    }
1749
1750    fn get_gate_evaluation(
1751        &self,
1752        user_internal: &StatsigUserInternal,
1753        gate_name: &str,
1754    ) -> (EvaluationDetails, Option<GateEvaluation>) {
1755        self.evaluate_spec(
1756            user_internal,
1757            gate_name,
1758            |eval_details| (eval_details, None),
1759            |mut result, eval_details| {
1760                let evaluation = result_to_gate_eval(gate_name, &mut result);
1761                (eval_details, Some(evaluation))
1762            },
1763            &SpecType::Gate,
1764        )
1765    }
1766
1767    fn get_dynamic_config_impl(
1768        &self,
1769        user_internal: &StatsigUserInternal,
1770        config_name: &str,
1771    ) -> DynamicConfig {
1772        self.evaluate_spec(
1773            user_internal,
1774            config_name,
1775            |eval_details| make_dynamic_config(config_name, None, eval_details),
1776            |mut result, eval_details| {
1777                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1778                make_dynamic_config(config_name, Some(evaluation), eval_details)
1779            },
1780            &SpecType::DynamicConfig,
1781        )
1782    }
1783
1784    fn get_experiment_impl(
1785        &self,
1786        user_internal: &StatsigUserInternal,
1787        experiment_name: &str,
1788    ) -> Experiment {
1789        self.evaluate_spec(
1790            user_internal,
1791            experiment_name,
1792            |eval_details| make_experiment(experiment_name, None, eval_details),
1793            |mut result, eval_details| {
1794                let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1795                make_experiment(experiment_name, Some(evaluation), eval_details)
1796            },
1797            &SpecType::Experiment,
1798        )
1799    }
1800
1801    fn get_layer_impl(
1802        &self,
1803        user_internal: StatsigUserInternal,
1804        layer_name: &str,
1805        evaluation_options: LayerEvaluationOptions,
1806    ) -> Layer {
1807        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1808
1809        if disable_exposure_logging {
1810            self.event_logger.increment_non_exposure_checks(layer_name);
1811        }
1812
1813        let mut layer = self.evaluate_spec(
1814            &user_internal,
1815            layer_name,
1816            |eval_details| {
1817                make_layer(
1818                    user_internal.to_loggable(),
1819                    layer_name,
1820                    None,
1821                    eval_details,
1822                    None,
1823                    disable_exposure_logging,
1824                )
1825            },
1826            |mut result, eval_details| {
1827                let evaluation = result_to_layer_eval(layer_name, &mut result);
1828                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1829
1830                make_layer(
1831                    user_internal.to_loggable(),
1832                    layer_name,
1833                    Some(evaluation),
1834                    eval_details,
1835                    Some(event_logger_ptr),
1836                    disable_exposure_logging,
1837                )
1838            },
1839            &SpecType::Layer,
1840        );
1841        if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1842            let event_logger_ptr = Arc::downgrade(&self.event_logger);
1843            p.try_apply_sticky_value_to_layer(
1844                &user_internal,
1845                &evaluation_options,
1846                &layer,
1847                Some(event_logger_ptr),
1848                disable_exposure_logging,
1849            )
1850        }) {
1851            layer = persisted_layer
1852        }
1853        layer
1854    }
1855
1856    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
1857        StatsigUserInternal::new(user, Some(self))
1858    }
1859
1860    fn set_default_environment_from_server(&self) {
1861        let data = read_lock_or_else!(self.spec_store.data, {
1862            return;
1863        });
1864
1865        if let Some(default_env) = data.values.default_environment.as_ref() {
1866            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1867
1868            if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1869                *fallback_env = Some(env_map);
1870            }
1871        }
1872    }
1873
1874    fn log_init_finish(
1875        &self,
1876        success: bool,
1877        error_message: &Option<String>,
1878        duration: &f64,
1879        specs_info: &SpecsInfo,
1880    ) {
1881        let is_store_populated = specs_info.source != SpecsSource::NoValues;
1882        let source_str = specs_info.source.to_string();
1883
1884        let event = ObservabilityEvent::new_event(
1885            MetricType::Dist,
1886            "initialization".to_string(),
1887            *duration,
1888            Some(HashMap::from([
1889                ("success".to_owned(), success.to_string()),
1890                ("source".to_owned(), source_str.clone()),
1891                ("store_populated".to_owned(), is_store_populated.to_string()),
1892                (
1893                    "spec_source_api".to_owned(),
1894                    specs_info.source_api.clone().unwrap_or_default(),
1895                ),
1896            ])),
1897        );
1898
1899        self.ops_stats.log(event);
1900        self.ops_stats.add_marker(
1901            {
1902                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
1903                    .with_is_success(success)
1904                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1905                    .with_source(source_str);
1906
1907                if let Some(msg) = &error_message {
1908                    marker.with_message(msg.to_string())
1909                } else {
1910                    marker
1911                }
1912            },
1913            Some(ContextType::Initialize),
1914        );
1915        self.ops_stats
1916            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1917    }
1918}
1919
1920fn initialize_event_logging_adapter(
1921    sdk_key: &str,
1922    options: &StatsigOptions,
1923) -> Arc<dyn EventLoggingAdapter> {
1924    options
1925        .event_logging_adapter
1926        .clone()
1927        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
1928}
1929
1930fn initialize_specs_adapter(
1931    sdk_key: &str,
1932    options: &StatsigOptions,
1933    hashing: &HashUtil,
1934) -> Arc<dyn SpecsAdapter> {
1935    if let Some(adapter) = options.specs_adapter.clone() {
1936        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1937        return adapter;
1938    }
1939
1940    if let Some(adapter_config) = options.spec_adapters_config.clone() {
1941        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1942            sdk_key,
1943            adapter_config,
1944            options,
1945            hashing,
1946        ));
1947    }
1948
1949    if let Some(data_adapter) = options.data_store.clone() {
1950        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1951            sdk_key,
1952            data_adapter,
1953            options,
1954            hashing,
1955        ));
1956    }
1957
1958    Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None))
1959}
1960
1961fn initialize_id_lists_adapter(
1962    sdk_key: &str,
1963    options: &StatsigOptions,
1964) -> Option<Arc<dyn IdListsAdapter>> {
1965    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1966        return Some(id_lists_adapter);
1967    }
1968
1969    if options.enable_id_lists.unwrap_or(false) {
1970        return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1971    }
1972
1973    None
1974}
1975
1976fn setup_ops_stats(
1977    sdk_key: &str,
1978    statsig_runtime: Arc<StatsigRuntime>,
1979    error_observer: &Arc<dyn OpsStatsEventObserver>,
1980    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1981    external_observer: &Option<Weak<dyn ObservabilityClient>>,
1982) -> Arc<OpsStatsForInstance> {
1983    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1984    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1985    ops_stat.subscribe(
1986        statsig_runtime.clone(),
1987        Arc::downgrade(diagnostics_observer),
1988    );
1989
1990    if let Some(ob_client) = external_observer {
1991        if let Some(client) = ob_client.upgrade() {
1992            client.init();
1993            let as_observer = client.to_ops_stats_event_observer();
1994            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1995        }
1996    }
1997
1998    ops_stat
1999}