use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Message {
pub sender: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub timestamp: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub reply_to: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub edited: Option<DateTime<Utc>>,
}
impl Message {
pub fn new(sender: impl Into<String>, content: impl Into<String>) -> Self {
Self {
sender: sender.into(),
content: content.into(),
timestamp: None,
id: None,
reply_to: None,
edited: None,
}
}
pub fn with_metadata(
sender: impl Into<String>,
content: impl Into<String>,
timestamp: Option<DateTime<Utc>>,
id: Option<u64>,
reply_to: Option<u64>,
edited: Option<DateTime<Utc>>,
) -> Self {
Self {
sender: sender.into(),
content: content.into(),
timestamp,
id,
reply_to,
edited,
}
}
#[must_use]
pub fn with_timestamp(mut self, ts: DateTime<Utc>) -> Self {
self.timestamp = Some(ts);
self
}
#[must_use]
pub fn with_id(mut self, id: u64) -> Self {
self.id = Some(id);
self
}
#[must_use]
pub fn with_reply_to(mut self, reply_id: u64) -> Self {
self.reply_to = Some(reply_id);
self
}
#[must_use]
pub fn with_edited(mut self, ts: DateTime<Utc>) -> Self {
self.edited = Some(ts);
self
}
pub fn sender(&self) -> &str {
&self.sender
}
pub fn content(&self) -> &str {
&self.content
}
pub fn timestamp(&self) -> Option<DateTime<Utc>> {
self.timestamp
}
pub fn id(&self) -> Option<u64> {
self.id
}
pub fn reply_to(&self) -> Option<u64> {
self.reply_to
}
pub fn edited(&self) -> Option<DateTime<Utc>> {
self.edited
}
pub fn has_metadata(&self) -> bool {
self.timestamp.is_some()
|| self.id.is_some()
|| self.reply_to.is_some()
|| self.edited.is_some()
}
pub fn is_empty(&self) -> bool {
self.content.trim().is_empty()
}
}
impl Default for Message {
fn default() -> Self {
Self::new("", "")
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::TimeZone;
#[test]
fn test_message_new() {
let msg = Message::new("Alice", "Hello");
assert_eq!(msg.sender(), "Alice");
assert_eq!(msg.content(), "Hello");
assert!(msg.timestamp().is_none());
assert!(msg.id().is_none());
}
#[test]
fn test_message_builder() {
let ts = Utc.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();
let msg = Message::new("Alice", "Hello")
.with_timestamp(ts)
.with_id(123)
.with_reply_to(122)
.with_edited(ts);
assert_eq!(msg.timestamp(), Some(ts));
assert_eq!(msg.id(), Some(123));
assert_eq!(msg.reply_to(), Some(122));
assert_eq!(msg.edited(), Some(ts));
}
#[test]
fn test_message_has_metadata() {
let msg1 = Message::new("Alice", "Hello");
assert!(!msg1.has_metadata());
let msg2 = Message::new("Alice", "Hello").with_id(123);
assert!(msg2.has_metadata());
}
#[test]
fn test_message_is_empty() {
assert!(Message::new("Alice", "").is_empty());
assert!(Message::new("Alice", " ").is_empty());
assert!(!Message::new("Alice", "Hello").is_empty());
}
#[test]
fn test_message_serialization() {
let msg = Message::new("Alice", "Hello").with_id(123);
let json = serde_json::to_string(&msg).unwrap();
assert!(json.contains("Alice"));
assert!(json.contains("123"));
assert!(!json.contains("timestamp"));
}
#[test]
fn test_message_deserialization() {
let json = r#"{"sender":"Bob","content":"Hi","id":456}"#;
let msg: Message = serde_json::from_str(json).unwrap();
assert_eq!(msg.sender(), "Bob");
assert_eq!(msg.content(), "Hi");
assert_eq!(msg.id(), Some(456));
assert!(msg.timestamp().is_none());
}
#[test]
fn test_message_accessors() {
let ts = Utc.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();
let msg = Message::new("Alice", "Hello")
.with_timestamp(ts)
.with_id(123)
.with_reply_to(122)
.with_edited(ts);
assert_eq!(msg.sender(), "Alice");
assert_eq!(msg.content(), "Hello");
assert_eq!(msg.timestamp(), Some(ts));
assert_eq!(msg.id(), Some(123));
assert_eq!(msg.reply_to(), Some(122));
assert_eq!(msg.edited(), Some(ts));
}
}