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