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