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