forge_core/config/
signals.rs1use std::time::Duration;
4
5use serde::{Deserialize, Serialize};
6
7use super::default_true;
8use super::types::DurationStr;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
15#[non_exhaustive]
16pub struct SignalsConfig {
17 #[serde(default = "default_true")]
19 pub enabled: bool,
20
21 #[serde(default = "default_true")]
23 pub auto_capture: bool,
24
25 #[serde(default = "default_true")]
27 pub diagnostics: bool,
28
29 #[serde(default = "default_session_timeout")]
31 pub session_timeout: DurationStr,
32
33 #[serde(default = "default_retention_days")]
35 pub retention_days: u32,
36
37 #[serde(default = "default_true")]
44 pub anonymize_ip: bool,
45
46 #[serde(default = "default_batch_size")]
48 pub batch_size: usize,
49
50 #[serde(default = "default_flush_interval")]
52 pub flush_interval: DurationStr,
53
54 #[serde(default = "default_channel_capacity")]
56 pub channel_capacity: usize,
57
58 #[serde(default)]
60 pub excluded_functions: Vec<String>,
61
62 #[serde(default = "default_true")]
64 pub bot_detection: bool,
65
66 #[serde(default)]
70 pub geoip_db_path: Option<String>,
71}
72
73impl Default for SignalsConfig {
74 fn default() -> Self {
75 Self {
76 enabled: true,
77 auto_capture: true,
78 diagnostics: true,
79 session_timeout: default_session_timeout(),
80 retention_days: default_retention_days(),
81 anonymize_ip: true,
82 batch_size: default_batch_size(),
83 flush_interval: default_flush_interval(),
84 channel_capacity: default_channel_capacity(),
85 excluded_functions: Vec::new(),
86 bot_detection: true,
87 geoip_db_path: None,
88 }
89 }
90}
91
92fn default_session_timeout() -> DurationStr {
93 DurationStr::new(Duration::from_secs(1800))
94}
95
96fn default_retention_days() -> u32 {
97 90
98}
99
100fn default_batch_size() -> usize {
101 100
102}
103
104fn default_flush_interval() -> DurationStr {
105 DurationStr::new(Duration::from_secs(5))
106}
107
108fn default_channel_capacity() -> usize {
109 10_000
110}
111
112#[cfg(test)]
113#[allow(clippy::unwrap_used)]
114mod tests {
115 use super::*;
116
117 #[tokio::test]
118 async fn default_config_has_correct_values() {
119 let config = SignalsConfig::default();
120 assert!(config.enabled);
121 assert!(config.auto_capture);
122 assert!(config.diagnostics);
123 assert_eq!(config.session_timeout.as_secs(), 1800);
124 assert_eq!(config.retention_days, 90);
125 assert!(config.anonymize_ip);
126 assert_eq!(config.batch_size, 100);
127 assert_eq!(config.flush_interval.as_secs(), 5);
128 assert!(config.excluded_functions.is_empty());
129 assert!(config.bot_detection);
130 }
131
132 #[tokio::test]
133 async fn deserializes_empty_toml() {
134 #[derive(Deserialize)]
135 struct Wrapper {
136 #[serde(default)]
137 signals: SignalsConfig,
138 }
139
140 let from_empty: SignalsConfig = toml::from_str("").unwrap();
141 let from_table: Wrapper = toml::from_str("[signals]").unwrap();
142
143 for config in [from_empty, from_table.signals] {
144 assert!(config.enabled);
145 assert!(config.auto_capture);
146 assert!(config.diagnostics);
147 assert_eq!(config.session_timeout.as_secs(), 1800);
148 assert_eq!(config.retention_days, 90);
149 assert!(config.anonymize_ip);
150 assert_eq!(config.batch_size, 100);
151 assert_eq!(config.flush_interval.as_secs(), 5);
152 assert!(config.excluded_functions.is_empty());
153 assert!(config.bot_detection);
154 }
155 }
156
157 #[tokio::test]
158 async fn partial_override_preserves_defaults() {
159 let config: SignalsConfig = toml::from_str("enabled = false").unwrap();
160 assert!(!config.enabled);
161 assert!(config.auto_capture);
162 assert!(config.diagnostics);
163 assert_eq!(config.session_timeout.as_secs(), 1800);
164 assert_eq!(config.retention_days, 90);
165 assert!(config.anonymize_ip);
166 assert_eq!(config.batch_size, 100);
167 assert_eq!(config.flush_interval.as_secs(), 5);
168 assert!(config.excluded_functions.is_empty());
169 assert!(config.bot_detection);
170 }
171
172 #[tokio::test]
173 async fn excluded_functions_from_toml() {
174 let config: SignalsConfig =
175 toml::from_str(r#"excluded_functions = ["health_check", "readiness"]"#).unwrap();
176 assert_eq!(config.excluded_functions, vec!["health_check", "readiness"]);
177 }
178
179 #[tokio::test]
180 async fn all_fields_overridden() {
181 let toml_str = r#"
182 enabled = false
183 auto_capture = false
184 diagnostics = false
185 session_timeout = "60m"
186 retention_days = 30
187 anonymize_ip = true
188 batch_size = 500
189 flush_interval = "10s"
190 excluded_functions = ["ping"]
191 bot_detection = false
192 "#;
193 let config: SignalsConfig = toml::from_str(toml_str).unwrap();
194 assert!(!config.enabled);
195 assert!(!config.auto_capture);
196 assert!(!config.diagnostics);
197 assert_eq!(config.session_timeout.as_secs(), 3600);
198 assert_eq!(config.retention_days, 30);
199 assert!(config.anonymize_ip);
200 assert_eq!(config.batch_size, 500);
201 assert_eq!(config.flush_interval.as_secs(), 10);
202 assert_eq!(config.excluded_functions, vec!["ping"]);
203 assert!(!config.bot_detection);
204 }
205}