use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InboundMessage {
pub channel: String,
pub sender_id: String,
pub chat_id: String,
pub content: String,
#[serde(default = "Utc::now")]
pub timestamp: DateTime<Utc>,
#[serde(default)]
pub media: Vec<String>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
impl InboundMessage {
pub fn session_key(&self) -> String {
format!("{}:{}", self.channel, self.chat_id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutboundMessage {
pub channel: String,
pub chat_id: String,
pub content: String,
#[serde(default)]
pub reply_to: Option<String>,
#[serde(default)]
pub media: Vec<String>,
#[serde(default)]
pub metadata: HashMap<String, serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inbound_session_key() {
let msg = InboundMessage {
channel: "telegram".into(),
sender_id: "user123".into(),
chat_id: "chat456".into(),
content: "hello".into(),
timestamp: Utc::now(),
media: vec![],
metadata: HashMap::new(),
};
assert_eq!(msg.session_key(), "telegram:chat456");
}
#[test]
fn inbound_serde_roundtrip() {
let msg = InboundMessage {
channel: "slack".into(),
sender_id: "U12345".into(),
chat_id: "C67890".into(),
content: "test message".into(),
timestamp: Utc::now(),
media: vec!["https://example.com/image.png".into()],
metadata: {
let mut m = HashMap::new();
m.insert("thread_ts".into(), serde_json::json!("123.456"));
m
},
};
let json = serde_json::to_string(&msg).unwrap();
let restored: InboundMessage = serde_json::from_str(&json).unwrap();
assert_eq!(restored.channel, "slack");
assert_eq!(restored.sender_id, "U12345");
assert_eq!(restored.chat_id, "C67890");
assert_eq!(restored.content, "test message");
assert_eq!(restored.media.len(), 1);
assert!(restored.metadata.contains_key("thread_ts"));
}
#[test]
fn inbound_defaults_on_missing_fields() {
let json = r#"{
"channel": "discord",
"sender_id": "u1",
"chat_id": "c1",
"content": "hi"
}"#;
let msg: InboundMessage = serde_json::from_str(json).unwrap();
assert!(msg.media.is_empty());
assert!(msg.metadata.is_empty());
}
#[test]
fn outbound_serde_roundtrip() {
let msg = OutboundMessage {
channel: "telegram".into(),
chat_id: "chat456".into(),
content: "reply".into(),
reply_to: Some("msg789".into()),
media: vec![],
metadata: HashMap::new(),
};
let json = serde_json::to_string(&msg).unwrap();
let restored: OutboundMessage = serde_json::from_str(&json).unwrap();
assert_eq!(restored.channel, "telegram");
assert_eq!(restored.reply_to.as_deref(), Some("msg789"));
}
#[test]
fn outbound_reply_to_optional() {
let json = r#"{
"channel": "slack",
"chat_id": "c1",
"content": "msg"
}"#;
let msg: OutboundMessage = serde_json::from_str(json).unwrap();
assert!(msg.reply_to.is_none());
assert!(msg.media.is_empty());
}
}