//! Account profile data structures and validation. //! //! Defines `AccountProfile` and `AccountsConfig` for multi-account support. //! Account names are validated to contain only alphanumeric characters, hyphens, and underscores. use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// Configuration for all accounts, stored in `~/.config/tele-tui/accounts.toml`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccountsConfig { /// Name of the default account to use when no `--account` flag is provided. pub default_account: String, /// List of configured accounts. pub accounts: Vec, } /// A single account profile. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AccountProfile { /// Unique identifier (used in directory names and CLI flag). pub name: String, /// Human-readable display name. pub display_name: String, } impl AccountsConfig { /// Creates a default config with a single "default" account. pub fn default_single() -> Self { Self { default_account: "default".to_string(), accounts: vec![AccountProfile { name: "default".to_string(), display_name: "Default".to_string(), }], } } /// Finds an account by name. pub fn find_account(&self, name: &str) -> Option<&AccountProfile> { self.accounts.iter().find(|a| a.name == name) } } impl AccountProfile { /// Computes the TDLib database directory path for this account. /// /// Returns `~/.local/share/tele-tui/accounts/{name}/tdlib_data` /// (or platform equivalent via `dirs::data_dir()`). pub fn db_path(&self) -> PathBuf { account_db_path(&self.name) } } /// Computes the TDLib database directory path for a given account name. /// /// Returns `{data_dir}/tele-tui/accounts/{name}/tdlib_data`. pub fn account_db_path(account_name: &str) -> PathBuf { let mut path = dirs::data_dir().unwrap_or_else(|| PathBuf::from(".")); path.push("tele-tui"); path.push("accounts"); path.push(account_name); path.push("tdlib_data"); path } /// Validates an account name. /// /// Valid names contain only lowercase alphanumeric characters, hyphens, and underscores. /// Must be 1-32 characters long. /// /// # Errors /// /// Returns a descriptive error message if the name is invalid. pub fn validate_account_name(name: &str) -> Result<(), String> { if name.is_empty() { return Err("Account name cannot be empty".to_string()); } if name.len() > 32 { return Err("Account name cannot be longer than 32 characters".to_string()); } if !name .chars() .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_') { return Err( "Account name can only contain lowercase letters, digits, hyphens, and underscores" .to_string(), ); } if name.starts_with('-') || name.starts_with('_') { return Err("Account name cannot start with a hyphen or underscore".to_string()); } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_validate_account_name_valid() { assert!(validate_account_name("default").is_ok()); assert!(validate_account_name("work").is_ok()); assert!(validate_account_name("my-account").is_ok()); assert!(validate_account_name("account_2").is_ok()); assert!(validate_account_name("a").is_ok()); } #[test] fn test_validate_account_name_invalid() { assert!(validate_account_name("").is_err()); assert!(validate_account_name("My Account").is_err()); assert!(validate_account_name("UPPER").is_err()); assert!(validate_account_name("with spaces").is_err()); assert!(validate_account_name("-starts-with-dash").is_err()); assert!(validate_account_name("_starts-with-underscore").is_err()); assert!(validate_account_name(&"a".repeat(33)).is_err()); } #[test] fn test_default_single_config() { let config = AccountsConfig::default_single(); assert_eq!(config.default_account, "default"); assert_eq!(config.accounts.len(), 1); assert_eq!(config.accounts[0].name, "default"); } #[test] fn test_find_account() { let config = AccountsConfig::default_single(); assert!(config.find_account("default").is_some()); assert!(config.find_account("nonexistent").is_none()); } #[test] fn test_db_path_contains_account_name() { let path = account_db_path("work"); let path_str = path.to_string_lossy(); assert!(path_str.contains("tele-tui")); assert!(path_str.contains("accounts")); assert!(path_str.contains("work")); assert!(path_str.ends_with("tdlib_data")); } }