use core::num::NonZeroUsize;
use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaskQueue<T> {
items: VecDeque<T>,
capacity: NonZeroUsize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueuePushError<T> {
Full(T),
}
impl<T> TaskQueue<T> {
#[must_use]
pub fn new(capacity: NonZeroUsize) -> Self {
Self {
items: VecDeque::with_capacity(capacity.get()),
capacity,
}
}
pub fn try_push(&mut self, item: T) -> Result<(), QueuePushError<T>> {
if self.is_full() {
return Err(QueuePushError::Full(item));
}
self.items.push_back(item);
Ok(())
}
pub fn pop(&mut self) -> Option<T> {
self.items.pop_front()
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[must_use]
pub const fn capacity(&self) -> usize {
self.capacity.get()
}
#[must_use]
pub fn is_full(&self) -> bool {
self.len() == self.capacity()
}
}
#[cfg(test)]
mod tests {
use super::{QueuePushError, TaskQueue};
use core::num::NonZeroUsize;
fn queue<T>(capacity: usize) -> TaskQueue<T> {
TaskQueue::new(NonZeroUsize::new(capacity).expect("test capacity is positive"))
}
#[test]
fn starts_empty_with_configured_capacity() {
let queue = queue::<u8>(2);
assert!(queue.is_empty());
assert!(!queue.is_full());
assert_eq!(queue.len(), 0);
assert_eq!(queue.capacity(), 2);
}
#[test]
fn preserves_fifo_order() {
let mut queue = queue(3);
assert_eq!(queue.try_push(10), Ok(()));
assert_eq!(queue.try_push(20), Ok(()));
assert_eq!(queue.try_push(30), Ok(()));
assert!(queue.is_full());
assert_eq!(queue.pop(), Some(10));
assert_eq!(queue.pop(), Some(20));
assert_eq!(queue.pop(), Some(30));
assert_eq!(queue.pop(), None);
assert!(queue.is_empty());
}
#[test]
fn rejects_full_queue_without_losing_item() {
let mut queue = queue(1);
assert_eq!(queue.try_push("accepted"), Ok(()));
assert_eq!(
queue.try_push("rejected"),
Err(QueuePushError::Full("rejected"))
);
assert_eq!(queue.pop(), Some("accepted"));
}
#[test]
fn capacity_one_can_be_reused_after_pop() {
let mut queue = queue(1);
assert_eq!(queue.try_push(1), Ok(()));
assert_eq!(queue.pop(), Some(1));
assert_eq!(queue.try_push(2), Ok(()));
assert_eq!(queue.pop(), Some(2));
}
}