statsig_rust/
statsig_options.rs

1use serde::ser::SerializeStruct;
2use serde::{Serialize, Serializer};
3
4use crate::data_store_interface::DataStoreTrait;
5use crate::evaluation::dynamic_value::DynamicValue;
6use crate::event_logging_adapter::EventLoggingAdapter;
7use crate::id_lists_adapter::IdListsAdapter;
8use crate::networking::proxy_config::ProxyConfig;
9use crate::output_logger::{LogLevel, OutputLogProvider};
10use crate::persistent_storage::persistent_storage_trait::PersistentStorage;
11use crate::{
12    serialize_if_not_none, ConfigCompressionMode, ObservabilityClient, OverrideAdapter,
13    SpecAdapterConfig, SpecsAdapter,
14};
15use std::collections::HashMap;
16use std::fmt;
17use std::sync::{Arc, Weak};
18
19pub const DEFAULT_INIT_TIMEOUT_MS: u64 = 3000;
20
21#[derive(Clone, Default)]
22pub struct StatsigOptions {
23    pub data_store: Option<Arc<dyn DataStoreTrait>>, // External DataStore
24
25    pub disable_all_logging: Option<bool>,
26    pub disable_country_lookup: Option<bool>,
27    pub disable_network: Option<bool>, // Disable all out-going network including get configs, log_events...
28    pub disable_user_agent_parsing: Option<bool>,
29
30    pub enable_id_lists: Option<bool>,
31    pub environment: Option<String>,
32    pub config_compression_mode: Option<ConfigCompressionMode>,
33
34    pub event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
35
36    #[deprecated]
37    pub event_logging_flush_interval_ms: Option<u32>,
38    pub event_logging_max_pending_batch_queue_size: Option<u32>,
39    pub event_logging_max_queue_size: Option<u32>,
40
41    pub fallback_to_statsig_api: Option<bool>,
42    pub global_custom_fields: Option<HashMap<String, DynamicValue>>,
43
44    pub id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
45    pub id_lists_sync_interval_ms: Option<u32>,
46    pub id_lists_url: Option<String>,
47
48    pub init_timeout_ms: Option<u64>,
49    pub log_event_url: Option<String>,
50    pub observability_client: Option<Weak<dyn ObservabilityClient>>,
51    pub output_log_level: Option<LogLevel>,
52    pub output_logger_provider: Option<Arc<dyn OutputLogProvider>>,
53    pub override_adapter: Option<Arc<dyn OverrideAdapter>>,
54    pub persistent_storage: Option<Arc<dyn PersistentStorage>>,
55    pub service_name: Option<String>,
56
57    pub spec_adapters_config: Option<Vec<SpecAdapterConfig>>, // Specs to customized spec adapter, order matters, reflecting priority of trying
58    pub specs_adapter: Option<Arc<dyn SpecsAdapter>>,
59    pub specs_sync_interval_ms: Option<u32>,
60    pub specs_url: Option<String>,
61
62    pub wait_for_country_lookup_init: Option<bool>,
63    pub wait_for_user_agent_init: Option<bool>,
64
65    pub proxy_config: Option<ProxyConfig>,
66}
67
68impl StatsigOptions {
69    #[must_use]
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    // The builder method for more complex initialization
75    #[must_use]
76    pub fn builder() -> StatsigOptionsBuilder {
77        StatsigOptionsBuilder::default()
78    }
79}
80
81#[derive(Default)]
82pub struct StatsigOptionsBuilder {
83    inner: StatsigOptions,
84}
85
86impl StatsigOptionsBuilder {
87    #[must_use]
88    pub fn new() -> Self {
89        Self::default()
90    }
91
92    // Specs
93
94    #[must_use]
95    pub fn specs_url(mut self, specs_url: Option<String>) -> Self {
96        self.inner.specs_url = specs_url;
97        self
98    }
99
100    #[must_use]
101    pub fn specs_adapter(mut self, specs_adapter: Option<Arc<dyn SpecsAdapter>>) -> Self {
102        self.inner.specs_adapter = specs_adapter;
103        self
104    }
105
106    #[must_use]
107    pub fn specs_sync_interval_ms(mut self, specs_sync_interval_ms: Option<u32>) -> Self {
108        self.inner.specs_sync_interval_ms = specs_sync_interval_ms;
109        self
110    }
111
112    // Event Logging
113
114    #[must_use]
115    pub fn log_event_url(mut self, log_event_url: Option<String>) -> Self {
116        self.inner.log_event_url = log_event_url;
117        self
118    }
119
120    #[must_use]
121    pub fn disable_all_logging(mut self, disable_all_logging: Option<bool>) -> Self {
122        self.inner.disable_all_logging = disable_all_logging;
123        self
124    }
125
126    #[must_use]
127    pub fn event_logging_adapter(
128        mut self,
129        event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
130    ) -> Self {
131        self.inner.event_logging_adapter = event_logging_adapter;
132        self
133    }
134
135    #[must_use]
136    #[deprecated(
137        note = "This field is deprecated in favor of smart log event. It is no longer consumed and can be removed safely."
138    )]
139    #[allow(deprecated)]
140    pub fn event_logging_flush_interval_ms(
141        mut self,
142        event_logging_flush_interval_ms: Option<u32>,
143    ) -> Self {
144        self.inner.event_logging_flush_interval_ms = event_logging_flush_interval_ms;
145        self
146    }
147
148    #[must_use]
149    pub fn event_logging_max_queue_size(
150        mut self,
151        event_logging_max_queue_size: Option<u32>,
152    ) -> Self {
153        self.inner.event_logging_max_queue_size = event_logging_max_queue_size;
154        self
155    }
156
157    #[must_use]
158    pub fn event_logging_max_pending_batch_queue_size(
159        mut self,
160        event_logging_max_pending_batch_queue_size: Option<u32>,
161    ) -> Self {
162        self.inner.event_logging_max_pending_batch_queue_size =
163            event_logging_max_pending_batch_queue_size;
164        self
165    }
166
167    // ID Lists
168
169    #[must_use]
170    pub fn enable_id_lists(mut self, enable_id_lists: Option<bool>) -> Self {
171        self.inner.enable_id_lists = enable_id_lists;
172        self
173    }
174
175    #[must_use]
176    pub fn id_lists_url(mut self, id_lists_url: Option<String>) -> Self {
177        self.inner.id_lists_url = id_lists_url;
178        self
179    }
180
181    #[must_use]
182    pub fn id_lists_adapter(mut self, id_lists_adapter: Option<Arc<dyn IdListsAdapter>>) -> Self {
183        self.inner.id_lists_adapter = id_lists_adapter;
184        self
185    }
186
187    #[must_use]
188    pub fn id_lists_sync_interval_ms(mut self, id_lists_sync_interval_ms: Option<u32>) -> Self {
189        self.inner.id_lists_sync_interval_ms = id_lists_sync_interval_ms;
190        self
191    }
192
193    // Other
194
195    #[must_use]
196    pub fn proxy_config(mut self, proxy_config: Option<ProxyConfig>) -> Self {
197        self.inner.proxy_config = proxy_config;
198        self
199    }
200
201    #[must_use]
202    pub fn environment(mut self, environment: Option<String>) -> Self {
203        self.inner.environment = environment;
204        self
205    }
206
207    #[must_use]
208    pub fn config_compression_mode(
209        mut self,
210        config_compression_mode: Option<ConfigCompressionMode>,
211    ) -> Self {
212        self.inner.config_compression_mode = config_compression_mode;
213        self
214    }
215
216    #[must_use]
217    pub fn output_log_level(mut self, output_log_level: Option<u32>) -> Self {
218        if let Some(level) = output_log_level {
219            self.inner.output_log_level = Some(LogLevel::from(level));
220        }
221        self
222    }
223
224    #[must_use]
225    pub fn output_logger_provider(
226        mut self,
227        output_logger_provider: Option<Arc<dyn OutputLogProvider>>,
228    ) -> Self {
229        self.inner.output_logger_provider = output_logger_provider;
230        self
231    }
232
233    #[must_use]
234    pub fn wait_for_country_lookup_init(
235        mut self,
236        wait_for_country_lookup_init: Option<bool>,
237    ) -> Self {
238        self.inner.wait_for_country_lookup_init = wait_for_country_lookup_init;
239        self
240    }
241
242    #[must_use]
243    pub fn wait_for_user_agent_init(mut self, wait_for_user_agent_init: Option<bool>) -> Self {
244        self.inner.wait_for_user_agent_init = wait_for_user_agent_init;
245        self
246    }
247
248    #[must_use]
249    pub fn disable_user_agent_parsing(mut self, disable_user_agent_parsing: Option<bool>) -> Self {
250        self.inner.disable_user_agent_parsing = disable_user_agent_parsing;
251        self
252    }
253
254    #[must_use]
255    pub fn disable_country_lookup(mut self, disable_country_lookup: Option<bool>) -> Self {
256        self.inner.disable_country_lookup = disable_country_lookup;
257        self
258    }
259
260    #[must_use]
261    pub fn service_name(mut self, service_name: Option<String>) -> Self {
262        self.inner.service_name = service_name;
263        self
264    }
265
266    #[must_use]
267    pub fn fallback_to_statsig_api(mut self, fallback_to_statsig_api: Option<bool>) -> Self {
268        self.inner.fallback_to_statsig_api = fallback_to_statsig_api;
269        self
270    }
271
272    #[must_use]
273    pub fn global_custom_fields(
274        mut self,
275        global_custom_fields: Option<HashMap<String, DynamicValue>>,
276    ) -> Self {
277        self.inner.global_custom_fields = global_custom_fields;
278        self
279    }
280
281    pub fn disable_network(mut self, disable_network: Option<bool>) -> Self {
282        self.inner.disable_network = disable_network;
283        self
284    }
285
286    #[must_use]
287    pub fn init_timeout_ms(mut self, init_timeout_ms: Option<u64>) -> Self {
288        self.inner.init_timeout_ms = init_timeout_ms;
289        self
290    }
291
292    #[must_use]
293    pub fn build(self) -> StatsigOptions {
294        self.inner
295    }
296
297    // interface related options
298
299    #[must_use]
300    pub fn observability_client(mut self, client: Option<Weak<dyn ObservabilityClient>>) -> Self {
301        self.inner.observability_client = client;
302        self
303    }
304
305    #[must_use]
306    pub fn data_store(mut self, data_store: Option<Arc<dyn DataStoreTrait>>) -> Self {
307        self.inner.data_store = data_store;
308        self
309    }
310}
311
312impl Serialize for StatsigOptions {
313    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
314    where
315        S: Serializer,
316    {
317        let mut state = serializer.serialize_struct("StatsigOptions", 20)?;
318        serialize_if_not_none!(state, "spec_url", &self.specs_url);
319        serialize_if_not_none!(
320            state,
321            "spec_adapter",
322            &get_display_name(&self.specs_adapter)
323        );
324        serialize_if_not_none!(state, "spec_adapter_configs", &self.spec_adapters_config);
325        serialize_if_not_none!(
326            state,
327            "specs_sync_interval_ms",
328            &self.specs_sync_interval_ms
329        );
330        serialize_if_not_none!(state, "init_timeout_ms", &self.init_timeout_ms);
331
332        serialize_if_not_none!(state, "data_store", &get_if_set(&self.data_store));
333
334        serialize_if_not_none!(state, "log_event_url", &self.log_event_url);
335        serialize_if_not_none!(state, "disable_all_logging", &self.disable_all_logging);
336        serialize_if_not_none!(state, "disable_network", &self.disable_network);
337
338        serialize_if_not_none!(state, "id_lists_url", &self.id_lists_url);
339        serialize_if_not_none!(state, "enable_id_lists", &self.enable_id_lists);
340        serialize_if_not_none!(
341            state,
342            "wait_for_user_agent_init",
343            &self.wait_for_user_agent_init
344        );
345        serialize_if_not_none!(
346            state,
347            "wait_for_country_lookup_init",
348            &self.wait_for_country_lookup_init
349        );
350        serialize_if_not_none!(
351            state,
352            "id_lists_sync_interval",
353            &self.id_lists_sync_interval_ms
354        );
355        serialize_if_not_none!(state, "environment", &self.environment);
356        serialize_if_not_none!(
357            state,
358            "id_list_adapter",
359            &get_display_name(&self.id_lists_adapter)
360        );
361        serialize_if_not_none!(
362            state,
363            "fallback_to_statsig_api",
364            &self.fallback_to_statsig_api
365        );
366        serialize_if_not_none!(
367            state,
368            "override_adapter",
369            &get_if_set(&self.override_adapter)
370        );
371        serialize_if_not_none!(state, "service_name", &get_if_set(&self.service_name));
372        serialize_if_not_none!(state, "global_custom_fields", &self.global_custom_fields);
373
374        state.end()
375    }
376}
377
378fn get_if_set<T>(s: &Option<T>) -> Option<&str> {
379    s.as_ref().map(|_| "set")
380}
381
382fn get_display_name<T: fmt::Debug>(s: &Option<T>) -> Option<String> {
383    s.as_ref().map(|st| format!("{st:?}"))
384}