voirs_cli/telemetry/
config.rs1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6use super::privacy::AnonymizationLevel;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct TelemetryConfig {
11 pub enabled: bool,
13
14 pub level: TelemetryLevel,
16
17 pub storage_path: PathBuf,
19
20 pub anonymization: AnonymizationLevel,
22
23 pub remote_endpoint: Option<String>,
25
26 pub batch_size: usize,
28
29 pub flush_interval_secs: u64,
31}
32
33impl Default for TelemetryConfig {
34 fn default() -> Self {
35 Self {
36 enabled: false, level: TelemetryLevel::Standard,
38 storage_path: Self::default_storage_path(),
39 anonymization: AnonymizationLevel::Medium,
40 remote_endpoint: None,
41 batch_size: 100,
42 flush_interval_secs: 300, }
44 }
45}
46
47impl TelemetryConfig {
48 fn default_storage_path() -> PathBuf {
50 dirs::data_local_dir()
51 .unwrap_or_else(|| PathBuf::from("."))
52 .join("voirs")
53 .join("telemetry")
54 }
55
56 pub fn enabled() -> Self {
58 Self {
59 enabled: true,
60 ..Default::default()
61 }
62 }
63
64 pub fn disabled() -> Self {
66 Self {
67 enabled: false,
68 ..Default::default()
69 }
70 }
71
72 pub fn with_level(mut self, level: TelemetryLevel) -> Self {
74 self.level = level;
75 self
76 }
77
78 pub fn with_storage_path(mut self, path: PathBuf) -> Self {
80 self.storage_path = path;
81 self
82 }
83
84 pub fn with_anonymization(mut self, level: AnonymizationLevel) -> Self {
86 self.anonymization = level;
87 self
88 }
89
90 pub fn with_remote_endpoint(mut self, endpoint: String) -> Self {
92 self.remote_endpoint = Some(endpoint);
93 self
94 }
95
96 pub fn validate(&self) -> Result<(), String> {
98 if self.batch_size == 0 {
99 return Err("Batch size must be greater than 0".to_string());
100 }
101
102 if self.flush_interval_secs == 0 {
103 return Err("Flush interval must be greater than 0".to_string());
104 }
105
106 if let Some(ref endpoint) = self.remote_endpoint {
107 if !endpoint.starts_with("http://") && !endpoint.starts_with("https://") {
108 return Err("Remote endpoint must be a valid HTTP(S) URL".to_string());
109 }
110 }
111
112 Ok(())
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
118pub enum TelemetryLevel {
119 Minimal,
121
122 Standard,
124
125 Detailed,
127
128 Debug,
130}
131
132impl TelemetryLevel {
133 pub fn includes_commands(&self) -> bool {
135 matches!(
136 self,
137 TelemetryLevel::Standard | TelemetryLevel::Detailed | TelemetryLevel::Debug
138 )
139 }
140
141 pub fn includes_performance(&self) -> bool {
143 matches!(
144 self,
145 TelemetryLevel::Standard | TelemetryLevel::Detailed | TelemetryLevel::Debug
146 )
147 }
148
149 pub fn includes_debug(&self) -> bool {
151 matches!(self, TelemetryLevel::Debug)
152 }
153
154 pub fn includes_errors(&self) -> bool {
156 true }
158}
159
160impl std::fmt::Display for TelemetryLevel {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 TelemetryLevel::Minimal => write!(f, "minimal"),
164 TelemetryLevel::Standard => write!(f, "standard"),
165 TelemetryLevel::Detailed => write!(f, "detailed"),
166 TelemetryLevel::Debug => write!(f, "debug"),
167 }
168 }
169}
170
171impl std::str::FromStr for TelemetryLevel {
172 type Err = String;
173
174 fn from_str(s: &str) -> Result<Self, Self::Err> {
175 match s.to_lowercase().as_str() {
176 "minimal" | "min" => Ok(TelemetryLevel::Minimal),
177 "standard" | "std" => Ok(TelemetryLevel::Standard),
178 "detailed" | "full" => Ok(TelemetryLevel::Detailed),
179 "debug" | "dbg" => Ok(TelemetryLevel::Debug),
180 _ => Err(format!("Invalid telemetry level: {}", s)),
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_default_config() {
191 let config = TelemetryConfig::default();
192 assert!(!config.enabled);
193 assert_eq!(config.level, TelemetryLevel::Standard);
194 assert!(config.remote_endpoint.is_none());
195 }
196
197 #[test]
198 fn test_enabled_config() {
199 let config = TelemetryConfig::enabled();
200 assert!(config.enabled);
201 }
202
203 #[test]
204 fn test_disabled_config() {
205 let config = TelemetryConfig::disabled();
206 assert!(!config.enabled);
207 }
208
209 #[test]
210 fn test_builder_pattern() {
211 let config = TelemetryConfig::default()
212 .with_level(TelemetryLevel::Detailed)
213 .with_remote_endpoint("https://telemetry.example.com".to_string());
214
215 assert_eq!(config.level, TelemetryLevel::Detailed);
216 assert_eq!(
217 config.remote_endpoint,
218 Some("https://telemetry.example.com".to_string())
219 );
220 }
221
222 #[test]
223 fn test_config_validation() {
224 let config = TelemetryConfig::default();
225 assert!(config.validate().is_ok());
226
227 let invalid_config = TelemetryConfig {
228 batch_size: 0,
229 ..Default::default()
230 };
231 assert!(invalid_config.validate().is_err());
232 }
233
234 #[test]
235 fn test_telemetry_level_includes() {
236 assert!(TelemetryLevel::Minimal.includes_errors());
237 assert!(!TelemetryLevel::Minimal.includes_commands());
238 assert!(!TelemetryLevel::Minimal.includes_debug());
239
240 assert!(TelemetryLevel::Standard.includes_commands());
241 assert!(TelemetryLevel::Standard.includes_performance());
242 assert!(!TelemetryLevel::Standard.includes_debug());
243
244 assert!(TelemetryLevel::Debug.includes_debug());
245 }
246
247 #[test]
248 fn test_telemetry_level_from_str() {
249 assert_eq!(
250 "minimal".parse::<TelemetryLevel>().unwrap(),
251 TelemetryLevel::Minimal
252 );
253 assert_eq!(
254 "standard".parse::<TelemetryLevel>().unwrap(),
255 TelemetryLevel::Standard
256 );
257 assert_eq!(
258 "detailed".parse::<TelemetryLevel>().unwrap(),
259 TelemetryLevel::Detailed
260 );
261 assert_eq!(
262 "debug".parse::<TelemetryLevel>().unwrap(),
263 TelemetryLevel::Debug
264 );
265 assert!("invalid".parse::<TelemetryLevel>().is_err());
266 }
267
268 #[test]
269 fn test_telemetry_level_display() {
270 assert_eq!(TelemetryLevel::Minimal.to_string(), "minimal");
271 assert_eq!(TelemetryLevel::Standard.to_string(), "standard");
272 assert_eq!(TelemetryLevel::Detailed.to_string(), "detailed");
273 assert_eq!(TelemetryLevel::Debug.to_string(), "debug");
274 }
275}