Skip to main content

que_luyo/
lib.rs

1//! Transactional, bite-sized FIFO queue consumption.
2//!
3//! A [`Bite`] temporarily removes a prefix from a [`BiteQueue`]. Committing
4//! keeps it removed; every other exit path restores it in original order.
5//!
6//! ```
7//! use que_luyo::BiteQueue;
8//!
9//! let mut queue = BiteQueue::from_iter([1, 2, 3]);
10//! {
11//!     let bite = queue.bite(2)?;
12//!     assert_eq!(bite.iter().copied().collect::<Vec<_>>(), [1, 2]);
13//! }
14//! assert_eq!(queue.iter().copied().collect::<Vec<_>>(), [1, 2, 3]);
15//!
16//! let eaten = queue.bite(2)?.commit();
17//! assert_eq!(eaten, [1, 2]);
18//! # Ok::<(), que_luyo::BiteError>(())
19//! ```
20
21use std::collections::VecDeque;
22use std::error::Error;
23use std::fmt::{self, Debug, Display, Formatter};
24use std::iter::FromIterator;
25
26/// A FIFO queue that lends transactional batches.
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
28pub struct BiteQueue<T> {
29    items: VecDeque<T>,
30}
31
32impl<T> BiteQueue<T> {
33    /// Creates an empty queue.
34    pub const fn new() -> Self {
35        Self {
36            items: VecDeque::new(),
37        }
38    }
39
40    /// Creates an empty queue with space for at least `capacity` items.
41    pub fn with_capacity(capacity: usize) -> Self {
42        Self {
43            items: VecDeque::with_capacity(capacity),
44        }
45    }
46
47    /// Adds an item at the back.
48    pub fn push(&mut self, item: T) {
49        self.items.push_back(item);
50    }
51
52    /// Returns the next item without removing it.
53    pub fn front(&self) -> Option<&T> {
54        self.items.front()
55    }
56
57    /// Returns the number of queued items.
58    pub fn len(&self) -> usize {
59        self.items.len()
60    }
61
62    /// Returns whether the queue contains no items.
63    pub fn is_empty(&self) -> bool {
64        self.items.is_empty()
65    }
66
67    /// Iterates in FIFO order without removing items.
68    pub fn iter(&self) -> impl ExactSizeIterator<Item = &T> + DoubleEndedIterator {
69        self.items.iter()
70    }
71
72    /// Takes up to `maximum` items as a transactional bite.
73    ///
74    /// Dropping the returned bite without committing it restores the items.
75    pub fn bite(&mut self, maximum: usize) -> Result<Bite<'_, T>, BiteError> {
76        if maximum == 0 {
77            return Err(BiteError::ZeroSize);
78        }
79        let amount = maximum.min(self.items.len());
80        Ok(self.take_prefix(amount))
81    }
82
83    /// Takes exactly `amount` items or leaves the queue unchanged.
84    pub fn bite_exact(&mut self, amount: usize) -> Result<Bite<'_, T>, BiteError> {
85        if amount == 0 {
86            return Err(BiteError::ZeroSize);
87        }
88        if self.items.len() < amount {
89            return Err(BiteError::InsufficientItems {
90                requested: amount,
91                available: self.items.len(),
92            });
93        }
94        Ok(self.take_prefix(amount))
95    }
96
97    /// Consumes the wrapper and returns the underlying FIFO storage.
98    pub fn into_inner(self) -> VecDeque<T> {
99        self.items
100    }
101
102    fn take_prefix(&mut self, amount: usize) -> Bite<'_, T> {
103        let items = self.items.drain(..amount).collect();
104        Bite {
105            source: self,
106            items,
107            finished: false,
108        }
109    }
110}
111
112impl<T> Extend<T> for BiteQueue<T> {
113    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
114        self.items.extend(iter);
115    }
116}
117
118impl<T> FromIterator<T> for BiteQueue<T> {
119    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
120        Self {
121            items: iter.into_iter().collect(),
122        }
123    }
124}
125
126impl<T> IntoIterator for BiteQueue<T> {
127    type Item = T;
128    type IntoIter = std::collections::vec_deque::IntoIter<T>;
129
130    fn into_iter(self) -> Self::IntoIter {
131        self.items.into_iter()
132    }
133}
134
135impl<'a, T> IntoIterator for &'a BiteQueue<T> {
136    type Item = &'a T;
137    type IntoIter = std::collections::vec_deque::Iter<'a, T>;
138
139    fn into_iter(self) -> Self::IntoIter {
140        self.items.iter()
141    }
142}
143
144/// A borrowed, transactional prefix of a [`BiteQueue`].
145///
146/// The source queue stays exclusively borrowed until this value is committed,
147/// explicitly rolled back, or dropped.
148#[must_use = "an uncommitted bite automatically rolls back"]
149pub struct Bite<'a, T> {
150    source: &'a mut BiteQueue<T>,
151    items: VecDeque<T>,
152    finished: bool,
153}
154
155impl<T: Debug> Debug for Bite<'_, T> {
156    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
157        f.debug_struct("Bite")
158            .field("items", &self.items)
159            .field("finished", &self.finished)
160            .finish_non_exhaustive()
161    }
162}
163
164impl<T> Bite<'_, T> {
165    /// Number of items in this bite.
166    pub fn len(&self) -> usize {
167        self.items.len()
168    }
169
170    /// Returns whether this bite contains no items.
171    ///
172    /// A bite from an empty queue can be empty even though its requested
173    /// maximum was non-zero.
174    pub fn is_empty(&self) -> bool {
175        self.items.is_empty()
176    }
177
178    /// First item in this bite.
179    pub fn front(&self) -> Option<&T> {
180        self.items.front()
181    }
182
183    /// Iterates over the bite in original FIFO order.
184    pub fn iter(&self) -> impl ExactSizeIterator<Item = &T> + DoubleEndedIterator {
185        self.items.iter()
186    }
187
188    /// Commits the bite and returns its items in FIFO order.
189    pub fn commit(mut self) -> Vec<T> {
190        self.finished = true;
191        self.items.drain(..).collect()
192    }
193
194    /// Explicitly restores the bite to the queue.
195    ///
196    /// Dropping without calling either `commit` or `rollback` has the same
197    /// restoration behavior.
198    pub fn rollback(mut self) {
199        self.restore();
200    }
201
202    fn restore(&mut self) {
203        while let Some(item) = self.items.pop_back() {
204            self.source.items.push_front(item);
205        }
206        self.finished = true;
207    }
208}
209
210impl<T> Drop for Bite<'_, T> {
211    fn drop(&mut self) {
212        if !self.finished {
213            self.restore();
214        }
215    }
216}
217
218impl<'a, T> IntoIterator for &'a Bite<'_, T> {
219    type Item = &'a T;
220    type IntoIter = std::collections::vec_deque::Iter<'a, T>;
221
222    fn into_iter(self) -> Self::IntoIter {
223        self.items.iter()
224    }
225}
226
227/// Invalid bite request.
228#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
229#[non_exhaustive]
230pub enum BiteError {
231    /// A zero-sized bite was requested.
232    ZeroSize,
233    /// An exact bite requested more items than were queued.
234    InsufficientItems {
235        /// Exact number requested.
236        requested: usize,
237        /// Number available when requested.
238        available: usize,
239    },
240}
241
242impl Display for BiteError {
243    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
244        match self {
245            Self::ZeroSize => f.write_str("bite size must be greater than zero"),
246            Self::InsufficientItems {
247                requested,
248                available,
249            } => write!(
250                f,
251                "requested {requested} items, but only {available} are available"
252            ),
253        }
254    }
255}
256
257impl Error for BiteError {}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use std::panic::{AssertUnwindSafe, catch_unwind};
263
264    fn values(queue: &BiteQueue<i32>) -> Vec<i32> {
265        queue.iter().copied().collect()
266    }
267
268    #[test]
269    fn commit_permanently_consumes_a_prefix() {
270        let mut queue = BiteQueue::from_iter([1, 2, 3, 4]);
271        let committed = queue.bite(2).unwrap().commit();
272        assert_eq!(committed, [1, 2]);
273        assert_eq!(values(&queue), [3, 4]);
274    }
275
276    #[test]
277    fn drop_rolls_back_in_original_order() {
278        let mut queue = BiteQueue::from_iter([1, 2, 3]);
279        {
280            let bite = queue.bite(2).unwrap();
281            assert_eq!(bite.iter().copied().collect::<Vec<_>>(), [1, 2]);
282        }
283        assert_eq!(values(&queue), [1, 2, 3]);
284    }
285
286    #[test]
287    fn explicit_rollback_restores_the_prefix() {
288        let mut queue = BiteQueue::from_iter([1, 2, 3]);
289        queue.bite(2).unwrap().rollback();
290        assert_eq!(values(&queue), [1, 2, 3]);
291    }
292
293    #[test]
294    fn panic_unwinding_rolls_back() {
295        let mut queue = BiteQueue::from_iter([1, 2, 3]);
296        let result = catch_unwind(AssertUnwindSafe(|| {
297            let _bite = queue.bite(2).unwrap();
298            panic!("processing failed");
299        }));
300        assert!(result.is_err());
301        assert_eq!(values(&queue), [1, 2, 3]);
302    }
303
304    #[test]
305    fn bite_uses_at_most_the_requested_size() {
306        let mut queue = BiteQueue::from_iter([1, 2]);
307        let bite = queue.bite(10).unwrap();
308        assert_eq!(bite.len(), 2);
309    }
310
311    #[test]
312    fn exact_failure_does_not_change_the_queue() {
313        let mut queue = BiteQueue::from_iter([1, 2]);
314        assert_eq!(
315            queue.bite_exact(3).unwrap_err(),
316            BiteError::InsufficientItems {
317                requested: 3,
318                available: 2,
319            }
320        );
321        assert_eq!(values(&queue), [1, 2]);
322    }
323
324    #[test]
325    fn zero_size_is_rejected() {
326        let mut queue = BiteQueue::<i32>::new();
327        assert_eq!(queue.bite(0).unwrap_err(), BiteError::ZeroSize);
328        assert_eq!(queue.bite_exact(0).unwrap_err(), BiteError::ZeroSize);
329    }
330}