heaplet 0.1.0

A small, in-process, Redis-inspired in-memory store for Rust.
Documentation
//! Internal entry types stored in the global key space.
//!
//! Each key maps to an [`Entry`], which carries:
//! - a logical [`ValueType`] (string/hash/set/list/deque/zset)
//! - [`Meta`] (including optional expiration timestamp)
//!
//! Most users won't interact with this module directly; use the typed facades on
//! [`Store`](crate::Store) instead (e.g. `kv()`, `hash("k")`, `zset("k")`).

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::sync::{Arc, Condvar, Mutex};
use std::time::Instant;

use ordered_float::OrderedFloat;

use crate::codec::Bytes;

/// The logical value type stored under a key.
///
/// This is used for type checking and for the `TYPE`/`type()` style API surface.
/// It is intentionally stable and user-facing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueType {
    /// A single opaque value (stored as bytes).
    String,
    /// A hash map of field -> value.
    Hash,
    /// An unordered set of members.
    Set,
    /// A list with blocking pop support.
    List,
    /// A local double-ended queue (non-blocking).
    Deque,
    /// A sorted set (member -> score) with range queries.
    ZSet,
}

impl ValueType {
    /// Returns a stable, lowercase type name.
    ///
    /// This string is intended for diagnostics and for a Redis-like `TYPE` result.
    #[must_use]
    pub fn as_str(self) -> &'static str {
        match self {
            ValueType::String => "string",
            ValueType::Hash => "hash",
            ValueType::Set => "set",
            ValueType::List => "list",
            ValueType::Deque => "deque",
            ValueType::ZSet => "zset",
        }
    }
}

/// Metadata attached to each entry.
///
/// - `value_type` is used for runtime type checks (`WrongType` errors).
/// - `expire_at` is an optional deadline used for key expiration.
///
/// Expiration is implemented as **lazy TTL**: the store checks `expire_at` on access and
/// removes the key when it has expired.
#[derive(Debug, Clone, Copy)]
pub struct Meta {
    /// The entry's logical value type.
    pub value_type: ValueType,
    /// Optional expiration deadline.
    pub expire_at: Option<Instant>,
}

impl Meta {
    /// Creates metadata for a non-expiring entry of the given type.
    #[must_use]
    pub fn new(value_type: ValueType) -> Self {
        Self {
            value_type,
            expire_at: None,
        }
    }
}

/// A string entry stores a single value as encoded bytes.
#[derive(Debug, Clone)]
pub struct StringEntry {
    pub meta: Meta,
    pub bytes: Bytes,
}

/// A hash entry stores a mapping of encoded field -> encoded value.
#[derive(Debug, Clone)]
pub struct HashEntry {
    pub meta: Meta,
    pub map: HashMap<Bytes, Bytes>,
}

/// A set entry stores encoded members.
#[derive(Debug, Clone)]
pub struct SetEntry {
    pub meta: Meta,
    pub set: HashSet<Bytes>,
}

/// Inner list state guarded by a `Mutex` for blocking operations.
#[derive(Debug)]
pub struct ListInner {
    pub deque: VecDeque<Bytes>,
}

/// A list entry supports blocking pops via a `(Mutex, Condvar)` pair.
///
/// The mutex guards the deque, and the condvar is used to wake waiters on push.
/// The pair is wrapped in an `Arc` so list references can clone the handle cheaply.
#[derive(Debug, Clone)]
pub struct ListEntry {
    pub meta: Meta,
    pub inner: Arc<(Mutex<ListInner>, Condvar)>,
}

/// A deque entry stores elements in a local `VecDeque`.
#[derive(Debug, Clone)]
pub struct DequeEntry {
    pub meta: Meta,
    pub deque: VecDeque<Bytes>,
}

/// A sorted set entry with both directions indexed.
///
/// - `member_to_score` is used for `zscore`, `zincrby`, and updates.
/// - `score_to_members` is used for range queries and rank operations.
///   Members are stored in a `BTreeSet` to make ordering deterministic (tie-break by bytes).
#[derive(Debug, Clone)]
pub struct ZSetEntry {
    pub meta: Meta,
    pub member_to_score: HashMap<Bytes, f64>,
    pub score_to_members: BTreeMap<OrderedFloat<f64>, BTreeSet<Bytes>>,
}

/// A typed entry stored in the global key space.
#[derive(Debug, Clone)]
pub enum Entry {
    String(StringEntry),
    Hash(HashEntry),
    Set(SetEntry),
    List(ListEntry),
    Deque(DequeEntry),
    ZSet(ZSetEntry),
}

