statsig_rust/
statsig.rs

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