196 lines
4.8 KiB
Rust
196 lines
4.8 KiB
Rust
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<Vec<u8>>,
|
|
/// The payload.
|
|
pub value: Vec<u8>,
|
|
/// User-defined headers (metadata).
|
|
pub headers: Vec<Header>,
|
|
/// 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<u8>,
|
|
}
|
|
|
|
/// 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<String> 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<Header> = (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");
|
|
}
|
|
}
|