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