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