refactor: implement newtype pattern for IDs (P2.4)

Добавлены типобезопасные обёртки ChatId, MessageId, UserId для предотвращения
смешивания разных типов идентификаторов на этапе компиляции.

Изменения:
- Создан src/types.rs с тремя newtype структурами
- Реализованы методы: new(), as_i64(), From<i64>, Display
- Добавлены traits: Hash, Eq, Serialize, Deserialize
- Обновлены 15+ модулей для использования новых типов:
  * tdlib: types.rs, chats.rs, messages.rs, users.rs, reactions.rs, client.rs
  * app: mod.rs, chat_state.rs
  * input: main_input.rs
  * tests: app_builder.rs, test_data.rs
- Исправлены 53 ошибки компиляции связанные с type conversions

Преимущества:
- Компилятор предотвращает смешивание разных типов ID
- Улучшенная читаемость кода (явные типы вместо i64)
- Самодокументирующиеся типы

Статус: Priority 2 теперь 60% (3/5 задач)
-  Error enum
-  Config validation
-  Newtype для ID
-  MessageInfo реструктуризация
-  MessageBuilder pattern

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-01-31 01:33:18 +03:00
parent 38e73befc1
commit 7081a886ad
15 changed files with 458 additions and 177 deletions

View File

@@ -1,4 +1,5 @@
use crate::constants::{MAX_MESSAGES_IN_CHAT, TDLIB_MESSAGE_LIMIT};
use crate::types::{ChatId, MessageId};
use tdlib_rs::enums::{ChatAction, InputMessageContent, InputMessageReplyTo, MessageContent, MessageSender, SearchMessagesFilter, TextParseMode};
use tdlib_rs::functions;
use tdlib_rs::types::{Chat as TdChat, FormattedText, InputMessageReplyToMessage, InputMessageText, Message as TdMessage, TextEntity, TextParseModeMarkdown};
@@ -8,10 +9,10 @@ use super::types::{ForwardInfo, MessageInfo, ReactionInfo, ReplyInfo};
/// Менеджер сообщений
pub struct MessageManager {
pub current_chat_messages: Vec<MessageInfo>,
pub current_chat_id: Option<i64>,
pub current_chat_id: Option<ChatId>,
pub current_pinned_message: Option<MessageInfo>,
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
pub pending_view_messages: Vec<(i64, Vec<i64>)>,
pub pending_view_messages: Vec<(ChatId, Vec<MessageId>)>,
client_id: i32,
}
@@ -39,14 +40,14 @@ impl MessageManager {
/// Получить историю чата
pub async fn get_chat_history(
&mut self,
chat_id: i64,
chat_id: ChatId,
limit: i32,
) -> Result<Vec<MessageInfo>, String> {
// Устанавливаем текущий чат для получения новых сообщений
self.current_chat_id = Some(chat_id);
let result = functions::get_chat_history(
chat_id,
chat_id.as_i64(),
0, // from_message_id
0, // offset
limit,
@@ -75,12 +76,12 @@ impl MessageManager {
/// Загрузить более старые сообщения
pub async fn load_older_messages(
&mut self,
chat_id: i64,
from_message_id: i64,
chat_id: ChatId,
from_message_id: MessageId,
) -> Result<Vec<MessageInfo>, String> {
let result = functions::get_chat_history(
chat_id,
from_message_id,
chat_id.as_i64(),
from_message_id.as_i64(),
0, // offset
TDLIB_MESSAGE_LIMIT,
false,
@@ -106,9 +107,9 @@ impl MessageManager {
}
/// Получить закреплённые сообщения
pub async fn get_pinned_messages(&mut self, chat_id: i64) -> Result<Vec<MessageInfo>, String> {
pub async fn get_pinned_messages(&mut self, chat_id: ChatId) -> Result<Vec<MessageInfo>, String> {
let result = functions::search_chat_messages(
chat_id,
chat_id.as_i64(),
String::new(),
None,
0, // from_message_id
@@ -137,7 +138,7 @@ impl MessageManager {
}
/// Загрузить текущее закреплённое сообщение
pub async fn load_current_pinned_message(&mut self, chat_id: i64) {
pub async fn load_current_pinned_message(&mut self, chat_id: ChatId) {
// TODO: В tdlib-rs 1.8.29 поле pinned_message_id было удалено из Chat.
// Нужно использовать getChatPinnedMessage или альтернативный способ.
// Временно отключено.
@@ -155,11 +156,11 @@ impl MessageManager {
/// Поиск сообщений в чате
pub async fn search_messages(
&self,
chat_id: i64,
chat_id: ChatId,
query: &str,
) -> Result<Vec<MessageInfo>, String> {
let result = functions::search_chat_messages(
chat_id,
chat_id.as_i64(),
query.to_string(),
None,
0, // from_message_id
@@ -190,9 +191,9 @@ impl MessageManager {
/// Отправить сообщение
pub async fn send_message(
&self,
chat_id: i64,
chat_id: ChatId,
text: String,
reply_to_message_id: Option<i64>,
reply_to_message_id: Option<MessageId>,
_reply_info: Option<ReplyInfo>,
) -> Result<MessageInfo, String> {
// Парсим markdown в тексте
@@ -224,13 +225,13 @@ impl MessageManager {
let reply_to = reply_to_message_id.map(|msg_id| {
InputMessageReplyTo::Message(InputMessageReplyToMessage {
chat_id: 0,
message_id: msg_id,
message_id: msg_id.as_i64(),
quote: None,
})
});
let result = functions::send_message(
chat_id,
chat_id.as_i64(),
0, // message_thread_id
reply_to,
None, // options
@@ -252,8 +253,8 @@ impl MessageManager {
/// Редактировать сообщение
pub async fn edit_message(
&self,
chat_id: i64,
message_id: i64,
chat_id: ChatId,
message_id: MessageId,
text: String,
) -> Result<MessageInfo, String> {
let formatted_text = match functions::parse_text_entities(
@@ -282,7 +283,7 @@ impl MessageManager {
});
let result =
functions::edit_message_text(chat_id, message_id, content, self.client_id).await;
functions::edit_message_text(chat_id.as_i64(), message_id.as_i64(), content, self.client_id).await;
match result {
Ok(tdlib_rs::enums::Message::Message(msg)) => self
@@ -297,12 +298,13 @@ impl MessageManager {
/// Удалить сообщения
pub async fn delete_messages(
&self,
chat_id: i64,
message_ids: Vec<i64>,
chat_id: ChatId,
message_ids: Vec<MessageId>,
revoke: bool,
) -> Result<(), String> {
let message_ids_i64: Vec<i64> = message_ids.into_iter().map(|id| id.as_i64()).collect();
let result =
functions::delete_messages(chat_id, message_ids, revoke, self.client_id).await;
functions::delete_messages(chat_id.as_i64(), message_ids_i64, revoke, self.client_id).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка удаления: {:?}", e)),
@@ -312,15 +314,16 @@ impl MessageManager {
/// Переслать сообщения
pub async fn forward_messages(
&self,
to_chat_id: i64,
from_chat_id: i64,
message_ids: Vec<i64>,
to_chat_id: ChatId,
from_chat_id: ChatId,
message_ids: Vec<MessageId>,
) -> Result<(), String> {
let message_ids_i64: Vec<i64> = message_ids.into_iter().map(|id| id.as_i64()).collect();
let result = functions::forward_messages(
to_chat_id,
to_chat_id.as_i64(),
0, // message_thread_id
from_chat_id,
message_ids,
from_chat_id.as_i64(),
message_ids_i64,
None, // options
false, // send_copy
false, // remove_caption
@@ -335,7 +338,7 @@ impl MessageManager {
}
/// Установить черновик
pub async fn set_draft_message(&self, chat_id: i64, text: String) -> Result<(), String> {
pub async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String> {
use tdlib_rs::types::DraftMessage;
let draft = if text.is_empty() {
@@ -355,7 +358,7 @@ impl MessageManager {
})
};
let result = functions::set_chat_draft_message(chat_id, 0, draft, self.client_id).await;
let result = functions::set_chat_draft_message(chat_id.as_i64(), 0, draft, self.client_id).await;
match result {
Ok(_) => Ok(()),