#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BackpressurePolicy {
Wait,
Reject,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BackpressureConfig {
capacity: usize,
policy: BackpressurePolicy,
}
impl BackpressureConfig {
pub fn new(capacity: usize, policy: BackpressurePolicy) -> Result<Self, BackpressureError> {
if capacity == 0 {
return Err(BackpressureError::ZeroCapacity);
}
Ok(Self { capacity, policy })
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub const fn policy(&self) -> BackpressurePolicy {
self.policy
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BackpressureState {
capacity: usize,
len: usize,
}
impl BackpressureState {
pub fn new(capacity: usize) -> Result<Self, BackpressureError> {
if capacity == 0 {
return Err(BackpressureError::ZeroCapacity);
}
Ok(Self { capacity, len: 0 })
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub const fn len(&self) -> usize {
self.len
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
pub const fn available(&self) -> usize {
self.capacity - self.len
}
pub const fn is_full(&self) -> bool {
self.len == self.capacity
}
pub fn push(&mut self) -> Result<(), BackpressureError> {
if self.is_full() {
return Err(BackpressureError::Full);
}
self.len += 1;
Ok(())
}
pub fn pop(&mut self) -> Result<(), BackpressureError> {
if self.len == 0 {
return Err(BackpressureError::Underflow);
}
self.len -= 1;
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BackpressureError {
ZeroCapacity,
Full,
Underflow,
}
impl std::fmt::Display for BackpressureError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ZeroCapacity => {
formatter.write_str("backpressure capacity must be greater than zero")
}
Self::Full => formatter.write_str("backpressure capacity is full"),
Self::Underflow => formatter.write_str("backpressure occupancy cannot become negative"),
}
}
}
impl std::error::Error for BackpressureError {}
#[cfg(test)]
mod tests {
use super::{BackpressureConfig, BackpressureError, BackpressurePolicy, BackpressureState};
#[test]
fn configuration_rejects_zero_capacity() {
assert_eq!(
BackpressureConfig::new(0, BackpressurePolicy::Wait),
Err(BackpressureError::ZeroCapacity)
);
}
#[test]
fn state_rejects_zero_capacity() {
assert_eq!(
BackpressureState::new(0),
Err(BackpressureError::ZeroCapacity)
);
}
#[test]
fn configuration_preserves_capacity_and_policy() {
let config = BackpressureConfig::new(4, BackpressurePolicy::Reject).unwrap();
assert_eq!(config.capacity(), 4);
assert_eq!(config.policy(), BackpressurePolicy::Reject);
}
#[test]
fn new_state_starts_empty() {
let state = BackpressureState::new(3).unwrap();
assert_eq!(state.capacity(), 3);
assert_eq!(state.len(), 0);
assert!(state.is_empty());
assert_eq!(state.available(), 3);
assert!(!state.is_full());
}
#[test]
fn push_increases_occupancy_until_full() {
let mut state = BackpressureState::new(3).unwrap();
assert_eq!(state.push(), Ok(()));
assert_eq!(state.len(), 1);
assert!(!state.is_empty());
assert_eq!(state.available(), 2);
assert_eq!(state.push(), Ok(()));
assert_eq!(state.len(), 2);
assert_eq!(state.available(), 1);
assert_eq!(state.push(), Ok(()));
assert_eq!(state.len(), 3);
assert_eq!(state.available(), 0);
assert!(state.is_full());
}
#[test]
fn push_when_full_is_explicit_and_does_not_change_state() {
let mut state = BackpressureState::new(2).unwrap();
state.push().unwrap();
state.push().unwrap();
assert_eq!(state.push(), Err(BackpressureError::Full));
assert_eq!(state.len(), 2);
assert_eq!(state.available(), 0);
assert!(state.is_full());
}
#[test]
fn pop_releases_capacity() {
let mut state = BackpressureState::new(3).unwrap();
state.push().unwrap();
state.push().unwrap();
state.push().unwrap();
assert_eq!(state.pop(), Ok(()));
assert_eq!(state.len(), 2);
assert_eq!(state.available(), 1);
assert!(!state.is_full());
}
#[test]
fn released_capacity_can_be_reused() {
let mut state = BackpressureState::new(2).unwrap();
state.push().unwrap();
state.push().unwrap();
state.pop().unwrap();
assert_eq!(state.push(), Ok(()));
assert_eq!(state.len(), 2);
assert!(state.is_full());
}
#[test]
fn pop_from_empty_state_is_explicit_and_does_not_change_state() {
let mut state = BackpressureState::new(2).unwrap();
assert_eq!(state.pop(), Err(BackpressureError::Underflow));
assert_eq!(state.len(), 0);
assert_eq!(state.available(), 2);
assert!(!state.is_full());
}
#[test]
fn capacity_invariant_is_preserved_across_operations() {
let mut state = BackpressureState::new(2).unwrap();
assert_eq!(state.len(), 0);
state.push().unwrap();
assert!((0..=state.capacity()).contains(&state.len()));
state.push().unwrap();
assert!((0..=state.capacity()).contains(&state.len()));
assert_eq!(state.push(), Err(BackpressureError::Full));
assert!((0..=state.capacity()).contains(&state.len()));
state.pop().unwrap();
assert!((0..=state.capacity()).contains(&state.len()));
state.pop().unwrap();
assert!((0..=state.capacity()).contains(&state.len()));
assert_eq!(state.pop(), Err(BackpressureError::Underflow));
assert!((0..=state.capacity()).contains(&state.len()));
}
#[test]
fn policies_are_distinct() {
assert_ne!(BackpressurePolicy::Wait, BackpressurePolicy::Reject);
}
#[test]
fn errors_have_stable_display_messages() {
assert_eq!(
BackpressureError::ZeroCapacity.to_string(),
"backpressure capacity must be greater than zero"
);
assert_eq!(
BackpressureError::Full.to_string(),
"backpressure capacity is full"
);
assert_eq!(
BackpressureError::Underflow.to_string(),
"backpressure occupancy cannot become negative"
);
}
}