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_for_user(
818        &self,
819        user: &StatsigUser,
820        parameter_store_name: &str,
821    ) -> ParameterStore<'_> {
822        self.get_parameter_store_with_user_and_options(
823            Some(user),
824            parameter_store_name,
825            ParameterStoreEvaluationOptions::default(),
826        )
827    }
828
829    pub fn get_parameter_store_with_options(
830        &self,
831        parameter_store_name: &str,
832        options: ParameterStoreEvaluationOptions,
833    ) -> ParameterStore<'_> {
834        self.get_parameter_store_with_user_and_options(None, parameter_store_name, options)
835    }
836
837    fn get_parameter_store_with_user_and_options(
838        &self,
839        user: Option<&StatsigUser>,
840        parameter_store_name: &str,
841        options: ParameterStoreEvaluationOptions,
842    ) -> ParameterStore<'_> {
843        let store_name_intern = InternedString::from_str_ref(parameter_store_name);
844
845        self.event_logger
846            .increment_non_exposure_checks(parameter_store_name);
847
848        let data = read_lock_or_else!(self.spec_store.data, {
849            log_error_to_statsig_and_console!(
850                self.ops_stats.clone(),
851                TAG,
852                StatsigErr::LockFailure(
853                    "Failed to acquire read lock for spec store data".to_string()
854                )
855            );
856            return ParameterStore {
857                name: parameter_store_name.to_string(),
858                parameters: HashMap::new(),
859                details: EvaluationDetails::unrecognized_no_data(),
860                options,
861                _statsig_ref: self,
862            };
863        });
864
865        if let Some(user) = user {
866            if let Some((override_result, parameters)) =
867                self.get_parameter_store_override(user, parameter_store_name)
868            {
869                let details = EvaluationDetails::recognized_but_overridden(
870                    data.values.time,
871                    data.time_received_at,
872                    override_result.override_reason.unwrap_or("Override"),
873                    override_result.version,
874                );
875
876                return ParameterStore {
877                    name: parameter_store_name.to_string(),
878                    parameters,
879                    details,
880                    options,
881                    _statsig_ref: self,
882                };
883            }
884        }
885
886        let stores = &data.values.param_stores;
887        let store = match stores {
888            Some(stores) => stores.get(&store_name_intern),
889            None => {
890                return ParameterStore {
891                    name: parameter_store_name.to_string(),
892                    parameters: HashMap::new(),
893                    details: EvaluationDetails::unrecognized(
894                        &data.source,
895                        data.values.time,
896                        data.time_received_at,
897                    ),
898                    options,
899                    _statsig_ref: self,
900                };
901            }
902        };
903        match store {
904            Some(store) => ParameterStore {
905                name: parameter_store_name.to_string(),
906                parameters: store.parameters.clone(),
907                details: EvaluationDetails::recognized(
908                    &data.source,
909                    data.values.time,
910                    data.time_received_at,
911                    &EvaluatorResult::default(),
912                ),
913                options,
914                _statsig_ref: self,
915            },
916            None => ParameterStore {
917                name: parameter_store_name.to_string(),
918                parameters: HashMap::new(),
919                details: EvaluationDetails::unrecognized(
920                    &data.source,
921                    data.values.time,
922                    data.time_received_at,
923                ),
924                options,
925                _statsig_ref: self,
926            },
927        }
928    }
929
930    pub(crate) fn get_parameter_store_override(
931        &self,
932        user: &StatsigUser,
933        parameter_store_name: &str,
934    ) -> Option<(EvaluatorResult, HashMap<String, Parameter>)> {
935        let adapter = self.override_adapter.as_ref()?;
936
937        let mut result = EvaluatorResult::default();
938        if !adapter.get_parameter_store_override(user, parameter_store_name, &mut result) {
939            return None;
940        }
941
942        let mut parameters = HashMap::new();
943        if let Some(json_value) = &result.json_value {
944            if let Some(map) = json_value.get_json() {
945                for (param_name, param_value) in map {
946                    if let Ok(parameter) = serde_json::from_value::<Parameter>(param_value) {
947                        parameters.insert(param_name, parameter);
948                    }
949                }
950            }
951        }
952
953        Some((result, parameters))
954    }
955}
956
957// ------------------------------------------------------------------------------- [ User Store ]
958
959impl Statsig {
960    pub fn identify(&self, user: &StatsigUser) {
961        let user_internal = self.internalize_user(user);
962
963        self.event_logger.enqueue(EnqueuePassthroughOp {
964            event: StatsigEventInternal::new_custom_event(
965                user_internal.to_loggable(),
966                "statsig::identify".to_string(),
967                None,
968                None,
969            ),
970        });
971    }
972}
973
974// ------------------------------------------------------------------------------- [ CMAB ]
975
976impl Statsig {
977    pub fn get_cmab_ranked_groups(
978        &self,
979        user: &StatsigUser,
980        cmab_name: &str,
981    ) -> Vec<CMABRankedGroup> {
982        self.event_logger.increment_non_exposure_checks(cmab_name);
983
984        let data = read_lock_or_else!(self.spec_store.data, {
985            log_error_to_statsig_and_console!(
986                self.ops_stats.clone(),
987                TAG,
988                StatsigErr::LockFailure(
989                    "Failed to acquire read lock for spec store data".to_string()
990                )
991            );
992            return vec![];
993        });
994        let user_internal = self.internalize_user(user);
995        let mut context = self.create_standard_eval_context(
996            &user_internal,
997            &data,
998            data.values.app_id.as_ref(),
999            self.override_adapter.as_ref(),
1000            true,
1001        );
1002        get_cmab_ranked_list(&mut context, cmab_name)
1003    }
1004
1005    pub fn log_cmab_exposure_for_group(
1006        &self,
1007        user: &StatsigUser,
1008        cmab_name: &str,
1009        group_id: String,
1010    ) {
1011        let user_internal = self.internalize_user(user);
1012
1013        let mut experiment = self.get_experiment_impl(&user_internal, cmab_name, None);
1014        experiment.rule_id = group_id;
1015
1016        self.event_logger.enqueue(EnqueueExperimentExpoOp {
1017            exposure_time: Utc::now().timestamp_millis() as u64,
1018            user: &user_internal,
1019            experiment: &experiment,
1020            trigger: ExposureTrigger::Manual,
1021        });
1022    }
1023}
1024
1025// ------------------------------------------------------------------------------- [ Override ]
1026
1027impl Statsig {
1028    pub fn override_gate(&self, gate_name: &str, value: bool, id: Option<&str>) {
1029        if let Some(adapter) = &self.override_adapter {
1030            adapter.override_gate(gate_name, value, id);
1031        }
1032    }
1033
1034    pub fn override_dynamic_config(
1035        &self,
1036        config_name: &str,
1037        value: HashMap<String, serde_json::Value>,
1038        id: Option<&str>,
1039    ) {
1040        if let Some(adapter) = &self.override_adapter {
1041            adapter.override_dynamic_config(config_name, value, id);
1042        }
1043    }
1044
1045    pub fn override_layer(
1046        &self,
1047        layer_name: &str,
1048        value: HashMap<String, serde_json::Value>,
1049        id: Option<&str>,
1050    ) {
1051        if let Some(adapter) = &self.override_adapter {
1052            adapter.override_layer(layer_name, value, id);
1053        }
1054    }
1055
1056    pub fn override_parameter_store(
1057        &self,
1058        param_name: &str,
1059        value: HashMap<String, serde_json::Value>,
1060        id: Option<&str>,
1061    ) {
1062        if let Some(adapter) = &self.override_adapter {
1063            adapter.override_parameter_store(param_name, value, id);
1064        }
1065    }
1066
1067    pub fn override_experiment(
1068        &self,
1069        experiment_name: &str,
1070        value: HashMap<String, serde_json::Value>,
1071        id: Option<&str>,
1072    ) {
1073        if let Some(adapter) = &self.override_adapter {
1074            adapter.override_experiment(experiment_name, value, id);
1075        }
1076    }
1077
1078    pub fn override_experiment_by_group_name(
1079        &self,
1080        experiment_name: &str,
1081        group_name: &str,
1082        id: Option<&str>,
1083    ) {
1084        if let Some(adapter) = &self.override_adapter {
1085            adapter.override_experiment_by_group_name(experiment_name, group_name, id);
1086        }
1087    }
1088
1089    pub fn remove_gate_override(&self, gate_name: &str, id: Option<&str>) {
1090        if let Some(adapter) = &self.override_adapter {
1091            adapter.remove_gate_override(gate_name, id);
1092        }
1093    }
1094
1095    pub fn remove_dynamic_config_override(&self, config_name: &str, id: Option<&str>) {
1096        if let Some(adapter) = &self.override_adapter {
1097            adapter.remove_dynamic_config_override(config_name, id);
1098        }
1099    }
1100
1101    pub fn remove_experiment_override(&self, experiment_name: &str, id: Option<&str>) {
1102        if let Some(adapter) = &self.override_adapter {
1103            adapter.remove_experiment_override(experiment_name, id);
1104        }
1105    }
1106
1107    pub fn remove_layer_override(&self, layer_name: &str, id: Option<&str>) {
1108        if let Some(adapter) = &self.override_adapter {
1109            adapter.remove_layer_override(layer_name, id);
1110        }
1111    }
1112
1113    pub fn remove_parameter_store_override(&self, parameter_store_name: &str, id: Option<&str>) {
1114        if let Some(adapter) = &self.override_adapter {
1115            adapter.remove_parameter_store_override(parameter_store_name, id);
1116        }
1117    }
1118
1119    pub fn remove_all_overrides(&self) {
1120        if let Some(adapter) = &self.override_adapter {
1121            adapter.remove_all_overrides();
1122        }
1123    }
1124}
1125
1126// ------------------------------------------------------------------------------- [ Debugging ]
1127
1128impl Statsig {
1129    pub fn get_feature_gate_list(&self) -> Vec<String> {
1130        self.spec_store
1131            .unperformant_keys_entity_filter("feature_gates", "feature_gate")
1132    }
1133
1134    pub fn get_dynamic_config_list(&self) -> Vec<String> {
1135        self.spec_store
1136            .unperformant_keys_entity_filter("dynamic_configs", "dynamic_config")
1137    }
1138
1139    pub fn get_experiment_list(&self) -> Vec<String> {
1140        self.spec_store
1141            .unperformant_keys_entity_filter("dynamic_configs", "experiment")
1142    }
1143
1144    pub fn get_autotune_list(&self) -> Vec<String> {
1145        self.spec_store
1146            .unperformant_keys_entity_filter("dynamic_configs", "autotune")
1147    }
1148
1149    pub fn get_parameter_store_list(&self) -> Vec<String> {
1150        self.spec_store
1151            .unperformant_keys_entity_filter("param_stores", "*")
1152    }
1153
1154    pub fn get_layer_list(&self) -> Vec<String> {
1155        self.spec_store
1156            .unperformant_keys_entity_filter("layer_configs", "*")
1157    }
1158
1159    pub fn __get_parsed_user_agent_value(
1160        &self,
1161        user: &StatsigUser,
1162    ) -> Option<ParsedUserAgentValue> {
1163        UserAgentParser::get_parsed_user_agent_value_for_user(user, &self.options)
1164    }
1165}
1166
1167// ------------------------------------------------------------------------------- [ Feature Gate ]
1168
1169impl Statsig {
1170    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
1171        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1172    }
1173
1174    pub fn check_gate_with_options(
1175        &self,
1176        user: &StatsigUser,
1177        gate_name: &str,
1178        options: FeatureGateEvaluationOptions,
1179    ) -> bool {
1180        let user_internal = self.internalize_user(user);
1181        let disable_exposure_logging = options.disable_exposure_logging;
1182        let (details, evaluation) = self.get_gate_evaluation(
1183            &user_internal,
1184            gate_name,
1185            Some(options.disable_exposure_logging),
1186        );
1187
1188        let value = evaluation.as_ref().map(|e| e.value).unwrap_or_default();
1189        let rule_id = evaluation
1190            .as_ref()
1191            .map(|e| e.base.rule_id.clone())
1192            .unwrap_or_default();
1193
1194        if disable_exposure_logging {
1195            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1196            self.event_logger.increment_non_exposure_checks(gate_name);
1197        } else {
1198            self.event_logger.enqueue(EnqueueGateExpoOp {
1199                exposure_time: Utc::now().timestamp_millis() as u64,
1200                user: &user_internal,
1201                queried_gate_name: gate_name,
1202                evaluation: evaluation.map(Cow::Owned),
1203                details: details.clone(),
1204                trigger: ExposureTrigger::Auto,
1205            });
1206        }
1207
1208        self.emit_gate_evaluated(gate_name, rule_id.as_str(), value, &details.reason);
1209
1210        value
1211    }
1212
1213    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
1214        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
1215    }
1216
1217    pub fn get_feature_gate_with_options(
1218        &self,
1219        user: &StatsigUser,
1220        gate_name: &str,
1221        options: FeatureGateEvaluationOptions,
1222    ) -> FeatureGate {
1223        let user_internal = self.internalize_user(user);
1224        let disable_exposure_logging = options.disable_exposure_logging;
1225        let (details, evaluation) = self.get_gate_evaluation(
1226            &user_internal,
1227            gate_name,
1228            Some(options.disable_exposure_logging),
1229        );
1230
1231        if disable_exposure_logging {
1232            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1233            self.event_logger.increment_non_exposure_checks(gate_name);
1234        } else {
1235            self.event_logger.enqueue(EnqueueGateExpoOp {
1236                exposure_time: Utc::now().timestamp_millis() as u64,
1237                user: &user_internal,
1238                queried_gate_name: gate_name,
1239                evaluation: evaluation.as_ref().map(Cow::Borrowed),
1240                details: details.clone(),
1241                trigger: ExposureTrigger::Auto,
1242            });
1243        }
1244
1245        let gate = make_feature_gate(gate_name, evaluation, details);
1246        self.emit_gate_evaluated(gate_name, &gate.rule_id, gate.value, &gate.details.reason);
1247        gate
1248    }
1249
1250    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
1251        let interned_gate_name = InternedString::from_str_ref(gate_name);
1252        let user_internal = self.internalize_user(user);
1253
1254        let (details, evaluation) =
1255            self.evaluate_spec_raw(&user_internal, gate_name, &SpecType::Gate, None);
1256
1257        self.event_logger.enqueue(EnqueueExposureOp::gate_exposure(
1258            &user_internal,
1259            &interned_gate_name,
1260            ExposureTrigger::Manual,
1261            details,
1262            evaluation,
1263        ));
1264    }
1265
1266    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
1267        self.spec_store
1268            .get_fields_used_for_entity(gate_name, SpecType::Gate)
1269    }
1270}
1271
1272// ------------------------------------------------------------------------------- [ Dynamic Config ]
1273
1274impl Statsig {
1275    pub fn get_dynamic_config(
1276        &self,
1277        user: &StatsigUser,
1278        dynamic_config_name: &str,
1279    ) -> DynamicConfig {
1280        self.get_dynamic_config_with_options(
1281            user,
1282            dynamic_config_name,
1283            DynamicConfigEvaluationOptions::default(),
1284        )
1285    }
1286
1287    pub fn get_dynamic_config_with_options(
1288        &self,
1289        user: &StatsigUser,
1290        dynamic_config_name: &str,
1291        options: DynamicConfigEvaluationOptions,
1292    ) -> DynamicConfig {
1293        let user_internal = self.internalize_user(user);
1294        let disable_exposure_logging = options.disable_exposure_logging;
1295        let dynamic_config = self.get_dynamic_config_impl(
1296            &user_internal,
1297            dynamic_config_name,
1298            Some(options.disable_exposure_logging),
1299        );
1300
1301        if disable_exposure_logging {
1302            log_d!(
1303                TAG,
1304                "Exposure logging is disabled for Dynamic Config {}",
1305                dynamic_config_name
1306            );
1307            self.event_logger
1308                .increment_non_exposure_checks(dynamic_config_name);
1309        } else {
1310            self.event_logger.enqueue(EnqueueConfigExpoOp {
1311                exposure_time: Utc::now().timestamp_millis() as u64,
1312                user: &user_internal,
1313                config: &dynamic_config,
1314                trigger: ExposureTrigger::Auto,
1315            });
1316        }
1317
1318        self.emit_dynamic_config_evaluated(&dynamic_config);
1319
1320        dynamic_config
1321    }
1322
1323    pub fn manually_log_dynamic_config_exposure(
1324        &self,
1325        user: &StatsigUser,
1326        dynamic_config_name: &str,
1327    ) {
1328        let interned_dynamic_config_name = InternedString::from_str_ref(dynamic_config_name);
1329        let user_internal = self.internalize_user(user);
1330
1331        let (details, evaluation) = self.evaluate_spec_raw(
1332            &user_internal,
1333            dynamic_config_name,
1334            &SpecType::DynamicConfig,
1335            None,
1336        );
1337
1338        self.event_logger
1339            .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1340                &user_internal,
1341                &interned_dynamic_config_name,
1342                ExposureTrigger::Manual,
1343                details,
1344                evaluation,
1345            ));
1346    }
1347
1348    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
1349        self.spec_store
1350            .get_fields_used_for_entity(config_name, SpecType::DynamicConfig)
1351    }
1352}
1353
1354// ------------------------------------------------------------------------------- [ Experiment ]
1355
1356impl Statsig {
1357    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
1358        self.get_experiment_with_options(
1359            user,
1360            experiment_name,
1361            ExperimentEvaluationOptions::default(),
1362        )
1363    }
1364
1365    pub fn get_experiment_with_options(
1366        &self,
1367        user: &StatsigUser,
1368        experiment_name: &str,
1369        options: ExperimentEvaluationOptions,
1370    ) -> Experiment {
1371        let user_internal = self.internalize_user(user);
1372        let disable_exposure_logging = options.disable_exposure_logging;
1373        let mut experiment = self.get_experiment_impl(
1374            &user_internal,
1375            experiment_name,
1376            Some(options.disable_exposure_logging),
1377        );
1378
1379        experiment = PersistentValuesManager::try_apply_sticky_value_to_experiment(
1380            &self.persistent_values_manager,
1381            &user_internal,
1382            &options,
1383            experiment,
1384        );
1385
1386        if disable_exposure_logging {
1387            log_d!(
1388                TAG,
1389                "Exposure logging is disabled for experiment {}",
1390                experiment_name
1391            );
1392            self.event_logger
1393                .increment_non_exposure_checks(experiment_name);
1394        } else {
1395            self.event_logger.enqueue(EnqueueExperimentExpoOp {
1396                exposure_time: Utc::now().timestamp_millis() as u64,
1397                user: &user_internal,
1398                experiment: &experiment,
1399                trigger: ExposureTrigger::Auto,
1400            });
1401        }
1402
1403        self.emit_experiment_evaluated(&experiment);
1404
1405        experiment
1406    }
1407
1408    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
1409        let interned_experiment_name = InternedString::from_str_ref(experiment_name);
1410        let user_internal = self.internalize_user(user);
1411        let (details, evaluation) =
1412            self.evaluate_spec_raw(&user_internal, experiment_name, &SpecType::Experiment, None);
1413
1414        self.event_logger
1415            .enqueue(EnqueueExposureOp::experiment_exposure(
1416                &user_internal,
1417                &interned_experiment_name,
1418                ExposureTrigger::Manual,
1419                details,
1420                evaluation,
1421            ));
1422    }
1423
1424    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1425        self.spec_store
1426            .get_fields_used_for_entity(experiment_name, SpecType::Experiment)
1427    }
1428
1429    pub fn get_experiment_by_group_name(
1430        &self,
1431        experiment_name: &str,
1432        group_name: &str,
1433    ) -> Experiment {
1434        self.get_experiment_by_group_name_impl(
1435            experiment_name,
1436            group_name,
1437            |spec_pointer, rule, details| {
1438                if let (Some(spec_pointer), Some(rule)) = (spec_pointer, rule) {
1439                    let value = rule.return_value.get_json().unwrap_or_default();
1440                    let rule_id = String::from(rule.id.as_str());
1441                    let id_type = rule.id_type.value.unperformant_to_string();
1442                    let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1443
1444                    return Experiment {
1445                        name: experiment_name.to_string(),
1446                        value,
1447                        rule_id,
1448                        id_type,
1449                        group_name,
1450                        details,
1451                        is_experiment_active: spec_pointer.as_spec_ref().is_active.unwrap_or(false),
1452                        __evaluation: None,
1453                    };
1454                }
1455
1456                make_experiment(experiment_name, None, details)
1457            },
1458        )
1459    }
1460
1461    pub fn get_experiment_by_group_id_advanced(
1462        &self,
1463        experiment_name: &str,
1464        group_id: &str,
1465    ) -> Experiment {
1466        self.get_experiment_by_group_id_advanced_impl(
1467            experiment_name,
1468            group_id,
1469            |spec_pointer, rule, details| {
1470                if let (Some(spec_pointer), Some(rule)) = (spec_pointer, rule) {
1471                    let value = rule.return_value.get_json().unwrap_or_default();
1472                    let rule_id = String::from(rule.id.as_str());
1473                    let id_type = rule.id_type.value.unperformant_to_string();
1474                    let group_name = rule.group_name.as_ref().map(|g| g.unperformant_to_string());
1475
1476                    return Experiment {
1477                        name: experiment_name.to_string(),
1478                        value,
1479                        rule_id,
1480                        id_type,
1481                        group_name,
1482                        details,
1483                        is_experiment_active: spec_pointer.as_spec_ref().is_active.unwrap_or(false),
1484                        __evaluation: None,
1485                    };
1486                }
1487
1488                make_experiment(experiment_name, None, details)
1489            },
1490        )
1491    }
1492
1493    fn get_experiment_by_group_name_impl<T>(
1494        &self,
1495        experiment_name: &str,
1496        group_name: &str,
1497        result_factory: impl FnOnce(Option<&SpecPointer>, Option<&Rule>, EvaluationDetails) -> T,
1498    ) -> T {
1499        self.get_experiment_by_rule_match_impl(
1500            experiment_name,
1501            |rule| rule.group_name.as_deref() == Some(group_name),
1502            result_factory,
1503        )
1504    }
1505
1506    fn get_experiment_by_group_id_advanced_impl<T>(
1507        &self,
1508        experiment_name: &str,
1509        rule_id: &str,
1510        result_factory: impl FnOnce(Option<&SpecPointer>, Option<&Rule>, EvaluationDetails) -> T,
1511    ) -> T {
1512        self.get_experiment_by_rule_match_impl(
1513            experiment_name,
1514            |rule| rule.id.as_str() == rule_id,
1515            result_factory,
1516        )
1517    }
1518
1519    fn get_experiment_by_rule_match_impl<T, P>(
1520        &self,
1521        experiment_name: &str,
1522        rule_predicate: P,
1523        result_factory: impl FnOnce(Option<&SpecPointer>, Option<&Rule>, EvaluationDetails) -> T,
1524    ) -> T
1525    where
1526        P: Fn(&Rule) -> bool,
1527    {
1528        let data = read_lock_or_else!(self.spec_store.data, {
1529            log_error_to_statsig_and_console!(
1530                self.ops_stats.clone(),
1531                TAG,
1532                StatsigErr::LockFailure(
1533                    "Failed to acquire read lock for spec store data".to_string()
1534                )
1535            );
1536            return result_factory(
1537                None,
1538                None,
1539                EvaluationDetails::error("Failed to acquire read lock for spec store data"),
1540            );
1541        });
1542
1543        let experiment_name = InternedString::from_str_ref(experiment_name);
1544        let experiment = data.values.dynamic_configs.get(&experiment_name);
1545
1546        let Some(exp) = experiment else {
1547            return result_factory(
1548                None,
1549                None,
1550                EvaluationDetails::unrecognized(
1551                    &data.source,
1552                    data.values.time,
1553                    data.time_received_at,
1554                ),
1555            );
1556        };
1557
1558        if let Some(rule) = exp
1559            .as_spec_ref()
1560            .rules
1561            .iter()
1562            .find(|rule| rule_predicate(rule))
1563        {
1564            return result_factory(
1565                Some(exp),
1566                Some(rule),
1567                EvaluationDetails::recognized_without_eval_result(
1568                    &data.source,
1569                    data.values.time,
1570                    data.time_received_at,
1571                ),
1572            );
1573        }
1574
1575        result_factory(
1576            None,
1577            None,
1578            EvaluationDetails::unrecognized(&data.source, data.values.time, data.time_received_at),
1579        )
1580    }
1581}
1582
1583// ------------------------------------------------------------------------------- [ Layer ]
1584
1585impl Statsig {
1586    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1587        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1588    }
1589
1590    pub fn get_layer_with_options(
1591        &self,
1592        user: &StatsigUser,
1593        layer_name: &str,
1594        options: LayerEvaluationOptions,
1595    ) -> Layer {
1596        let user_internal = self.internalize_user(user);
1597        self.get_layer_impl(user_internal, layer_name, options)
1598    }
1599
1600    pub fn manually_log_layer_parameter_exposure(
1601        &self,
1602        user: &StatsigUser,
1603        layer_name: &str,
1604        parameter_name: String,
1605    ) {
1606        let interned_layer_name = InternedString::from_str_ref(layer_name);
1607        let interned_parameter_name = InternedString::from_string(parameter_name);
1608        let user_internal = self.internalize_user(user);
1609        let (details, evaluation) =
1610            self.evaluate_spec_raw(&user_internal, layer_name, &SpecType::Layer, None);
1611
1612        self.event_logger
1613            .enqueue(EnqueueExposureOp::layer_param_exposure(
1614                &user_internal,
1615                &interned_layer_name,
1616                interned_parameter_name,
1617                ExposureTrigger::Manual,
1618                details,
1619                evaluation,
1620            ));
1621    }
1622
1623    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1624        self.spec_store
1625            .get_fields_used_for_entity(layer_name, SpecType::Layer)
1626    }
1627}
1628
1629// ------------------------------------------------------------------------------- [ Feat: ffi-support ]
1630
1631#[cfg(feature = "ffi-support")]
1632impl Statsig {
1633    pub fn get_raw_feature_gate_with_options(
1634        &self,
1635        user: &StatsigUser,
1636        gate_name: &str,
1637        options: FeatureGateEvaluationOptions,
1638    ) -> String {
1639        use crate::evaluation::evaluator_result::result_to_gate_raw;
1640
1641        let interned_gate_name = InternedString::from_str_ref(gate_name);
1642        let user_internal = self.internalize_user(user);
1643
1644        let (details, evaluation) =
1645            self.evaluate_spec_raw(&user_internal, gate_name, &SpecType::Gate, None);
1646
1647        let raw = result_to_gate_raw(gate_name, &details, evaluation.as_ref());
1648
1649        self.emit_gate_evaluated_parts(gate_name, details.reason.as_str(), evaluation.as_ref());
1650
1651        if options.disable_exposure_logging {
1652            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
1653            self.event_logger.increment_non_exposure_checks(gate_name);
1654        } else {
1655            self.event_logger.enqueue(EnqueueExposureOp::gate_exposure(
1656                &user_internal,
1657                &interned_gate_name,
1658                ExposureTrigger::Auto,
1659                details,
1660                evaluation,
1661            ));
1662        }
1663
1664        raw
1665    }
1666
1667    pub fn get_raw_dynamic_config_with_options(
1668        &self,
1669        user: &StatsigUser,
1670        dynamic_config_name: &str,
1671        options: DynamicConfigEvaluationOptions,
1672    ) -> String {
1673        use crate::evaluation::evaluator_result::result_to_dynamic_config_raw;
1674
1675        let interned_dynamic_config_name = InternedString::from_str_ref(dynamic_config_name);
1676        let user_internal = self.internalize_user(user);
1677        let disable_exposure_logging: bool = options.disable_exposure_logging;
1678
1679        let (details, evaluation) = self.evaluate_spec_raw(
1680            &user_internal,
1681            dynamic_config_name,
1682            &SpecType::DynamicConfig,
1683            Some(disable_exposure_logging),
1684        );
1685
1686        let raw = result_to_dynamic_config_raw(dynamic_config_name, &details, evaluation.as_ref());
1687
1688        self.emit_dynamic_config_evaluated_parts(
1689            dynamic_config_name,
1690            details.reason.as_str(),
1691            evaluation.as_ref(),
1692        );
1693
1694        if disable_exposure_logging {
1695            log_d!(
1696                TAG,
1697                "Exposure logging is disabled for Dynamic Config {}",
1698                dynamic_config_name
1699            );
1700            self.event_logger
1701                .increment_non_exposure_checks(dynamic_config_name);
1702        } else {
1703            self.event_logger
1704                .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1705                    &user_internal,
1706                    &interned_dynamic_config_name,
1707                    ExposureTrigger::Auto,
1708                    details,
1709                    evaluation,
1710                ));
1711        }
1712
1713        raw
1714    }
1715
1716    pub fn get_raw_experiment_by_group_name(
1717        &self,
1718        experiment_name: &str,
1719        group_name: &str,
1720    ) -> String {
1721        use crate::evaluation::evaluator_result::rule_to_experiment_raw;
1722
1723        self.get_experiment_by_group_name_impl(
1724            experiment_name,
1725            group_name,
1726            |spec_pointer, rule, details| {
1727                rule_to_experiment_raw(experiment_name, spec_pointer, rule, details)
1728            },
1729        )
1730    }
1731
1732    pub fn get_raw_experiment_with_options(
1733        &self,
1734        user: &StatsigUser,
1735        experiment_name: &str,
1736        options: ExperimentEvaluationOptions,
1737    ) -> String {
1738        use crate::evaluation::evaluator_result::result_to_experiment_raw;
1739
1740        let interned_experiment_name = InternedString::from_str_ref(experiment_name);
1741        let user_internal = self.internalize_user(user);
1742        let disable_exposure_logging: bool = options.disable_exposure_logging;
1743
1744        let (details, result) = self.evaluate_spec_raw(
1745            &user_internal,
1746            experiment_name,
1747            &SpecType::Experiment,
1748            Some(disable_exposure_logging),
1749        );
1750
1751        let (result, details) = PersistentValuesManager::try_apply_sticky_value_to_raw_experiment(
1752            &self.persistent_values_manager,
1753            &user_internal,
1754            &options,
1755            details,
1756            result,
1757        );
1758
1759        let raw = result_to_experiment_raw(experiment_name, &details, result.as_ref());
1760
1761        self.emit_experiment_evaluated_parts(
1762            experiment_name,
1763            details.reason.as_str(),
1764            result.as_ref(),
1765        );
1766
1767        if disable_exposure_logging {
1768            log_d!(
1769                TAG,
1770                "Exposure logging is disabled for Experiment {}",
1771                experiment_name
1772            );
1773            self.event_logger
1774                .increment_non_exposure_checks(experiment_name);
1775        } else {
1776            self.event_logger
1777                .enqueue(EnqueueExposureOp::dynamic_config_exposure(
1778                    &user_internal,
1779                    &interned_experiment_name,
1780                    ExposureTrigger::Auto,
1781                    details,
1782                    result,
1783                ));
1784        }
1785
1786        raw
1787    }
1788
1789    pub fn get_raw_layer_with_options(
1790        &self,
1791        user: &StatsigUser,
1792        layer_name: &str,
1793        options: LayerEvaluationOptions,
1794    ) -> String {
1795        use crate::evaluation::evaluator_result::result_to_layer_raw;
1796
1797        let user_internal = self.internalize_user(user);
1798        let disable_exposure_logging: bool = options.disable_exposure_logging;
1799
1800        let (details, result) = self.evaluate_spec_raw(
1801            &user_internal,
1802            layer_name,
1803            &SpecType::Layer,
1804            Some(disable_exposure_logging),
1805        );
1806
1807        let (result, details) = PersistentValuesManager::try_apply_sticky_value_to_raw_layer(
1808            &self.persistent_values_manager,
1809            &user_internal,
1810            &options,
1811            &self.spec_store,
1812            &self.ops_stats,
1813            details,
1814            result,
1815        );
1816
1817        let raw = result_to_layer_raw(
1818            &user_internal,
1819            layer_name,
1820            options,
1821            &details,
1822            result.as_ref(),
1823        );
1824
1825        self.emit_layer_evaluated_parts(layer_name, details.reason.as_str(), result.as_ref());
1826
1827        if disable_exposure_logging {
1828            log_d!(TAG, "Exposure logging is disabled for Layer {}", layer_name);
1829            self.event_logger.increment_non_exposure_checks(layer_name);
1830        }
1831
1832        raw
1833    }
1834
1835    pub fn log_layer_param_exposure_from_raw(&self, raw: String, param_name: String) {
1836        use crate::statsig_types_raw::PartialLayerRaw;
1837
1838        let partial_raw = match serde_json::from_str::<PartialLayerRaw>(&raw) {
1839            Ok(partial_raw) => partial_raw,
1840            Err(e) => {
1841                log_e!(TAG, "Failed to parse partial layer raw: {}", e);
1842                return;
1843            }
1844        };
1845
1846        if partial_raw.disable_exposure {
1847            self.event_logger
1848                .increment_non_exposure_checks(&partial_raw.name);
1849            return;
1850        }
1851
1852        let interned_parameter_name = InternedString::from_string(param_name);
1853
1854        self.event_logger
1855            .enqueue(EnqueueExposureOp::layer_param_exposure_from_partial_raw(
1856                interned_parameter_name,
1857                ExposureTrigger::Auto,
1858                partial_raw,
1859            ));
1860    }
1861}
1862
1863// ------------------------------------------------------------------------------- [ Internal ]
1864
1865impl Statsig {
1866    pub(crate) fn get_from_statsig_env(&self, key: &str) -> Option<DynamicValue> {
1867        if let Some(env) = &self.statsig_environment {
1868            return env.get(key).cloned();
1869        }
1870
1871        if let Some(fallback_env) = self
1872            .fallback_environment
1873            .try_lock_for(Duration::from_secs(5))
1874        {
1875            if let Some(env) = &*fallback_env {
1876                return env.get(key).cloned();
1877            }
1878        }
1879
1880        None
1881    }
1882
1883    pub(crate) fn get_value_from_global_custom_fields(&self, key: &str) -> Option<&DynamicValue> {
1884        if let Some(env) = &self.options.global_custom_fields {
1885            return env.get(key);
1886        }
1887
1888        None
1889    }
1890
1891    pub(crate) fn use_global_custom_fields<T>(
1892        &self,
1893        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1894    ) -> T {
1895        f(self.options.global_custom_fields.as_ref())
1896    }
1897
1898    pub(crate) fn use_statsig_env<T>(
1899        &self,
1900        f: impl FnOnce(Option<&HashMap<String, DynamicValue>>) -> T,
1901    ) -> T {
1902        if let Some(env) = &self.statsig_environment {
1903            return f(Some(env));
1904        }
1905
1906        if let Some(fallback_env) = self
1907            .fallback_environment
1908            .try_lock_for(Duration::from_secs(5))
1909        {
1910            if let Some(env) = &*fallback_env {
1911                return f(Some(env));
1912            }
1913        }
1914
1915        f(None)
1916    }
1917}
1918
1919// ------------------------------------------------------------------------------- [ Private ]
1920
1921impl Statsig {
1922    async fn start_background_tasks(
1923        statsig_runtime: Arc<StatsigRuntime>,
1924        id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
1925        specs_adapter: Arc<dyn SpecsAdapter>,
1926        ops_stats: Arc<OpsStatsForInstance>,
1927        bg_tasks_started: Arc<AtomicBool>,
1928    ) -> bool {
1929        if bg_tasks_started.load(Ordering::SeqCst) {
1930            return true;
1931        }
1932
1933        let mut success = true;
1934
1935        if let Some(adapter) = &id_lists_adapter {
1936            if let Err(e) = adapter
1937                .clone()
1938                .schedule_background_sync(&statsig_runtime)
1939                .await
1940            {
1941                success = false;
1942                log_w!(TAG, "Failed to schedule idlist background job {}", e);
1943            }
1944        }
1945
1946        if let Err(e) = specs_adapter
1947            .clone()
1948            .schedule_background_sync(&statsig_runtime)
1949            .await
1950        {
1951            success = false;
1952            log_error_to_statsig_and_console!(
1953                ops_stats,
1954                TAG,
1955                StatsigErr::SpecsAdapterSkipPoll(format!(
1956                    "Failed to schedule specs adapter background job: {e}"
1957                ))
1958            );
1959        }
1960
1961        bg_tasks_started.store(true, Ordering::SeqCst);
1962
1963        success
1964    }
1965
1966    async fn apply_timeout_to_init(
1967        &self,
1968        timeout_ms: u64,
1969    ) -> Result<InitializeDetails, StatsigErr> {
1970        let timeout = Duration::from_millis(timeout_ms);
1971
1972        let init_future = self.initialize_impl_with_details();
1973        let timeout_future = sleep(timeout);
1974
1975        let statsig_runtime = self.statsig_runtime.clone();
1976        let id_lists_adapter = self.id_lists_adapter.inner.clone();
1977        let specs_adapter = self.specs_adapter.inner.clone();
1978        let ops_stats = self.ops_stats.clone();
1979        let background_tasks_started = self.background_tasks_started.clone();
1980        // Create another clone specifically for the closure
1981        let statsig_runtime_for_closure = statsig_runtime.clone();
1982
1983        tokio::select! {
1984            result = init_future => {
1985                result
1986            },
1987            _ = timeout_future => {
1988                statsig_runtime.spawn(
1989                    "start_background_tasks",
1990                    |_shutdown_notify| async move {
1991                        Self::start_background_tasks(
1992                            statsig_runtime_for_closure,
1993                            id_lists_adapter,
1994                            specs_adapter,
1995                            ops_stats,
1996                            background_tasks_started,
1997                        ).await;
1998                    }
1999                )?;
2000                Ok(InitializeDetails::from_timeout_failure(timeout_ms))
2001            },
2002        }
2003    }
2004
2005    async fn initialize_impl_with_details(&self) -> Result<InitializeDetails, StatsigErr> {
2006        let start_time = Instant::now();
2007        self.spec_store.set_source(SpecsSource::Loading);
2008        self.specs_adapter.inner.initialize(self.spec_store.clone());
2009        let use_third_party_ua_parser = self.should_user_third_party_parser();
2010
2011        let mut error_message = None;
2012        let mut id_list_ready = None;
2013
2014        let init_country_lookup = if !self.options.disable_country_lookup.unwrap_or_default() {
2015            Some(self.statsig_runtime.spawn(INIT_IP_TAG, |_| async {
2016                CountryLookup::load_country_lookup();
2017            }))
2018        } else {
2019            None
2020        };
2021
2022        let init_ua = if use_third_party_ua_parser {
2023            Some(self.statsig_runtime.spawn(INIT_UA_TAG, |_| async {
2024                UserAgentParser::load_parser();
2025            }))
2026        } else {
2027            None
2028        };
2029
2030        let init_res = match self
2031            .specs_adapter
2032            .inner
2033            .clone()
2034            .start(&self.statsig_runtime)
2035            .await
2036        {
2037            Ok(()) => Ok(()),
2038            Err(e) => {
2039                self.spec_store.set_source(SpecsSource::NoValues);
2040                error_message = Some(format!("Failed to start specs adapter: {e}"));
2041                Err(e)
2042            }
2043        };
2044
2045        if let Some(adapter) = &self.id_lists_adapter.inner {
2046            match adapter
2047                .clone()
2048                .start(&self.statsig_runtime, self.spec_store.clone())
2049                .await
2050            {
2051                Ok(()) => {
2052                    id_list_ready = Some(true);
2053                }
2054                Err(e) => {
2055                    id_list_ready = Some(false);
2056                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
2057                }
2058            }
2059        }
2060
2061        if let Err(e) = self
2062            .event_logging_adapter
2063            .clone()
2064            .start(&self.statsig_runtime)
2065            .await
2066        {
2067            log_error_to_statsig_and_console!(
2068                self.ops_stats.clone(),
2069                TAG,
2070                StatsigErr::UnstartedAdapter(format!("Failed to start event logging adapter: {e}"))
2071            );
2072        }
2073
2074        let spec_info = self.spec_store.get_current_specs_info();
2075        let duration = start_time.elapsed().as_millis() as u64;
2076
2077        self.set_default_environment_from_server();
2078
2079        if self.options.wait_for_country_lookup_init.unwrap_or(false) {
2080            match init_country_lookup {
2081                Some(Ok(task_id)) => {
2082                    let _ = self
2083                        .statsig_runtime
2084                        .await_join_handle(INIT_IP_TAG, &task_id)
2085                        .await;
2086                }
2087                Some(Err(e)) => {
2088                    log_error_to_statsig_and_console!(
2089                        self.ops_stats.clone(),
2090                        TAG,
2091                        StatsigErr::UnstartedAdapter(format!(
2092                            "Failed to spawn country lookup task: {e}"
2093                        ))
2094                    );
2095                }
2096                _ => {}
2097            }
2098        }
2099        if self.options.wait_for_user_agent_init.unwrap_or(false) {
2100            match init_ua {
2101                Some(Ok(task_id)) => {
2102                    let _ = self
2103                        .statsig_runtime
2104                        .await_join_handle(INIT_UA_TAG, &task_id)
2105                        .await;
2106                }
2107                Some(Err(e)) => {
2108                    log_error_to_statsig_and_console!(
2109                        self.ops_stats.clone(),
2110                        TAG,
2111                        StatsigErr::UnstartedAdapter(format!(
2112                            "Failed to spawn user agent parser task: {e}"
2113                        ))
2114                    );
2115                }
2116                _ => {}
2117            }
2118        }
2119
2120        let error = init_res.clone().err();
2121
2122        let success = Self::start_background_tasks(
2123            self.statsig_runtime.clone(),
2124            self.id_lists_adapter.inner.clone(),
2125            self.specs_adapter.inner.clone(),
2126            self.ops_stats.clone(),
2127            self.background_tasks_started.clone(),
2128        )
2129        .await;
2130
2131        Ok(InitializeDetails::new(
2132            success,
2133            duration,
2134            spec_info,
2135            id_list_ready,
2136            error,
2137        ))
2138    }
2139
2140    fn log_init_details(&self, init_details: &Result<InitializeDetails, StatsigErr>) {
2141        match init_details {
2142            Ok(details) => {
2143                self.log_init_finish(
2144                    details.init_success,
2145                    &None,
2146                    &details.duration_ms,
2147                    &self.spec_store.get_current_specs_info(),
2148                );
2149                if let Some(failure) = &details.failure_details {
2150                    log_error_to_statsig_and_console!(
2151                        self.ops_stats,
2152                        TAG,
2153                        StatsigErr::InitializationError(failure.reason.clone())
2154                    );
2155                }
2156            }
2157            Err(err) => {
2158                // we store errors on init details so we should never return error and thus do not need to log
2159                log_w!(TAG, "Initialization error: {:?}", err);
2160            }
2161        }
2162    }
2163
2164    fn create_standard_eval_context<'a>(
2165        &'a self,
2166        user_internal: &'a StatsigUserInternal,
2167        data: &'a SpecStoreData,
2168        app_id: Option<&'a DynamicValue>,
2169        override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
2170        disable_exposure_logging: bool,
2171    ) -> EvaluatorContext<'a> {
2172        EvaluatorContext::new(
2173            user_internal,
2174            &data.values,
2175            IdListResolution::MapLookup(&data.id_lists),
2176            &self.hashing,
2177            app_id,
2178            override_adapter,
2179            self.should_user_third_party_parser(),
2180            Some(self),
2181            disable_exposure_logging,
2182        )
2183    }
2184
2185    fn create_gcir_eval_context<'a>(
2186        &'a self,
2187        user_internal: &'a StatsigUserInternal,
2188        data: &'a SpecStoreData,
2189        options: &'a ClientInitResponseOptions,
2190    ) -> EvaluatorContext<'a> {
2191        let app_id = select_app_id_for_gcir(options, &data.values, &self.hashing);
2192        let override_adapter = match options.include_local_overrides {
2193            Some(true) => self.override_adapter.as_ref(),
2194            _ => None,
2195        };
2196
2197        EvaluatorContext::new(
2198            user_internal,
2199            &data.values,
2200            IdListResolution::MapLookup(&data.id_lists),
2201            &self.hashing,
2202            app_id,
2203            override_adapter,
2204            self.should_user_third_party_parser(),
2205            None,
2206            true,
2207        )
2208    }
2209
2210    fn evaluate_spec_raw(
2211        &self,
2212        user_internal: &StatsigUserInternal,
2213        spec_name: &str,
2214        spec_type: &SpecType,
2215        disable_exposure_logging: Option<bool>,
2216    ) -> (EvaluationDetails, Option<EvaluatorResult>) {
2217        let data = read_lock_or_else!(self.spec_store.data, {
2218            log_error_to_statsig_and_console!(
2219                &self.ops_stats,
2220                TAG,
2221                StatsigErr::LockFailure(
2222                    "Failed to acquire read lock for spec store data".to_string()
2223                )
2224            );
2225            return (EvaluationDetails::unrecognized_no_data(), None);
2226        });
2227
2228        let mut context = self.create_standard_eval_context(
2229            user_internal,
2230            &data,
2231            data.values.app_id.as_ref(),
2232            self.override_adapter.as_ref(),
2233            disable_exposure_logging.unwrap_or(false),
2234        );
2235
2236        match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2237            Ok(eval_details) => (eval_details, Some(context.result)),
2238            Err(e) => {
2239                log_error_to_statsig_and_console!(
2240                    &self.ops_stats,
2241                    TAG,
2242                    StatsigErr::EvaluationError(e.to_string())
2243                );
2244                (EvaluationDetails::error(&e.to_string()), None)
2245            }
2246        }
2247    }
2248
2249    #[allow(clippy::too_many_arguments)]
2250    fn evaluate_spec<T>(
2251        &self,
2252        user_internal: &StatsigUserInternal,
2253        spec_name: &str,
2254        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
2255        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
2256        spec_type: &SpecType,
2257        disable_exposure_logging: Option<bool>,
2258    ) -> T {
2259        let data = read_lock_or_else!(self.spec_store.data, {
2260            log_error_to_statsig_and_console!(
2261                &self.ops_stats,
2262                TAG,
2263                StatsigErr::LockFailure(
2264                    "Failed to acquire read lock for spec store data".to_string()
2265                )
2266            );
2267            return make_empty_result(EvaluationDetails::unrecognized_no_data());
2268        });
2269
2270        let mut context = self.create_standard_eval_context(
2271            user_internal,
2272            &data,
2273            data.values.app_id.as_ref(),
2274            self.override_adapter.as_ref(),
2275            disable_exposure_logging.unwrap_or(false),
2276        );
2277
2278        match Self::evaluate_with_details(&mut context, &data, spec_name, spec_type) {
2279            Ok(eval_details) => make_result(context.result, eval_details),
2280            Err(e) => {
2281                log_error_to_statsig_and_console!(
2282                    &self.ops_stats,
2283                    TAG,
2284                    StatsigErr::EvaluationError(e.to_string())
2285                );
2286                make_empty_result(EvaluationDetails::error(&e.to_string()))
2287            }
2288        }
2289    }
2290
2291    fn evaluate_with_details(
2292        ctx: &mut EvaluatorContext,
2293        spec_store_data: &SpecStoreData,
2294        spec_name: &str,
2295        spec_type: &SpecType,
2296    ) -> Result<EvaluationDetails, StatsigErr> {
2297        let recognition = Evaluator::evaluate(ctx, spec_name, spec_type)?;
2298
2299        if recognition == Recognition::Unrecognized {
2300            return Ok(EvaluationDetails::unrecognized(
2301                &spec_store_data.source,
2302                spec_store_data.values.time,
2303                spec_store_data.time_received_at,
2304            ));
2305        }
2306
2307        if let Some(reason) = ctx.result.override_reason {
2308            return Ok(EvaluationDetails::recognized_but_overridden(
2309                spec_store_data.values.time,
2310                spec_store_data.time_received_at,
2311                reason,
2312                ctx.result.version,
2313            ));
2314        }
2315
2316        Ok(EvaluationDetails::recognized(
2317            &spec_store_data.source,
2318            spec_store_data.values.time,
2319            spec_store_data.time_received_at,
2320            &ctx.result,
2321        ))
2322    }
2323
2324    fn stringify_gcir_response<T: Serialize>(
2325        &self,
2326        input: Result<T, StatsigErr>,
2327        fallback: impl FnOnce() -> T,
2328    ) -> String {
2329        match input {
2330            Ok(value) => serde_json::to_string(&value).unwrap_or_default(),
2331            Err(e) => {
2332                log_error_to_statsig_and_console!(
2333                    &self.ops_stats,
2334                    TAG,
2335                    StatsigErr::GCIRError(e.to_string())
2336                );
2337                serde_json::to_string(&fallback()).unwrap_or_default()
2338            }
2339        }
2340    }
2341
2342    fn get_gate_evaluation(
2343        &self,
2344        user_internal: &StatsigUserInternal,
2345        gate_name: &str,
2346        disable_exposure_logging: Option<bool>,
2347    ) -> (EvaluationDetails, Option<GateEvaluation>) {
2348        self.evaluate_spec(
2349            user_internal,
2350            gate_name,
2351            |eval_details| (eval_details, None),
2352            |mut result, eval_details| {
2353                let evaluation = result_to_gate_eval(gate_name, &mut result);
2354                (eval_details, Some(evaluation))
2355            },
2356            &SpecType::Gate,
2357            disable_exposure_logging,
2358        )
2359    }
2360
2361    fn get_dynamic_config_impl(
2362        &self,
2363        user_internal: &StatsigUserInternal,
2364        config_name: &str,
2365        disable_exposure_logging: Option<bool>,
2366    ) -> DynamicConfig {
2367        self.evaluate_spec(
2368            user_internal,
2369            config_name,
2370            |eval_details| make_dynamic_config(config_name, None, eval_details),
2371            |mut result, eval_details| {
2372                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
2373                make_dynamic_config(config_name, Some(evaluation), eval_details)
2374            },
2375            &SpecType::DynamicConfig,
2376            disable_exposure_logging,
2377        )
2378    }
2379
2380    fn get_experiment_impl(
2381        &self,
2382        user_internal: &StatsigUserInternal,
2383        experiment_name: &str,
2384        disable_exposure_logging: Option<bool>,
2385    ) -> Experiment {
2386        self.evaluate_spec(
2387            user_internal,
2388            experiment_name,
2389            |eval_details| make_experiment(experiment_name, None, eval_details),
2390            |mut result, eval_details| {
2391                let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
2392                make_experiment(experiment_name, Some(evaluation), eval_details)
2393            },
2394            &SpecType::Experiment,
2395            disable_exposure_logging,
2396        )
2397    }
2398
2399    fn get_layer_impl(
2400        &self,
2401        user_internal: StatsigUserInternal,
2402        layer_name: &str,
2403        evaluation_options: LayerEvaluationOptions,
2404    ) -> Layer {
2405        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
2406
2407        if disable_exposure_logging {
2408            self.event_logger.increment_non_exposure_checks(layer_name);
2409        }
2410
2411        let mut layer = self.evaluate_spec(
2412            &user_internal,
2413            layer_name,
2414            |eval_details| {
2415                make_layer(
2416                    user_internal.to_loggable(),
2417                    layer_name,
2418                    None,
2419                    eval_details,
2420                    None,
2421                    disable_exposure_logging,
2422                )
2423            },
2424            |mut result, eval_details| {
2425                let evaluation = result_to_layer_eval(layer_name, &mut result);
2426                let event_logger_ptr = Arc::downgrade(&self.event_logger);
2427
2428                make_layer(
2429                    user_internal.to_loggable(),
2430                    layer_name,
2431                    Some(evaluation),
2432                    eval_details,
2433                    Some(event_logger_ptr),
2434                    disable_exposure_logging,
2435                )
2436            },
2437            &SpecType::Layer,
2438            Some(evaluation_options.disable_exposure_logging),
2439        );
2440
2441        layer = PersistentValuesManager::try_apply_sticky_value_to_layer(
2442            &self.persistent_values_manager,
2443            &user_internal,
2444            &evaluation_options,
2445            &self.spec_store,
2446            &self.ops_stats,
2447            layer,
2448        );
2449
2450        self.emit_layer_evaluated(&layer);
2451
2452        layer
2453    }
2454
2455    fn internalize_user<'s, 'u>(&'s self, user: &'u StatsigUser) -> StatsigUserInternal<'s, 'u> {
2456        StatsigUserInternal::new(user, Some(self))
2457    }
2458
2459    fn set_default_environment_from_server(&self) {
2460        let data = read_lock_or_else!(self.spec_store.data, {
2461            return;
2462        });
2463
2464        if let Some(default_env) = data.values.default_environment.as_ref() {
2465            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
2466
2467            match self
2468                .fallback_environment
2469                .try_lock_for(Duration::from_secs(5))
2470            {
2471                Some(mut fallback_env) => {
2472                    *fallback_env = Some(env_map);
2473                }
2474                None => {
2475                    log_e!(TAG, "Failed to lock fallback_environment");
2476                }
2477            }
2478        }
2479    }
2480
2481    fn log_init_finish(
2482        &self,
2483        success: bool,
2484        error_message: &Option<String>,
2485        duration_ms: &u64,
2486        specs_info: &SpecsInfo,
2487    ) {
2488        let is_store_populated = specs_info.source != SpecsSource::NoValues;
2489        let source_str = specs_info.source.to_string();
2490
2491        let event = ObservabilityEvent::new_event(
2492            MetricType::Dist,
2493            "initialization".to_string(),
2494            *duration_ms as f64,
2495            Some(HashMap::from([
2496                ("success".to_owned(), success.to_string()),
2497                ("source".to_owned(), source_str.clone()),
2498                ("store_populated".to_owned(), is_store_populated.to_string()),
2499                (
2500                    "spec_source_api".to_owned(),
2501                    specs_info.source_api.clone().unwrap_or_default(),
2502                ),
2503            ])),
2504        );
2505
2506        self.ops_stats.log(event);
2507        self.ops_stats.add_marker(
2508            {
2509                let marker = Marker::new(KeyType::Overall, ActionType::End, None)
2510                    .with_is_success(success)
2511                    .with_config_spec_ready(specs_info.source != SpecsSource::NoValues)
2512                    .with_source(source_str);
2513
2514                if let Some(msg) = &error_message {
2515                    marker.with_message(msg.to_string())
2516                } else {
2517                    marker
2518                }
2519            },
2520            Some(ContextType::Initialize),
2521        );
2522        self.ops_stats
2523            .enqueue_diagnostics_event(None, Some(ContextType::Initialize));
2524    }
2525
2526    fn should_user_third_party_parser(&self) -> bool {
2527        self.options.use_third_party_ua_parser.unwrap_or(false)
2528    }
2529}
2530
2531fn initialize_event_logging_adapter(
2532    sdk_key: &str,
2533    options: &StatsigOptions,
2534) -> Arc<dyn EventLoggingAdapter> {
2535    options
2536        .event_logging_adapter
2537        .clone()
2538        .unwrap_or_else(|| Arc::new(StatsigHttpEventLoggingAdapter::new(sdk_key, Some(options))))
2539}
2540
2541fn initialize_specs_adapter(
2542    sdk_key: &str,
2543    data_store_key: &str,
2544    options: &StatsigOptions,
2545) -> SpecsAdapterHousing {
2546    if let Some(adapter) = options.specs_adapter.clone() {
2547        log_d!(TAG, "Using provided SpecsAdapter: {}", sdk_key);
2548        return SpecsAdapterHousing {
2549            inner: adapter,
2550            as_default_adapter: None,
2551        };
2552    }
2553
2554    if let Some(adapter_config) = options.spec_adapters_config.clone() {
2555        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
2556            sdk_key,
2557            data_store_key,
2558            adapter_config,
2559            options,
2560        ));
2561
2562        return SpecsAdapterHousing {
2563            inner: adapter,
2564            as_default_adapter: None,
2565        };
2566    }
2567
2568    if let Some(data_store) = options.data_store.clone() {
2569        let adapter = Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
2570            sdk_key,
2571            data_store_key,
2572            data_store,
2573            options,
2574        ));
2575
2576        return SpecsAdapterHousing {
2577            inner: adapter,
2578            as_default_adapter: None,
2579        };
2580    }
2581
2582    let adapter = Arc::new(StatsigHttpSpecsAdapter::new(sdk_key, Some(options), None));
2583
2584    SpecsAdapterHousing {
2585        inner: adapter.clone(),
2586        as_default_adapter: Some(adapter),
2587    }
2588}
2589
2590fn initialize_id_lists_adapter(sdk_key: &str, options: &StatsigOptions) -> IdListsAdapterHousing {
2591    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
2592        return IdListsAdapterHousing {
2593            inner: Some(id_lists_adapter),
2594            as_default_adapter: None,
2595        };
2596    }
2597
2598    if options.enable_id_lists.unwrap_or(false) {
2599        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options));
2600
2601        return IdListsAdapterHousing {
2602            inner: Some(adapter.clone()),
2603            as_default_adapter: Some(adapter),
2604        };
2605    }
2606
2607    IdListsAdapterHousing {
2608        inner: None,
2609        as_default_adapter: None,
2610    }
2611}
2612
2613struct IdListsAdapterHousing {
2614    inner: Option<Arc<dyn IdListsAdapter>>,
2615    as_default_adapter: Option<Arc<StatsigHttpIdListsAdapter>>,
2616}
2617
2618struct SpecsAdapterHousing {
2619    inner: Arc<dyn SpecsAdapter>,
2620    as_default_adapter: Option<Arc<StatsigHttpSpecsAdapter>>,
2621}
2622
2623fn setup_ops_stats(
2624    sdk_key: &str,
2625    statsig_runtime: Arc<StatsigRuntime>,
2626    error_observer: &Arc<dyn OpsStatsEventObserver>,
2627    diagnostics_observer: &Arc<dyn OpsStatsEventObserver>,
2628    console_capture_observer: &Arc<dyn OpsStatsEventObserver>,
2629    external_observer: &Option<Weak<dyn ObservabilityClient>>,
2630) -> Arc<OpsStatsForInstance> {
2631    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
2632    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
2633    ops_stat.subscribe(
2634        statsig_runtime.clone(),
2635        Arc::downgrade(diagnostics_observer),
2636    );
2637    ops_stat.subscribe(
2638        statsig_runtime.clone(),
2639        Arc::downgrade(console_capture_observer),
2640    );
2641    if let Some(ob_client) = external_observer {
2642        if let Some(client) = ob_client.upgrade() {
2643            client.init();
2644            let as_observer = client.to_ops_stats_event_observer();
2645            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
2646        }
2647    }
2648
2649    ops_stat
2650}