rollbuf 0.1.0

An extendable buffer which rolls the last incomplete part to the beginning so it can be completed and consumed.
Documentation
use std::io;
use std::error::Error;
use std::cmp;

const DAFAULT_BUF_SIZE: usize = 8 * 1024;

#[derive(Debug)]
pub struct RollBuf<R>  {
    inner: R,
    buf: Vec<u8>,
    pos: usize,
    end: usize,
    aux: Vec<u8>,
    is_rolled: bool,
    max_cap: usize,
}

impl<R: io::Read> RollBuf<R> {
    pub fn new(inner: R) -> RollBuf<R> {
        Self::with_capacity(DAFAULT_BUF_SIZE, inner)
    }

    pub fn with_capacity(cap: usize, inner: R) -> RollBuf<R> {
        RollBuf {
            inner ,
            buf: vec![0; cap],
            pos: 0,
            end: 0,
            aux: vec![0; cap],
            is_rolled: false,
            max_cap: 1 << 24,
        }
    }

    #[inline]
    pub fn capacity(&self) -> usize {
        self.buf.len()
    }

    #[inline]
    pub fn end_position(&self) -> usize {
        self.end
    }

    pub fn fill_buf(&mut self) -> Result<bool, Box<Error>> {
        if self.pos >= self.end {
            debug_assert!(self.pos == self.end);
            self.end = self.inner.read(&mut self.buf)?;
            self.pos = 0;
        }

        if self.is_rolled {
            self.end += self.inner.read(&mut self.buf[self.end..])?;
            self.is_rolled = false;
        }

        let is_full = self.buf.len() == self.end;
            
        Ok(is_full)
    }

    pub fn consume(&mut self, n: usize) {
        self.pos = cmp::min(self.pos + n, self.end);
    }

    pub fn roll(&mut self) {
        if self.pos > 0 {
            self.aux.clear();
            self.aux.extend_from_slice(&self.buf[self.pos..self.end]);
            let new_end = self.aux.len();
            self.buf[0..new_end].copy_from_slice(&self.aux);
            self.pos = 0;
            self.end = new_end;
        } else {
            let old_len = self.buf.len();
            let reserved = cmp::min(old_len, self.max_cap - old_len);
            self.buf.reserve(reserved);
            self.buf.extend((0..reserved).map(|_| 0));
        }
        self.is_rolled = true;
    }

    pub fn contents(&self) -> &[u8] {
        &self.buf[self.pos..self.end]
    }

    pub fn is_full(&self) -> bool {
        self.buf.len() == self.end
    }
}
        

#[cfg(test)]
mod tests {
    use super::{RollBuf};

    #[test]
    fn test_rollbuf_no_extend() {
        let inner: &[u8] = &[1, 2, 3, 4, 5, 6, 7];
        let mut b = RollBuf::with_capacity(3, inner);

        struct TestCase {
            consume: usize,
            roll: bool,
            want: (Vec<u8>, bool),
        }

        let test_cases = vec![
            TestCase { consume: 2, roll: false, want: (vec![1, 2, 3], true)  },
            TestCase { consume: 2, roll: false, want: (vec![3],       true)  },
            TestCase { consume: 1, roll: false, want: (vec![4, 5, 6], true)  },
            TestCase { consume: 2, roll: true,  want: (vec![6, 7],    false) },
            TestCase { consume: 2, roll: false, want: (vec![],        false) },
        ];

        for t in test_cases {
            b.consume(t.consume);
            if t.roll {
                b.roll();
            }
            let is_full = b.fill_buf().unwrap();
            let contents = b.contents();
            assert_eq!((contents, is_full), (t.want.0.as_slice(), t.want.1));
        }
    }

    #[test]
    fn test_rollbuf_extend() {
        let inner: &[u8] = &[1, 2, 3, 4, 5, 6, 7];
        let mut b = RollBuf::with_capacity(3, inner);

        struct TestCase {
            consume: usize,
            roll: bool,
            want: (Vec<u8>, bool),
        }

        let test_cases = vec![
            TestCase { consume: 2, roll: false, want: (vec![1, 2, 3],           true)  },
            TestCase { consume: 0, roll: true,  want: (vec![1, 2, 3, 4, 5, 6],  true)  },
            TestCase { consume: 4, roll: false, want: (vec![5, 6],              true)  },
            TestCase { consume: 2, roll: true,  want: (vec![7],                 false) },
            TestCase { consume: 2, roll: false, want: (vec![],                  false) },
        ];

        for t in test_cases {
            b.consume(t.consume);
            if t.roll {
                b.roll();
            }
            let is_full = b.fill_buf().unwrap();
            let contents = b.contents();
            assert_eq!((contents, is_full), (t.want.0.as_slice(), t.want.1));
        }
    }
}