statsig_rust/
statsig.rs

1use crate::client_init_response_formatter::{
2    ClientInitResponseFormatter, ClientInitResponseOptions,
3};
4use crate::evaluation::cmab_evaluator::{get_cmab_ranked_list, CMABRankedGroup};
5use crate::evaluation::country_lookup::CountryLookup;
6use crate::evaluation::dynamic_value::DynamicValue;
7use crate::evaluation::evaluation_details::EvaluationDetails;
8use crate::evaluation::evaluation_types::{AnyEvaluation, BaseEvaluation, ExperimentEvaluation};
9use crate::evaluation::evaluator::{Evaluator, SpecType};
10use crate::evaluation::evaluator_context::EvaluatorContext;
11use crate::evaluation::evaluator_result::{
12    result_to_dynamic_config_eval, result_to_experiment_eval, result_to_gate_eval,
13    result_to_layer_eval, EvaluatorResult,
14};
15use crate::evaluation::ua_parser::UserAgentParser;
16use crate::event_logging::config_exposure::ConfigExposure;
17use crate::event_logging::event_logger::{EventLogger, QueuedEventPayload};
18use crate::event_logging::gate_exposure::GateExposure;
19use crate::event_logging::layer_exposure::LayerExposure;
20use crate::event_logging::statsig_event::StatsigEvent;
21use crate::event_logging::statsig_event_internal::make_custom_event;
22use crate::event_logging_adapter::EventLoggingAdapter;
23use crate::event_logging_adapter::StatsigHttpEventLoggingAdapter;
24use crate::hashing::HashUtil;
25use crate::initialize_response::InitializeResponse;
26use crate::observability::observability_client_adapter::{MetricType, ObservabilityEvent};
27use crate::observability::ops_stats::{OpsStatsForInstance, OPS_STATS};
28use crate::observability::sdk_errors_observer::{ErrorBoundaryEvent, SDKErrorsObserver};
29use crate::output_logger::initialize_simple_output_logger;
30use crate::sdk_diagnostics::diagnostics::Diagnostics;
31use crate::spec_store::{SpecStore, SpecStoreData};
32use crate::specs_adapter::{StatsigCustomizedSpecsAdapter, StatsigHttpSpecsAdapter};
33use crate::statsig_err::StatsigErr;
34use crate::statsig_metadata::StatsigMetadata;
35use crate::statsig_options::StatsigOptions;
36use crate::statsig_runtime::StatsigRuntime;
37use crate::statsig_type_factories::{
38    make_dynamic_config, make_experiment, make_feature_gate, make_layer,
39};
40use crate::statsig_types::{
41    DynamicConfig, Experiment, FeatureGate, Layer, OverrideAdapterType, ParameterStore,
42};
43use crate::statsig_user_internal::StatsigUserInternal;
44use crate::{
45    dyn_value, log_d, log_e, log_w, read_lock_or_else, IdListsAdapter, ObservabilityClient,
46    OpsStatsEventObserver, OverrideAdapter, SamplingProcessor, SpecsAdapter, SpecsInfo,
47    SpecsSource, SpecsUpdateListener, StatsigHttpIdListsAdapter, StatsigLocalOverrideAdapter,
48    StatsigUser,
49};
50use crate::{
51    log_error_to_statsig_and_console,
52    statsig_core_api_options::{
53        DynamicConfigEvaluationOptions, ExperimentEvaluationOptions, FeatureGateEvaluationOptions,
54        LayerEvaluationOptions,
55    },
56};
57use serde::de::DeserializeOwned;
58use serde_json::json;
59use serde_json::Value;
60use std::collections::HashMap;
61use std::sync::Mutex;
62use std::sync::{Arc, Weak};
63use std::time::{Duration, Instant};
64use tokio::try_join;
65
66const TAG: &str = stringify!(Statsig);
67
68pub struct Statsig {
69    pub statsig_runtime: Arc<StatsigRuntime>,
70
71    sdk_key: String,
72    options: Arc<StatsigOptions>,
73    event_logger: Arc<EventLogger>,
74    specs_adapter: Arc<dyn SpecsAdapter>,
75    event_logging_adapter: Arc<dyn EventLoggingAdapter>,
76    id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
77    override_adapter: Option<Arc<dyn OverrideAdapter>>,
78    spec_store: Arc<SpecStore>,
79    hashing: Arc<HashUtil>,
80    gcir_formatter: Arc<ClientInitResponseFormatter>,
81    statsig_environment: Option<HashMap<String, DynamicValue>>,
82    fallback_environment: Mutex<Option<HashMap<String, DynamicValue>>>,
83    diagnostics: Diagnostics,
84    ops_stats: Arc<OpsStatsForInstance>,
85    error_observer: Arc<dyn OpsStatsEventObserver>,
86    sampling_processor: Arc<SamplingProcessor>,
87}
88
89pub struct StatsigContext {
90    pub sdk_key: String,
91    pub options: Arc<StatsigOptions>,
92    pub local_override_adapter: Option<Arc<dyn OverrideAdapter>>,
93    pub spec_store_data: Option<SpecStoreData>,
94    pub error_observer: Arc<dyn OpsStatsEventObserver>,
95}
96#[derive(Debug)]
97pub struct FailureDetails {
98    pub reason: String,
99    pub error: Option<StatsigErr>,
100}
101#[derive(Debug)]
102pub struct StatsigInitializeDetails {
103    pub duration: f64,
104    pub init_success: bool,
105    pub is_config_spec_ready: bool,
106    pub source: SpecsSource,
107    pub failure_details: Option<FailureDetails>,
108}
109
110impl Statsig {
111    pub fn new(sdk_key: &str, options: Option<Arc<StatsigOptions>>) -> Self {
112        let statsig_runtime = StatsigRuntime::get_runtime();
113        let options = options.unwrap_or_default();
114
115        let hashing = Arc::new(HashUtil::new());
116        let error_observer: Arc<dyn OpsStatsEventObserver> = Arc::new(SDKErrorsObserver::new(
117            sdk_key,
118            serde_json::to_string(options.as_ref()).unwrap_or_default(),
119        ));
120
121        let ops_stats = setup_ops_stats(
122            sdk_key,
123            &options,
124            statsig_runtime.clone(),
125            &error_observer,
126            &options.observability_client,
127        );
128
129        let spec_store = Arc::new(SpecStore::new(
130            hashing.sha256(&sdk_key.to_string()),
131            options.data_store.clone(),
132            Some(statsig_runtime.clone()),
133            ops_stats.clone(),
134        ));
135
136        let specs_adapter = initialize_specs_adapter(sdk_key, &options, &hashing);
137        let id_lists_adapter = initialize_id_lists_adapter(sdk_key, &options);
138        let event_logging_adapter = initialize_event_logging_adapter(sdk_key, &options);
139        let override_adapter = match options.override_adapter.as_ref() {
140            Some(adapter) => Some(Arc::clone(adapter)),
141            None => Some(Arc::new(StatsigLocalOverrideAdapter::new()) as Arc<dyn OverrideAdapter>),
142        };
143
144        if options.enable_user_agent_parsing.unwrap_or(false) {
145            UserAgentParser::load_parser();
146        }
147
148        if options.enable_country_lookup.unwrap_or(false) {
149            CountryLookup::load_country_lookup();
150        }
151
152        let environment = options
153            .environment
154            .as_ref()
155            .map(|env| HashMap::from([("tier".into(), dyn_value!(env.as_str()))]));
156
157        let event_logger = Arc::new(EventLogger::new(
158            sdk_key,
159            event_logging_adapter.clone(),
160            &options,
161            &statsig_runtime,
162        ));
163        let diagnostics = Diagnostics::new(event_logger.clone(), spec_store.clone());
164        let sampling_processor = Arc::new(SamplingProcessor::new(
165            &statsig_runtime,
166            &spec_store,
167            hashing.clone(),
168        ));
169
170        StatsigMetadata::update_service_name(options.service_name.clone());
171
172        Statsig {
173            sdk_key: sdk_key.to_string(),
174            options,
175            gcir_formatter: Arc::new(ClientInitResponseFormatter::new(
176                &spec_store,
177                &override_adapter,
178            )),
179            event_logger,
180            hashing,
181            statsig_environment: environment,
182            fallback_environment: Mutex::new(None),
183            override_adapter,
184            spec_store,
185            specs_adapter,
186            event_logging_adapter,
187            id_lists_adapter,
188            diagnostics,
189            statsig_runtime,
190            ops_stats,
191            error_observer,
192            sampling_processor,
193        }
194    }
195
196    pub async fn initialize(&self) -> Result<(), StatsigErr> {
197        self.initialize_with_details().await.map(|_| ())
198    }
199
200    pub async fn initialize_with_details(&self) -> Result<StatsigInitializeDetails, StatsigErr> {
201        let start_time = Instant::now();
202        self.diagnostics.mark_init_overall_start();
203        self.spec_store.set_source(SpecsSource::Loading);
204        self.event_logger
205            .clone()
206            .start_background_task(&self.statsig_runtime);
207
208        self.specs_adapter.initialize(self.spec_store.clone());
209
210        let mut success = true;
211        let mut error_message = None;
212
213        let init_res = match self
214            .specs_adapter
215            .clone()
216            .start(&self.statsig_runtime)
217            .await
218        {
219            Ok(()) => Ok(()),
220            Err(e) => {
221                success = false;
222                self.spec_store.set_source(SpecsSource::NoValues);
223                error_message = Some(format!("Failed to start specs adapter: {e}"));
224                Err(e)
225            }
226        };
227
228        if let Some(adapter) = &self.id_lists_adapter {
229            match adapter
230                .clone()
231                .start(&self.statsig_runtime, self.spec_store.clone())
232                .await
233            {
234                Ok(()) => {}
235                Err(e) => {
236                    success = false;
237                    error_message.get_or_insert_with(|| format!("Failed to sync ID lists: {e}"));
238                }
239            }
240            if let Err(e) = adapter
241                .clone()
242                .schedule_background_sync(&self.statsig_runtime)
243                .await
244            {
245                log_w!(TAG, "Failed to schedule idlist background job {}", e);
246            }
247        }
248
249        self.event_logging_adapter
250            .clone()
251            .start(&self.statsig_runtime)
252            .await
253            .map_err(|e| {
254                success = false;
255                log_error_to_statsig_and_console!(
256                    self.ops_stats.clone(),
257                    TAG,
258                    "Failed to start event logging adaper {}",
259                    e
260                );
261                e
262            })?;
263
264        if let Err(e) = self
265            .specs_adapter
266            .clone()
267            .schedule_background_sync(&self.statsig_runtime)
268            .await
269        {
270            log_error_to_statsig_and_console!(
271                self.ops_stats,
272                TAG,
273                "Failed to schedule SpecAdapter({}) background job. Error: {}",
274                self.specs_adapter.get_type_name(),
275                e,
276            );
277        }
278
279        let spec_info = self.spec_store.get_current_specs_info();
280        let duration = start_time.elapsed().as_millis() as f64;
281
282        self.set_default_environment_from_server();
283        self.log_init_finish(success, &error_message, &duration, &spec_info);
284        let error = init_res.clone().err();
285        if let Some(ref e) = error {
286            log_error_to_statsig_and_console!(
287                self.ops_stats,
288                TAG,
289                "{}",
290                error_message.clone().unwrap_or(e.to_string())
291            );
292        }
293
294        Ok(StatsigInitializeDetails {
295            init_success: success,
296            is_config_spec_ready: spec_info.lcut.is_some(),
297            source: spec_info.source,
298            failure_details: error.as_ref().map(|e| FailureDetails {
299                reason: e.to_string(),
300                error: Some(e.clone()),
301            }),
302            duration,
303        })
304    }
305
306    pub async fn shutdown(&self) -> Result<(), StatsigErr> {
307        self.shutdown_with_timeout(Duration::from_secs(3)).await
308    }
309
310    pub async fn shutdown_with_timeout(&self, timeout: Duration) -> Result<(), StatsigErr> {
311        log_d!(
312            TAG,
313            "Shutting down Statsig with timeout {}ms",
314            timeout.as_millis()
315        );
316
317        let start = Instant::now();
318        let final_result = self.__shutdown_internal(timeout).await;
319        self.finalize_shutdown(timeout.saturating_sub(start.elapsed()));
320        final_result
321    }
322
323    pub async fn __shutdown_internal(&self, timeout: Duration) -> Result<(), StatsigErr> {
324        log_d!(
325            TAG,
326            "Shutting down Statsig with timeout {}ms",
327            timeout.as_millis()
328        );
329
330        let start = Instant::now();
331        let shutdown_result = tokio::select! {
332            () = tokio::time::sleep(timeout) => {
333                log_w!(TAG, "Statsig shutdown timed out. {}", start.elapsed().as_millis());
334                Err(StatsigErr::ShutdownTimeout)
335            }
336            sub_result = async {
337                let id_list_shutdown: Pin<Box<_>> = if let Some(adapter) = &self.id_lists_adapter {
338                    adapter.shutdown(timeout)
339                } else {
340                    Box::pin(async { Ok(()) })
341                };
342
343                try_join!(
344                    id_list_shutdown,
345                    self.event_logger.shutdown(timeout),
346                    self.specs_adapter.shutdown(timeout, &self.statsig_runtime),
347                )
348            } => {
349                match sub_result {
350                    Ok(_) => {
351                        log_d!(TAG, "All Statsig tasks shutdown successfully");
352                        Ok(())
353                    }
354                    Err(e) => {
355                        log_w!(TAG, "Error during shutdown: {:?}", e);
356                        Err(e)
357                    }
358                }
359            }
360        };
361
362        shutdown_result
363    }
364
365    pub fn sequenced_shutdown_prepare<F>(&self, callback: F)
366    where
367        F: FnOnce() + Send + 'static,
368    {
369        let event_logger = self.event_logger.clone();
370        let specs_adapter = self.specs_adapter.clone();
371        let runtime: Arc<StatsigRuntime> = self.statsig_runtime.clone();
372
373        self.statsig_runtime
374            .spawn("sequenced_shutdown_prep", |_shutdown_notify| async move {
375                let timeout = Duration::from_millis(1000);
376
377                let result = try_join!(
378                    event_logger.shutdown(timeout),
379                    specs_adapter.shutdown(timeout, &runtime)
380                );
381
382                match result {
383                    Ok(_) => {
384                        log_d!(TAG, "Shutdown successfully");
385                        callback();
386                    }
387                    Err(e) => {
388                        log_e!(TAG, "Shutdown failed: {:?}", e);
389                        callback();
390                    }
391                }
392            });
393    }
394
395    pub fn finalize_shutdown(&self, timeout: Duration) {
396        self.statsig_runtime.shutdown(timeout);
397    }
398
399    pub fn get_context(&self) -> StatsigContext {
400        StatsigContext {
401            sdk_key: self.sdk_key.clone(),
402            options: self.options.clone(),
403            local_override_adapter: self.override_adapter.clone(),
404            spec_store_data: self.get_current_values(),
405            error_observer: self.error_observer.clone(),
406        }
407    }
408
409    // todo: merge into get_context
410    pub fn get_current_values(&self) -> Option<SpecStoreData> {
411        // TODO better error handling here
412        Some(self.spec_store.data.read().ok()?.clone())
413    }
414
415    pub fn log_event(
416        &self,
417        user: &StatsigUser,
418        event_name: &str,
419        value: Option<String>,
420        metadata: Option<HashMap<String, String>>,
421    ) {
422        let user_internal = self.internalize_user(user);
423
424        self.event_logger
425            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
426                user_internal,
427                StatsigEvent {
428                    event_name: event_name.to_string(),
429                    value: value.map(|v| json!(v)),
430                    metadata,
431                    statsig_metadata: None,
432                },
433            )));
434    }
435
436    pub fn log_event_with_number(
437        &self,
438        user: &StatsigUser,
439        event_name: &str,
440        value: Option<f64>,
441        metadata: Option<HashMap<String, String>>,
442    ) {
443        let user_internal = self.internalize_user(user);
444
445        self.event_logger
446            .enqueue(QueuedEventPayload::CustomEvent(make_custom_event(
447                user_internal,
448                StatsigEvent {
449                    event_name: event_name.to_string(),
450                    value: value.map(|v| json!(v)),
451                    metadata,
452                    statsig_metadata: None,
453                },
454            )));
455    }
456
457    pub fn log_layer_param_exposure_with_layer_json(
458        &self,
459        layer_json: String,
460        parameter_name: String,
461    ) {
462        let layer = match serde_json::from_str::<Layer>(&layer_json) {
463            Ok(layer) => layer,
464            Err(e) => {
465                log_error_to_statsig_and_console!(
466                    self.ops_stats.clone(),
467                    TAG,
468                    "Shutdown failed: {:?}",
469                    e
470                );
471                return;
472            }
473        };
474
475        self.log_layer_param_exposure_with_layer(layer, parameter_name);
476    }
477
478    pub fn log_layer_param_exposure_with_layer(&self, layer: Layer, parameter_name: String) {
479        if layer.__disable_exposure {
480            self.event_logger
481                .increment_non_exposure_checks_count(layer.name.clone());
482            return;
483        }
484
485        let layer_eval = layer.__evaluation.as_ref();
486
487        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
488            &layer.__user,
489            layer_eval.map(AnyEvaluation::from).as_ref(),
490            Some(&parameter_name),
491        );
492
493        if !sampling_details.should_send_exposure {
494            return;
495        }
496
497        self.event_logger
498            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
499                user: layer.__user,
500                parameter_name,
501                evaluation: layer.__evaluation,
502                layer_name: layer.name,
503                evaluation_details: layer.details,
504                version: layer.__version,
505                is_manual_exposure: false,
506                sampling_details,
507                override_config_name: layer.__override_config_name.clone(),
508            }));
509    }
510
511    pub async fn flush_events(&self) {
512        self.event_logger.flush_blocking().await;
513    }
514
515    pub fn get_client_init_response(&self, user: &StatsigUser) -> InitializeResponse {
516        self.get_client_init_response_with_options(user, self.gcir_formatter.get_default_options())
517    }
518
519    pub fn get_client_init_response_with_options(
520        &self,
521        user: &StatsigUser,
522        options: &ClientInitResponseOptions,
523    ) -> InitializeResponse {
524        let user_internal = self.internalize_user(user);
525        self.gcir_formatter
526            .get(user_internal, &self.hashing, options)
527    }
528
529    pub fn get_client_init_response_as_string(&self, user: &StatsigUser) -> String {
530        json!(self.get_client_init_response(user)).to_string()
531    }
532
533    pub fn get_client_init_response_with_options_as_string(
534        &self,
535        user: &StatsigUser,
536        options: &ClientInitResponseOptions,
537    ) -> String {
538        json!(self.get_client_init_response_with_options(user, options)).to_string()
539    }
540
541    pub fn get_string_parameter_from_store(
542        &self,
543        user: &StatsigUser,
544        parameter_store_name: &str,
545        parameter_name: &str,
546        fallback: Option<String>,
547    ) -> Option<String> {
548        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
549    }
550
551    pub fn get_boolean_parameter_from_store(
552        &self,
553        user: &StatsigUser,
554        parameter_store_name: &str,
555        parameter_name: &str,
556        fallback: Option<bool>,
557    ) -> Option<bool> {
558        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
559    }
560
561    pub fn get_float_parameter_from_store(
562        &self,
563        user: &StatsigUser,
564        parameter_store_name: &str,
565        parameter_name: &str,
566        fallback: Option<f64>,
567    ) -> Option<f64> {
568        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
569    }
570
571    pub fn get_integer_parameter_from_store(
572        &self,
573        user: &StatsigUser,
574        parameter_store_name: &str,
575        parameter_name: &str,
576        fallback: Option<i64>,
577    ) -> Option<i64> {
578        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
579    }
580
581    pub fn get_array_parameter_from_store(
582        &self,
583        user: &StatsigUser,
584        parameter_store_name: &str,
585        parameter_name: &str,
586        fallback: Option<Vec<Value>>,
587    ) -> Option<Vec<Value>> {
588        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
589    }
590
591    pub fn get_object_parameter_from_store(
592        &self,
593        user: &StatsigUser,
594        parameter_store_name: &str,
595        parameter_name: &str,
596        fallback: Option<HashMap<String, Value>>,
597    ) -> Option<HashMap<String, Value>> {
598        self.get_parameter_from_store(user, parameter_store_name, parameter_name, fallback)
599    }
600
601    pub fn get_parameter_from_store<T: DeserializeOwned>(
602        &self,
603        user: &StatsigUser,
604        parameter_store_name: &str,
605        parameter_name: &str,
606        fallback: Option<T>,
607    ) -> Option<T> {
608        let store = self.get_parameter_store(parameter_store_name);
609        match fallback {
610            Some(fallback) => Some(store.get(user, parameter_name, fallback)),
611            None => store.get_opt(user, parameter_name),
612        }
613    }
614
615    pub fn get_parameter_store(&self, parameter_store_name: &str) -> ParameterStore {
616        self.event_logger
617            .increment_non_exposure_checks_count(parameter_store_name.to_string());
618        let data = read_lock_or_else!(self.spec_store.data, {
619            log_error_to_statsig_and_console!(
620                self.ops_stats.clone(),
621                TAG,
622                "Failed to acquire read lock for spec store data"
623            );
624            return ParameterStore {
625                name: parameter_store_name.to_string(),
626                parameters: HashMap::new(),
627                details: EvaluationDetails::unrecognized_no_data(),
628                _statsig_ref: self,
629            };
630        });
631
632        let stores = &data.values.param_stores;
633        let store = match stores {
634            Some(stores) => stores.get(parameter_store_name),
635            None => {
636                return ParameterStore {
637                    name: parameter_store_name.to_string(),
638                    parameters: HashMap::new(),
639                    details: EvaluationDetails::unrecognized(&data),
640                    _statsig_ref: self,
641                };
642            }
643        };
644        match store {
645            Some(store) => ParameterStore {
646                name: parameter_store_name.to_string(),
647                parameters: store.parameters.clone(),
648                details: EvaluationDetails::recognized(&data, &EvaluatorResult::default()),
649                _statsig_ref: self,
650            },
651            None => ParameterStore {
652                name: parameter_store_name.to_string(),
653                parameters: HashMap::new(),
654                details: EvaluationDetails::unrecognized(&data),
655                _statsig_ref: self,
656            },
657        }
658    }
659}
660
661// -------------------------
662//   CMAB Functions
663// -------------------------
664
665impl Statsig {
666    pub fn get_cmab_ranked_groups(
667        &self,
668        user: &StatsigUser,
669        cmab_name: &str,
670    ) -> Vec<CMABRankedGroup> {
671        self.event_logger
672            .increment_non_exposure_checks_count(cmab_name.to_string());
673        let data = read_lock_or_else!(self.spec_store.data, {
674            log_error_to_statsig_and_console!(
675                self.ops_stats.clone(),
676                TAG,
677                "Failed to acquire read lock for spec store data"
678            );
679            return vec![];
680        });
681        let user_internal = self.internalize_user(user);
682        get_cmab_ranked_list(
683            &mut EvaluatorContext::new(
684                &user_internal,
685                &data,
686                &self.hashing,
687                &data.values.app_id.as_ref(),
688                &self.override_adapter,
689            ),
690            cmab_name,
691        )
692    }
693
694    pub fn log_cmab_exposure_for_group(
695        &self,
696        user: &StatsigUser,
697        cmab_name: &str,
698        group_id: String,
699    ) {
700        let user_internal = self.internalize_user(user);
701        let experiment = self.get_experiment_impl(&user_internal, cmab_name);
702        let base_eval = BaseEvaluation {
703            name: cmab_name.to_string(),
704            rule_id: group_id.clone(),
705            secondary_exposures: match experiment.__evaluation {
706                Some(ref eval) => eval.base.secondary_exposures.clone(),
707                None => vec![],
708            },
709            sampling_rate: match experiment.__evaluation {
710                Some(ref eval) => eval.base.sampling_rate,
711                None => Some(1),
712            },
713            forward_all_exposures: match experiment.__evaluation {
714                Some(ref eval) => eval.base.forward_all_exposures,
715                None => Some(true),
716            },
717        };
718        let experiment_eval = ExperimentEvaluation {
719            base: base_eval.clone(),
720            id_type: experiment.id_type.clone(),
721            value: HashMap::new(),
722            group: group_id,
723            is_device_based: false,
724            is_in_layer: false,
725            explicit_parameters: None,
726            group_name: None,
727            is_experiment_active: Some(true),
728            is_user_in_experiment: Some(true),
729        };
730
731        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
732            &user_internal,
733            Some(&AnyEvaluation::from(&experiment_eval)),
734            None,
735        );
736
737        if !sampling_details.should_send_exposure {
738            return;
739        }
740
741        self.event_logger
742            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
743                user: user_internal,
744                evaluation: Some(base_eval),
745                evaluation_details: experiment.details.clone(),
746                config_name: cmab_name.to_string(),
747                rule_passed: None,
748                version: experiment.__version,
749                is_manual_exposure: true,
750                sampling_details,
751                override_config_name: experiment.__override_config_name.clone(),
752            }));
753    }
754}
755
756// -------------------------
757//   Feature Gate Functions
758// -------------------------
759
760impl Statsig {
761    pub fn check_gate(&self, user: &StatsigUser, gate_name: &str) -> bool {
762        self.check_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
763    }
764
765    pub fn check_gate_with_options(
766        &self,
767        user: &StatsigUser,
768        gate_name: &str,
769        options: FeatureGateEvaluationOptions,
770    ) -> bool {
771        self.get_feature_gate_with_options(user, gate_name, options)
772            .value
773    }
774
775    pub fn get_feature_gate(&self, user: &StatsigUser, gate_name: &str) -> FeatureGate {
776        self.get_feature_gate_with_options(user, gate_name, FeatureGateEvaluationOptions::default())
777    }
778
779    pub fn get_feature_gate_with_options(
780        &self,
781        user: &StatsigUser,
782        gate_name: &str,
783        options: FeatureGateEvaluationOptions,
784    ) -> FeatureGate {
785        log_d!(TAG, "Get Feature Gate {}", gate_name);
786
787        let user_internal = self.internalize_user(user);
788
789        let disable_exposure_logging = options.disable_exposure_logging;
790        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
791
792        if disable_exposure_logging {
793            log_d!(TAG, "Exposure logging is disabled for gate {}", gate_name);
794            self.event_logger
795                .increment_non_exposure_checks_count(gate_name.to_string());
796        } else {
797            self.log_gate_exposure(user_internal, gate_name, &gate, false);
798        }
799
800        gate
801    }
802
803    pub fn manually_log_gate_exposure(&self, user: &StatsigUser, gate_name: &str) {
804        let user_internal = self.internalize_user(user);
805        let gate = self.get_feature_gate_impl(&user_internal, gate_name);
806        self.log_gate_exposure(user_internal, gate_name, &gate, true);
807    }
808
809    pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> {
810        let data = read_lock_or_else!(self.spec_store.data, {
811            log_error_to_statsig_and_console!(
812                self.ops_stats.clone(),
813                TAG,
814                "Failed to acquire read lock for spec store data"
815            );
816            return vec![];
817        });
818
819        let gate = data.values.feature_gates.get(gate_name);
820        match gate {
821            Some(gate) => match &gate.fields_used {
822                Some(fields) => fields.clone(),
823                None => vec![],
824            },
825            None => vec![],
826        }
827    }
828
829    pub fn override_gate(
830        &self,
831        gate_name: &str,
832        value: bool,
833        _adapter: Option<&OverrideAdapterType>,
834    ) {
835        if let Some(adapter) = &self.override_adapter {
836            adapter.override_gate(gate_name, value);
837        }
838    }
839
840    pub fn override_dynamic_config(
841        &self,
842        config_name: &str,
843        value: HashMap<String, serde_json::Value>,
844        _adapter: Option<&OverrideAdapterType>,
845    ) {
846        if let Some(adapter) = &self.override_adapter {
847            adapter.override_dynamic_config(config_name, value);
848        }
849    }
850
851    pub fn override_layer(
852        &self,
853        layer_name: &str,
854        value: HashMap<String, serde_json::Value>,
855        _adapter: Option<&OverrideAdapterType>,
856    ) {
857        if let Some(adapter) = &self.override_adapter {
858            adapter.override_layer(layer_name, value);
859        }
860    }
861
862    pub fn override_experiment(
863        &self,
864        experiment_name: &str,
865        value: HashMap<String, serde_json::Value>,
866        _adapter: Option<&OverrideAdapterType>,
867    ) {
868        if let Some(adapter) = &self.override_adapter {
869            adapter.override_experiment(experiment_name, value);
870        }
871    }
872
873    pub fn override_experiment_by_group_name(
874        &self,
875        experiment_name: &str,
876        group_name: &str,
877        _adapter: Option<&OverrideAdapterType>,
878    ) {
879        if let Some(adapter) = &self.override_adapter {
880            adapter.override_experiment_by_group_name(experiment_name, group_name);
881        }
882    }
883}
884
885// -------------------------
886//   Dynamic Config Functions
887// -------------------------
888
889impl Statsig {
890    pub fn get_dynamic_config(
891        &self,
892        user: &StatsigUser,
893        dynamic_config_name: &str,
894    ) -> DynamicConfig {
895        self.get_dynamic_config_with_options(
896            user,
897            dynamic_config_name,
898            DynamicConfigEvaluationOptions::default(),
899        )
900    }
901
902    pub fn get_dynamic_config_with_options(
903        &self,
904        user: &StatsigUser,
905        dynamic_config_name: &str,
906        options: DynamicConfigEvaluationOptions,
907    ) -> DynamicConfig {
908        let user_internal = self.internalize_user(user);
909        let disable_exposure_logging = options.disable_exposure_logging;
910        let config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
911
912        if disable_exposure_logging {
913            log_d!(
914                TAG,
915                "Exposure logging is disabled for Dynamic Config {}",
916                dynamic_config_name
917            );
918            self.event_logger
919                .increment_non_exposure_checks_count(dynamic_config_name.to_string());
920        } else {
921            self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &config, false);
922        }
923
924        config
925    }
926
927    pub fn manually_log_dynamic_config_exposure(
928        &self,
929        user: &StatsigUser,
930        dynamic_config_name: &str,
931    ) {
932        let user_internal = self.internalize_user(user);
933        let dynamic_config = self.get_dynamic_config_impl(&user_internal, dynamic_config_name);
934        self.log_dynamic_config_exposure(user_internal, dynamic_config_name, &dynamic_config, true);
935    }
936
937    pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> {
938        let data = read_lock_or_else!(self.spec_store.data, {
939            log_error_to_statsig_and_console!(
940                self.ops_stats.clone(),
941                TAG,
942                "Failed to acquire read lock for spec store data"
943            );
944            return vec![];
945        });
946
947        let config = data.values.dynamic_configs.get(config_name);
948        match config {
949            Some(config) => match &config.fields_used {
950                Some(fields) => fields.clone(),
951                None => vec![],
952            },
953            None => vec![],
954        }
955    }
956}
957
958// -------------------------
959//   Experiment Functions
960// -------------------------
961
962impl Statsig {
963    pub fn get_experiment(&self, user: &StatsigUser, experiment_name: &str) -> Experiment {
964        self.get_experiment_with_options(
965            user,
966            experiment_name,
967            ExperimentEvaluationOptions::default(),
968        )
969    }
970
971    pub fn get_experiment_with_options(
972        &self,
973        user: &StatsigUser,
974        experiment_name: &str,
975        options: ExperimentEvaluationOptions,
976    ) -> Experiment {
977        let user_internal = self.internalize_user(user);
978        let disable_exposure_logging = options.disable_exposure_logging;
979        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
980
981        if disable_exposure_logging {
982            log_d!(
983                TAG,
984                "Exposure logging is disabled for experiment {}",
985                experiment_name
986            );
987            self.event_logger
988                .increment_non_exposure_checks_count(experiment_name.to_string());
989        } else {
990            self.log_experiment_exposure(user_internal, experiment_name, &experiment, false);
991        }
992
993        experiment
994    }
995
996    pub fn manually_log_experiment_exposure(&self, user: &StatsigUser, experiment_name: &str) {
997        let user_internal = self.internalize_user(user);
998        let experiment = self.get_experiment_impl(&user_internal, experiment_name);
999        self.log_experiment_exposure(user_internal, experiment_name, &experiment, true);
1000    }
1001
1002    pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> {
1003        let data = read_lock_or_else!(self.spec_store.data, {
1004            log_error_to_statsig_and_console!(
1005                self.ops_stats.clone(),
1006                TAG,
1007                "Failed to acquire read lock for spec store data"
1008            );
1009            return vec![];
1010        });
1011
1012        let config = data.values.dynamic_configs.get(experiment_name);
1013        match config {
1014            Some(config) => match &config.fields_used {
1015                Some(fields) => fields.clone(),
1016                None => vec![],
1017            },
1018            None => vec![],
1019        }
1020    }
1021}
1022
1023// -------------------------
1024//   Layer Functions
1025// -------------------------
1026
1027impl Statsig {
1028    pub fn get_layer(&self, user: &StatsigUser, layer_name: &str) -> Layer {
1029        self.get_layer_with_options(user, layer_name, LayerEvaluationOptions::default())
1030    }
1031
1032    pub fn get_layer_with_options(
1033        &self,
1034        user: &StatsigUser,
1035        layer_name: &str,
1036        options: LayerEvaluationOptions,
1037    ) -> Layer {
1038        let user_internal = self.internalize_user(user);
1039        self.get_layer_impl(&user_internal, layer_name, options)
1040    }
1041
1042    pub fn manually_log_layer_parameter_exposure(
1043        &self,
1044        user: &StatsigUser,
1045        layer_name: &str,
1046        parameter_name: String,
1047    ) {
1048        let user_internal = self.internalize_user(user);
1049        let layer = self.get_layer_impl(
1050            &user_internal,
1051            layer_name,
1052            LayerEvaluationOptions::default(),
1053        );
1054
1055        let layer_eval = layer.__evaluation.as_ref();
1056
1057        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1058            &layer.__user,
1059            layer_eval.map(AnyEvaluation::from).as_ref(),
1060            Some(parameter_name.as_str()),
1061        );
1062
1063        if !sampling_details.should_send_exposure {
1064            return;
1065        }
1066
1067        self.event_logger
1068            .enqueue(QueuedEventPayload::LayerExposure(LayerExposure {
1069                user: layer.__user,
1070                parameter_name,
1071                evaluation: layer.__evaluation,
1072                layer_name: layer.name,
1073                evaluation_details: layer.details,
1074                version: layer.__version,
1075                is_manual_exposure: true,
1076                sampling_details,
1077                override_config_name: layer.__override_config_name.clone(),
1078            }));
1079    }
1080
1081    pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> {
1082        let data = read_lock_or_else!(self.spec_store.data, {
1083            log_error_to_statsig_and_console!(
1084                self.ops_stats.clone(),
1085                TAG,
1086                "Failed to acquire read lock for spec store data"
1087            );
1088            return vec![];
1089        });
1090
1091        let layer = data.values.layer_configs.get(layer_name);
1092        match layer {
1093            Some(layer) => match &layer.fields_used {
1094                Some(fields) => fields.clone(),
1095                None => vec![],
1096            },
1097            None => vec![],
1098        }
1099    }
1100}
1101
1102// -------------------------
1103//   Private Functions
1104// -------------------------
1105
1106impl Statsig {
1107    fn evaluate_spec<T>(
1108        &self,
1109        user_internal: &StatsigUserInternal,
1110        spec_name: &str,
1111        make_empty_result: impl FnOnce(EvaluationDetails) -> T,
1112        make_result: impl FnOnce(EvaluatorResult, EvaluationDetails) -> T,
1113        spec_type: &SpecType,
1114    ) -> T {
1115        let data = read_lock_or_else!(self.spec_store.data, {
1116            log_error_to_statsig_and_console!(
1117                &self.ops_stats,
1118                TAG,
1119                "Failed to acquire read lock for spec store data"
1120            );
1121            return make_empty_result(EvaluationDetails::unrecognized_no_data());
1122        });
1123        let app_id = data.values.app_id.as_ref();
1124        let mut context = EvaluatorContext::new(
1125            user_internal,
1126            &data,
1127            &self.hashing,
1128            &app_id,
1129            &self.override_adapter,
1130        );
1131
1132        match Evaluator::evaluate_with_details(&mut context, spec_name, spec_type) {
1133            Ok(eval_details) => make_result(context.result, eval_details),
1134            Err(e) => {
1135                log_error_to_statsig_and_console!(&self.ops_stats, TAG, "Error evaluating: {}", e);
1136                make_empty_result(EvaluationDetails::error(&e.to_string()))
1137            }
1138        }
1139    }
1140
1141    fn get_feature_gate_impl(
1142        &self,
1143        user_internal: &StatsigUserInternal,
1144        gate_name: &str,
1145    ) -> FeatureGate {
1146        self.evaluate_spec(
1147            user_internal,
1148            gate_name,
1149            |eval_details| make_feature_gate(gate_name, None, eval_details, None, None),
1150            |mut result, eval_details| {
1151                let evaluation = result_to_gate_eval(gate_name, &mut result);
1152                make_feature_gate(
1153                    gate_name,
1154                    Some(evaluation),
1155                    eval_details,
1156                    result.version,
1157                    result.override_config_name,
1158                )
1159            },
1160            &SpecType::Gate,
1161        )
1162    }
1163
1164    fn get_dynamic_config_impl(
1165        &self,
1166        user_internal: &StatsigUserInternal,
1167        config_name: &str,
1168    ) -> DynamicConfig {
1169        self.evaluate_spec(
1170            user_internal,
1171            config_name,
1172            |eval_details| make_dynamic_config(config_name, None, eval_details, None, None),
1173            |mut result, eval_details| {
1174                let evaluation = result_to_dynamic_config_eval(config_name, &mut result);
1175                make_dynamic_config(
1176                    config_name,
1177                    Some(evaluation),
1178                    eval_details,
1179                    result.version,
1180                    result.override_config_name,
1181                )
1182            },
1183            &SpecType::DynamicConfig,
1184        )
1185    }
1186
1187    fn get_experiment_impl(
1188        &self,
1189        user_internal: &StatsigUserInternal,
1190        experiment_name: &str,
1191    ) -> Experiment {
1192        self.evaluate_spec(
1193            user_internal,
1194            experiment_name,
1195            |eval_details| make_experiment(experiment_name, None, eval_details, None, None),
1196            |mut result, eval_details| {
1197                let data = read_lock_or_else!(self.spec_store.data, {
1198                    let evaluation = result_to_experiment_eval(experiment_name, None, &mut result);
1199                    return make_experiment(
1200                        experiment_name,
1201                        Some(evaluation),
1202                        eval_details,
1203                        result.version,
1204                        result.override_config_name,
1205                    );
1206                });
1207                let evaluation = result_to_experiment_eval(
1208                    experiment_name,
1209                    data.values.dynamic_configs.get(experiment_name),
1210                    &mut result,
1211                );
1212                make_experiment(
1213                    experiment_name,
1214                    Some(evaluation),
1215                    eval_details,
1216                    result.version,
1217                    result.override_config_name,
1218                )
1219            },
1220            &SpecType::Experiment,
1221        )
1222    }
1223
1224    fn get_layer_impl(
1225        &self,
1226        user_internal: &StatsigUserInternal,
1227        layer_name: &str,
1228        evaluation_options: LayerEvaluationOptions,
1229    ) -> Layer {
1230        let disable_exposure_logging = evaluation_options.disable_exposure_logging;
1231        if disable_exposure_logging {
1232            self.event_logger
1233                .increment_non_exposure_checks_count(layer_name.to_string());
1234        }
1235
1236        self.evaluate_spec(
1237            user_internal,
1238            layer_name,
1239            |eval_details| {
1240                make_layer(
1241                    user_internal,
1242                    layer_name,
1243                    None,
1244                    eval_details,
1245                    None,
1246                    None,
1247                    disable_exposure_logging,
1248                    None,
1249                    None,
1250                )
1251            },
1252            |mut result, eval_details| {
1253                let evaluation = result_to_layer_eval(layer_name, &mut result);
1254                let event_logger_ptr = Arc::downgrade(&self.event_logger);
1255                let sampling_processor_ptr = Arc::downgrade(&self.sampling_processor);
1256
1257                make_layer(
1258                    user_internal,
1259                    layer_name,
1260                    Some(evaluation),
1261                    eval_details,
1262                    Some(event_logger_ptr),
1263                    result.version,
1264                    disable_exposure_logging,
1265                    Some(sampling_processor_ptr),
1266                    result.override_config_name,
1267                )
1268            },
1269            &SpecType::Layer,
1270        )
1271    }
1272
1273    fn log_gate_exposure(
1274        &self,
1275        user_internal: StatsigUserInternal,
1276        gate_name: &str,
1277        gate: &FeatureGate,
1278        is_manual: bool,
1279    ) {
1280        let gate_eval = gate.__evaluation.as_ref();
1281        let secondary_exposures = gate_eval.map(|eval| &eval.base.secondary_exposures);
1282
1283        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1284            &user_internal,
1285            gate_eval.map(AnyEvaluation::from).as_ref(),
1286            None,
1287        );
1288
1289        if !sampling_details.should_send_exposure {
1290            return;
1291        }
1292
1293        self.event_logger
1294            .enqueue(QueuedEventPayload::GateExposure(GateExposure {
1295                user: user_internal,
1296                gate_name: gate_name.to_string(),
1297                value: gate.value,
1298                rule_id: Some(gate.rule_id.clone()),
1299                secondary_exposures: secondary_exposures.cloned(),
1300                evaluation_details: gate.details.clone(),
1301                version: gate.__version,
1302                is_manual_exposure: is_manual,
1303                sampling_details,
1304                override_config_name: gate.__override_config_name.clone(),
1305            }));
1306    }
1307
1308    fn log_dynamic_config_exposure(
1309        &self,
1310        user_internal: StatsigUserInternal,
1311        dynamic_config_name: &str,
1312        dynamic_config: &DynamicConfig,
1313        is_manual: bool,
1314    ) {
1315        let config_eval = dynamic_config.__evaluation.as_ref();
1316        let base_eval = config_eval.map(|eval| eval.base.clone());
1317
1318        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1319            &user_internal,
1320            config_eval.map(AnyEvaluation::from).as_ref(),
1321            None,
1322        );
1323
1324        if !sampling_details.should_send_exposure {
1325            return;
1326        }
1327
1328        self.event_logger
1329            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1330                user: user_internal,
1331                evaluation: base_eval,
1332                evaluation_details: dynamic_config.details.clone(),
1333                config_name: dynamic_config_name.to_string(),
1334                rule_passed: dynamic_config.__evaluation.as_ref().map(|eval| eval.passed),
1335                version: dynamic_config.__version,
1336                is_manual_exposure: is_manual,
1337                sampling_details,
1338                override_config_name: dynamic_config.__override_config_name.clone(),
1339            }));
1340    }
1341
1342    fn log_experiment_exposure(
1343        &self,
1344        user_internal: StatsigUserInternal,
1345        experiment_name: &str,
1346        experiment: &Experiment,
1347        is_manual: bool,
1348    ) {
1349        let experiment_eval = experiment.__evaluation.as_ref();
1350        let base_eval = experiment_eval.map(|eval| eval.base.clone());
1351
1352        let sampling_details = self.sampling_processor.get_sampling_decision_and_details(
1353            &user_internal,
1354            experiment_eval.map(AnyEvaluation::from).as_ref(),
1355            None,
1356        );
1357
1358        if !sampling_details.should_send_exposure {
1359            return;
1360        }
1361
1362        self.event_logger
1363            .enqueue(QueuedEventPayload::ConfigExposure(ConfigExposure {
1364                user: user_internal,
1365                evaluation: base_eval,
1366                evaluation_details: experiment.details.clone(),
1367                config_name: experiment_name.to_string(),
1368                rule_passed: None,
1369                version: experiment.__version,
1370                is_manual_exposure: is_manual,
1371                sampling_details,
1372                override_config_name: experiment.__override_config_name.clone(),
1373            }));
1374    }
1375
1376    fn internalize_user(&self, user: &StatsigUser) -> StatsigUserInternal {
1377        StatsigUserInternal::new(
1378            user,
1379            self.get_statsig_env(),
1380            self.options.global_custom_fields.clone(),
1381        )
1382    }
1383
1384    fn get_statsig_env(&self) -> Option<HashMap<String, DynamicValue>> {
1385        if let Some(env) = &self.statsig_environment {
1386            return Some(env.clone());
1387        }
1388
1389        if let Ok(fallback_env) = self.fallback_environment.lock() {
1390            if let Some(env) = &*fallback_env {
1391                return Some(env.clone());
1392            }
1393        }
1394
1395        None
1396    }
1397
1398    fn set_default_environment_from_server(&self) {
1399        let data = read_lock_or_else!(self.spec_store.data, {
1400            return;
1401        });
1402
1403        if let Some(default_env) = data.values.default_environment.as_ref() {
1404            let env_map = HashMap::from([("tier".to_string(), dyn_value!(default_env.as_str()))]);
1405
1406            if let Ok(mut fallback_env) = self.fallback_environment.lock() {
1407                *fallback_env = Some(env_map);
1408            }
1409        }
1410    }
1411
1412    fn log_init_finish(
1413        &self,
1414        success: bool,
1415        error_message: &Option<String>,
1416        duration: &f64,
1417        specs_info: &SpecsInfo,
1418    ) {
1419        self.ops_stats.log(ObservabilityEvent::new_event(
1420            MetricType::Dist,
1421            "initialization".to_string(),
1422            *duration,
1423            Some(HashMap::from([
1424                ("success".to_owned(), success.to_string()),
1425                ("source".to_owned(), specs_info.source.to_string()),
1426                (
1427                    "store_populated".to_owned(),
1428                    (specs_info.source != SpecsSource::NoValues).to_string(),
1429                ),
1430            ])),
1431        ));
1432        self.diagnostics.mark_init_overall_end(
1433            success,
1434            error_message.clone(),
1435            EvaluationDetails {
1436                reason: format!("{}", specs_info.source),
1437                lcut: specs_info.lcut,
1438                received_at: None,
1439            },
1440        );
1441    }
1442}
1443
1444fn initialize_event_logging_adapter(
1445    sdk_key: &str,
1446    options: &StatsigOptions,
1447) -> Arc<dyn EventLoggingAdapter> {
1448    let adapter = options.event_logging_adapter.clone().unwrap_or_else(|| {
1449        Arc::new(StatsigHttpEventLoggingAdapter::new(
1450            sdk_key,
1451            options.log_event_url.as_ref(),
1452        ))
1453    });
1454    adapter
1455}
1456
1457fn initialize_specs_adapter(
1458    sdk_key: &str,
1459    options: &StatsigOptions,
1460    hashing: &HashUtil,
1461) -> Arc<dyn SpecsAdapter> {
1462    if let Some(adapter) = options.specs_adapter.clone() {
1463        return adapter;
1464    }
1465
1466    if let Some(adapter_config) = options.spec_adapters_config.clone() {
1467        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_config(
1468            sdk_key,
1469            adapter_config,
1470            options,
1471            hashing,
1472        ));
1473    }
1474
1475    if let Some(data_adapter) = options.data_store.clone() {
1476        return Arc::new(StatsigCustomizedSpecsAdapter::new_from_data_store(
1477            sdk_key,
1478            data_adapter,
1479            options,
1480            hashing,
1481        ));
1482    }
1483
1484    Arc::new(StatsigHttpSpecsAdapter::new(
1485        sdk_key,
1486        options.specs_url.as_ref(),
1487        options.fallback_to_statsig_api.unwrap_or(false),
1488        options.specs_sync_interval_ms,
1489    ))
1490}
1491
1492fn initialize_id_lists_adapter(
1493    sdk_key: &str,
1494    options: &StatsigOptions,
1495) -> Option<Arc<dyn IdListsAdapter>> {
1496    if let Some(id_lists_adapter) = options.id_lists_adapter.clone() {
1497        return Some(id_lists_adapter);
1498    }
1499
1500    if options.enable_id_lists.unwrap_or(false) {
1501        return Some(Arc::new(StatsigHttpIdListsAdapter::new(sdk_key, options)));
1502    }
1503
1504    None
1505}
1506
1507fn setup_ops_stats(
1508    sdk_key: &str,
1509    options: &StatsigOptions,
1510    statsig_runtime: Arc<StatsigRuntime>,
1511    error_observer: &Arc<dyn OpsStatsEventObserver>,
1512    external_observer: &Option<Weak<dyn ObservabilityClient>>,
1513) -> Arc<OpsStatsForInstance> {
1514    // TODO migrate output logger to use ops_stats
1515    initialize_simple_output_logger(&options.output_log_level);
1516
1517    let ops_stat = OPS_STATS.get_for_instance(sdk_key);
1518    ops_stat.subscribe(statsig_runtime.clone(), Arc::downgrade(error_observer));
1519
1520    if let Some(ob_client) = external_observer {
1521        if let Some(client) = ob_client.upgrade() {
1522            client.init();
1523            let as_observer = client.to_ops_stats_event_observer();
1524            ops_stat.subscribe(statsig_runtime, Arc::downgrade(&as_observer));
1525        }
1526    }
1527
1528    ops_stat
1529}
1530
1531#[cfg(test)]
1532mod tests {
1533    use super::*;
1534    use crate::hashing::djb2;
1535    use crate::{
1536        evaluation::evaluation_types::AnyConfigEvaluation, output_logger::LogLevel,
1537        StatsigHttpIdListsAdapter,
1538    };
1539    use std::env;
1540
1541    fn get_sdk_key() -> String {
1542        let key = env::var("test_api_key").expect("test_api_key environment variable not set");
1543        assert!(key.starts_with("secret-9IWf"));
1544        key
1545    }
1546
1547    #[tokio::test]
1548    async fn test_check_gate() {
1549        let user = StatsigUser {
1550            email: Some(dyn_value!("daniel@statsig.com")),
1551            ..StatsigUser::with_user_id("a-user".to_string())
1552        };
1553
1554        let statsig = Statsig::new(&get_sdk_key(), None);
1555        statsig.initialize().await.unwrap();
1556
1557        let gate_result = statsig.check_gate(&user, "test_50_50");
1558
1559        assert!(gate_result);
1560    }
1561
1562    #[tokio::test]
1563    async fn test_check_gate_id_list() {
1564        let user = StatsigUser {
1565            custom_ids: Some(HashMap::from([(
1566                "companyID".to_string(),
1567                dyn_value!("marcos_1"),
1568            )])),
1569            ..StatsigUser::with_user_id("marcos_1".to_string())
1570        };
1571
1572        let mut opts = StatsigOptions::new();
1573
1574        let adapter = Arc::new(StatsigHttpIdListsAdapter::new(&get_sdk_key(), &opts));
1575        opts.id_lists_adapter = Some(adapter);
1576
1577        let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1578        statsig.initialize().await.unwrap();
1579
1580        let gate_result = statsig.check_gate(&user, "test_id_list");
1581
1582        assert!(gate_result);
1583    }
1584
1585    #[tokio::test]
1586    async fn test_get_experiment() {
1587        let user = StatsigUser {
1588            email: Some(dyn_value!("daniel@statsig.com")),
1589            ..StatsigUser::with_user_id("a-user".to_string())
1590        };
1591
1592        let statsig = Statsig::new(&get_sdk_key(), None);
1593        statsig.initialize().await.unwrap();
1594
1595        let experiment = statsig.get_experiment(&user, "running_exp_in_unlayered_with_holdout");
1596        let _ = statsig.shutdown().await;
1597
1598        assert_ne!(experiment.value.len(), 0);
1599    }
1600
1601    #[tokio::test]
1602    async fn test_gcir() {
1603        let user = StatsigUser {
1604            email: Some(dyn_value!("daniel@statsig.com")),
1605            ..StatsigUser::with_user_id("a-user".to_string())
1606        };
1607        let opts = StatsigOptions {
1608            output_log_level: Some(LogLevel::Debug),
1609            ..StatsigOptions::new()
1610        };
1611
1612        let statsig = Statsig::new(&get_sdk_key(), Some(Arc::new(opts)));
1613        statsig.initialize().await.unwrap();
1614
1615        let response = statsig.get_client_init_response(&user);
1616        let _ = statsig.shutdown().await;
1617
1618        let gates = response.feature_gates;
1619        assert_eq!(gates.len(), 69);
1620
1621        let configs = response.dynamic_configs.len();
1622        assert_eq!(configs, 62);
1623
1624        let a_config_opt = response.dynamic_configs.get(&djb2("big_number"));
1625        let a_config = match a_config_opt {
1626            Some(v) => match v {
1627                AnyConfigEvaluation::DynamicConfig(config) => &config.value,
1628                AnyConfigEvaluation::Experiment(exp) => &exp.value,
1629            },
1630            None => panic!("Should have values"),
1631        };
1632
1633        assert!(!a_config.is_empty());
1634    }
1635}