statsig_rust/
statsig.rs

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