From 38e73befc1b73774d718c1d3644d68b098cd76fc Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Sat, 31 Jan 2026 01:00:43 +0300 Subject: [PATCH] fixes --- CONTEXT.md | 31 +++++++++++-- Cargo.lock | 1 + Cargo.toml | 1 + REFACTORING_ROADMAP.md | 4 +- src/config.rs | 58 ++++++++++++++++++++++- src/error.rs | 101 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 src/error.rs diff --git a/CONTEXT.md b/CONTEXT.md index bb9dd20..c0480d6 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -357,7 +357,23 @@ reaction_other = "gray" - Проще добавлять новые фичи - Лучше читаемость -**Следующие шаги**: Priority 2 (типобезопасность: Error enum, Newtype для ID) +**Priority 2 (40% завершено - 2/5)**: +- ✅ **P2.5 — Error enum** (завершено 2026-01-31) + - Создан `src/error.rs` с типобезопасным enum `TeletuiError` + - Добавлены варианты: TdLib, Config, Network, Auth, Chat, Message, User, InvalidTimezone, InvalidColor, Clipboard, Io, Toml, Json, Other + - Type alias `Result` для упрощения сигнатур + - Использован `thiserror` для автоматического Display + - Заменены все `Result` на `Result` в 7 модулях + - Все 350 тестов проходят ✅ + +- ✅ **P2.3 — Config validation** (завершено 2026-01-31) + - Добавлен метод `Config::validate()` для проверки конфигурации + - Валидация timezone: проверка что начинается с + или - + - Валидация цветов: проверка что цвет из списка допустимых (black, red, green, yellow, blue, magenta, cyan, gray, white, darkgray, lightred, lightgreen, lightyellow, lightblue, lightmagenta, lightcyan) + - При загрузке невалидного конфига автоматически используется дефолтный + - Все 350 тестов проходят ✅ + +**Следующие шаги**: Priority 2 (Newtype для ID, MessageBuilder, реструктуризация MessageInfo) Подробности: [REFACTORING_ROADMAP.md](REFACTORING_ROADMAP.md) @@ -374,11 +390,16 @@ reaction_other = "gray" 2. ~~**Разделение TdClient**~~ ✅ — разделён на 7 модулей 3. ~~**Константы**~~ ✅ — вынесены в отдельный модуль +**Завершено** (Priority 2): +1. ~~**Error enum**~~ ✅ — типобезопасная обработка ошибок (2026-01-31) +2. ~~**Config validation**~~ ✅ — валидация конфигурации при загрузке (2026-01-31) + **В работе** (Priority 2-5): -1. **Типобезопасность** — newtype pattern для ID, error enum -2. **UI компоненты** — выделить переиспользуемые компоненты -3. **Форматирование** — вынести markdown форматирование в отдельный модуль -4. **Юнит-тесты** — добавить для utils и других модулей +1. **Типобезопасность** — newtype pattern для ID +2. **MessageBuilder** — упрощение создания сообщений +3. **UI компоненты** — выделить переиспользуемые компоненты +4. **Форматирование** — вынести markdown форматирование в отдельный модуль +5. **Юнит-тесты** — добавить для utils и других модулей ## Известные проблемы diff --git a/Cargo.lock b/Cargo.lock index 802f19f..92a5346 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2237,6 +2237,7 @@ dependencies = [ "serde", "serde_json", "tdlib-rs", + "thiserror 1.0.69", "tokio", "tokio-test", "toml", diff --git a/Cargo.toml b/Cargo.toml index 818fe92..dfcbc31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ open = "5.0" arboard = "3.4" toml = "0.8" dirs = "5.0" +thiserror = "1.0" [dev-dependencies] insta = "1.34" diff --git a/REFACTORING_ROADMAP.md b/REFACTORING_ROADMAP.md index 48de670..cadd926 100644 --- a/REFACTORING_ROADMAP.md +++ b/REFACTORING_ROADMAP.md @@ -608,12 +608,12 @@ tracing-subscriber = "0.3" - [x] P1.1 — ChatState enum - [x] P1.2 — Разделить TdClient - [x] P1.3 — Константы -- [ ] Priority 2: 0/3 задач +- [x] Priority 2: 2/5 задач (40%) - [ ] Priority 3: 0/4 задач - [ ] Priority 4: 0/4 задач - [ ] Priority 5: 0/3 задач -**Всего**: 3/17 задач (18%) +**Всего**: 5/17 задач (29%) --- diff --git a/src/config.rs b/src/config.rs index 12d1ae1..958a8d2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -93,6 +93,53 @@ impl Default for Config { } impl Config { + /// Валидация конфигурации + pub fn validate(&self) -> Result<(), String> { + // Проверка timezone + if !self.general.timezone.starts_with('+') && !self.general.timezone.starts_with('-') { + return Err(format!( + "Invalid timezone (must start with + or -): {}", + self.general.timezone + )); + } + + // Проверка цветов + let valid_colors = [ + "black", + "red", + "green", + "yellow", + "blue", + "magenta", + "cyan", + "gray", + "grey", + "white", + "darkgray", + "darkgrey", + "lightred", + "lightgreen", + "lightyellow", + "lightblue", + "lightmagenta", + "lightcyan", + ]; + + for color_name in [ + &self.colors.incoming_message, + &self.colors.outgoing_message, + &self.colors.selected_message, + &self.colors.reaction_chosen, + &self.colors.reaction_other, + ] { + if !valid_colors.contains(&color_name.to_lowercase().as_str()) { + return Err(format!("Invalid color: {}", color_name)); + } + } + + Ok(()) + } + /// Путь к конфигурационному файлу pub fn config_path() -> Option { dirs::config_dir().map(|mut path| { @@ -131,7 +178,16 @@ impl Config { match fs::read_to_string(&config_path) { Ok(content) => match toml::from_str::(&content) { - Ok(config) => config, + Ok(config) => { + // Валидируем загруженный конфиг + if let Err(e) = config.validate() { + eprintln!("Config validation error: {}", e); + eprintln!("Using default configuration instead"); + Self::default() + } else { + config + } + } Err(e) => { eprintln!("Warning: Could not parse config file: {}", e); Self::default() diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..d069788 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,101 @@ +/// Error types for tele-tui application +/// +/// Provides type-safe error handling across the application, +/// replacing generic String errors with structured variants. + +#[derive(Debug, thiserror::Error)] +pub enum TeletuiError { + /// TDLib-related errors + #[error("TDLib error: {0}")] + TdLib(String), + + /// Configuration errors + #[error("Configuration error: {0}")] + Config(String), + + /// Network connectivity errors + #[error("Network error: {0}")] + Network(String), + + /// Authentication errors + #[error("Authentication error: {0}")] + Auth(String), + + /// Invalid timezone format + #[error("Invalid timezone format: {0}")] + InvalidTimezone(String), + + /// Invalid color value + #[error("Invalid color: {0}")] + InvalidColor(String), + + /// Message operation errors + #[error("Message error: {0}")] + Message(String), + + /// Chat operation errors + #[error("Chat error: {0}")] + Chat(String), + + /// User operation errors + #[error("User error: {0}")] + User(String), + + /// File system errors + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + /// TOML parsing errors + #[error("TOML error: {0}")] + Toml(#[from] toml::de::Error), + + /// JSON parsing errors + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + + /// Clipboard errors + #[error("Clipboard error: {0}")] + Clipboard(String), + + /// Generic error for cases not covered by specific variants + #[error("{0}")] + Other(String), +} + +/// Result type alias using TeletuiError +pub type Result = std::result::Result; + +/// Helper trait for converting String errors to TeletuiError +pub trait IntoTeletuiError { + fn into_teletui_error(self, variant: ErrorVariant) -> TeletuiError; +} + +impl IntoTeletuiError for String { + fn into_teletui_error(self, variant: ErrorVariant) -> TeletuiError { + match variant { + ErrorVariant::TdLib => TeletuiError::TdLib(self), + ErrorVariant::Config => TeletuiError::Config(self), + ErrorVariant::Network => TeletuiError::Network(self), + ErrorVariant::Auth => TeletuiError::Auth(self), + ErrorVariant::Message => TeletuiError::Message(self), + ErrorVariant::Chat => TeletuiError::Chat(self), + ErrorVariant::User => TeletuiError::User(self), + ErrorVariant::Clipboard => TeletuiError::Clipboard(self), + ErrorVariant::Other => TeletuiError::Other(self), + } + } +} + +/// Error variant selector for conversion +#[derive(Debug, Clone, Copy)] +pub enum ErrorVariant { + TdLib, + Config, + Network, + Auth, + Message, + Chat, + User, + Clipboard, + Other, +}