1use once_cell::sync::Lazy;
2use serde::{self, Deserialize, Serialize};
3use std::borrow::Cow;
4
5pub static APP_FOLDER: &str = "VKTEAMS_BOT_CONFIG";
6pub static CONFIG: Lazy<Config> = Lazy::new(Config::new);
7#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)]
9#[serde(rename_all = "snake_case")]
10pub struct Config {
11 #[cfg(feature = "otlp")]
12 #[serde(default)]
13 pub otlp: OtlpConfig,
14 #[cfg(feature = "ratelimit")]
15 #[serde(default)]
16 pub rate_limit: RateLimit,
17 #[serde(default)]
18 pub network: NetworkConfig,
19 #[cfg(feature = "longpoll")]
20 #[serde(default)]
21 pub listener: EventListenerConfig,
22 #[cfg(feature = "storage")]
23 #[serde(default)]
24 pub storage: crate::storage::StorageConfig,
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
29#[serde(rename_all = "snake_case")]
30#[repr(C)]
31pub struct OtlpConfig {
32 #[serde(default = "default_instance_id")]
33 pub instance_id: Cow<'static, str>,
34 #[serde(default = "default_deployment_environment_name")]
35 pub deployment_environment_name: Cow<'static, str>,
36 #[serde(default = "default_exporter_endpoint")]
37 pub exporter_endpoint: Option<Cow<'static, str>>,
38 #[serde(default = "default_exporter_timeout")]
39 pub exporter_timeout: u64,
40 #[serde(default = "default_exporter_metric_interval")]
41 pub exporter_metric_interval: u64,
42 #[serde(default = "default_ratio")]
43 pub ratio: f64,
44 #[serde(default = "default_otlp_filter_default")]
45 pub otel_filter_default: Cow<'static, str>,
46 #[serde(default = "default_fmt_filter_default")]
47 pub fmt_filter_default: Cow<'static, str>,
48 #[serde(default = "default_fmt_ansi")]
49 pub fmt_ansi: bool,
50 #[serde(default = "default_fmt_filter_self_directive")]
51 pub fmt_filter_self_directive: Cow<'static, str>,
52 #[serde(default = "default_otel")]
53 pub otel: Vec<OtelDirective>,
54 #[serde(default = "default_fmt")]
55 pub fmt: Vec<FmtDirective>,
56 #[serde(default)]
57 pub log_format: LogFormat,
58}
59
60impl Default for OtlpConfig {
61 fn default() -> Self {
62 Self {
63 instance_id: default_instance_id(),
64 deployment_environment_name: default_deployment_environment_name(),
65 exporter_endpoint: default_exporter_endpoint(),
66 exporter_timeout: default_exporter_timeout(),
67 exporter_metric_interval: default_exporter_metric_interval(),
68 ratio: default_ratio(),
69 otel_filter_default: default_otlp_filter_default(),
70 fmt_filter_default: default_fmt_filter_default(),
71 fmt_ansi: default_fmt_ansi(),
72 fmt_filter_self_directive: default_fmt_filter_self_directive(),
73 otel: default_otel(),
74 fmt: default_fmt(),
75 log_format: LogFormat::default(),
76 }
77 }
78}
79
80fn default_instance_id() -> Cow<'static, str> {
81 Cow::Borrowed("bot")
82}
83fn default_deployment_environment_name() -> Cow<'static, str> {
84 Cow::Borrowed("dev")
85}
86fn default_exporter_endpoint() -> Option<Cow<'static, str>> {
87 None
88}
89fn default_exporter_timeout() -> u64 {
90 0
91}
92fn default_exporter_metric_interval() -> u64 {
93 0
94}
95fn default_ratio() -> f64 {
96 1.0
97}
98fn default_otlp_filter_default() -> Cow<'static, str> {
99 Cow::Borrowed("debug")
100}
101fn default_fmt_filter_default() -> Cow<'static, str> {
102 Cow::Borrowed("debug")
103}
104fn default_fmt_ansi() -> bool {
105 true
106}
107fn default_fmt_filter_self_directive() -> Cow<'static, str> {
108 Cow::Borrowed("debug")
109}
110fn default_otel() -> Vec<OtelDirective> {
111 vec![OtelDirective {
112 otel_filter_directive: Cow::Borrowed("h2=off"),
113 }]
114}
115fn default_fmt() -> Vec<FmtDirective> {
116 vec![
117 FmtDirective {
118 fmt_filter_directive: Cow::Borrowed("axum=trace"),
119 },
120 FmtDirective {
121 fmt_filter_directive: Cow::Borrowed("tower_http=debug"),
122 },
123 FmtDirective {
124 fmt_filter_directive: Cow::Borrowed("opentelemetry=info"),
125 },
126 ]
127}
128
129#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
130#[serde(rename_all = "snake_case")]
131#[repr(C)]
132pub struct OtelDirective {
133 pub otel_filter_directive: Cow<'static, str>,
134}
135
136#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
137#[serde(rename_all = "snake_case")]
138#[repr(C)]
139pub struct FmtDirective {
140 pub fmt_filter_directive: Cow<'static, str>,
141}
142#[cfg(feature = "ratelimit")]
144#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
145#[serde(rename_all = "snake_case")]
146pub struct RateLimit {
147 #[serde(default = "default_limit")]
148 pub limit: usize,
149 #[serde(default = "default_duration")]
150 pub duration: u64,
151 #[serde(default = "default_retry_delay")]
152 pub retry_delay: u64,
153 #[serde(default = "default_retry_attempts")]
154 pub retry_attempts: u16,
155 #[serde(default = "default_init_bucket")]
156 pub init_bucket: usize,
157 #[serde(default = "default_cleanup_interval")]
158 pub cleanup_interval: u64,
159 #[serde(default = "default_bucket_lifetime")]
160 pub bucket_lifetime: u64,
161}
162
163#[cfg(feature = "ratelimit")]
164impl Default for RateLimit {
165 fn default() -> Self {
166 Self {
167 limit: default_limit(),
168 duration: default_duration(),
169 retry_delay: default_retry_delay(),
170 retry_attempts: default_retry_attempts(),
171 init_bucket: default_init_bucket(),
172 cleanup_interval: default_cleanup_interval(),
173 bucket_lifetime: default_bucket_lifetime(),
174 }
175 }
176}
177#[cfg(feature = "ratelimit")]
178fn default_limit() -> usize {
179 100
180}
181#[cfg(feature = "ratelimit")]
182fn default_duration() -> u64 {
183 60
184}
185#[cfg(feature = "ratelimit")]
186fn default_retry_delay() -> u64 {
187 1000
188}
189#[cfg(feature = "ratelimit")]
190fn default_retry_attempts() -> u16 {
191 3
192}
193#[cfg(feature = "ratelimit")]
194fn default_init_bucket() -> usize {
195 1
196}
197#[cfg(feature = "ratelimit")]
198fn default_cleanup_interval() -> u64 {
199 600
200}
201#[cfg(feature = "ratelimit")]
202fn default_bucket_lifetime() -> u64 {
203 3600
204}
205
206#[cfg(feature = "longpoll")]
208#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
209#[serde(rename_all = "snake_case")]
210pub struct EventListenerConfig {
211 #[serde(default = "default_max_events_per_batch")]
213 pub max_events_per_batch: usize,
214 #[serde(default = "default_empty_backoff_ms")]
216 pub empty_backoff_ms: u64,
217 #[serde(default = "default_max_backoff_ms")]
219 pub max_backoff_ms: u64,
220 #[serde(default = "default_use_exponential_backoff")]
222 pub use_exponential_backoff: bool,
223 #[serde(default = "default_max_memory_usage")]
225 pub max_memory_usage: usize,
226}
227
228#[cfg(feature = "longpoll")]
229impl Default for EventListenerConfig {
230 fn default() -> Self {
231 Self {
232 max_events_per_batch: default_max_events_per_batch(),
233 empty_backoff_ms: default_empty_backoff_ms(),
234 max_backoff_ms: default_max_backoff_ms(),
235 use_exponential_backoff: default_use_exponential_backoff(),
236 max_memory_usage: default_max_memory_usage(),
237 }
238 }
239}
240
241#[cfg(feature = "longpoll")]
242fn default_max_events_per_batch() -> usize {
243 50
244}
245#[cfg(feature = "longpoll")]
246fn default_empty_backoff_ms() -> u64 {
247 500
248}
249fn default_max_backoff_ms() -> u64 {
250 5000
251}
252#[cfg(feature = "longpoll")]
253fn default_use_exponential_backoff() -> bool {
254 true
255}
256#[cfg(feature = "longpoll")]
257fn default_max_memory_usage() -> usize {
258 0
259}
260
261#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
263#[serde(rename_all = "snake_case")]
264pub struct NetworkConfig {
265 #[serde(default = "default_retries")]
267 pub retries: usize,
268 #[serde(default = "default_max_backoff_ms")]
270 pub max_backoff_ms: u64,
271 #[serde(default = "default_request_timeout_secs")]
273 pub request_timeout_secs: u64,
274 #[serde(default = "default_connect_timeout_secs")]
276 pub connect_timeout_secs: u64,
277 #[serde(default = "default_pool_idle_timeout_secs")]
279 pub pool_idle_timeout_secs: u64,
280 #[serde(default = "default_max_idle_connections")]
282 pub max_idle_connections: usize,
283}
284
285impl Default for NetworkConfig {
286 fn default() -> Self {
287 Self {
288 retries: default_retries(),
289 max_backoff_ms: default_max_backoff_ms(),
290 request_timeout_secs: default_request_timeout_secs(),
291 connect_timeout_secs: default_connect_timeout_secs(),
292 pool_idle_timeout_secs: default_pool_idle_timeout_secs(),
293 max_idle_connections: default_max_idle_connections(),
294 }
295 }
296}
297
298fn default_retries() -> usize {
299 3
300}
301fn default_request_timeout_secs() -> u64 {
302 30
303}
304fn default_connect_timeout_secs() -> u64 {
305 10
306}
307fn default_pool_idle_timeout_secs() -> u64 {
308 90
309}
310fn default_max_idle_connections() -> usize {
311 10
312}
313
314#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
315#[serde(rename_all = "snake_case")]
316pub enum LogFormat {
317 #[default]
318 Pretty,
319 Json,
320 Full,
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use toml;
327
328 #[test]
329 fn test_config_defaults() {
330 let config = Config::default();
331 #[cfg(feature = "otlp")]
332 {
333 assert_eq!(config.otlp.instance_id, "bot");
335 }
336 #[cfg(feature = "ratelimit")]
337 {
338 assert_eq!(config.rate_limit.limit, 100);
340 }
341 #[cfg(feature = "storage")]
342 {
343 assert_eq!(config.storage.database.max_connections, 20);
345 assert!(config.storage.database.auto_migrate);
346 }
347 assert_eq!(config.network.retries, 3);
349 assert_eq!(config.network.request_timeout_secs, 30);
350 }
351
352 #[test]
353 fn test_serialize_deserialize_config() {
354 let mut config = Config::default();
355 #[cfg(feature = "otlp")]
356 {
357 config.otlp.instance_id = "test_id".into();
358 }
359 #[cfg(feature = "storage")]
360 {
361 config.storage.database.max_connections = 42;
362 }
363 config.network.retries = 7;
364 let toml_str = toml::to_string(&config).unwrap();
365 let deser: Config = toml::from_str(&toml_str).unwrap();
366 #[cfg(feature = "otlp")]
367 {
368 assert_eq!(deser.otlp.instance_id, "test_id");
369 }
370 #[cfg(feature = "storage")]
371 {
372 assert_eq!(deser.storage.database.max_connections, 42);
373 }
374 assert_eq!(deser.network.retries, 7);
375 }
376
377 #[cfg(feature = "ratelimit")]
378 #[test]
379 fn test_default_limit() {
380 assert_eq!(default_limit(), 100);
381 }
382
383 #[cfg(feature = "ratelimit")]
384 #[test]
385 fn test_default_duration() {
386 assert_eq!(default_duration(), 60);
387 }
388
389 #[cfg(feature = "ratelimit")]
390 #[test]
391 fn test_default_retry_delay() {
392 assert_eq!(default_retry_delay(), 1000);
393 }
394}