Skip to main content

statsig_rust/
statsig.rs

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