impl Entry {
    /// Returns this entry's logical value type.
    #[must_use]
    pub fn value_type(&self) -> ValueType {
        match self {
            Entry::String(_) => ValueType::String,
            Entry::Hash(_) => ValueType::Hash,
            Entry::Set(_) => ValueType::Set,
            Entry::List(_) => ValueType::List,
            Entry::Deque(_) => ValueType::Deque,
            Entry::ZSet(_) => ValueType::ZSet,
        }
    }

    /// Returns an immutable view of the entry metadata.
    #[must_use]
    pub fn meta(&self) -> &Meta {
        match self {
            Entry::String(e) => &e.meta,
            Entry::Hash(e) => &e.meta,
            Entry::Set(e) => &e.meta,
            Entry::List(e) => &e.meta,
            Entry::Deque(e) => &e.meta,
            Entry::ZSet(e) => &e.meta,
        }
    }

    /// Returns a mutable view of the entry metadata.
    pub fn meta_mut(&mut self) -> &mut Meta {
        match self {
            Entry::String(e) => &mut e.meta,
            Entry::Hash(e) => &mut e.meta,
            Entry::Set(e) => &mut e.meta,
            Entry::List(e) => &mut e.meta,
            Entry::Deque(e) => &mut e.meta,
            Entry::ZSet(e) => &mut e.meta,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::Duration;

    fn b(s: &str) -> Bytes {
        Arc::from(s.as_bytes())
    }

    #[test]
    fn test_meta() {
        let meta = Meta::new(ValueType::String);
        assert_eq!(meta.value_type, ValueType::String);
        assert_eq!(meta.expire_at, None);

        let meta_with_expiry = Meta {
            value_type: ValueType::Hash,
            expire_at: Some(Instant::now() + Duration::from_secs(10)),
        };
        assert_eq!(meta_with_expiry.value_type, ValueType::Hash);
        assert!(meta_with_expiry.expire_at.is_some());
    }

    #[test]
    fn test_string_entry() {
        let bytes: Bytes = Arc::from(vec![1, 2, 3]);
        let e = StringEntry {
            meta: Meta::new(ValueType::String),
            bytes: bytes.clone(),
        };
        assert_eq!(e.meta.value_type, ValueType::String);
        assert_eq!(&*e.bytes, &*bytes);
    }

    #[test]
    fn test_hash_entry() {
        let map = HashMap::from([(b("key"), b("value"))]);
        let e = HashEntry {
            meta: Meta::new(ValueType::Hash),
            map: map.clone(),
        };
        assert_eq!(e.meta.value_type, ValueType::Hash);
        assert_eq!(e.map, map);
    }

    #[test]
    fn test_set_entry() {
        let set = HashSet::from([b("member")]);
        let e = SetEntry {
            meta: Meta::new(ValueType::Set),
            set: set.clone(),
        };
        assert_eq!(e.meta.value_type, ValueType::Set);
        assert_eq!(e.set, set);
    }

    #[test]
    fn test_list_entry() {
        let inner = Arc::new((
            Mutex::new(ListInner {
                deque: VecDeque::new(),
            }),
            Condvar::new(),
        ));
        let e = ListEntry {
            meta: Meta::new(ValueType::List),
            inner: inner.clone(),
        };
        assert_eq!(e.meta.value_type, ValueType::List);
        assert!(Arc::ptr_eq(&e.inner, &inner));
    }

    #[test]
    fn test_deque_entry() {
        let deque = VecDeque::from(vec![b("item1"), b("item2")]);
        let e = DequeEntry {
            meta: Meta::new(ValueType::Deque),
            deque: deque.clone(),
        };
        assert_eq!(e.meta.value_type, ValueType::Deque);
        assert_eq!(e.deque, deque);
    }

    #[test]
    fn test_zset_entry() {
        let member_to_score = HashMap::from([(b("member"), 1.0)]);
        let score_to_members = BTreeMap::from([(OrderedFloat(1.0), BTreeSet::from([b("member")]))]);

        let e = ZSetEntry {
            meta: Meta::new(ValueType::ZSet),
            member_to_score: member_to_score.clone(),
            score_to_members: score_to_members.clone(),
        };
        assert_eq!(e.meta.value_type, ValueType::ZSet);
        assert_eq!(e.member_to_score, member_to_score);
        assert_eq!(e.score_to_members, score_to_members);
    }
}