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        );
854        get_cmab_ranked_list(&mut context, cmab_name)
855    }
856
857    pub fn log_cmab_exposure_for_group(
858        &self,
859        user: &StatsigUser,
860        cmab_name: &str,
861        group_id: String,
862    ) {
863        let user_internal = self.internalize_user(user);
864
865        let mut experiment = self.get_experiment_impl(&user_internal, cmab_name);
866        experiment.rule_id = group_id;
867
868        self.event_logger.enqueue(EnqueueExperimentExpoOp {
869            exposure_time: Utc::now().timestamp_millis() as u64,
870            user: &user_internal,
871            experiment: &experiment,
872            trigger: ExposureTrigger::Manual,
873        });
874    }
875}
876
877// ------------------------------------------------------------------------------- [ Override ]
878
879impl Statsig {
880    pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
881        if let Some(adapter) = &self.override_adapter {
882            adapter.override_gate(gate_name, value, id);
883        }
884    }
885
886    pub fn override_dynamic_config(
887        &self,
888        config_name: &str,
889        value: HashMap<String, serde_json::Value>,
890        id: Option<&str>,
891    ) {
892        if let Some(adapter) = &self.override_adapter {
893            adapter.override_dynamic_config(config_name, value, id);
894        }
895    }
896
897    pub fn override_layer(
898        &self,
899        layer_name: &str,
900        value: HashMap<String, serde_json::Value>,
901        id: Option<&str>,
902    ) {
903        if let Some(adapter) = &self.override_adapter {
904            adapter.override_layer(layer_name, value, id);
905        }
906    }
907
908    pub fn override_experiment(
909        &self,
910        experiment_name: &str,
911        value: HashMap<String, serde_json::Value>,
912        id: Option<&str>,
913    ) {
914        if let Some(adapter) = &self.override_adapter {
915            adapter.override_experiment(experiment_name, value, id);
916        }
917    }
918
919    pub fn override_experiment_by_group_name(
920        &self,
921        experiment_name: &str,
922        group_name: &str,
923        id: Option<&str>,
924    ) {
925        if let Some(adapter) = &self.override_adapter {
926            adapter.override_experiment_by_group_name(experiment_name, group_name, id);
927        }
928    }
929
930    pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
931        if let Some(adapter) = &self.override_adapter {
932            adapter.remove_gate_override(gate_name, id);
933        }
934    }
935
936    pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
937        if let Some(adapter) = &self.override_adapter {
938            adapter.remove_dynamic_config_override(config_name, id);
939        }
940    }
941
942    pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
943        if let Some(adapter) = &self.override_adapter {
944            adapter.remove_experiment_override(experiment_name, id);
945        }
946    }
947
948    pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
949        if let Some(adapter) = &self.override_adapter {
950            adapter.remove_layer_override(layer_name, id);
951        }
952    }
953
954    pub fn remove_all_overrides(&self) {
955        if let Some(adapter) = &self.override_adapter {
956            adapter.remove_all_overrides();
957        }
958    }
959}
960
961// ------------------------------------------------------------------------------- [ Debugging ]
962
963impl Statsig {
964    pub fn get_feature_gate_list(&self) -> Vec<String> {
965        let data = read_lock_or_else!(self.spec_store.data, {
966            log_error_to_statsig_and_console!(
967                &self.ops_stats,
968                TAG,
969                StatsigErr::LockFailure(
970                    "Failed to acquire read lock for spec store data".to_string()
971                )
972            );
973            return vec![];
974        });
975
976        data.values.feature_gates.unperformant_keys()
977    }
978
979    pub fn get_dynamic_config_list(&self) -> Vec<String> {
980        let data = read_lock_or_else!(self.spec_store.data, {
981            log_error_to_statsig_and_console!(
982                &self.ops_stats,
983                TAG,
984                StatsigErr::LockFailure(
985                    "Failed to acquire read lock for spec store data".to_string()
986                )
987            );
988            return vec![];
989        });
990
991        data.values
992            .dynamic_configs
993            .unperformant_keys_entity_filter("dynamic_config")
994    }
995
996    pub fn get_experiment_list(&self) -> Vec<String> {
997        let data = read_lock_or_else!(self.spec_store.data, {
998            log_error_to_statsig_and_console!(
999                &self.ops_stats,
1000                TAG,
1001                StatsigErr::LockFailure(
1002                    "Failed to acquire read lock for spec store data".to_string()
1003                )
1004            );
1005            return vec![];
1006        });
1007
1008        data.values
1009            .dynamic_configs
1010            .unperformant_keys_entity_filter("experiment")
1011    }
1012
1013    pub fn get_autotune_list(&self) -> Vec<String> {
1014        let data = read_lock_or_else!(self.spec_store.data, {
1015            log_error_to_statsig_and_console!(
1016                &self.ops_stats,
1017                TAG,
1018                StatsigErr::LockFailure(
1019                    "Failed to acquire read lock for spec store data".to_string()
1020                )
1021            );
1022            return vec![];
1023        });
1024
1025        data.values
1026            .dynamic_configs
1027            .unperformant_keys_entity_filter("autotune")
1028    }
1029
1030    pub fn get_parameter_store_list(&self) -> Vec<String> {
1031        let data = read_lock_or_else!(self.spec_store.data, {
1032            log_error_to_statsig_and_console!(
1033                &self.ops_stats,
1034                TAG,
1035                StatsigErr::LockFailure(
1036                    "Failed to acquire read lock for spec store data".to_string()
1037                )
1038            );
1039            return vec![];
1040        });
1041
1042        match &data.values.param_stores {
1043            Some(param_stores) => param_stores.keys().cloned().collect(),
1044            None => vec![],
1045        }
1046    }
1047
1048    pub fn get_layer_list(&self) -> Vec<String> {
1049        let data = read_lock_or_else!(self.spec_store.data, {
1050            log_error_to_statsig_and_console!(
1051                &self.ops_stats,
1052                TAG,
1053                StatsigErr::LockFailure(
1054                    "Failed to acquire read lock for spec store data".to_string()
1055                )
1056            );
1057            return vec![];
1058        });
1059
1060        data.values.layer_configs.unperformant_keys()
1061    }
1062
1063    pub fn __get_parsed_user_agent_value(
1064        &self,
1065        user: &StatsigUser,
1066    ) -> Option<ParsedUserAgentValue> {
1067        UserAgentParser::get_parsed_user_agent_value_for_user(user, &self.options)
1068    }
1069}
1070
1071// ------------------------------------------------------------------------------- [ Feature Gate ]
1072
1073impl Statsig {
1074    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1075        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1076    }
1077
1078    pub fn check_gate_with_options(
1079        &self,
1080        user: &StatsigUser,
1081        gate_name: &str,
1082        options: FeatureGateEvaluationOptions,
1083    ) -> bool {
1084        let user_internal = self.internalize_user(user);
1085        let disable_exposure_logging = options.disable_exposure_logging;
1086        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1087
1088        let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1089        let rule_id = evaluation
1090            .as_ref()
1091            .map(|e| e.base.rule_id.clone())
1092            .unwrap_or_default();
1093
1094        if disable_exposure_logging {
1095            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1096            self.event_logger.increment_non_exposure_checks(gate_name);
1097        } else {
1098            self.event_logger.enqueue(EnqueueGateExpoOp {
1099                exposure_time: Utc::now().timestamp_millis() as u64,
1100                user: &user_internal,
1101                queried_gate_name: gate_name,
1102                evaluation: evaluation.map(Cow::Owned),
1103                details: details.clone(),
1104                trigger: ExposureTrigger::Auto,
1105            });
1106        }
1107
1108        self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1109
1110        value
1111    }
1112
1113    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1114        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1115    }
1116
1117    pub fn get_feature_gate_with_options(
1118        &self,
1119        user: &StatsigUser,
1120        gate_name: &str,
1121        options: FeatureGateEvaluationOptions,
1122    ) -> FeatureGate {
1123        let user_internal = self.internalize_user(user);
1124        let disable_exposure_logging = options.disable_exposure_logging;
1125        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1126
1127        if disable_exposure_logging {
1128            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1129            self.event_logger.increment_non_exposure_checks(gate_name);
1130        } else {
1131            self.event_logger.enqueue(EnqueueGateExpoOp {
1132                exposure_time: Utc::now().timestamp_millis() as u64,
1133                user: &user_internal,
1134                queried_gate_name: gate_name,
1135                evaluation: evaluation.as_ref().map(Cow::Borrowed),
1136                details: details.clone(),
1137                trigger: ExposureTrigger::Auto,
1138            });
1139        }
1140
1141        let gate = make_feature_gate(gate_name, evaluation, details);
1142        self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1143        gate
1144    }
1145
1146    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1147        let user_internal = self.internalize_user(user);
1148        let (details, evaluation) = self.get_gate_evaluation(&user_internal, gate_name);
1149        self.event_logger.enqueue(EnqueueGateExpoOp {
1150            exposure_time: Utc::now().timestamp_millis() as u64,
1151            user: &user_internal,
1152            queried_gate_name: gate_name,
1153            evaluation: evaluation.map(Cow::Owned),
1154            details: details.clone(),
1155            trigger: ExposureTrigger::Manual,
1156        });
1157    }
1158
1159    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1160        let data = read_lock_or_else!(self.spec_store.data, {
1161            log_error_to_statsig_and_console!(
1162                self.ops_stats.clone(),
1163                TAG,
1164                StatsigErr::LockFailure(
1165                    "Failed to acquire read lock for spec store data".to_string()
1166                )
1167            );
1168            return vec![];
1169        });
1170
1171        let gate = data.values.feature_gates.get(gate_name);
1172        match gate {
1173            Some(gate) => match &gate.spec.fields_used {
1174                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1175                None => vec![],
1176            },
1177            None => vec![],
1178        }
1179    }
1180}
1181
1182// ------------------------------------------------------------------------------- [ Dynamic Config ]
1183
1184impl Statsig {
1185    pub fn get_dynamic_config(
1186        &self,
1187        user: &StatsigUser,
1188        dynamic_config_name: &str,
1189    ) -> DynamicConfig {
1190        self.get_dynamic_config_with_options(
1191            user,
1192            dynamic_config_name,
1193            DynamicConfigEvaluationOptions::default(),
1194        )
1195    }
1196
1197    pub fn get_dynamic_config_with_options(
1198        &self,
1199        user: &StatsigUser,
1200        dynamic_config_name: &str,
1201        options: DynamicConfigEvaluationOptions,
1202    ) -> DynamicConfig {
1203        let user_internal = self.internalize_user(user);
1204        let disable_exposure_logging = options.disable_exposure_logging;
1205        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1206
1207        if disable_exposure_logging {
1208            log_d!(
1209                TAG,
1210                "Exposure logging is disabled for Dynamic Config {}",
1211                dynamic_config_name
1212            );
1213            self.event_logger
1214                .increment_non_exposure_checks(dynamic_config_name);
1215        } else {
1216            self.event_logger.enqueue(EnqueueConfigExpoOp {
1217                exposure_time: Utc::now().timestamp_millis() as u64,
1218                user: &user_internal,
1219                config: &dynamic_config,
1220                trigger: ExposureTrigger::Auto,
1221            });
1222        }
1223
1224        self.emit_dynamic_config_evaluated(&dynamic_config);
1225
1226        dynamic_config
1227    }
1228
1229    pub fn manually_log_dynamic_config_exposure(
1230        &self,
1231        user: &StatsigUser,
1232        dynamic_config_name: &str,
1233    ) {
1234        let user_internal = self.internalize_user(user);
1235        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
1236        self.event_logger.enqueue(EnqueueConfigExpoOp {
1237            exposure_time: Utc::now().timestamp_millis() as u64,
1238            user: &user_internal,
1239            config: &dynamic_config,
1240            trigger: ExposureTrigger::Manual,
1241        });
1242    }
1243
1244    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1245        let data = read_lock_or_else!(self.spec_store.data, {
1246            log_error_to_statsig_and_console!(
1247                self.ops_stats.clone(),
1248                TAG,
1249                StatsigErr::LockFailure(
1250                    "Failed to acquire read lock for spec store data".to_string()
1251                )
1252            );
1253            return vec![];
1254        });
1255
1256        let config = data.values.dynamic_configs.get(config_name);
1257        match config {
1258            Some(config) => match &config.spec.fields_used {
1259                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1260                None => vec![],
1261            },
1262            None => vec![],
1263        }
1264    }
1265}
1266
1267// ------------------------------------------------------------------------------- [ Experiment ]
1268
1269impl Statsig {
1270    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1271        self.get_experiment_with_options(
1272            user,
1273            experiment_name,
1274            ExperimentEvaluationOptions::default(),
1275        )
1276    }
1277
1278    pub fn get_experiment_with_options(
1279        &self,
1280        user: &StatsigUser,
1281        experiment_name: &str,
1282        options: ExperimentEvaluationOptions,
1283    ) -> Experiment {
1284        let user_internal = self.internalize_user(user);
1285        let disable_exposure_logging = options.disable_exposure_logging;
1286        let mut experiment = self.get_experiment_impl(&user_internal, experiment_name);
1287        if let Some(persisted_experiment) = self.persistent_values_manager.as_ref().and_then(|m| {
1288            m.try_apply_sticky_value_to_experiment(&user_internal, &options, &experiment)
1289        }) {
1290            experiment = persisted_experiment
1291        }
1292
1293        if disable_exposure_logging {
1294            log_d!(
1295                TAG,
1296                "Exposure logging is disabled for experiment {}",
1297                experiment_name
1298            );
1299            self.event_logger
1300                .increment_non_exposure_checks(experiment_name);
1301        } else {
1302            self.event_logger.enqueue(EnqueueExperimentExpoOp {
1303                exposure_time: Utc::now().timestamp_millis() as u64,
1304                user: &user_internal,
1305                experiment: &experiment,
1306                trigger: ExposureTrigger::Auto,
1307            });
1308        }
1309
1310        self.emit_experiment_evaluated(&experiment);
1311
1312        experiment
1313    }
1314
1315    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1316        let user_internal = self.internalize_user(user);
1317        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
1318        self.event_logger.enqueue(EnqueueExperimentExpoOp {
1319            exposure_time: Utc::now().timestamp_millis() as u64,
1320            user: &user_internal,
1321            experiment: &experiment,
1322            trigger: ExposureTrigger::Manual,
1323        });
1324    }
1325
1326    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1327        let data = read_lock_or_else!(self.spec_store.data, {
1328            log_error_to_statsig_and_console!(
1329                self.ops_stats.clone(),
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        let config = data.values.dynamic_configs.get(experiment_name);
1339        match config {
1340            Some(config) => match &config.spec.fields_used {
1341                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1342                None => vec![],
1343            },
1344            None => vec![],
1345        }
1346    }
1347
1348    pub fn get_experiment_by_group_name(
1349        &self,
1350        experiment_name: &str,
1351        group_name: &str,
1352    ) -> Experiment {
1353        let data = read_lock_or_else!(self.spec_store.data, {
1354            log_error_to_statsig_and_console!(
1355                self.ops_stats.clone(),
1356                TAG,
1357                StatsigErr::LockFailure(
1358                    "Failed to acquire read lock for spec store data".to_string()
1359                )
1360            );
1361            return make_experiment(
1362                experiment_name,
1363                None,
1364                EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1365            );
1366        });
1367
1368        let Some(exp) = data.values.dynamic_configs.get(experiment_name) else {
1369            return make_experiment(
1370                experiment_name,
1371                None,
1372                EvaluationDetails::unrecognized(
1373                    &data.source,
1374                    data.values.time,
1375                    data.time_received_at,
1376                ),
1377            );
1378        };
1379
1380        if let Some(rule) = exp
1381            .spec
1382            .rules
1383            .iter()
1384            .find(|rule| rule.group_name.as_deref() == Some(group_name))
1385        {
1386            let value = rule.return_value.get_json().unwrap_or_default();
1387            let rule_id = String::from(rule.id.as_str());
1388            let id_type = rule.id_type.value.unperformant_to_string();
1389            let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1390
1391            return Experiment {
1392                name: experiment_name.to_string(),
1393                value,
1394                rule_id,
1395                id_type,
1396                group_name,
1397                details: EvaluationDetails::recognized_without_eval_result(
1398                    &data.source,
1399                    data.values.time,
1400                    data.time_received_at,
1401                ),
1402                is_experiment_active: exp.spec.is_active.unwrap_or(false),
1403                __evaluation: None,
1404            };
1405        }
1406
1407        make_experiment(
1408            experiment_name,
1409            None,
1410            EvaluationDetails::unrecognized(&data.source, data.values.time, data.time_received_at),
1411        )
1412    }
1413}
1414
1415// ------------------------------------------------------------------------------- [ Layer ]
1416
1417impl Statsig {
1418    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1419        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1420    }
1421
1422    pub fn get_layer_with_options(
1423        &self,
1424        user: &StatsigUser,
1425        layer_name: &str,
1426        options: LayerEvaluationOptions,
1427    ) -> Layer {
1428        let user_internal = self.internalize_user(user);
1429        self.get_layer_impl(user_internal, layer_name, options)
1430    }
1431
1432    pub fn manually_log_layer_parameter_exposure(
1433        &self,
1434        user: &StatsigUser,
1435        layer_name: &str,
1436        parameter_name: String,
1437    ) {
1438        let user_internal = self.internalize_user(user);
1439        let layer =
1440            self.get_layer_impl(user_internal, layer_name, LayerEvaluationOptions::default());
1441
1442        self.event_logger
1443            .enqueue(EnqueueLayerParamExpoOp::LayerOwned(
1444                Utc::now().timestamp_millis() as u64,
1445                Box::new(layer),
1446                parameter_name,
1447                ExposureTrigger::Manual,
1448            ));
1449    }
1450
1451    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1452        let data = read_lock_or_else!(self.spec_store.data, {
1453            log_error_to_statsig_and_console!(
1454                self.ops_stats.clone(),
1455                TAG,
1456                StatsigErr::LockFailure(
1457                    "Failed to acquire read lock for spec store data".to_string()
1458                )
1459            );
1460            return vec![];
1461        });
1462
1463        let layer = data.values.layer_configs.get(layer_name);
1464        match layer {
1465            Some(layer) => match &layer.spec.fields_used {
1466                Some(fields) => fields.iter().map(|f| f.unperformant_to_string()).collect(),
1467                None => vec![],
1468            },
1469            None => vec![],
1470        }
1471    }
1472}
1473
1474// ------------------------------------------------------------------------------- [ Internal ]
1475
1476impl Statsig {
1477    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1478        if let Some(env) = &self.statsig_environment {
1479            return env.get(key).cloned();
1480        }
1481
1482        if let Some(fallback_env) = self
1483            .fallback_environment
1484            .try_lock_for(Duration::from_secs(5))
1485        {
1486            if let Some(env) = &*fallback_env {
1487                return env.get(key).cloned();
1488            }
1489        }
1490
1491        None
1492    }
1493
1494    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1495        if let Some(env) = &self.options.global_custom_fields {
1496            return env.get(key);
1497        }
1498
1499        None
1500    }
1501
1502    pub(crate) fn use_global_custom_fields<T>(
1503        &self,
1504        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1505    ) -> T {
1506        f(self.options.global_custom_fields.as_ref())
1507    }
1508
1509    pub(crate) fn use_statsig_env<T>(
1510        &self,
1511        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1512    ) -> T {
1513        if let Some(env) = &self.statsig_environment {
1514            return f(Some(env));
1515        }
1516
1517        if let Some(fallback_env) = self
1518            .fallback_environment
1519            .try_lock_for(Duration::from_secs(5))
1520        {
1521            if let Some(env) = &*fallback_env {
1522                return f(Some(env));
1523            }
1524        }
1525
1526        f(None)
1527    }
1528}
1529
1530// ------------------------------------------------------------------------------- [ Private ]
1531
1532impl Statsig {
1533    async fn start_background_tasks(
1534        statsig_runtime: Arc<StatsigRuntime>,
1535        id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
1536        specs_adapter: Arc<dyn SpecsAdapter>,
1537        ops_stats: Arc<OpsStatsForInstance>,
1538        bg_tasks_started: Arc<AtomicBool>,
1539    ) -> bool {
1540        if bg_tasks_started.load(Ordering::SeqCst) {
1541            return true;
1542        }
1543
1544        let mut success = true;
1545
1546        if let Some(adapter) = &id_lists_adapter {
1547            if let Err(e) = adapter
1548                .clone()
1549                .schedule_background_sync(&statsig_runtime)
1550                .await
1551            {
1552                success = false;
1553                log_w!(TAG, "Failed to schedule idlist background job {}", e);
1554            }
1555        }
1556
1557        if let Err(e) = specs_adapter
1558            .clone()
1559            .schedule_background_sync(&statsig_runtime)
1560            .await
1561        {
1562            success = false;
1563            log_error_to_statsig_and_console!(
1564                ops_stats,
1565                TAG,
1566                StatsigErr::SpecsAdapterSkipPoll(format!(
1567                    "Failed to schedule specs adapter background job: {e}"
1568                ))
1569            );
1570        }
1571
1572        bg_tasks_started.store(true, Ordering::SeqCst);
1573
1574        success
1575    }
1576
1577    async fn apply_timeout_to_init(
1578        &self,
1579        timeout_ms: u64,
1580    ) -> Result<InitializeDetails, StatsigErr> {
1581        let timeout = Duration::from_millis(timeout_ms);
1582
1583        let init_future = self.initialize_impl_with_details();
1584        let timeout_future = sleep(timeout);
1585
1586        let statsig_runtime = self.statsig_runtime.clone();
1587        let id_lists_adapter = self.id_lists_adapter.inner.clone();
1588        let specs_adapter = self.specs_adapter.inner.clone();
1589        let ops_stats = self.ops_stats.clone();
1590        let background_tasks_started = self.background_tasks_started.clone();
1591        // Create another clone specifically for the closure
1592        let statsig_runtime_for_closure = statsig_runtime.clone();
1593
1594        tokio::select! {
1595            result = init_future => {
1596                result
1597            },
1598            _ = timeout_future => {
1599                statsig_runtime.spawn(
1600                    "start_background_tasks",
1601                    |_shutdown_notify| async move {
1602                        Self::start_background_tasks(
1603                            statsig_runtime_for_closure,
1604                            id_lists_adapter,
1605                            specs_adapter,
1606                            ops_stats,
1607                            background_tasks_started,
1608                        ).await;
1609                    }
1610                )?;
1611                Ok(InitializeDetails::from_timeout_failure(timeout_ms))
1612            },
1613        }
1614    }
1615
1616    async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
1617        let start_time = Instant::now();
1618        self.spec_store.set_source(SpecsSource::Loading);
1619        self.specs_adapter.inner.initialize(self.spec_store.clone());
1620        let use_third_party_ua_parser = self.should_user_third_party_parser();
1621
1622        let mut error_message = None;
1623        let mut id_list_ready = None;
1624
1625        let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
1626            Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
1627                CountryLookup::load_country_lookup();
1628            }))
1629        } else {
1630            None
1631        };
1632
1633        let init_ua = if use_third_party_ua_parser {
1634            Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
1635                UserAgentParser::load_parser();
1636            }))
1637        } else {
1638            None
1639        };
1640
1641        let init_res = match self
1642            .specs_adapter
1643            .inner
1644            .clone()
1645            .start(&self.statsig_runtime)
1646            .await
1647        {
1648            Ok(()) => Ok(()),
1649            Err(e) => {
1650                self.spec_store.set_source(SpecsSource::NoValues);
1651                error_message = Some(format!("Failed to start specs adapter: {e}"));
1652                Err(e)
1653            }
1654        };
1655
1656        if let Some(adapter) = &self.id_lists_adapter.inner {
1657            match adapter
1658                .clone()
1659                .start(&self.statsig_runtime, self.spec_store.clone())
1660                .await
1661            {
1662                Ok(()) => {
1663                    id_list_ready = Some(true);
1664                }
1665                Err(e) => {
1666                    id_list_ready = Some(false);
1667                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
1668                }
1669            }
1670        }
1671
1672        if let Err(e) = self
1673            .event_logging_adapter
1674            .clone()
1675            .start(&self.statsig_runtime)
1676            .await
1677        {
1678            log_error_to_statsig_and_console!(
1679                self.ops_stats.clone(),
1680                TAG,
1681                StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
1682            );
1683        }
1684
1685        let spec_info = self.spec_store.get_current_specs_info();
1686        let duration = start_time.elapsed().as_millis() as u64;
1687
1688        self.set_default_environment_from_server();
1689
1690        if self.options.wait_for_country_lookup_init.unwrap_or(false) {
1691            match init_country_lookup {
1692                Some(Ok(task_id)) => {
1693                    let _ = self
1694                        .statsig_runtime
1695                        .await_join_handle(INIT_IP_TAG, &task_id)
1696                        .await;
1697                }
1698                Some(Err(e)) => {
1699                    log_error_to_statsig_and_console!(
1700                        self.ops_stats.clone(),
1701                        TAG,
1702                        StatsigErr::UnstartedAdapter(format!(
1703                            "Failed to spawn country lookup task: {e}"
1704                        ))
1705                    );
1706                }
1707                _ => {}
1708            }
1709        }
1710        if self.options.wait_for_user_agent_init.unwrap_or(false) {
1711            match init_ua {
1712                Some(Ok(task_id)) => {
1713                    let _ = self
1714                        .statsig_runtime
1715                        .await_join_handle(INIT_UA_TAG, &task_id)
1716                        .await;
1717                }
1718                Some(Err(e)) => {
1719                    log_error_to_statsig_and_console!(
1720                        self.ops_stats.clone(),
1721                        TAG,
1722                        StatsigErr::UnstartedAdapter(format!(
1723                            "Failed to spawn user agent parser task: {e}"
1724                        ))
1725                    );
1726                }
1727                _ => {}
1728            }
1729        }
1730
1731        let error = init_res.clone().err();
1732
1733        let success = Self::start_background_tasks(
1734            self.statsig_runtime.clone(),
1735            self.id_lists_adapter.inner.clone(),
1736            self.specs_adapter.inner.clone(),
1737            self.ops_stats.clone(),
1738            self.background_tasks_started.clone(),
1739        )
1740        .await;
1741
1742        Ok(InitializeDetails::new(
1743            success,
1744            duration,
1745            spec_info,
1746            id_list_ready,
1747            error,
1748        ))
1749    }
1750
1751    fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
1752        match init_details {
1753            Ok(details) => {
1754                self.log_init_finish(
1755                    details.init_success,
1756                    &None,
1757                    &details.duration_ms,
1758                    &self.spec_store.get_current_specs_info(),
1759                );
1760                if let Some(failure) = &details.failure_details {
1761                    log_error_to_statsig_and_console!(
1762                        self.ops_stats,
1763                        TAG,
1764                        StatsigErr::InitializationError(failure.reason.clone())
1765                    );
1766                }
1767            }
1768            Err(err) => {
1769                // we store errors on init details so we should never return error and thus do not need to log
1770                log_w!(TAG, "Initialization error: {:?}", err);
1771            }
1772        }
1773    }
1774
1775    fn create_standard_eval_context<'a>(
1776        &'a self,
1777        user_internal: &'a StatsigUserInternal,
1778        data: &'a SpecStoreData,
1779        app_id: Option<&'a DynamicValue>,
1780        override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
1781    ) -> EvaluatorContext<'a> {
1782        EvaluatorContext::new(
1783            user_internal,
1784            &data.values,
1785            IdListResolution::MapLookup(&data.id_lists),
1786            &self.hashing,
1787            app_id,
1788            override_adapter,
1789            self.should_user_third_party_parser(),
1790        )
1791    }
1792
1793    fn create_gcir_eval_context<'a>(
1794        &'a self,
1795        user_internal: &'a StatsigUserInternal,
1796        data: &'a SpecStoreData,
1797        options: &'a ClientInitResponseOptions,
1798    ) -> EvaluatorContext<'a> {
1799        let app_id = select_app_id_for_gcir(options, &data.values, &self.hashing);
1800        let override_adapter = match options.include_local_overrides {
1801            Some(true) => self.override_adapter.as_ref(),
1802            _ => None,
1803        };
1804
1805        EvaluatorContext::new(
1806            user_internal,
1807            &data.values,
1808            IdListResolution::MapLookup(&data.id_lists),
1809            &self.hashing,
1810            app_id,
1811            override_adapter,
1812            self.should_user_third_party_parser(),
1813        )
1814    }
1815
1816    fn evaluate_spec<T>(
1817        &self,
1818        user_internal: &StatsigUserInternal,
1819        spec_name: &str,
1820        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1821        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1822        spec_type: &SpecType,
1823    ) -> T {
1824        let data = read_lock_or_else!(self.spec_store.data, {
1825            log_error_to_statsig_and_console!(
1826                &self.ops_stats,
1827                TAG,
1828                StatsigErr::LockFailure(
1829                    "Failed to acquire read lock for spec store data".to_string()
1830                )
1831            );
1832            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1833        });
1834
1835        let mut context = self.create_standard_eval_context(
1836            user_internal,
1837            &data,
1838            data.values.app_id.as_ref(),
1839            self.override_adapter.as_ref(),
1840        );
1841
1842        match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
1843            Ok(eval_details) => make_result(context.result, eval_details),
1844            Err(e) => {
1845                log_error_to_statsig_and_console!(
1846                    &self.ops_stats,
1847                    TAG,
1848                    StatsigErr::EvaluationError(e.to_string())
1849                );
1850                make_empty_result(EvaluationDetails::error(&e.to_string()))
1851            }
1852        }
1853    }
1854
1855    fn evaluate_with_details(
1856        ctx: &mut EvaluatorContext,
1857        spec_store_data: &SpecStoreData,
1858        spec_name: &str,
1859        spec_type: &SpecType,
1860    ) -> Result<EvaluationDetails, StatsigErr> {
1861        let recognition = Evaluator::evaluate(ctx, spec_name, spec_type)?;
1862
1863        if recognition == Recognition::Unrecognized {
1864            return Ok(EvaluationDetails::unrecognized(
1865                &spec_store_data.source,
1866                spec_store_data.values.time,
1867                spec_store_data.time_received_at,
1868            ));
1869        }
1870
1871        if let Some(reason) = ctx.result.override_reason {
1872            return Ok(EvaluationDetails::recognized_but_overridden(
1873                spec_store_data.values.time,
1874                spec_store_data.time_received_at,
1875                reason,
1876                ctx.result.version,
1877            ));
1878        }
1879
1880        Ok(EvaluationDetails::recognized(
1881            &spec_store_data.source,
1882            spec_store_data.values.time,
1883            spec_store_data.time_received_at,
1884            &ctx.result,
1885        ))
1886    }
1887
1888    fn stringify_gcir_response<T: Serialize>(
1889        &self,
1890        input: Result<T, StatsigErr>,
1891        fallback: impl FnOnce() -> T,
1892    ) -> String {
1893        match input {
1894            Ok(value) => serde_json::to_string(&value).unwrap_or_default(),
1895            Err(e) => {
1896                log_error_to_statsig_and_console!(
1897                    &self.ops_stats,
1898                    TAG,
1899                    StatsigErr::GCIRError(e.to_string())
1900                );
1901                serde_json::to_string(&fallback()).unwrap_or_default()
1902            }
1903        }
1904    }
1905
1906    fn get_gate_evaluation(
1907        &self,
1908        user_internal: &StatsigUserInternal,
1909        gate_name: &str,
1910    ) -> (EvaluationDetails, Option<GateEvaluation>) {
1911        self.evaluate_spec(
1912            user_internal,
1913            gate_name,
1914            |eval_details| (eval_details, None),
1915            |mut result, eval_details| {
1916                let evaluation = result_to_gate_eval(gate_name, &mut result);
1917                (eval_details, Some(evaluation))
1918            },
1919            &SpecType::Gate,
1920        )
1921    }
1922
1923    fn get_dynamic_config_impl(
1924        &self,
1925        user_internal: &StatsigUserInternal,
1926        config_name: &str,
1927    ) -> DynamicConfig {
1928        self.evaluate_spec(
1929            user_internal,
1930            config_name,
1931            |eval_details| make_dynamic_config(config_name, None, eval_details),
1932            |mut result, eval_details| {
1933                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1934                make_dynamic_config(config_name, Some(evaluation), eval_details)
1935            },
1936            &SpecType::DynamicConfig,
1937        )
1938    }
1939
1940    fn get_experiment_impl(
1941        &self,
1942        user_internal: &StatsigUserInternal,
1943        experiment_name: &str,
1944    ) -> Experiment {
1945        self.evaluate_spec(
1946            user_internal,
1947            experiment_name,
1948            |eval_details| make_experiment(experiment_name, None, eval_details),
1949            |mut result, eval_details| {
1950                let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1951                make_experiment(experiment_name, Some(evaluation), eval_details)
1952            },
1953            &SpecType::Experiment,
1954        )
1955    }
1956
1957    fn get_layer_impl(
1958        &self,
1959        user_internal: StatsigUserInternal,
1960        layer_name: &str,
1961        evaluation_options: LayerEvaluationOptions,
1962    ) -> Layer {
1963        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1964
1965        if disable_exposure_logging {
1966            self.event_logger.increment_non_exposure_checks(layer_name);
1967        }
1968
1969        let mut layer = self.evaluate_spec(
1970            &user_internal,
1971            layer_name,
1972            |eval_details| {
1973                make_layer(
1974                    user_internal.to_loggable(),
1975                    layer_name,
1976                    None,
1977                    eval_details,
1978                    None,
1979                    disable_exposure_logging,
1980                )
1981            },
1982            |mut result, eval_details| {
1983                let evaluation = result_to_layer_eval(layer_name, &mut result);
1984                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1985
1986                make_layer(
1987                    user_internal.to_loggable(),
1988                    layer_name,
1989                    Some(evaluation),
1990                    eval_details,
1991                    Some(event_logger_ptr),
1992                    disable_exposure_logging,
1993                )
1994            },
1995            &SpecType::Layer,
1996        );
1997
1998        let data = read_lock_or_else!(self.spec_store.data, {
1999            log_error_to_statsig_and_console!(
2000                &self.ops_stats,
2001                TAG,
2002                StatsigErr::LockFailure(
2003                    "Failed to acquire read lock for spec store data".to_string()
2004                )
2005            );
2006            return layer;
2007        });
2008        if let Some(persisted_layer) = self.persistent_values_manager.as_ref().and_then(|p| {
2009            let event_logger_ptr = Arc::downgrade(&self.event_logger);
2010            p.try_apply_sticky_value_to_layer(
2011                &user_internal,
2012                &evaluation_options,
2013                &layer,
2014                Some(event_logger_ptr),
2015                disable_exposure_logging,
2016                &data,
2017            )
2018        }) {
2019            layer = persisted_layer
2020        }
2021
2022        self.emit_layer_evaluated(&layer);
2023
2024        layer
2025    }
2026
2027    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2028        StatsigUserInternal::new(user, Some(self))
2029    }
2030
2031    fn set_default_environment_from_server(&self) {
2032        let data = read_lock_or_else!(self.spec_store.data, {
2033            return;
2034        });
2035
2036        if let Some(default_env) = data.values.default_environment.as_ref() {
2037            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2038
2039            match self
2040                .fallback_environment
2041                .try_lock_for(Duration::from_secs(5))
2042            {
2043                Some(mut fallback_env) => {
2044                    *fallback_env = Some(env_map);
2045                }
2046                None => {
2047                    log_e!(TAG, "Failed to lock fallback_environment");
2048                }
2049            }
2050        }
2051    }
2052
2053    fn log_init_finish(
2054        &self,
2055        success: bool,
2056        error_message: &Option<String>,
2057        duration_ms: &u64,
2058        specs_info: &SpecsInfo,
2059    ) {
2060        let is_store_populated = specs_info.source != SpecsSource::NoValues;
2061        let source_str = specs_info.source.to_string();
2062
2063        let event = ObservabilityEvent::new_event(
2064            MetricType::Dist,
2065            "initialization".to_string(),
2066            *duration_ms as f64,
2067            Some(HashMap::from([
2068                ("success".to_owned(), success.to_string()),
2069                ("source".to_owned(), source_str.clone()),
2070                ("store_populated".to_owned(), is_store_populated.to_string()),
2071                (
2072                    "spec_source_api".to_owned(),
2073                    specs_info.source_api.clone().unwrap_or_default(),
2074                ),
2075            ])),
2076        );
2077
2078        self.ops_stats.log(event);
2079        self.ops_stats.add_marker(
2080            {
2081                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2082                    .with_is_success(success)
2083                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2084                    .with_source(source_str);
2085
2086                if let Some(msg) = &error_message {
2087                    marker.with_message(msg.to_string())
2088                } else {
2089                    marker
2090                }
2091            },
2092            Some(ContextType::Initialize),
2093        );
2094        self.ops_stats
2095            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2096    }
2097
2098    fn should_user_third_party_parser(&self) -> bool {
2099        self.options.use_third_party_ua_parser.unwrap_or(false)
2100    }
2101}
2102
2103fn initialize_event_logging_adapter(
2104    sdk_key: &str,
2105    options: &StatsigOptions,
2106) -> Arc<dyn EventLoggingAdapter> {
2107    options
2108        .event_logging_adapter
2109        .clone()
2110        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2111}
2112
2113fn initialize_specs_adapter(
2114    sdk_key: &str,
2115    options: &StatsigOptions,
2116    hashing: &HashUtil,
2117) -> SpecsAdapterHousing {
2118    if let Some(adapter) = options.specs_adapter.clone() {
2119        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2120        return SpecsAdapterHousing {
2121            inner: adapter,
2122            as_default_adapter: None,
2123        };
2124    }
2125
2126    if let Some(adapter_config) = options.spec_adapters_config.clone() {
2127        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2128            sdk_key,
2129            adapter_config,
2130            options,
2131            hashing,
2132        ));
2133
2134        return SpecsAdapterHousing {
2135            inner: adapter,
2136            as_default_adapter: None,
2137        };
2138    }
2139
2140    if let Some(data_adapter) = options.data_store.clone() {
2141        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2142            sdk_key,
2143            data_adapter,
2144            options,
2145            hashing,
2146        ));
2147
2148        return SpecsAdapterHousing {
2149            inner: adapter,
2150            as_default_adapter: None,
2151        };
2152    }
2153
2154    let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2155
2156    SpecsAdapterHousing {
2157        inner: adapter.clone(),
2158        as_default_adapter: Some(adapter),
2159    }
2160}
2161
2162fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2163    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2164        return IdListsAdapterHousing {
2165            inner: Some(id_lists_adapter),
2166            as_default_adapter: None,
2167        };
2168    }
2169
2170    if options.enable_id_lists.unwrap_or(false) {
2171        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2172
2173        return IdListsAdapterHousing {
2174            inner: Some(adapter.clone()),
2175            as_default_adapter: Some(adapter),
2176        };
2177    }
2178
2179    IdListsAdapterHousing {
2180        inner: None,
2181        as_default_adapter: None,
2182    }
2183}
2184
2185struct IdListsAdapterHousing {
2186    inner: Option<Arc<dyn IdListsAdapter>>,
2187    as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2188}
2189
2190struct SpecsAdapterHousing {
2191    inner: Arc<dyn SpecsAdapter>,
2192    as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2193}
2194
2195fn setup_ops_stats(
2196    sdk_key: &str,
2197    statsig_runtime: Arc<StatsigRuntime>,
2198    error_observer: &Arc<dyn OpsStatsEventObserver>,
2199    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2200    external_observer: &Option<Weak<dyn ObservabilityClient>>,
2201) -> Arc<OpsStatsForInstance> {
2202    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2203    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2204    ops_stat.subscribe(
2205        statsig_runtime.clone(),
2206        Arc::downgrade(diagnostics_observer),
2207    );
2208
2209    if let Some(ob_client) = external_observer {
2210        if let Some(client) = ob_client.upgrade() {
2211            client.init();
2212            let as_observer = client.to_ops_stats_event_observer();
2213            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2214        }
2215    }
2216
2217    ops_stat
2218}