feat: add capnp

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2026-02-27 12:15:35 +01:00
parent 3162971c89
commit 749ae245c7
115 changed files with 16596 additions and 31 deletions

View File

@@ -0,0 +1,195 @@
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");
}
}