Files
telegram-tui/src/accounts/profile.rs
Mikhail Kilin 8bd08318bb
Some checks failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
fixes
2026-02-14 17:57:37 +03:00

148 lines
4.8 KiB
Rust

//! 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<AccountProfile>,
}
/// 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"));
}
}