use std::fmt; /// A single message in the queue. #[derive(Clone, Debug, PartialEq)] pub struct Message { /// Monotonically increasing within a topic-partition. Assigned by the server. pub offset: u64, /// Topic this message belongs to. pub topic: TopicName, /// Partition within the topic. pub partition: u32, /// Optional partitioning key. pub key: Option>, /// The payload. pub value: Vec, /// User-defined headers (metadata). pub headers: Vec
, /// Server-assigned wall-clock timestamp (millis since epoch). pub timestamp_ms: u64, } /// A key-value header attached to a message. #[derive(Clone, Debug, PartialEq)] pub struct Header { pub key: String, pub value: Vec, } /// A topic name wrapper. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TopicName(pub String); impl TopicName { pub fn as_str(&self) -> &str { &self.0 } } impl fmt::Display for TopicName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl From<&str> for TopicName { fn from(s: &str) -> Self { Self(s.to_string()) } } impl From for TopicName { fn from(s: String) -> Self { Self(s) } } /// Information about a closed WAL segment ready for shipping. #[derive(Clone, Debug)] pub struct ClosedSegment { pub path: std::path::PathBuf, pub topic: TopicName, pub partition: u32, pub base_offset: u64, pub end_offset: u64, pub size_bytes: u64, } #[cfg(test)] mod tests { use super::*; #[test] fn test_message_construction() { let msg = Message { offset: 42, topic: TopicName::from("orders"), partition: 0, key: Some(b"user-123".to_vec()), value: b"hello world".to_vec(), headers: vec![Header { key: "content-type".to_string(), value: b"text/plain".to_vec(), }], timestamp_ms: 1700000000000, }; assert_eq!(msg.offset, 42); assert_eq!(msg.topic.as_str(), "orders"); assert_eq!(msg.partition, 0); assert_eq!(msg.key.as_deref(), Some(b"user-123".as_slice())); assert_eq!(msg.value, b"hello world"); assert_eq!(msg.headers.len(), 1); assert_eq!(msg.headers[0].key, "content-type"); } #[test] fn test_message_no_key_no_headers() { let msg = Message { offset: 0, topic: TopicName::from("events"), partition: 1, key: None, value: b"payload".to_vec(), headers: vec![], timestamp_ms: 0, }; assert!(msg.key.is_none()); assert!(msg.headers.is_empty()); } #[test] fn test_message_clone_eq() { let msg = Message { offset: 1, topic: TopicName::from("test"), partition: 0, key: None, value: b"data".to_vec(), headers: vec![], timestamp_ms: 100, }; let cloned = msg.clone(); assert_eq!(msg, cloned); } #[test] fn test_topic_name_ordering() { let a = TopicName::from("alpha"); let b = TopicName::from("beta"); assert!(a < b); } #[test] fn test_topic_name_display() { let t = TopicName::from("my-topic"); assert_eq!(format!("{t}"), "my-topic"); } #[test] fn test_message_empty_value() { let msg = Message { offset: 0, topic: TopicName::from("t"), partition: 0, key: None, value: vec![], headers: vec![], timestamp_ms: 0, }; assert!(msg.value.is_empty()); } #[test] fn test_message_large_value() { let large = vec![0xFFu8; 1024 * 1024]; // 1MB let msg = Message { offset: 0, topic: TopicName::from("t"), partition: 0, key: None, value: large.clone(), headers: vec![], timestamp_ms: 0, }; assert_eq!(msg.value.len(), 1024 * 1024); assert_eq!(msg.value, large); } #[test] fn test_message_many_headers() { let headers: Vec
= (0..100) .map(|i| Header { key: format!("header-{i}"), value: format!("value-{i}").into_bytes(), }) .collect(); let msg = Message { offset: 0, topic: TopicName::from("t"), partition: 0, key: None, value: vec![], headers, timestamp_ms: 0, }; assert_eq!(msg.headers.len(), 100); assert_eq!(msg.headers[99].key, "header-99"); } }