ts_io/
cursor.rs

1//! A simple cursor implementation to have sane error cases.
2
3#[derive(Clone, Debug)]
4/// A simple cursor over a slice.
5pub struct Cursor<'a, T: Copy + Default> {
6    /// The current index of the collection.
7    index: usize,
8    /// The collection.
9    collection: &'a [T],
10}
11
12impl<T: Copy + Default> core::fmt::Display for Cursor<'_, T> {
13    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
14        write!(f, "index {} of {}", self.index, self.collection.len())
15    }
16}
17
18impl<'a, T: Copy + Default> Cursor<'a, T> {
19    /// Pull some items from this source into the specified buffer, returning how many items were
20    /// read.
21    ///
22    /// If this function returns `0` then either:
23    /// 1. The `buffer` is of length zero.
24    /// 2. All items have been read from the source.
25    ///
26    /// # Panics
27    /// * If it attempts to read out of bounds, which should only happen if the implementation is
28    ///   incorrect.
29    pub fn read(&mut self, buffer: &mut [T]) -> usize {
30        let item_count = buffer.len().min(self.collection.len() - self.index);
31        if item_count == 0 {
32            return 0;
33        }
34        let data = self
35            .read_count(item_count)
36            .expect("read should never read out of bounds");
37        buffer
38            .get_mut(..item_count)
39            .expect("read should never read out of bounds")
40            .copy_from_slice(data);
41        item_count
42    }
43
44    /// Pull exactly `N` items from the source into an array.
45    pub fn read_array<const N: usize>(&mut self) -> Result<[T; N], OutOfBounds> {
46        let mut output = [T::default(); N];
47        let data = self.read_count(N)?;
48        output.copy_from_slice(data);
49        Ok(output)
50    }
51
52    /// Pull the next `count` items from the source.
53    pub fn read_count<N: Into<usize>>(&mut self, count: N) -> Result<&[T], OutOfBounds> {
54        let count = count.into();
55        let data = self
56            .collection
57            .get(self.index..self.index + count)
58            .ok_or_else(|| OutOfBounds::new(count))?;
59        self.index += count;
60
61        Ok(data)
62    }
63}
64
65impl std::io::Read for Cursor<'_, u8> {
66    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
67        Ok(self.read(buf))
68    }
69}
70
71/// A read would take the cursor out of bounds.
72#[derive(Clone, Copy, Debug)]
73#[non_exhaustive]
74pub struct OutOfBounds {
75    /// The number of items requested.
76    requested: usize,
77}
78impl OutOfBounds {
79    /// Create a new instance of an out of bounds error.
80    fn new(requested: usize) -> Self {
81        Self { requested }
82    }
83}
84impl core::fmt::Display for OutOfBounds {
85    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
86        write!(
87            f,
88            "reading {} items would take the cursor out of bounds",
89            self.requested,
90        )
91    }
92}
93impl core::error::Error for OutOfBounds {}