use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelemetryConfig {
pub enabled: bool,
pub db_path: PathBuf,
pub privacy: PrivacyConfig,
pub community_contribution: bool,
pub batch_size: usize,
pub flush_interval_secs: u64,
pub max_db_size_mb: u64,
pub retention_days: u32,
pub enable_aggregation: bool,
pub aggregation_interval_hours: u32,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
enabled: false, db_path: PathBuf::from(".rk_telemetry.db"),
privacy: PrivacyConfig::default(),
community_contribution: false, batch_size: 100,
flush_interval_secs: 60,
max_db_size_mb: 100, retention_days: 90, enable_aggregation: true,
aggregation_interval_hours: 24,
}
}
}
impl TelemetryConfig {
pub fn minimal() -> Self {
Self {
enabled: true,
db_path: PathBuf::from(":memory:"),
privacy: PrivacyConfig::strict(),
community_contribution: false,
batch_size: 10,
flush_interval_secs: 5,
max_db_size_mb: 0,
retention_days: 0,
enable_aggregation: false,
aggregation_interval_hours: 24,
}
}
pub fn production() -> Self {
Self {
enabled: true,
db_path: Self::default_db_path(),
privacy: PrivacyConfig::default(),
community_contribution: false,
batch_size: 100,
flush_interval_secs: 60,
max_db_size_mb: 500,
retention_days: 365,
enable_aggregation: true,
aggregation_interval_hours: 24,
}
}
pub fn default_db_path() -> PathBuf {
if let Some(data_dir) = dirs::data_local_dir() {
data_dir.join("reasonkit").join(".rk_telemetry.db")
} else {
PathBuf::from(".rk_telemetry.db")
}
}
pub fn from_env() -> Self {
let mut config = Self::default();
if let Ok(val) = std::env::var("RK_TELEMETRY_ENABLED") {
config.enabled = val.to_lowercase() == "true" || val == "1";
}
if let Ok(path) = std::env::var("RK_TELEMETRY_PATH") {
config.db_path = PathBuf::from(path);
}
if let Ok(val) = std::env::var("RK_TELEMETRY_COMMUNITY") {
config.community_contribution = val.to_lowercase() == "true" || val == "1";
}
config
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivacyConfig {
pub strip_pii: bool,
pub block_sensitive: bool,
pub differential_privacy: bool,
pub dp_epsilon: f64,
pub redact_file_paths: bool,
}
impl Default for PrivacyConfig {
fn default() -> Self {
Self {
strip_pii: true,
block_sensitive: false,
differential_privacy: false,
dp_epsilon: 1.0,
redact_file_paths: true,
}
}
}
impl PrivacyConfig {
pub fn strict() -> Self {
Self {
strip_pii: true,
block_sensitive: true,
differential_privacy: true,
dp_epsilon: 0.1, redact_file_paths: true,
}
}
pub fn relaxed() -> Self {
Self {
strip_pii: true,
block_sensitive: false,
differential_privacy: false,
dp_epsilon: 1.0,
redact_file_paths: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsentRecord {
pub id: uuid::Uuid,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub local_telemetry: bool,
pub aggregated_sharing: bool,
pub community_contribution: bool,
pub consent_version: u32,
pub ip_hash: Option<String>,
}
impl ConsentRecord {
pub const CURRENT_VERSION: u32 = 1;
pub fn allow_all() -> Self {
Self {
id: uuid::Uuid::new_v4(),
timestamp: chrono::Utc::now(),
local_telemetry: true,
aggregated_sharing: true,
community_contribution: true,
consent_version: Self::CURRENT_VERSION,
ip_hash: None,
}
}
pub fn minimal() -> Self {
Self {
id: uuid::Uuid::new_v4(),
timestamp: chrono::Utc::now(),
local_telemetry: true,
aggregated_sharing: false,
community_contribution: false,
consent_version: Self::CURRENT_VERSION,
ip_hash: None,
}
}
pub fn deny_all() -> Self {
Self {
id: uuid::Uuid::new_v4(),
timestamp: chrono::Utc::now(),
local_telemetry: false,
aggregated_sharing: false,
community_contribution: false,
consent_version: Self::CURRENT_VERSION,
ip_hash: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_disabled() {
let config = TelemetryConfig::default();
assert!(!config.enabled); }
#[test]
fn test_strict_privacy() {
let privacy = PrivacyConfig::strict();
assert!(privacy.strip_pii);
assert!(privacy.block_sensitive);
assert!(privacy.differential_privacy);
assert!(privacy.dp_epsilon < 1.0); }
#[test]
fn test_consent_versions() {
let consent = ConsentRecord::allow_all();
assert_eq!(consent.consent_version, ConsentRecord::CURRENT_VERSION);
}
#[test]
fn test_from_env() {
let config = TelemetryConfig::from_env();
assert!(!config.db_path.as_os_str().is_empty());
}
}