use std::fmt;
use std::sync::Arc;
use uuid::Uuid;
const MAX_NAME_LEN: usize = 255;
macro_rules! uuid_id {
($name:ident) => {
#[doc = concat!("A 128-bit Ozzy ", stringify!($name), ".")]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(Uuid);
impl $name {
pub fn new() -> Self {
Self(Uuid::now_v7())
}
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self(Uuid::from_bytes(bytes))
}
pub const fn as_bytes(&self) -> &[u8; 16] {
self.0.as_bytes()
}
}
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple(stringify!($name)).field(&self.0).finish()
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
};
}
uuid_id!(NodeId);
uuid_id!(GroupId);
uuid_id!(TopicId);
uuid_id!(VolumeId);
uuid_id!(StoreId);
uuid_id!(CheckpointId);
uuid_id!(PartitionIncarnation);
uuid_id!(ProducerId);
uuid_id!(SubscriptionId);
uuid_id!(ConsumerGroupId);
uuid_id!(ConsumerMemberId);
uuid_id!(MessageId);
uuid_id!(RequestId);
uuid_id!(LinkSessionId);
uuid_id!(OperationId);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Offset(u64);
impl Offset {
pub const ZERO: Self = Self(0);
pub const fn new(value: u64) -> Self {
Self(value)
}
pub const fn get(self) -> u64 {
self.0
}
pub const fn checked_next(self) -> Option<Self> {
match self.0.checked_add(1) {
Some(value) => Some(Self(value)),
None => None,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PartitionId(u32);
impl PartitionId {
pub const ZERO: Self = Self(0);
pub const fn new(value: u32) -> Self {
Self(value)
}
pub const fn get(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OwnerEpoch(u64);
impl OwnerEpoch {
pub const INITIAL: Self = Self(1);
pub const fn new(value: u64) -> Self {
Self(value)
}
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProducerEpoch(u64);
impl ProducerEpoch {
pub const INITIAL: Self = Self(1);
pub const fn new(value: u64) -> Self {
Self(value)
}
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProducerSequence(u64);
impl ProducerSequence {
pub const ZERO: Self = Self(0);
pub const fn new(value: u64) -> Self {
Self(value)
}
pub const fn get(self) -> u64 {
self.0
}
pub const fn checked_next(self) -> Option<Self> {
match self.0.checked_add(1) {
Some(value) => Some(Self(value)),
None => None,
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Topic {
inner: Arc<TopicInner>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct TopicInner {
stream: String,
name: String,
}
impl Topic {
pub fn new(stream: impl Into<String>, name: impl Into<String>) -> Result<Self, NameError> {
let stream = stream.into();
let name = name.into();
validate_name("stream", &stream)?;
validate_name("topic", &name)?;
Ok(Self {
inner: Arc::new(TopicInner { stream, name }),
})
}
pub fn stream(&self) -> &str {
&self.inner.stream
}
pub fn name(&self) -> &str {
&self.inner.name
}
}
impl fmt::Debug for Topic {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Topic")
.field("stream", &self.inner.stream)
.field("name", &self.inner.name)
.finish()
}
}
fn validate_name(field: &'static str, value: &str) -> Result<(), NameError> {
if value.is_empty() {
return Err(NameError::Empty(field));
}
if value.len() > MAX_NAME_LEN {
return Err(NameError::TooLong {
field,
len: value.len(),
max: MAX_NAME_LEN,
});
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum NameError {
#[error("{0} name must not be empty")]
Empty(&'static str),
#[error("{field} name is {len} bytes; maximum is {max}")]
TooLong {
field: &'static str,
len: usize,
max: usize,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_names() {
assert!(matches!(
Topic::new("", "events"),
Err(NameError::Empty("stream"))
));
assert!(matches!(
Topic::new("events", "x".repeat(MAX_NAME_LEN + 1)),
Err(NameError::TooLong { field: "topic", .. })
));
}
#[test]
fn topic_is_one_shared_pointer() {
assert_eq!(std::mem::size_of::<Topic>(), std::mem::size_of::<usize>());
let topic = Topic::new("orders", "created").unwrap();
let clone = topic.clone();
assert_eq!(clone.stream(), "orders");
assert_eq!(clone.name(), "created");
assert!(Arc::ptr_eq(&topic.inner, &clone.inner));
}
}