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
173 lines
3.7 KiB
Rust
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);
|
|
}
|
|
}
|