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:
170
src/types.rs
Normal file
170
src/types.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
/// Type-safe ID wrappers to prevent mixing up different ID types
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// Chat identifier
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ChatId(pub i64);
|
||||
|
||||
impl ChatId {
|
||||
pub fn new(id: i64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
pub fn as_i64(&self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for ChatId {
|
||||
fn from(id: i64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChatId> for i64 {
|
||||
fn from(id: ChatId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ChatId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Message identifier
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct MessageId(pub i64);
|
||||
|
||||
impl MessageId {
|
||||
pub fn new(id: i64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
pub fn as_i64(&self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for MessageId {
|
||||
fn from(id: i64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageId> for i64 {
|
||||
fn from(id: MessageId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MessageId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// User identifier
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct UserId(pub i64);
|
||||
|
||||
impl UserId {
|
||||
pub fn new(id: i64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
pub fn as_i64(&self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for UserId {
|
||||
fn from(id: i64) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserId> for i64 {
|
||||
fn from(id: UserId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UserId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_chat_id() {
|
||||
let id = ChatId::new(123);
|
||||
assert_eq!(id.as_i64(), 123);
|
||||
assert_eq!(i64::from(id), 123);
|
||||
|
||||
let id2: ChatId = 456.into();
|
||||
assert_eq!(id2.0, 456);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_id() {
|
||||
let id = MessageId::new(789);
|
||||
assert_eq!(id.as_i64(), 789);
|
||||
assert_eq!(i64::from(id), 789);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_user_id() {
|
||||
let id = UserId::new(111);
|
||||
assert_eq!(id.as_i64(), 111);
|
||||
assert_eq!(i64::from(id), 111);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_safety() {
|
||||
// Type safety is enforced at compile time
|
||||
// The following would not compile:
|
||||
// let chat_id = ChatId::new(1);
|
||||
// let message_id = MessageId::new(1);
|
||||
// if chat_id == message_id { } // ERROR: mismatched types
|
||||
|
||||
// Runtime values can be the same, but types are different
|
||||
let chat_id = ChatId::new(1);
|
||||
let message_id = MessageId::new(1);
|
||||
assert_eq!(chat_id.as_i64(), 1);
|
||||
assert_eq!(message_id.as_i64(), 1);
|
||||
// But they cannot be compared directly due to type safety
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
let chat_id = ChatId::new(123);
|
||||
assert_eq!(format!("{}", chat_id), "123");
|
||||
|
||||
let message_id = MessageId::new(456);
|
||||
assert_eq!(format!("{}", message_id), "456");
|
||||
|
||||
let user_id = UserId::new(789);
|
||||
assert_eq!(format!("{}", user_id), "789");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_map() {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(ChatId::new(1), "chat1");
|
||||
map.insert(ChatId::new(2), "chat2");
|
||||
|
||||
assert_eq!(map.get(&ChatId::new(1)), Some(&"chat1"));
|
||||
assert_eq!(map.get(&ChatId::new(2)), Some(&"chat2"));
|
||||
assert_eq!(map.get(&ChatId::new(3)), None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user