heaplet 0.1.0

A small, in-process, Redis-inspired in-memory store for Rust.
Documentation
//! Local double-ended queue stored under a key.
//!
//! [`DequeRef`] is a lightweight, non-blocking deque API,
//! intended for in-process usage. Values are stored as bytes encoded via the store
//! [`Codec`].
//!
//! Features:
//! - push/pop both ends: `push_front`, `push_back`, `pop_front`, `pop_back`
//! - peek: `peek_front`, `peek_back`, `peek_at`
//! - indexed removal: `pop_at`
//! - maintenance: `len`, `is_empty`, `truncate`, `clear`

use serde::{Serialize, de::DeserializeOwned};

use crate::codec::{Bytes, Codec};
use crate::error::Error;
use crate::store::Store;

/// A Redis-inspired double-ended queue stored under a key.
///
/// Values are stored as encoded bytes using the store's [`Codec`].
/// This API is synchronous and intended for in-process usage.
///
/// # Semantics (MVP)
/// - Missing keys behave like an empty deque.
/// - Mutating operations may create an empty deque entry on demand.
///
/// # Example
///
/// ```rust
/// use heaplet::Store;
///
/// let store = Store::new();
/// let d = store.deque("d");
///
/// d.push_back(&1_i64).unwrap();
/// d.push_front(&0_i64).unwrap();
///
/// let front: Option<i64> = d.peek_front().unwrap();
/// assert_eq!(front, Some(0));
///
/// let x: Option<i64> = d.pop_back().unwrap();
/// assert_eq!(x, Some(1));
///
/// assert_eq!(d.len().unwrap(), 1);
/// d.clear().unwrap();
/// assert!(d.is_empty().unwrap());
/// ```
pub struct DequeRef<'a, C: Codec> {
    store: &'a Store<C>,
    key: &'a str,
}

impl<'a, C: Codec> DequeRef<'a, C> {
    pub(crate) fn new(store: &'a Store<C>, key: &'a str) -> Self {
        Self { store, key }
    }

    #[inline]
    fn enc<T: Serialize>(&self, v: &T) -> Result<Bytes, Error> {
        self.store.codec().encode(v)
    }

    #[inline]
    fn dec<T: DeserializeOwned>(&self, b: &[u8]) -> Result<T, Error> {
        self.store.codec().decode(b)
    }

    /// Pushes a value to the front of the deque.
    ///
    /// Returns the new length of the deque.
    pub fn push_front<T: Serialize>(&self, value: &T) -> Result<usize, Error> {
        let b = self.enc(value)?;
        self.store.with_deque_mut(self.key, |dq| {
            dq.push_front(b);
            Ok(dq.len())
        })
    }

    /// Pushes a value to the back of the deque.
    ///
    /// Returns the new length of the deque.
    pub fn push_back<T: Serialize>(&self, value: &T) -> Result<usize, Error> {
        let b = self.enc(value)?;
        self.store.with_deque_mut(self.key, |dq| {
            dq.push_back(b);
            Ok(dq.len())
        })
    }

    /// Pops a value from the front of the deque.
    ///
    /// Returns `Ok(None)` if the deque is empty or the key does not exist.
    pub fn pop_front<T: DeserializeOwned>(&self) -> Result<Option<T>, Error> {
        self.store.with_deque_mut(self.key, |dq| {
            let Some(b) = dq.pop_front() else {
                return Ok(None);
            };
            Ok(Some(self.dec::<T>(&b)?))
        })
    }

    /// Pops a value from the back of the deque.
    ///
    /// Returns `Ok(None)` if the deque is empty or the key does not exist.
    pub fn pop_back<T: DeserializeOwned>(&self) -> Result<Option<T>, Error> {
        self.store.with_deque_mut(self.key, |dq| {
            let Some(b) = dq.pop_back() else {
                return Ok(None);
            };
            Ok(Some(self.dec::<T>(&b)?))
        })
    }

