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:
@@ -1,3 +1,4 @@
|
||||
use crate::types::{ChatId, MessageId, UserId};
|
||||
use std::env;
|
||||
use std::time::Instant;
|
||||
use tdlib_rs::enums::{
|
||||
@@ -82,15 +83,15 @@ impl TdClient {
|
||||
self.chat_manager.load_folder_chats(folder_id, limit).await
|
||||
}
|
||||
|
||||
pub async fn leave_chat(&self, chat_id: i64) -> Result<(), String> {
|
||||
pub async fn leave_chat(&self, chat_id: ChatId) -> Result<(), String> {
|
||||
self.chat_manager.leave_chat(chat_id).await
|
||||
}
|
||||
|
||||
pub async fn get_profile_info(&self, chat_id: i64) -> Result<ProfileInfo, String> {
|
||||
pub async fn get_profile_info(&self, chat_id: ChatId) -> Result<ProfileInfo, String> {
|
||||
self.chat_manager.get_profile_info(chat_id).await
|
||||
}
|
||||
|
||||
pub async fn send_chat_action(&self, chat_id: i64, action: tdlib_rs::enums::ChatAction) {
|
||||
pub async fn send_chat_action(&self, chat_id: ChatId, action: tdlib_rs::enums::ChatAction) {
|
||||
self.chat_manager.send_chat_action(chat_id, action).await
|
||||
}
|
||||
|
||||
@@ -105,7 +106,7 @@ impl TdClient {
|
||||
// Делегирование к message_manager
|
||||
pub async fn get_chat_history(
|
||||
&mut self,
|
||||
chat_id: i64,
|
||||
chat_id: ChatId,
|
||||
limit: i32,
|
||||
) -> Result<Vec<MessageInfo>, String> {
|
||||
self.message_manager.get_chat_history(chat_id, limit).await
|
||||
@@ -113,25 +114,25 @@ impl TdClient {
|
||||
|
||||
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> {
|
||||
self.message_manager
|
||||
.load_older_messages(chat_id, from_message_id)
|
||||
.await
|
||||
}
|
||||
|
||||
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> {
|
||||
self.message_manager.get_pinned_messages(chat_id).await
|
||||
}
|
||||
|
||||
pub async fn load_current_pinned_message(&mut self, chat_id: i64) {
|
||||
pub async fn load_current_pinned_message(&mut self, chat_id: ChatId) {
|
||||
self.message_manager.load_current_pinned_message(chat_id).await
|
||||
}
|
||||
|
||||
pub async fn search_messages(
|
||||
&self,
|
||||
chat_id: i64,
|
||||
chat_id: ChatId,
|
||||
query: &str,
|
||||
) -> Result<Vec<MessageInfo>, String> {
|
||||
self.message_manager.search_messages(chat_id, query).await
|
||||
@@ -139,9 +140,9 @@ impl TdClient {
|
||||
|
||||
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<super::types::ReplyInfo>,
|
||||
) -> Result<MessageInfo, String> {
|
||||
self.message_manager
|
||||
@@ -151,8 +152,8 @@ impl TdClient {
|
||||
|
||||
pub async fn edit_message(
|
||||
&self,
|
||||
chat_id: i64,
|
||||
message_id: i64,
|
||||
chat_id: ChatId,
|
||||
message_id: MessageId,
|
||||
text: String,
|
||||
) -> Result<MessageInfo, String> {
|
||||
self.message_manager
|
||||
@@ -162,8 +163,8 @@ impl TdClient {
|
||||
|
||||
pub async fn delete_messages(
|
||||
&self,
|
||||
chat_id: i64,
|
||||
message_ids: Vec<i64>,
|
||||
chat_id: ChatId,
|
||||
message_ids: Vec<MessageId>,
|
||||
revoke: bool,
|
||||
) -> Result<(), String> {
|
||||
self.message_manager
|
||||
@@ -173,16 +174,16 @@ impl TdClient {
|
||||
|
||||
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> {
|
||||
self.message_manager
|
||||
.forward_messages(to_chat_id, from_chat_id, message_ids)
|
||||
.await
|
||||
}
|
||||
|
||||
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> {
|
||||
self.message_manager.set_draft_message(chat_id, text).await
|
||||
}
|
||||
|
||||
@@ -199,11 +200,11 @@ impl TdClient {
|
||||
}
|
||||
|
||||
// Делегирование к user_cache
|
||||
pub async fn get_user_name(&self, user_id: i64) -> String {
|
||||
pub async fn get_user_name(&self, user_id: UserId) -> String {
|
||||
self.user_cache.get_user_name(user_id).await
|
||||
}
|
||||
|
||||
pub fn get_user_status_by_chat_id(&self, chat_id: i64) -> Option<&UserOnlineStatus> {
|
||||
pub fn get_user_status_by_chat_id(&self, chat_id: ChatId) -> Option<&UserOnlineStatus> {
|
||||
self.user_cache.get_status_by_chat_id(chat_id)
|
||||
}
|
||||
|
||||
@@ -214,8 +215,8 @@ impl TdClient {
|
||||
// Делегирование к reaction_manager
|
||||
pub async fn get_message_available_reactions(
|
||||
&self,
|
||||
chat_id: i64,
|
||||
message_id: i64,
|
||||
chat_id: ChatId,
|
||||
message_id: MessageId,
|
||||
) -> Result<Vec<String>, String> {
|
||||
self.reaction_manager
|
||||
.get_message_available_reactions(chat_id, message_id)
|
||||
@@ -224,8 +225,8 @@ impl TdClient {
|
||||
|
||||
pub async fn toggle_reaction(
|
||||
&self,
|
||||
chat_id: i64,
|
||||
message_id: i64,
|
||||
chat_id: ChatId,
|
||||
message_id: MessageId,
|
||||
emoji: String,
|
||||
) -> Result<(), String> {
|
||||
self.reaction_manager
|
||||
@@ -275,11 +276,11 @@ impl TdClient {
|
||||
&mut self.message_manager.current_chat_messages
|
||||
}
|
||||
|
||||
pub fn current_chat_id(&self) -> Option<i64> {
|
||||
pub fn current_chat_id(&self) -> Option<ChatId> {
|
||||
self.message_manager.current_chat_id
|
||||
}
|
||||
|
||||
pub fn set_current_chat_id(&mut self, chat_id: Option<i64>) {
|
||||
pub fn set_current_chat_id(&mut self, chat_id: Option<ChatId>) {
|
||||
self.message_manager.current_chat_id = chat_id;
|
||||
}
|
||||
|
||||
@@ -371,7 +372,7 @@ impl TdClient {
|
||||
self.add_or_update_chat(&td_chat);
|
||||
}
|
||||
Update::ChatLastMessage(update) => {
|
||||
let chat_id = update.chat_id;
|
||||
let chat_id = ChatId::new(update.chat_id);
|
||||
let (last_message_text, last_message_date) = update
|
||||
.last_message
|
||||
.as_ref()
|
||||
@@ -397,30 +398,31 @@ impl TdClient {
|
||||
self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order));
|
||||
}
|
||||
Update::ChatReadInbox(update) => {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||
chat.unread_count = update.unread_count;
|
||||
}
|
||||
}
|
||||
Update::ChatUnreadMentionCount(update) => {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||
chat.unread_mention_count = update.unread_mention_count;
|
||||
}
|
||||
}
|
||||
Update::ChatNotificationSettings(update) => {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||
// mute_for > 0 означает что чат замьючен
|
||||
chat.is_muted = update.notification_settings.mute_for > 0;
|
||||
}
|
||||
}
|
||||
Update::ChatReadOutbox(update) => {
|
||||
// Обновляем last_read_outbox_message_id когда собеседник прочитал сообщения
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
||||
chat.last_read_outbox_message_id = update.last_read_outbox_message_id;
|
||||
let last_read_msg_id = MessageId::new(update.last_read_outbox_message_id);
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||
chat.last_read_outbox_message_id = last_read_msg_id;
|
||||
}
|
||||
// Если это текущий открытый чат — обновляем is_read у сообщений
|
||||
if Some(update.chat_id) == self.current_chat_id() {
|
||||
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
|
||||
for msg in self.current_chat_messages_mut().iter_mut() {
|
||||
if msg.is_outgoing && msg.id <= update.last_read_outbox_message_id {
|
||||
if msg.is_outgoing && msg.id <= last_read_msg_id {
|
||||
msg.is_read = true;
|
||||
}
|
||||
}
|
||||
@@ -428,13 +430,14 @@ impl TdClient {
|
||||
}
|
||||
Update::ChatPosition(update) => {
|
||||
// Обновляем позицию чата или удаляем его из списка
|
||||
let chat_id = ChatId::new(update.chat_id);
|
||||
match &update.position.list {
|
||||
ChatList::Main => {
|
||||
if update.position.order == 0 {
|
||||
// Чат больше не в Main (перемещён в архив и т.д.)
|
||||
self.chats_mut().retain(|c| c.id != update.chat_id);
|
||||
self.chats_mut().retain(|c| c.id != chat_id);
|
||||
} else if let Some(chat) =
|
||||
self.chats_mut().iter_mut().find(|c| c.id == update.chat_id)
|
||||
self.chats_mut().iter_mut().find(|c| c.id == chat_id)
|
||||
{
|
||||
// Обновляем позицию существующего чата
|
||||
chat.order = update.position.order;
|
||||
@@ -445,7 +448,7 @@ impl TdClient {
|
||||
}
|
||||
ChatList::Folder(folder) => {
|
||||
// Обновляем folder_ids для чата
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == chat_id) {
|
||||
if update.position.order == 0 {
|
||||
// Чат удалён из папки
|
||||
chat.folder_ids.retain(|&id| id != folder.chat_folder_id);
|
||||
@@ -464,7 +467,7 @@ impl TdClient {
|
||||
}
|
||||
Update::NewMessage(new_msg) => {
|
||||
// Добавляем новое сообщение если это текущий открытый чат
|
||||
let chat_id = new_msg.message.chat_id;
|
||||
let chat_id = ChatId::new(new_msg.message.chat_id);
|
||||
if Some(chat_id) == self.current_chat_id() {
|
||||
let msg_info = self.convert_message(&new_msg.message, chat_id);
|
||||
let msg_id = msg_info.id;
|
||||
@@ -563,7 +566,7 @@ impl TdClient {
|
||||
UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth,
|
||||
UserStatus::Empty => UserOnlineStatus::LongTimeAgo,
|
||||
};
|
||||
self.user_cache.user_statuses.insert(update.user_id, status);
|
||||
self.user_cache.user_statuses.insert(UserId::new(update.user_id), status);
|
||||
}
|
||||
Update::ConnectionState(update) => {
|
||||
// Обновляем состояние сетевого соединения
|
||||
@@ -577,10 +580,10 @@ impl TdClient {
|
||||
}
|
||||
Update::ChatAction(update) => {
|
||||
// Обрабатываем только для текущего открытого чата
|
||||
if Some(update.chat_id) == self.current_chat_id() {
|
||||
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
|
||||
// Извлекаем user_id из sender_id
|
||||
let user_id = match update.sender_id {
|
||||
MessageSender::User(user) => Some(user.user_id),
|
||||
MessageSender::User(user) => Some(UserId::new(user.user_id)),
|
||||
MessageSender::Chat(_) => None, // Игнорируем действия от имени чата
|
||||
};
|
||||
|
||||
@@ -624,7 +627,7 @@ impl TdClient {
|
||||
}
|
||||
Update::ChatDraftMessage(update) => {
|
||||
// Обновляем черновик в списке чатов
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||
chat.draft_text = update.draft_message.as_ref().and_then(|draft| {
|
||||
// Извлекаем текст из InputMessageText
|
||||
if let tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) =
|
||||
@@ -639,11 +642,11 @@ impl TdClient {
|
||||
}
|
||||
Update::MessageInteractionInfo(update) => {
|
||||
// Обновляем реакции в текущем открытом чате
|
||||
if Some(update.chat_id) == self.current_chat_id() {
|
||||
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
|
||||
if let Some(msg) = self
|
||||
.current_chat_messages_mut()
|
||||
.iter_mut()
|
||||
.find(|m| m.id == update.message_id)
|
||||
.find(|m| m.id == MessageId::new(update.message_id))
|
||||
{
|
||||
// Извлекаем реакции из interaction_info
|
||||
msg.reactions = update
|
||||
@@ -702,7 +705,7 @@ impl TdClient {
|
||||
// Пропускаем удалённые аккаунты
|
||||
if td_chat.title == "Deleted Account" || td_chat.title.is_empty() {
|
||||
// Удаляем из списка если уже был добавлен
|
||||
self.chats_mut().retain(|c| c.id != td_chat.id);
|
||||
self.chats_mut().retain(|c| c.id != ChatId::new(td_chat.id));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -727,18 +730,20 @@ impl TdClient {
|
||||
let username = match &td_chat.r#type {
|
||||
ChatType::Private(private) => {
|
||||
// Ограничиваем размер chat_user_ids
|
||||
let chat_id = ChatId::new(td_chat.id);
|
||||
if self.user_cache.chat_user_ids.len() >= MAX_CHAT_USER_IDS
|
||||
&& !self.user_cache.chat_user_ids.contains_key(&td_chat.id)
|
||||
&& !self.user_cache.chat_user_ids.contains_key(&chat_id)
|
||||
{
|
||||
// Удаляем случайную запись (первую найденную)
|
||||
if let Some(&key) = self.user_cache.chat_user_ids.keys().next() {
|
||||
self.user_cache.chat_user_ids.remove(&key);
|
||||
}
|
||||
}
|
||||
self.user_cache.chat_user_ids.insert(td_chat.id, private.user_id);
|
||||
let user_id = UserId::new(private.user_id);
|
||||
self.user_cache.chat_user_ids.insert(chat_id, user_id);
|
||||
// Проверяем, есть ли уже username в кэше (peek не обновляет LRU)
|
||||
self.user_cache.user_usernames
|
||||
.peek(&private.user_id)
|
||||
.peek(&user_id)
|
||||
.map(|u| format!("@{}", u))
|
||||
}
|
||||
_ => None,
|
||||
@@ -761,7 +766,7 @@ impl TdClient {
|
||||
let is_muted = td_chat.notification_settings.mute_for > 0;
|
||||
|
||||
let chat_info = ChatInfo {
|
||||
id: td_chat.id,
|
||||
id: ChatId::new(td_chat.id),
|
||||
title: td_chat.title.clone(),
|
||||
username,
|
||||
last_message,
|
||||
@@ -770,13 +775,13 @@ impl TdClient {
|
||||
unread_mention_count: td_chat.unread_mention_count,
|
||||
is_pinned,
|
||||
order,
|
||||
last_read_outbox_message_id: td_chat.last_read_outbox_message_id,
|
||||
last_read_outbox_message_id: MessageId::new(td_chat.last_read_outbox_message_id),
|
||||
folder_ids,
|
||||
is_muted,
|
||||
draft_text: None,
|
||||
};
|
||||
|
||||
if let Some(existing) = self.chats_mut().iter_mut().find(|c| c.id == td_chat.id) {
|
||||
if let Some(existing) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(td_chat.id)) {
|
||||
existing.title = chat_info.title;
|
||||
existing.last_message = chat_info.last_message;
|
||||
existing.last_message_date = chat_info.last_message_date;
|
||||
@@ -815,37 +820,40 @@ impl TdClient {
|
||||
self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order));
|
||||
}
|
||||
|
||||
fn convert_message(&mut self, message: &TdMessage, chat_id: i64) -> MessageInfo {
|
||||
fn convert_message(&mut self, message: &TdMessage, chat_id: ChatId) -> MessageInfo {
|
||||
let sender_name = match &message.sender_id {
|
||||
tdlib_rs::enums::MessageSender::User(user) => {
|
||||
// Пробуем получить имя из кеша (get обновляет LRU порядок)
|
||||
if let Some(name) = self.user_cache.user_names.get(&user.user_id).cloned() {
|
||||
let user_id = UserId::new(user.user_id);
|
||||
if let Some(name) = self.user_cache.user_names.get(&user_id).cloned() {
|
||||
name
|
||||
} else {
|
||||
// Добавляем в очередь для загрузки
|
||||
if !self.pending_user_ids().contains(&user.user_id) {
|
||||
self.pending_user_ids_mut().push(user.user_id);
|
||||
if !self.pending_user_ids().contains(&user_id) {
|
||||
self.pending_user_ids_mut().push(user_id);
|
||||
}
|
||||
format!("User_{}", user.user_id)
|
||||
format!("User_{}", user_id.as_i64())
|
||||
}
|
||||
}
|
||||
tdlib_rs::enums::MessageSender::Chat(chat) => {
|
||||
// Для чатов используем название чата
|
||||
let sender_chat_id = ChatId::new(chat.chat_id);
|
||||
self.chats()
|
||||
.iter()
|
||||
.find(|c| c.id == chat.chat_id)
|
||||
.find(|c| c.id == sender_chat_id)
|
||||
.map(|c| c.title.clone())
|
||||
.unwrap_or_else(|| format!("Chat_{}", chat.chat_id))
|
||||
.unwrap_or_else(|| format!("Chat_{}", sender_chat_id.as_i64()))
|
||||
}
|
||||
};
|
||||
|
||||
// Определяем, прочитано ли исходящее сообщение
|
||||
let message_id = MessageId::new(message.id);
|
||||
let is_read = if message.is_outgoing {
|
||||
// Сообщение прочитано, если его ID <= last_read_outbox_message_id чата
|
||||
self.chats()
|
||||
.iter()
|
||||
.find(|c| c.id == chat_id)
|
||||
.map(|c| message.id <= c.last_read_outbox_message_id)
|
||||
.map(|c| message_id <= c.last_read_outbox_message_id)
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
true // Входящие сообщения не показывают галочки
|
||||
@@ -863,7 +871,7 @@ impl TdClient {
|
||||
let reactions = self.extract_reactions(message);
|
||||
|
||||
MessageInfo {
|
||||
id: message.id,
|
||||
id: message_id,
|
||||
sender_name,
|
||||
is_outgoing: message.is_outgoing,
|
||||
content,
|
||||
@@ -891,14 +899,16 @@ impl TdClient {
|
||||
self.get_origin_sender_name(origin)
|
||||
} else {
|
||||
// Пробуем найти оригинальное сообщение в текущем списке
|
||||
let reply_msg_id = MessageId::new(reply.message_id);
|
||||
self.current_chat_messages()
|
||||
.iter()
|
||||
.find(|m| m.id == reply.message_id)
|
||||
.find(|m| m.id == reply_msg_id)
|
||||
.map(|m| m.sender_name.clone())
|
||||
.unwrap_or_else(|| "...".to_string())
|
||||
};
|
||||
|
||||
// Получаем текст из content или quote
|
||||
let reply_msg_id = MessageId::new(reply.message_id);
|
||||
let text = if let Some(quote) = &reply.quote {
|
||||
quote.text.text.clone()
|
||||
} else if let Some(content) = &reply.content {
|
||||
@@ -907,12 +917,12 @@ impl TdClient {
|
||||
// Пробуем найти в текущих сообщениях
|
||||
self.current_chat_messages()
|
||||
.iter()
|
||||
.find(|m| m.id == reply.message_id)
|
||||
.find(|m| m.id == reply_msg_id)
|
||||
.map(|m| m.content.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
Some(ReplyInfo { message_id: reply.message_id, sender_name, text })
|
||||
Some(ReplyInfo { message_id: reply_msg_id, sender_name, text })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user