use crate::{CoreError, Epoch};
pub const MAX_SEQ_KEY_LEN: usize = 128;
pub const DEFAULT_MAX_SEQ_BATCH_KEYS: u32 = 128;
pub const DEFAULT_MAX_SEQ_COUNT: u32 = 65_536;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SeqKey(String);
impl SeqKey {
pub fn try_new(s: impl Into<String>) -> Result<Self, CoreError> {
let s = s.into();
if s.is_empty() {
return Err(CoreError::SeqKeyEmpty);
}
if s.len() > MAX_SEQ_KEY_LEN {
return Err(CoreError::SeqKeyTooLong {
len: s.len(),
max: MAX_SEQ_KEY_LEN,
});
}
Ok(SeqKey(s))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SeqKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SeqKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
SeqKey::try_new(s).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SeqGrant {
key: SeqKey,
start: u64,
count: u32,
epoch: Epoch,
}
impl SeqGrant {
pub fn try_new(key: SeqKey, start: u64, count: u32, epoch: Epoch) -> Result<Self, CoreError> {
if count == 0 {
return Err(CoreError::SeqCountZero);
}
if start.checked_add(u64::from(count) - 1).is_none() {
return Err(CoreError::SeqBlockOverflow { start, count });
}
Ok(SeqGrant {
key,
start,
count,
epoch,
})
}
pub fn key(&self) -> &SeqKey {
&self.key
}
pub fn start(&self) -> u64 {
self.start
}
pub fn count(&self) -> u32 {
self.count
}
pub fn epoch(&self) -> Epoch {
self.epoch
}
pub fn last(&self) -> u64 {
self.start + (u64::from(self.count) - 1)
}
}
#[derive(Debug)]
enum SeqState {
NotLeader,
Leader { epoch: Epoch },
}
pub struct SeqAllocator {
state: SeqState,
}
impl SeqAllocator {
pub fn new() -> Self {
SeqAllocator {
state: SeqState::NotLeader,
}
}
pub fn become_leader(&mut self, epoch: Epoch) {
self.state = SeqState::Leader { epoch };
}
pub fn step_down(&mut self) {
self.state = SeqState::NotLeader;
}
pub fn is_leader(&self) -> bool {
matches!(self.state, SeqState::Leader { .. })
}
pub fn epoch(&self) -> Option<Epoch> {
match self.state {
SeqState::Leader { epoch } => Some(epoch),
SeqState::NotLeader => None,
}
}
pub fn validate_request(
&self,
key: &str,
count: u32,
max_count: u32,
) -> Result<SeqKey, CoreError> {
if !self.is_leader() {
return Err(CoreError::NotLeader);
}
if count == 0 {
return Err(CoreError::SeqCountZero);
}
if count > max_count {
return Err(CoreError::SeqCountTooLarge {
count,
max: max_count,
});
}
SeqKey::try_new(key)
}
}
impl Default for SeqAllocator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod seqallocator_tests {
use super::*;
#[test]
fn new_is_not_leader() {
let a = SeqAllocator::new();
assert!(!a.is_leader());
assert_eq!(a.epoch(), None);
}
#[test]
fn become_leader_then_step_down() {
let mut a = SeqAllocator::new();
a.become_leader(Epoch(3));
assert!(a.is_leader());
assert_eq!(a.epoch(), Some(Epoch(3)));
a.step_down();
assert!(!a.is_leader());
assert_eq!(a.epoch(), None);
}
#[test]
fn validate_request_off_leader_is_not_leader() {
let a = SeqAllocator::new();
assert_eq!(
a.validate_request("orders", 1, DEFAULT_MAX_SEQ_COUNT),
Err(CoreError::NotLeader)
);
}
#[test]
fn validate_request_rejects_zero_count() {
let mut a = SeqAllocator::new();
a.become_leader(Epoch(1));
assert_eq!(
a.validate_request("orders", 0, DEFAULT_MAX_SEQ_COUNT),
Err(CoreError::SeqCountZero)
);
}
#[test]
fn validate_request_rejects_oversized_count() {
let mut a = SeqAllocator::new();
a.become_leader(Epoch(1));
assert_eq!(
a.validate_request("orders", DEFAULT_MAX_SEQ_COUNT + 1, DEFAULT_MAX_SEQ_COUNT),
Err(CoreError::SeqCountTooLarge {
count: DEFAULT_MAX_SEQ_COUNT + 1,
max: DEFAULT_MAX_SEQ_COUNT
})
);
}
#[test]
fn validate_request_uses_caller_provided_max() {
let mut a = SeqAllocator::new();
a.become_leader(Epoch(1));
assert_eq!(
a.validate_request("orders", 11, 10),
Err(CoreError::SeqCountTooLarge { count: 11, max: 10 }),
"count above a small configured cap must be rejected",
);
assert!(
a.validate_request("orders", 10, 10).is_ok(),
"count at the configured cap must be accepted",
);
assert!(
a.validate_request(
"orders",
DEFAULT_MAX_SEQ_COUNT + 1,
DEFAULT_MAX_SEQ_COUNT * 2
)
.is_ok(),
"a larger configured cap must accept counts above the default",
);
}
#[test]
fn validate_request_rejects_bad_key() {
let mut a = SeqAllocator::new();
a.become_leader(Epoch(1));
assert_eq!(
a.validate_request("", 1, DEFAULT_MAX_SEQ_COUNT),
Err(CoreError::SeqKeyEmpty)
);
}
#[test]
fn validate_request_ok_returns_key() {
let mut a = SeqAllocator::new();
a.become_leader(Epoch(1));
let k = a
.validate_request("orders", 10, DEFAULT_MAX_SEQ_COUNT)
.unwrap();
assert_eq!(k.as_str(), "orders");
}
#[test]
fn validate_request_accepts_max_count_exactly() {
let mut a = SeqAllocator::new();
a.become_leader(Epoch(1));
assert!(
a.validate_request("orders", DEFAULT_MAX_SEQ_COUNT, DEFAULT_MAX_SEQ_COUNT)
.is_ok()
);
}
}
#[cfg(test)]
mod seqgrant_tests {
use super::*;
#[test]
fn exposes_fields_and_last() {
let key = SeqKey::try_new("users").unwrap();
let g = SeqGrant::try_new(key.clone(), 100, 5, Epoch(7)).unwrap();
assert_eq!(g.key().as_str(), "users");
assert_eq!(g.start(), 100);
assert_eq!(g.count(), 5);
assert_eq!(g.epoch(), Epoch(7));
assert_eq!(g.last(), 104);
}
#[test]
fn last_equals_start_when_count_is_one() {
let g1 = SeqGrant::try_new(SeqKey::try_new("x").unwrap(), 42, 1, Epoch(1)).unwrap();
assert_eq!(g1.last(), 42);
}
#[test]
fn try_new_rejects_zero_count() {
let key = SeqKey::try_new("x").unwrap();
assert_eq!(
SeqGrant::try_new(key, 5, 0, Epoch(1)),
Err(CoreError::SeqCountZero)
);
}
#[test]
fn try_new_rejects_block_overflow() {
let key = SeqKey::try_new("x").unwrap();
assert_eq!(
SeqGrant::try_new(key, u64::MAX, 2, Epoch(1)),
Err(CoreError::SeqBlockOverflow {
start: u64::MAX,
count: 2
})
);
}
#[test]
fn try_new_accepts_max_boundary() {
let key = SeqKey::try_new("x").unwrap();
let g = SeqGrant::try_new(key, u64::MAX - 4, 5, Epoch(1)).unwrap();
assert_eq!(g.last(), u64::MAX);
}
}
#[cfg(test)]
mod seqkey_tests {
use super::*;
#[test]
fn accepts_normal_key() {
let k = SeqKey::try_new("orders").unwrap();
assert_eq!(k.as_str(), "orders");
}
#[test]
fn rejects_empty() {
assert_eq!(SeqKey::try_new(""), Err(CoreError::SeqKeyEmpty));
}
#[test]
fn accepts_max_length() {
let s = "a".repeat(MAX_SEQ_KEY_LEN);
assert!(SeqKey::try_new(&s).is_ok());
}
#[test]
fn rejects_one_past_max_length() {
let s = "a".repeat(MAX_SEQ_KEY_LEN + 1);
assert_eq!(
SeqKey::try_new(&s),
Err(CoreError::SeqKeyTooLong {
len: MAX_SEQ_KEY_LEN + 1,
max: MAX_SEQ_KEY_LEN
})
);
}
#[test]
fn length_is_measured_in_utf8_bytes_not_chars() {
let ok = "é".repeat(MAX_SEQ_KEY_LEN / 2);
assert!(SeqKey::try_new(&ok).is_ok());
let too_long = "é".repeat(MAX_SEQ_KEY_LEN / 2 + 1);
assert!(matches!(
SeqKey::try_new(&too_long),
Err(CoreError::SeqKeyTooLong { .. })
));
}
#[cfg(feature = "serde")]
#[test]
fn serde_deserialize_rejects_empty_key() {
let bytes = postcard::to_stdvec(&String::new()).expect("encode empty string");
let decoded = postcard::from_bytes::<SeqKey>(&bytes);
assert!(
decoded.is_err(),
"empty-key postcard payload must fail to decode, got {decoded:?}",
);
}
#[cfg(feature = "serde")]
#[test]
fn serde_deserialize_rejects_oversized_key() {
let oversized = "a".repeat(MAX_SEQ_KEY_LEN + 1);
let bytes = postcard::to_stdvec(&oversized).expect("encode oversized string");
let decoded = postcard::from_bytes::<SeqKey>(&bytes);
assert!(
decoded.is_err(),
"oversized-key postcard payload must fail to decode, got {decoded:?}",
);
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trip_preserves_valid_key() {
let key = SeqKey::try_new("orders").unwrap();
let bytes = postcard::to_stdvec(&key).expect("encode key");
assert_eq!(bytes, postcard::to_stdvec(&"orders".to_string()).unwrap());
let back: SeqKey = postcard::from_bytes(&bytes).expect("decode key");
assert_eq!(back, key);
assert_eq!(back.as_str(), "orders");
}
#[cfg(feature = "serde")]
#[test]
fn serde_deserialize_accepts_max_length_key() {
let max = "a".repeat(MAX_SEQ_KEY_LEN);
let bytes = postcard::to_stdvec(&max).expect("encode max-length string");
let back: SeqKey = postcard::from_bytes(&bytes).expect("max-length key must decode");
assert_eq!(back.as_str(), max);
}
}