Files
telegram-tui/src/types.rs
Mikhail Kilin 264f183510
Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
style: auto-format entire codebase with cargo fmt (stable rustfmt.toml)
2026-02-22 17:09:51 +03:00

173 lines
3.7 KiB
Rust

//! Type-safe ID wrappers to prevent mixing up different ID types.
//!
//! Provides `ChatId` and `MessageId` newtypes for compile-time safety.
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, PartialOrd, Ord, 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);
}
}