Skip to main content

statsig_rust/
statsig_options.rs

1use serde::ser::SerializeStruct;
2use serde::{Serialize, Serializer};
3
4use crate::console_capture::console_capture_options::ConsoleCaptureOptions;
5use crate::data_store_interface::{DataStoreKeyVersion, DataStoreTrait};
6use crate::evaluation::dynamic_value::DynamicValue;
7use crate::event_logging_adapter::EventLoggingAdapter;
8use crate::id_lists_adapter::IdListsAdapter;
9use crate::networking::proxy_config::ProxyConfig;
10use crate::output_logger::{LogLevel, OutputLogProvider};
11use crate::persistent_storage::persistent_storage_trait::PersistentStorage;
12use crate::{
13    log_d, log_w, serialize_if_not_none, ConfigCompressionMode, ObservabilityClient,
14    OverrideAdapter, SpecAdapterConfig, SpecsAdapter,
15};
16use std::collections::{HashMap, HashSet};
17use std::fmt;
18use std::sync::{Arc, Weak};
19
20pub const DEFAULT_INIT_TIMEOUT_MS: u64 = 3000;
21const MIN_SYNC_INTERVAL: u32 = 1000;
22const TEST_ENV_FLAG: &str = "STATSIG_RUNNING_TESTS";
23
24#[derive(Clone, Default)]
25pub struct StatsigOptions {
26    pub data_store: Option<Arc<dyn DataStoreTrait>>, // External DataStore
27    pub data_store_key_schema_version: Option<DataStoreKeyVersion>,
28
29    pub disable_all_logging: Option<bool>,
30    pub disable_country_lookup: Option<bool>,
31    pub disable_network: Option<bool>, // Disable all out-going network including get configs, log_events...
32
33    pub enable_id_lists: Option<bool>,
34    pub environment: Option<String>,
35    pub config_compression_mode: Option<ConfigCompressionMode>,
36
37    pub event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
38
39    #[deprecated]
40    pub event_logging_flush_interval_ms: Option<u32>,
41    pub event_logging_max_pending_batch_queue_size: Option<u32>,
42    pub event_logging_max_queue_size: Option<u32>,
43
44    pub fallback_to_statsig_api: Option<bool>,
45    pub global_custom_fields: Option<HashMap<String, DynamicValue>>,
46
47    pub id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
48    pub id_lists_sync_interval_ms: Option<u32>,
49    pub id_lists_url: Option<String>,
50
51    pub init_timeout_ms: Option<u64>,
52    pub log_event_url: Option<String>,
53    pub observability_client: Option<Weak<dyn ObservabilityClient>>,
54    pub output_log_level: Option<LogLevel>,
55    pub output_logger_provider: Option<Arc<dyn OutputLogProvider>>,
56    pub override_adapter: Option<Arc<dyn OverrideAdapter>>,
57    pub persistent_storage: Option<Arc<dyn PersistentStorage>>,
58    pub service_name: Option<String>,
59
60    pub spec_adapters_config: Option<Vec<SpecAdapterConfig>>, // Specs to customized spec adapter, order matters, reflecting priority of trying
61    pub specs_adapter: Option<Arc<dyn SpecsAdapter>>,
62    pub specs_sync_interval_ms: Option<u32>,
63    pub specs_url: Option<String>,
64
65    pub wait_for_country_lookup_init: Option<bool>,
66    pub wait_for_user_agent_init: Option<bool>,
67
68    pub proxy_config: Option<ProxyConfig>,
69
70    pub console_capture_options: Option<ConsoleCaptureOptions>,
71
72    pub use_third_party_ua_parser: Option<bool>,
73    pub disable_disk_access: Option<bool>,
74
75    pub experimental_flags: Option<HashSet<String>>,
76}
77
78impl StatsigOptions {
79    #[must_use]
80    pub fn new() -> Self {
81        Self::default()
82    }
83
84    // The builder method for more complex initialization
85    #[must_use]
86    pub fn builder() -> StatsigOptionsBuilder {
87        StatsigOptionsBuilder::default()
88    }
89}
90
91#[derive(Default)]
92pub struct StatsigOptionsBuilder {
93    inner: StatsigOptions,
94}
95
96impl StatsigOptionsBuilder {
97    #[must_use]
98    pub fn new() -> Self {
99        Self::default()
100    }
101
102    // Specs
103
104    #[must_use]
105    pub fn specs_url(mut self, specs_url: Option<String>) -> Self {
106        self.inner.specs_url = specs_url;
107        self
108    }
109
110    #[must_use]
111    pub fn specs_adapter(mut self, specs_adapter: Option<Arc<dyn SpecsAdapter>>) -> Self {
112        self.inner.specs_adapter = specs_adapter;
113        self
114    }
115
116    #[must_use]
117    pub fn specs_sync_interval_ms(mut self, specs_sync_interval_ms: Option<u32>) -> Self {
118        self.inner.specs_sync_interval_ms = specs_sync_interval_ms;
119        self
120    }
121
122    #[must_use]
123    pub fn spec_adapters_config(
124        mut self,
125        spec_adapters_config: Option<Vec<SpecAdapterConfig>>,
126    ) -> Self {
127        self.inner.spec_adapters_config = spec_adapters_config;
128        self
129    }
130
131    // Event Logging
132
133    #[must_use]
134    pub fn log_event_url(mut self, log_event_url: Option<String>) -> Self {
135        self.inner.log_event_url = log_event_url;
136        self
137    }
138
139    #[must_use]
140    pub fn disable_all_logging(mut self, disable_all_logging: Option<bool>) -> Self {
141        self.inner.disable_all_logging = disable_all_logging;
142        self
143    }
144
145    #[must_use]
146    pub fn event_logging_adapter(
147        mut self,
148        event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
149    ) -> Self {
150        self.inner.event_logging_adapter = event_logging_adapter;
151        self
152    }
153
154    #[must_use]
155    #[deprecated(
156        note = "This field is deprecated in favor of smart log event. It is no longer consumed and can be removed safely."
157    )]
158    #[allow(deprecated)]
159    pub fn event_logging_flush_interval_ms(
160        mut self,
161        event_logging_flush_interval_ms: Option<u32>,
162    ) -> Self {
163        self.inner.event_logging_flush_interval_ms = event_logging_flush_interval_ms;
164        self
165    }
166
167    #[must_use]
168    pub fn event_logging_max_queue_size(
169        mut self,
170        event_logging_max_queue_size: Option<u32>,
171    ) -> Self {
172        self.inner.event_logging_max_queue_size = event_logging_max_queue_size;
173        self
174    }
175
176    #[must_use]
177    pub fn event_logging_max_pending_batch_queue_size(
178        mut self,
179        event_logging_max_pending_batch_queue_size: Option<u32>,
180    ) -> Self {
181        self.inner.event_logging_max_pending_batch_queue_size =
182            event_logging_max_pending_batch_queue_size;
183        self
184    }
185
186    // ID Lists
187
188    #[must_use]
189    pub fn enable_id_lists(mut self, enable_id_lists: Option<bool>) -> Self {
190        self.inner.enable_id_lists = enable_id_lists;
191        self
192    }
193
194    #[must_use]
195    pub fn id_lists_url(mut self, id_lists_url: Option<String>) -> Self {
196        self.inner.id_lists_url = id_lists_url;
197        self
198    }
199
200    #[must_use]
201    pub fn id_lists_adapter(mut self, id_lists_adapter: Option<Arc<dyn IdListsAdapter>>) -> Self {
202        self.inner.id_lists_adapter = id_lists_adapter;
203        self
204    }
205
206    #[must_use]
207    pub fn id_lists_sync_interval_ms(mut self, id_lists_sync_interval_ms: Option<u32>) -> Self {
208        self.inner.id_lists_sync_interval_ms = id_lists_sync_interval_ms;
209        self
210    }
211
212    // Other
213
214    #[must_use]
215    pub fn proxy_config(mut self, proxy_config: Option<ProxyConfig>) -> Self {
216        self.inner.proxy_config = proxy_config;
217        self
218    }
219
220    #[must_use]
221    pub fn environment(mut self, environment: Option<String>) -> Self {
222        self.inner.environment = environment;
223        self
224    }
225
226    #[must_use]
227    #[deprecated(
228        note = "This field is deprecated and will be removed in a future release. It is no longer consumed and can be removed safely."
229    )]
230    pub fn config_compression_mode(
231        mut self,
232        config_compression_mode: Option<ConfigCompressionMode>,
233    ) -> Self {
234        self.inner.config_compression_mode = config_compression_mode;
235        self
236    }
237
238    #[must_use]
239    pub fn output_log_level(mut self, output_log_level: Option<u32>) -> Self {
240        if let Some(level) = output_log_level {
241            self.inner.output_log_level = Some(LogLevel::from(level));
242        }
243        self
244    }
245
246    #[must_use]
247    pub fn output_logger_provider(
248        mut self,
249        output_logger_provider: Option<Arc<dyn OutputLogProvider>>,
250    ) -> Self {
251        self.inner.output_logger_provider = output_logger_provider;
252        self
253    }
254
255    #[must_use]
256    pub fn wait_for_country_lookup_init(
257        mut self,
258        wait_for_country_lookup_init: Option<bool>,
259    ) -> Self {
260        self.inner.wait_for_country_lookup_init = wait_for_country_lookup_init;
261        self
262    }
263
264    #[must_use]
265    pub fn wait_for_user_agent_init(mut self, wait_for_user_agent_init: Option<bool>) -> Self {
266        self.inner.wait_for_user_agent_init = wait_for_user_agent_init;
267        self
268    }
269
270    #[must_use]
271    pub fn disable_country_lookup(mut self, disable_country_lookup: Option<bool>) -> Self {
272        self.inner.disable_country_lookup = disable_country_lookup;
273        self
274    }
275
276    #[must_use]
277    pub fn service_name(mut self, service_name: Option<String>) -> Self {
278        self.inner.service_name = service_name;
279        self
280    }
281
282    #[must_use]
283    pub fn fallback_to_statsig_api(mut self, fallback_to_statsig_api: Option<bool>) -> Self {
284        self.inner.fallback_to_statsig_api = fallback_to_statsig_api;
285        self
286    }
287
288    #[must_use]
289    pub fn global_custom_fields(
290        mut self,
291        global_custom_fields: Option<HashMap<String, DynamicValue>>,
292    ) -> Self {
293        self.inner.global_custom_fields = global_custom_fields;
294        self
295    }
296
297    pub fn disable_network(mut self, disable_network: Option<bool>) -> Self {
298        self.inner.disable_network = disable_network;
299        self
300    }
301
302    #[must_use]
303    pub fn use_third_party_ua_parser(mut self, use_third_party_ua_parser: Option<bool>) -> Self {
304        self.inner.use_third_party_ua_parser = use_third_party_ua_parser;
305        self
306    }
307
308    #[must_use]
309    pub fn init_timeout_ms(mut self, init_timeout_ms: Option<u64>) -> Self {
310        self.inner.init_timeout_ms = init_timeout_ms;
311        self
312    }
313
314    #[must_use]
315    pub fn build(self) -> StatsigOptions {
316        self.inner
317    }
318
319    // interface related options
320
321    #[must_use]
322    pub fn persistent_storage(
323        mut self,
324        persistent_storage: Option<Arc<dyn PersistentStorage>>,
325    ) -> Self {
326        self.inner.persistent_storage = persistent_storage;
327        self
328    }
329
330    #[must_use]
331    pub fn observability_client(mut self, client: Option<Weak<dyn ObservabilityClient>>) -> Self {
332        self.inner.observability_client = client;
333        self
334    }
335
336    #[must_use]
337    pub fn data_store(mut self, data_store: Option<Arc<dyn DataStoreTrait>>) -> Self {
338        self.inner.data_store = data_store;
339        self
340    }
341}
342
343impl Serialize for StatsigOptions {
344    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
345    where
346        S: Serializer,
347    {
348        let mut state = serializer.serialize_struct("StatsigOptions", 20)?;
349        serialize_if_not_none!(state, "spec_url", &self.specs_url);
350        serialize_if_not_none!(
351            state,
352            "spec_adapter",
353            &get_display_name(&self.specs_adapter)
354        );
355        serialize_if_not_none!(state, "spec_adapter_configs", &self.spec_adapters_config);
356        serialize_if_not_none!(
357            state,
358            "specs_sync_interval_ms",
359            &self.specs_sync_interval_ms
360        );
361        serialize_if_not_none!(state, "init_timeout_ms", &self.init_timeout_ms);
362
363        serialize_if_not_none!(state, "data_store", &get_if_set(&self.data_store));
364
365        serialize_if_not_none!(state, "log_event_url", &self.log_event_url);
366        serialize_if_not_none!(state, "disable_all_logging", &self.disable_all_logging);
367        serialize_if_not_none!(state, "disable_network", &self.disable_network);
368
369        serialize_if_not_none!(state, "id_lists_url", &self.id_lists_url);
370        serialize_if_not_none!(state, "enable_id_lists", &self.enable_id_lists);
371        serialize_if_not_none!(
372            state,
373            "wait_for_user_agent_init",
374            &self.wait_for_user_agent_init
375        );
376        serialize_if_not_none!(
377            state,
378            "wait_for_country_lookup_init",
379            &self.wait_for_country_lookup_init
380        );
381        serialize_if_not_none!(
382            state,
383            "id_lists_sync_interval",
384            &self.id_lists_sync_interval_ms
385        );
386        serialize_if_not_none!(state, "environment", &self.environment);
387        serialize_if_not_none!(
388            state,
389            "id_list_adapter",
390            &get_display_name(&self.id_lists_adapter)
391        );
392        serialize_if_not_none!(
393            state,
394            "fallback_to_statsig_api",
395            &self.fallback_to_statsig_api
396        );
397        serialize_if_not_none!(
398            state,
399            "override_adapter",
400            &get_if_set(&self.override_adapter)
401        );
402        serialize_if_not_none!(state, "service_name", &get_if_set(&self.service_name));
403        serialize_if_not_none!(state, "global_custom_fields", &self.global_custom_fields);
404
405        state.end()
406    }
407}
408
409fn get_if_set<T>(s: &Option<T>) -> Option<&str> {
410    s.as_ref().map(|_| "set")
411}
412
413fn get_display_name<T: fmt::Debug>(s: &Option<T>) -> Option<String> {
414    s.as_ref().map(|st| format!("{st:?}"))
415}
416
417//-------------------------------Validator---------------------------------
418
419const TAG: &str = "StatsigOptionValidator";
420impl StatsigOptions {
421    pub fn validate_and_fix(self: Arc<Self>) -> Arc<Self> {
422        if std::env::var(TEST_ENV_FLAG).is_ok() {
423            log_d!(
424                TAG,
425                "Skipping StatsigOptions validation in testing environment"
426            );
427            return self;
428        }
429
430        let mut opts_clone: Arc<StatsigOptions> = self.clone();
431        let mut_ref = Arc::make_mut(&mut opts_clone);
432
433        if is_sync_interval_invalid(&self.specs_sync_interval_ms) {
434            log_w!(
435                TAG,
436                "Invalid 'specs_sync_interval_ms', value must be greater than {}, received {:?}",
437                MIN_SYNC_INTERVAL,
438                &self.specs_sync_interval_ms
439            );
440            mut_ref.specs_sync_interval_ms = None;
441        }
442
443        if is_sync_interval_invalid(&self.id_lists_sync_interval_ms) {
444            log_w!(
445                TAG,
446                "Invalid 'id_lists_sync_interval_ms', value must be greater than {}, received {:?}",
447                MIN_SYNC_INTERVAL,
448                &self.id_lists_sync_interval_ms
449            );
450            mut_ref.id_lists_sync_interval_ms = None;
451        }
452
453        if should_fix_null_url(&self.specs_url) {
454            log_d!(TAG, "Setting specs_url to be default url");
455            mut_ref.specs_url = None;
456        }
457
458        if should_fix_null_url(&self.id_lists_url) {
459            log_d!(TAG, "Setting id_lists_url to be default url");
460            mut_ref.id_lists_url = None;
461        }
462
463        if should_fix_null_url(&self.log_event_url) {
464            log_d!(TAG, "Setting log_event_url to be default url");
465            mut_ref.log_event_url = None;
466        }
467
468        opts_clone
469    }
470}
471
472fn is_sync_interval_invalid(interval_ms: &Option<u32>) -> bool {
473    if let Some(interval) = interval_ms {
474        return *interval < MIN_SYNC_INTERVAL;
475    }
476    false
477}
478
479fn should_fix_null_url(maybe_url: &Option<String>) -> bool {
480    if let Some(url) = maybe_url {
481        return url.is_empty() || url.eq_ignore_ascii_case("null");
482    }
483
484    false
485}