use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InboundMessage {
pub channel: String,
pub sender_id: String,
pub chat_id: String,
pub content: String,
pub media: Option<MediaAttachment>,
pub session_key: String,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutboundMessage {
pub channel: String,
pub chat_id: String,
pub content: String,
pub reply_to: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MediaAttachment {
pub media_type: MediaType,
pub url: Option<String>,
pub data: Option<Vec<u8>>,
pub filename: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum MediaType {
Image,
Audio,
Video,
Document,
}
impl InboundMessage {
pub fn new(channel: &str, sender_id: &str, chat_id: &str, content: &str) -> Self {
Self {
channel: channel.to_string(),
sender_id: sender_id.to_string(),
chat_id: chat_id.to_string(),
content: content.to_string(),
media: None,
session_key: format!("{}:{}", channel, chat_id),
metadata: HashMap::new(),
}
}
pub fn with_media(mut self, media: MediaAttachment) -> Self {
self.media = Some(media);
self
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn has_media(&self) -> bool {
self.media.is_some()
}
}
impl OutboundMessage {
pub fn new(channel: &str, chat_id: &str, content: &str) -> Self {
Self {
channel: channel.to_string(),
chat_id: chat_id.to_string(),
content: content.to_string(),
reply_to: None,
metadata: HashMap::new(),
}
}
pub fn with_reply(mut self, message_id: &str) -> Self {
self.reply_to = Some(message_id.to_string());
self
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn reply_to(msg: &InboundMessage, content: &str) -> Self {
Self::new(&msg.channel, &msg.chat_id, content)
}
}
impl MediaAttachment {
pub fn new(media_type: MediaType) -> Self {
Self {
media_type,
url: None,
data: None,
filename: None,
}
}
pub fn with_url(mut self, url: &str) -> Self {
self.url = Some(url.to_string());
self
}
pub fn with_data(mut self, data: Vec<u8>) -> Self {
self.data = Some(data);
self
}
pub fn with_filename(mut self, filename: &str) -> Self {
self.filename = Some(filename.to_string());
self
}
pub fn has_url(&self) -> bool {
self.url.is_some()
}
pub fn has_data(&self) -> bool {
self.data.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inbound_message_creation() {
let msg = InboundMessage::new("telegram", "user123", "chat456", "Hello");
assert_eq!(msg.channel, "telegram");
assert_eq!(msg.sender_id, "user123");
assert_eq!(msg.chat_id, "chat456");
assert_eq!(msg.content, "Hello");
assert_eq!(msg.session_key, "telegram:chat456");
assert!(msg.media.is_none());
assert!(msg.metadata.is_empty());
}
#[test]
fn test_inbound_message_with_media() {
let media = MediaAttachment::new(MediaType::Image)
.with_url("https://example.com/image.png")
.with_filename("image.png");
let msg =
InboundMessage::new("discord", "user1", "channel1", "Check this").with_media(media);
assert!(msg.has_media());
let attachment = msg.media.unwrap();
assert_eq!(attachment.media_type, MediaType::Image);
assert_eq!(
attachment.url,
Some("https://example.com/image.png".to_string())
);
assert_eq!(attachment.filename, Some("image.png".to_string()));
}
#[test]
fn test_inbound_message_with_metadata() {
let msg = InboundMessage::new("telegram", "user123", "chat456", "Hello")
.with_metadata("message_id", "12345")
.with_metadata("timestamp", "2024-01-01T00:00:00Z");
assert_eq!(msg.metadata.len(), 2);
assert_eq!(msg.metadata.get("message_id"), Some(&"12345".to_string()));
assert_eq!(
msg.metadata.get("timestamp"),
Some(&"2024-01-01T00:00:00Z".to_string())
);
}
#[test]
fn test_outbound_message_creation() {
let msg = OutboundMessage::new("telegram", "chat456", "Response");
assert_eq!(msg.channel, "telegram");
assert_eq!(msg.chat_id, "chat456");
assert_eq!(msg.content, "Response");
assert!(msg.reply_to.is_none());
assert!(msg.metadata.is_empty());
}
#[test]
fn test_outbound_message_with_reply() {
let msg = OutboundMessage::new("telegram", "chat456", "This is a reply")
.with_reply("original_msg_123");
assert_eq!(msg.reply_to, Some("original_msg_123".to_string()));
}
#[test]
fn test_outbound_message_with_metadata() {
let msg = OutboundMessage::new("discord", "channel1", "Hello")
.with_metadata("discord_thread_name", "Daily Updates")
.with_metadata("discord_thread_auto_archive_minutes", "60");
assert_eq!(
msg.metadata.get("discord_thread_name"),
Some(&"Daily Updates".to_string())
);
assert_eq!(
msg.metadata.get("discord_thread_auto_archive_minutes"),
Some(&"60".to_string())
);
}
#[test]
fn test_outbound_reply_to_inbound() {
let inbound = InboundMessage::new("telegram", "user123", "chat456", "Hello");
let response = OutboundMessage::reply_to(&inbound, "Hello back!");
assert_eq!(response.channel, "telegram");
assert_eq!(response.chat_id, "chat456");
assert_eq!(response.content, "Hello back!");
}
#[test]
fn test_media_attachment_creation() {
let media = MediaAttachment::new(MediaType::Audio)
.with_url("https://example.com/audio.mp3")
.with_data(vec![1, 2, 3, 4])
.with_filename("audio.mp3");
assert_eq!(media.media_type, MediaType::Audio);
assert!(media.has_url());
assert!(media.has_data());
assert_eq!(media.filename, Some("audio.mp3".to_string()));
}
#[test]
fn test_media_type_equality() {
assert_eq!(MediaType::Image, MediaType::Image);
assert_ne!(MediaType::Image, MediaType::Audio);
assert_ne!(MediaType::Video, MediaType::Document);
}
#[test]
fn test_message_serialization() {
let msg = InboundMessage::new("telegram", "user123", "chat456", "Hello")
.with_metadata("key", "value");
let json = serde_json::to_string(&msg).expect("Failed to serialize");
let deserialized: InboundMessage =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(deserialized.channel, "telegram");
assert_eq!(deserialized.content, "Hello");
assert_eq!(deserialized.metadata.get("key"), Some(&"value".to_string()));
}
#[test]
fn test_outbound_message_serialization() {
let msg = OutboundMessage::new("discord", "channel1", "Hello Discord!")
.with_reply("msg_123")
.with_metadata("discord_thread_name", "ops-thread");
let json = serde_json::to_string(&msg).expect("Failed to serialize");
let deserialized: OutboundMessage =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(deserialized.channel, "discord");
assert_eq!(deserialized.reply_to, Some("msg_123".to_string()));
assert_eq!(
deserialized.metadata.get("discord_thread_name"),
Some(&"ops-thread".to_string())
);
}
}