    /// Peeks the front value without removing it.
    ///
    /// Returns `Ok(None)` if the deque is empty or the key does not exist.
    pub fn peek_front<T: DeserializeOwned>(&self) -> Result<Option<T>, Error> {
        self.store.with_deque_read(self.key, |opt| {
            let Some(dq) = opt else {
                return Ok(None);
            };
            let Some(b) = dq.front() else {
                return Ok(None);
            };
            Ok(Some(self.dec::<T>(b)?))
        })
    }

    /// Peeks the back value without removing it.
    ///
    /// Returns `Ok(None)` if the deque is empty or the key does not exist.
    pub fn peek_back<T: DeserializeOwned>(&self) -> Result<Option<T>, Error> {
        self.store.with_deque_read(self.key, |opt| {
            let Some(dq) = opt else {
                return Ok(None);
            };
            let Some(b) = dq.back() else {
                return Ok(None);
            };
            Ok(Some(self.dec::<T>(b)?))
        })
    }

    /// Removes and returns the value at `index`.
    ///
    /// Returns `Ok(None)` if `index` is out of bounds or the deque/key is missing.
    pub fn pop_at<T: DeserializeOwned>(&self, index: usize) -> Result<Option<T>, Error> {
        self.store.with_deque_mut(self.key, |dq| {
            let Some(b) = dq.remove(index) else {
                return Ok(None);
            };
            Ok(Some(self.dec::<T>(&b)?))
        })
    }

    /// Returns the value at `index` without removing it.
    ///
    /// Returns `Ok(None)` if `index` is out of bounds or the deque/key is missing.
    pub fn peek_at<T: DeserializeOwned>(&self, index: usize) -> Result<Option<T>, Error> {
        self.store.with_deque_read(self.key, |opt| {
            let Some(dq) = opt else {
                return Ok(None);
            };
            let Some(b) = dq.get(index) else {
                return Ok(None);
            };
            Ok(Some(self.dec::<T>(b)?))
        })
    }

    /// Truncates the deque to at most `len` elements.
    pub fn truncate(&self, len: usize) -> Result<(), Error> {
        self.store.with_deque_mut(self.key, |dq| {
            dq.truncate(len);
            Ok(())
        })
    }

    /// Returns the number of elements in the deque.
    pub fn len(&self) -> Result<usize, Error> {
        self.store
            .with_deque_read(self.key, |opt| Ok(opt.map(|d| d.len()).unwrap_or(0)))
    }

    /// Returns `true` if the deque is empty.
    pub fn is_empty(&self) -> Result<bool, Error> {
        Ok(self.len()? == 0)
    }

    /// Removes all elements from the deque.
    pub fn clear(&self) -> Result<(), Error> {
        self.store.with_deque_mut(self.key, |dq| {
            dq.clear();
            Ok(())
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::Store;

    #[test]
    fn deque_basic_ops() {
        let store = Store::new();
        let d = store.deque("d");

        d.push_back(&1_i64).unwrap();
        d.push_front(&0_i64).unwrap();
        assert_eq!(d.len().unwrap(), 2);
        assert!(!d.is_empty().unwrap());

        let a: Option<i64> = d.peek_front().unwrap();
        assert_eq!(a, Some(0));

        let x: Option<i64> = d.pop_at(1).unwrap();
        assert_eq!(x, Some(1));

        assert_eq!(d.len().unwrap(), 1);
        d.clear().unwrap();
        assert_eq!(d.len().unwrap(), 0);
        assert!(d.is_empty().unwrap());
    }

    #[test]
    fn deque_cover_more_paths() {
        let store = Store::new();
        let d = store.deque("d2");

        d.push_front(&"a").unwrap();
        d.push_back(&"b").unwrap();

        let back: Option<String> = d.peek_back().unwrap();
        assert_eq!(back.as_deref(), Some("b"));

        let x: Option<String> = d.pop_back().unwrap();
        assert_eq!(x.as_deref(), Some("b"));

        d.push_back(&"c").unwrap();
        d.push_back(&"d").unwrap();
        d.truncate(1).unwrap();
        assert_eq!(d.len().unwrap(), 1);

        let y: Option<String> = d.peek_at(0).unwrap();
        assert!(y.is_some());
    }
}