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                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.clone(),
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.clone(),
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.clone(),
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_parameter_store_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.clone(),
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        match &data.values.param_stores {
1335            Some(param_stores) => param_stores.keys().cloned().collect(),
1336            None => vec![],
1337        }
1338    }
1339}
1340
1341// -------------------------
1342//   Dynamic Config Functions
1343// -------------------------
1344
1345impl Statsig {
1346    pub fn get_dynamic_config(
1347        &self,
1348        user: &StatsigUser,
1349        dynamic_config_name: &str,
1350    ) -> DynamicConfig {
1351        self.get_dynamic_config_with_options(
1352            user,
1353            dynamic_config_name,
1354            DynamicConfigEvaluationOptions::default(),
1355        )
1356    }
1357
1358    pub fn get_dynamic_config_with_options(
1359        &self,
1360        user: &StatsigUser,
1361        dynamic_config_name: &str,
1362        options: DynamicConfigEvaluationOptions,
1363    ) -> DynamicConfig {
1364        let user_internal = self.internalize_user(user);
1365        let disable_exposure_logging = options.disable_exposure_logging;
1366        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1367
1368        if disable_exposure_logging {
1369            log_d!(
1370                TAG,
1371                "Exposure logging is disabled for Dynamic Config {}",
1372                dynamic_config_name
1373            );
1374            self.event_logger
1375                .increment_non_exposure_checks(dynamic_config_name);
1376        } else {
1377            self.event_logger.enqueue(EnqueueConfigExpoOp {
1378                user: &user_internal,
1379                config: &dynamic_config,
1380                trigger: ExposureTrigger::Auto,
1381            });
1382        }
1383
1384        dynamic_config
1385    }
1386
1387    pub fn manually_log_dynamic_config_exposure(
1388        &self,
1389        user: &StatsigUser,
1390        dynamic_config_name: &str,
1391    ) {
1392        let user_internal = self.internalize_user(user);
1393        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1394        self.event_logger.enqueue(EnqueueConfigExpoOp {
1395            user: &user_internal,
1396            config: &dynamic_config,
1397            trigger: ExposureTrigger::Manual,
1398        });
1399    }
1400
1401    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1402        let data = read_lock_or_else!(self.spec_store.data, {
1403            log_error_to_statsig_and_console!(
1404                self.ops_stats.clone(),
1405                TAG,
1406                StatsigErr::LockFailure(
1407                    "Failed to acquire read lock for spec store data".to_string()
1408                )
1409            );
1410            return vec![];
1411        });
1412
1413        let config = data.values.dynamic_configs.get(config_name);
1414        match config {
1415            Some(config) => match &config.spec.fields_used {
1416                Some(fields) => fields.clone(),
1417                None => vec![],
1418            },
1419            None => vec![],
1420        }
1421    }
1422}
1423
1424// -------------------------
1425//   Experiment Functions
1426// -------------------------
1427
1428impl Statsig {
1429    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1430        self.get_experiment_with_options(
1431            user,
1432            experiment_name,
1433            ExperimentEvaluationOptions::default(),
1434        )
1435    }
1436
1437    pub fn get_experiment_with_options(
1438        &self,
1439        user: &StatsigUser,
1440        experiment_name: &str,
1441        options: ExperimentEvaluationOptions,
1442    ) -> Experiment {
1443        let user_internal = self.internalize_user(user);
1444        let disable_exposure_logging = options.disable_exposure_logging;
1445        let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1446        if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1447            m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1448        }) {
1449            experiment = persisted_experiment
1450        }
1451
1452        if disable_exposure_logging {
1453            log_d!(
1454                TAG,
1455                "Exposure logging is disabled for experiment {}",
1456                experiment_name
1457            );
1458            self.event_logger
1459                .increment_non_exposure_checks(experiment_name);
1460        } else {
1461            self.event_logger.enqueue(EnqueueExperimentExpoOp {
1462                user: &user_internal,
1463                experiment: &experiment,
1464                trigger: ExposureTrigger::Auto,
1465            });
1466        }
1467
1468        experiment
1469    }
1470
1471    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1472        let user_internal = self.internalize_user(user);
1473        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1474        self.event_logger.enqueue(EnqueueExperimentExpoOp {
1475            user: &user_internal,
1476            experiment: &experiment,
1477            trigger: ExposureTrigger::Manual,
1478        });
1479    }
1480
1481    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1482        let data = read_lock_or_else!(self.spec_store.data, {
1483            log_error_to_statsig_and_console!(
1484                self.ops_stats.clone(),
1485                TAG,
1486                StatsigErr::LockFailure(
1487                    "Failed to acquire read lock for spec store data".to_string()
1488                )
1489            );
1490            return vec![];
1491        });
1492
1493        let config = data.values.dynamic_configs.get(experiment_name);
1494        match config {
1495            Some(config) => match &config.spec.fields_used {
1496                Some(fields) => fields.clone(),
1497                None => vec![],
1498            },
1499            None => vec![],
1500        }
1501    }
1502}
1503
1504// -------------------------
1505//   Layer Functions
1506// -------------------------
1507
1508impl Statsig {
1509    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1510        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1511    }
1512
1513    pub fn get_layer_with_options(
1514        &self,
1515        user: &StatsigUser,
1516        layer_name: &str,
1517        options: LayerEvaluationOptions,
1518    ) -> Layer {
1519        let user_internal = self.internalize_user(user);
1520        self.get_layer_impl(user_internal, layer_name, options)
1521    }
1522
1523    pub fn manually_log_layer_parameter_exposure(
1524        &self,
1525        user: &StatsigUser,
1526        layer_name: &str,
1527        parameter_name: String,
1528    ) {
1529        let user_internal = self.internalize_user(user);
1530        let layer =
1531            self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1532
1533        self.event_logger
1534            .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1535                layer,
1536                parameter_name,
1537                ExposureTrigger::Manual,
1538            ));
1539    }
1540
1541    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1542        let data = read_lock_or_else!(self.spec_store.data, {
1543            log_error_to_statsig_and_console!(
1544                self.ops_stats.clone(),
1545                TAG,
1546                StatsigErr::LockFailure(
1547                    "Failed to acquire read lock for spec store data".to_string()
1548                )
1549            );
1550            return vec![];
1551        });
1552
1553        let layer = data.values.layer_configs.get(layer_name);
1554        match layer {
1555            Some(layer) => match &layer.spec.fields_used {
1556                Some(fields) => fields.clone(),
1557                None => vec![],
1558            },
1559            None => vec![],
1560        }
1561    }
1562}
1563
1564// -------------------------
1565//   Internal Functions
1566// -------------------------
1567
1568impl Statsig {
1569    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1570        if let Some(env) = &self.statsig_environment {
1571            return env.get(key).cloned();
1572        }
1573
1574        if let Ok(fallback_env) = self.fallback_environment.lock() {
1575            if let Some(env) = &*fallback_env {
1576                return env.get(key).cloned();
1577            }
1578        }
1579
1580        None
1581    }
1582
1583    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1584        if let Some(env) = &self.options.global_custom_fields {
1585            return env.get(key);
1586        }
1587
1588        None
1589    }
1590
1591    pub(crate) fn use_global_custom_fields<T>(
1592        &self,
1593        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1594    ) -> T {
1595        f(self.options.global_custom_fields.as_ref())
1596    }
1597
1598    pub(crate) fn use_statsig_env<T>(
1599        &self,
1600        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1601    ) -> T {
1602        if let Some(env) = &self.statsig_environment {
1603            return f(Some(env));
1604        }
1605
1606        if let Ok(fallback_env) = self.fallback_environment.lock() {
1607            if let Some(env) = &*fallback_env {
1608                return f(Some(env));
1609            }
1610        }
1611
1612        f(None)
1613    }
1614}
1615
1616// -------------------------
1617//   Private Functions
1618// -------------------------
1619
1620impl Statsig {
1621    fn evaluate_spec<T>(
1622        &self,
1623        user_internal: &StatsigUserInternal,
1624        spec_name: &str,
1625        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1626        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1627        spec_type: &SpecType,
1628    ) -> T {
1629        let data = read_lock_or_else!(self.spec_store.data, {
1630            log_error_to_statsig_and_console!(
1631                &self.ops_stats,
1632                TAG,
1633                StatsigErr::LockFailure(
1634                    "Failed to acquire read lock for spec store data".to_string()
1635                )
1636            );
1637            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1638        });
1639        let app_id = data.values.app_id.as_ref();
1640        let mut context = EvaluatorContext::new(
1641            user_internal,
1642            &data,
1643            &self.hashing,
1644            app_id,
1645            self.override_adapter.as_ref(),
1646        );
1647
1648        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1649            Ok(eval_details) => make_result(context.result, eval_details),
1650            Err(e) => {
1651                log_error_to_statsig_and_console!(
1652                    &self.ops_stats,
1653                    TAG,
1654                    StatsigErr::EvaluationError(e.to_string())
1655                );
1656                make_empty_result(EvaluationDetails::error(&e.to_string()))
1657            }
1658        }
1659    }
1660
1661    fn get_gate_evaluation(
1662        &self,
1663        user_internal: &StatsigUserInternal,
1664        gate_name: &str,
1665    ) -> (EvaluationDetails, Option<GateEvaluation>) {
1666        self.evaluate_spec(
1667            user_internal,
1668            gate_name,
1669            |eval_details| (eval_details, None),
1670            |mut result, eval_details| {
1671                let evaluation = result_to_gate_eval(gate_name, &mut result);
1672                (eval_details, Some(evaluation))
1673            },
1674            &SpecType::Gate,
1675        )
1676    }
1677
1678    fn get_dynamic_config_impl(
1679        &self,
1680        user_internal: &StatsigUserInternal,
1681        config_name: &str,
1682    ) -> DynamicConfig {
1683        self.evaluate_spec(
1684            user_internal,
1685            config_name,
1686            |eval_details| make_dynamic_config(config_name, None, eval_details),
1687            |mut result, eval_details| {
1688                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1689                make_dynamic_config(config_name, Some(evaluation), eval_details)
1690            },
1691            &SpecType::DynamicConfig,
1692        )
1693    }
1694
1695    fn get_experiment_impl(
1696        &self,
1697        user_internal: &StatsigUserInternal,
1698        experiment_name: &str,
1699    ) -> Experiment {
1700        self.evaluate_spec(
1701            user_internal,
1702            experiment_name,
1703            |eval_details| make_experiment(experiment_name, None, eval_details),
1704            |mut result, eval_details| {
1705                let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1706                make_experiment(experiment_name, Some(evaluation), eval_details)
1707            },
1708            &SpecType::Experiment,
1709        )
1710    }
1711
1712    fn get_layer_impl(
1713        &self,
1714        user_internal: StatsigUserInternal,
1715        layer_name: &str,
1716        evaluation_options: LayerEvaluationOptions,
1717    ) -> Layer {
1718        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1719
1720        if disable_exposure_logging {
1721            self.event_logger.increment_non_exposure_checks(layer_name);
1722        }
1723
1724        let mut layer = self.evaluate_spec(
1725            &user_internal,
1726            layer_name,
1727            |eval_details| {
1728                make_layer(
1729                    user_internal.to_loggable(),
1730                    layer_name,
1731                    None,
1732                    eval_details,
1733                    None,
1734                    disable_exposure_logging,
1735                )
1736            },
1737            |mut result, eval_details| {
1738                let evaluation = result_to_layer_eval(layer_name, &mut result);
1739                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1740
1741                make_layer(
1742                    user_internal.to_loggable(),
1743                    layer_name,
1744                    Some(evaluation),
1745                    eval_details,
1746                    Some(event_logger_ptr),
1747                    disable_exposure_logging,
1748                )
1749            },
1750            &SpecType::Layer,
1751        );
1752        if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
1753            let event_logger_ptr = Arc::downgrade(&self.event_logger);
1754            p.try_apply_sticky_value_to_layer(
1755                &user_internal,
1756                &evaluation_options,
1757                &layer,
1758                Some(event_logger_ptr),
1759                disable_exposure_logging,
1760            )
1761        }) {
1762            layer = persisted_layer
1763        }
1764        layer
1765    }
1766
1767    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
1768        StatsigUserInternal::new(user, Some(self))
1769    }
1770
1771    fn set_default_environment_from_server(&self) {
1772        let data = read_lock_or_else!(self.spec_store.data, {
1773            return;
1774        });
1775
1776        if let Some(default_env) = data.values.default_environment.as_ref() {
1777            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1778
1779            if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1780                *fallback_env = Some(env_map);
1781            }
1782        }
1783    }
1784
1785    fn log_init_finish(
1786        &self,
1787        success: bool,
1788        error_message: &Option<String>,
1789        duration: &f64,
1790        specs_info: &SpecsInfo,
1791    ) {
1792        let is_store_populated = specs_info.source != SpecsSource::NoValues;
1793        let source_str = specs_info.source.to_string();
1794
1795        let event = ObservabilityEvent::new_event(
1796            MetricType::Dist,
1797            "initialization".to_string(),
1798            *duration,
1799            Some(HashMap::from([
1800                ("success".to_owned(), success.to_string()),
1801                ("source".to_owned(), source_str.clone()),
1802                ("store_populated".to_owned(), is_store_populated.to_string()),
1803                (
1804                    "spec_source_api".to_owned(),
1805                    specs_info.source_api.clone().unwrap_or_default(),
1806                ),
1807            ])),
1808        );
1809
1810        self.ops_stats.log(event);
1811        self.ops_stats.add_marker(
1812            {
1813                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
1814                    .with_is_success(success)
1815                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
1816                    .with_source(source_str);
1817
1818                if let Some(msg) = &error_message {
1819                    marker.with_message(msg.to_string())
1820                } else {
1821                    marker
1822                }
1823            },
1824            Some(ContextType::Initialize),
1825        );
1826        self.ops_stats
1827            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
1828    }
1829}
1830
1831fn initialize_event_logging_adapter(
1832    sdk_key: &str,
1833    options: &StatsigOptions,
1834) -> Arc<dyn EventLoggingAdapter> {
1835    options
1836        .event_logging_adapter
1837        .clone()
1838        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
1839}
1840
1841fn initialize_specs_adapter(
1842    sdk_key: &str,
1843    options: &StatsigOptions,
1844    hashing: &HashUtil,
1845) -> Arc<dyn SpecsAdapter> {
1846    if let Some(adapter) = options.specs_adapter.clone() {
1847        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
1848        return adapter;
1849    }
1850
1851    if let Some(adapter_config) = options.spec_adapters_config.clone() {
1852        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1853            sdk_key,
1854            adapter_config,
1855            options,
1856            hashing,
1857        ));
1858    }
1859
1860    if let Some(data_adapter) = options.data_store.clone() {
1861        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1862            sdk_key,
1863            data_adapter,
1864            options,
1865            hashing,
1866        ));
1867    }
1868
1869    Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None))
1870}
1871
1872fn initialize_id_lists_adapter(
1873    sdk_key: &str,
1874    options: &StatsigOptions,
1875) -> Option<Arc<dyn IdListsAdapter>> {
1876    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1877        return Some(id_lists_adapter);
1878    }
1879
1880    if options.enable_id_lists.unwrap_or(false) {
1881        return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1882    }
1883
1884    None
1885}
1886
1887fn setup_ops_stats(
1888    sdk_key: &str,
1889    statsig_runtime: Arc<StatsigRuntime>,
1890    error_observer: &Arc<dyn OpsStatsEventObserver>,
1891    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
1892    external_observer: &Option<Weak<dyn ObservabilityClient>>,
1893) -> Arc<OpsStatsForInstance> {
1894    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1895    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1896    ops_stat.subscribe(
1897        statsig_runtime.clone(),
1898        Arc::downgrade(diagnostics_observer),
1899    );
1900
1901    if let Some(ob_client) = external_observer {
1902        if let Some(client) = ob_client.upgrade() {
1903            client.init();
1904            let as_observer = client.to_ops_stats_event_observer();
1905            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1906        }
1907    }
1908
1909    ops_stat
1910}