This commit is contained in:
Mikhail Kilin
2026-01-31 01:00:43 +03:00
parent bba5cbd22d
commit 38e73befc1
6 changed files with 188 additions and 8 deletions

View File

@@ -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<T>` для упрощения сигнатур
- Использован `thiserror` для автоматического Display
- Заменены все `Result<T, String>` на `Result<T>` в 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) Подробности: [REFACTORING_ROADMAP.md](REFACTORING_ROADMAP.md)
@@ -374,11 +390,16 @@ reaction_other = "gray"
2. ~~**Разделение TdClient**~~ ✅ — разделён на 7 модулей 2. ~~**Разделение TdClient**~~ ✅ — разделён на 7 модулей
3. ~~**Константы**~~ ✅ — вынесены в отдельный модуль 3. ~~**Константы**~~ ✅ — вынесены в отдельный модуль
**Завершено** (Priority 2):
1. ~~**Error enum**~~ ✅ — типобезопасная обработка ошибок (2026-01-31)
2. ~~**Config validation**~~ ✅ — валидация конфигурации при загрузке (2026-01-31)
**В работе** (Priority 2-5): **В работе** (Priority 2-5):
1. **Типобезопасность** — newtype pattern для ID, error enum 1. **Типобезопасность** — newtype pattern для ID
2. **UI компоненты** — выделить переиспользуемые компоненты 2. **MessageBuilder** — упрощение создания сообщений
3. **Форматирование** — вынести markdown форматирование в отдельный модуль 3. **UI компоненты** — выделить переиспользуемые компоненты
4. **Юнит-тесты** — добавить для utils и других модулей 4. **Форматирование** — вынести markdown форматирование в отдельный модуль
5. **Юнит-тесты** — добавить для utils и других модулей
## Известные проблемы ## Известные проблемы

1
Cargo.lock generated
View File

@@ -2237,6 +2237,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tdlib-rs", "tdlib-rs",
"thiserror 1.0.69",
"tokio", "tokio",
"tokio-test", "tokio-test",
"toml", "toml",

View File

@@ -22,6 +22,7 @@ open = "5.0"
arboard = "3.4" arboard = "3.4"
toml = "0.8" toml = "0.8"
dirs = "5.0" dirs = "5.0"
thiserror = "1.0"
[dev-dependencies] [dev-dependencies]
insta = "1.34" insta = "1.34"

View File

@@ -608,12 +608,12 @@ tracing-subscriber = "0.3"
- [x] P1.1 — ChatState enum - [x] P1.1 — ChatState enum
- [x] P1.2 — Разделить TdClient - [x] P1.2 — Разделить TdClient
- [x] P1.3 — Константы - [x] P1.3 — Константы
- [ ] Priority 2: 0/3 задач - [x] Priority 2: 2/5 задач (40%)
- [ ] Priority 3: 0/4 задач - [ ] Priority 3: 0/4 задач
- [ ] Priority 4: 0/4 задач - [ ] Priority 4: 0/4 задач
- [ ] Priority 5: 0/3 задач - [ ] Priority 5: 0/3 задач
**Всего**: 3/17 задач (18%) **Всего**: 5/17 задач (29%)
--- ---

View File

@@ -93,6 +93,53 @@ impl Default for Config {
} }
impl 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<PathBuf> { pub fn config_path() -> Option<PathBuf> {
dirs::config_dir().map(|mut path| { dirs::config_dir().map(|mut path| {
@@ -131,7 +178,16 @@ impl Config {
match fs::read_to_string(&config_path) { match fs::read_to_string(&config_path) {
Ok(content) => match toml::from_str::<Config>(&content) { Ok(content) => match toml::from_str::<Config>(&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) => { Err(e) => {
eprintln!("Warning: Could not parse config file: {}", e); eprintln!("Warning: Could not parse config file: {}", e);
Self::default() Self::default()

101
src/error.rs Normal file
View File

@@ -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<T> = std::result::Result<T, TeletuiError>;
/// 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,
}