Skip to main content

statsig_rust/
statsig.rs

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