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