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::output_logger::LogLevel;
9use crate::{
10    serialize_if_not_none, ObservabilityClient, OverrideAdapter, SpecAdapterConfig, SpecsAdapter,
11};
12use std::collections::HashMap;
13use std::fmt;
14use std::sync::{Arc, Weak};
15
16pub const DEFAULT_INIT_TIMEOUT_MS: u64 = 3000;
17
18#[derive(Clone, Default)]
19pub struct StatsigOptions {
20    pub data_store: Option<Arc<dyn DataStoreTrait>>, // External DataStore
21    pub enable_id_lists: Option<bool>,
22    pub enable_user_agent_parsing: Option<bool>,
23    pub enable_country_lookup: Option<bool>,
24    pub environment: Option<String>,
25
26    pub disable_network: Option<bool>, // Disable all out-going network including get configs, log_events...
27    pub disable_all_logging: Option<bool>,
28
29    pub event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
30    pub event_logging_flush_interval_ms: Option<u32>,
31    pub event_logging_max_queue_size: Option<u32>,
32
33    pub fallback_to_statsig_api: Option<bool>,
34
35    pub id_lists_adapter: Option<Arc<dyn IdListsAdapter>>,
36    pub id_lists_sync_interval_ms: Option<u32>,
37    pub id_lists_url: Option<String>,
38
39    pub init_timeout_ms: Option<u64>,
40    pub log_event_url: Option<String>,
41    pub observability_client: Option<Weak<dyn ObservabilityClient>>,
42    pub output_log_level: Option<LogLevel>,
43    pub override_adapter: Option<Arc<dyn OverrideAdapter>>,
44
45    pub spec_adapters_config: Option<Vec<SpecAdapterConfig>>, // Specs to customized spec adapter, order matters, reflecting priority of trying
46    pub specs_adapter: Option<Arc<dyn SpecsAdapter>>,
47    pub specs_sync_interval_ms: Option<u32>,
48    pub specs_url: Option<String>,
49    pub service_name: Option<String>,
50    pub global_custom_fields: Option<HashMap<String, DynamicValue>>,
51}
52
53impl StatsigOptions {
54    #[must_use]
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    // The builder method for more complex initialization
60    #[must_use]
61    pub fn builder() -> StatsigOptionsBuilder {
62        StatsigOptionsBuilder::default()
63    }
64}
65
66#[derive(Default)]
67pub struct StatsigOptionsBuilder {
68    inner: StatsigOptions,
69}
70
71impl StatsigOptionsBuilder {
72    #[must_use]
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    // Specs
78
79    #[must_use]
80    pub fn specs_url(mut self, specs_url: Option<String>) -> Self {
81        self.inner.specs_url = specs_url;
82        self
83    }
84
85    #[must_use]
86    pub fn specs_adapter(mut self, specs_adapter: Option<Arc<dyn SpecsAdapter>>) -> Self {
87        self.inner.specs_adapter = specs_adapter;
88        self
89    }
90
91    #[must_use]
92    pub fn specs_sync_interval_ms(mut self, specs_sync_interval_ms: Option<u32>) -> Self {
93        self.inner.specs_sync_interval_ms = specs_sync_interval_ms;
94        self
95    }
96
97    // Event Logging
98
99    #[must_use]
100    pub fn log_event_url(mut self, log_event_url: Option<String>) -> Self {
101        self.inner.log_event_url = log_event_url;
102        self
103    }
104
105    #[must_use]
106    pub fn disable_all_logging(mut self, disable_all_logging: Option<bool>) -> Self {
107        self.inner.disable_all_logging = disable_all_logging;
108        self
109    }
110
111    #[must_use]
112    pub fn event_logging_adapter(
113        mut self,
114        event_logging_adapter: Option<Arc<dyn EventLoggingAdapter>>,
115    ) -> Self {
116        self.inner.event_logging_adapter = event_logging_adapter;
117        self
118    }
119
120    #[must_use]
121    pub fn event_logging_flush_interval_ms(
122        mut self,
123        event_logging_flush_interval_ms: Option<u32>,
124    ) -> Self {
125        self.inner.event_logging_flush_interval_ms = event_logging_flush_interval_ms;
126        self
127    }
128
129    #[must_use]
130    pub fn event_logging_max_queue_size(
131        mut self,
132        event_logging_max_queue_size: Option<u32>,
133    ) -> Self {
134        self.inner.event_logging_max_queue_size = event_logging_max_queue_size;
135        self
136    }
137
138    // ID Lists
139
140    #[must_use]
141    pub fn enable_id_lists(mut self, enable_id_lists: Option<bool>) -> Self {
142        self.inner.enable_id_lists = enable_id_lists;
143        self
144    }
145
146    #[must_use]
147    pub fn id_lists_url(mut self, id_lists_url: Option<String>) -> Self {
148        self.inner.id_lists_url = id_lists_url;
149        self
150    }
151
152    #[must_use]
153    pub fn id_lists_adapter(mut self, id_lists_adapter: Option<Arc<dyn IdListsAdapter>>) -> Self {
154        self.inner.id_lists_adapter = id_lists_adapter;
155        self
156    }
157
158    #[must_use]
159    pub fn id_lists_sync_interval_ms(mut self, id_lists_sync_interval_ms: Option<u32>) -> Self {
160        self.inner.id_lists_sync_interval_ms = id_lists_sync_interval_ms;
161        self
162    }
163
164    // Other
165
166    #[must_use]
167    pub fn environment(mut self, environment: Option<String>) -> Self {
168        self.inner.environment = environment;
169        self
170    }
171
172    #[must_use]
173    pub fn output_log_level(mut self, output_log_level: Option<u32>) -> Self {
174        if let Some(level) = output_log_level {
175            self.inner.output_log_level = Some(LogLevel::from(level));
176        }
177        self
178    }
179
180    #[must_use]
181    pub fn service_name(mut self, service_name: Option<String>) -> Self {
182        self.inner.service_name = service_name;
183        self
184    }
185
186    #[must_use]
187    pub fn fallback_to_statsig_api(mut self, fallback_to_statsig_api: Option<bool>) -> Self {
188        self.inner.fallback_to_statsig_api = fallback_to_statsig_api;
189        self
190    }
191
192    #[must_use]
193    pub fn enable_user_agent_parsing(mut self, enable_user_agent_parsing: Option<bool>) -> Self {
194        self.inner.enable_user_agent_parsing = enable_user_agent_parsing;
195        self
196    }
197
198    #[must_use]
199    pub fn enable_country_lookup(mut self, enable_country_lookup: Option<bool>) -> Self {
200        self.inner.enable_country_lookup = enable_country_lookup;
201        self
202    }
203
204    #[must_use]
205    pub fn global_custom_fields(
206        mut self,
207        global_custom_fields: Option<HashMap<String, DynamicValue>>,
208    ) -> Self {
209        self.inner.global_custom_fields = global_custom_fields;
210        self
211    }
212
213    pub fn disable_network(mut self, disable_network: Option<bool>) -> Self {
214        self.inner.disable_network = disable_network;
215        self
216    }
217
218    #[must_use]
219    pub fn observability_client(mut self, client: Option<Weak<dyn ObservabilityClient>>) -> Self {
220        self.inner.observability_client = client;
221        self
222    }
223
224    #[must_use]
225    pub fn build(self) -> StatsigOptions {
226        self.inner
227    }
228}
229
230impl Serialize for StatsigOptions {
231    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232    where
233        S: Serializer,
234    {
235        let mut state = serializer.serialize_struct("StatsigOptions", 20)?;
236        serialize_if_not_none!(state, "spec_url", &self.specs_url);
237        serialize_if_not_none!(
238            state,
239            "spec_adapter",
240            &get_display_name(&self.specs_adapter)
241        );
242        serialize_if_not_none!(state, "spec_adapter_configs", &self.spec_adapters_config);
243        serialize_if_not_none!(
244            state,
245            "specs_sync_interval_ms",
246            &self.specs_sync_interval_ms
247        );
248        serialize_if_not_none!(state, "init_timeout_ms", &self.init_timeout_ms);
249
250        serialize_if_not_none!(state, "data_store", &get_if_set(&self.data_store));
251
252        serialize_if_not_none!(state, "log_event_url", &self.log_event_url);
253        serialize_if_not_none!(state, "disable_all_logging", &self.disable_all_logging);
254        serialize_if_not_none!(state, "disable_network", &self.disable_network);
255
256        serialize_if_not_none!(state, "id_lists_url", &self.id_lists_url);
257        serialize_if_not_none!(state, "enable_id_lists", &self.enable_id_lists);
258        serialize_if_not_none!(
259            state,
260            "enable_user_agent_parsing",
261            &self.enable_user_agent_parsing
262        );
263        serialize_if_not_none!(state, "enable_country_lookup", &self.enable_country_lookup);
264        serialize_if_not_none!(
265            state,
266            "id_lists_sync_interval",
267            &self.id_lists_sync_interval_ms
268        );
269        serialize_if_not_none!(state, "environment", &self.environment);
270        serialize_if_not_none!(
271            state,
272            "id_list_adapter",
273            &get_display_name(&self.id_lists_adapter)
274        );
275        serialize_if_not_none!(
276            state,
277            "fallback_to_statsig_api",
278            &self.fallback_to_statsig_api
279        );
280        serialize_if_not_none!(
281            state,
282            "override_adapter",
283            &get_if_set(&self.override_adapter)
284        );
285        serialize_if_not_none!(state, "service_name", &get_if_set(&self.service_name));
286        serialize_if_not_none!(state, "global_custom_fields", &self.global_custom_fields);
287
288        state.end()
289    }
290}
291
292fn get_if_set<T>(s: &Option<T>) -> Option<&str> {
293    s.as_ref().map(|_| "set")
294}
295
296fn get_display_name<T: fmt::Debug>(s: &Option<T>) -> Option<String> {
297    s.as_ref().map(|st| format!("{st:?}"))
298}