multi-tier-cache 0.6.7

Customizable multi-tier cache with L1 (Moka in-memory) + L2 (Redis distributed) defaults, expandable to L3/L4+, cross-instance invalidation via Pub/Sub, stampede protection, and flexible TTL scaling
Documentation
//! Cache Backend Implementations
//!
//! This module contains all cache backend implementations for the multi-tier cache system.
//!
//! # Available Backends
//!
//! ## In-Memory (L1 Tier)
//! - **Moka** - High-performance concurrent cache with automatic eviction (default L1)
//! - **`DashMap`** - Simple concurrent HashMap-based cache
//! - **`QuickCache`** - Lightweight, optimized for maximum performance (feature: `backend-quickcache`)
//!
//! ## Distributed (L2 Tier)
//! - **Redis** - Industry-standard distributed cache with persistence (default L2)
//! - **Memcached** - Lightweight distributed cache (feature: `backend-memcached`)
//!
//! ## On-Disk (L3/L4 Tier)
//! - **`RocksDB`** - Embedded persistent key-value store (coming soon)
//!
//! # Usage
//!
//! ```rust,no_run
//! use multi_tier_cache::backends::{MokaCache, MokaCacheConfig, RedisCache};
//!
//! # async fn example() -> anyhow::Result<()> {
//! // Explicit backend selection
//! let moka = MokaCache::new(MokaCacheConfig::default())?;
//! let redis = RedisCache::new().await?;
//!
//! // Or use type aliases for backward compatibility
//! use multi_tier_cache::backends::{L1Cache, L2Cache};
//! let l1 = L1Cache::new(MokaCacheConfig::default())?;  // MokaCache
//! let l2 = L2Cache::new().await?;  // RedisCache
//! # Ok(())
//! # }
//! ```

// Core backends (now optional via features)
// Core backends
pub mod dashmap_cache;

#[cfg(feature = "moka")]
#[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
pub mod moka_cache;

#[cfg(feature = "redis")]
#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
pub mod redis_cache;

// Optional backends (feature-gated)
#[cfg(feature = "memcached")]
#[cfg_attr(docsrs, doc(cfg(feature = "memcached")))]
pub mod memcached_cache;

#[cfg(feature = "quick_cache")]
#[cfg_attr(docsrs, doc(cfg(feature = "quick_cache")))]
pub mod quickcache_cache;

// Re-export backend types
pub use dashmap_cache::DashMapCache;

#[cfg(feature = "moka")]
#[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
pub use moka_cache::{MokaCache, MokaCacheConfig};

#[cfg(feature = "redis")]
#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
pub use redis_cache::RedisCache;

#[cfg(feature = "memcached")]
#[cfg_attr(docsrs, doc(cfg(feature = "memcached")))]
pub use memcached_cache::MemcachedCache;

#[cfg(feature = "quick_cache")]
#[cfg_attr(docsrs, doc(cfg(feature = "quick_cache")))]
pub use quickcache_cache::QuickCacheBackend;

// Type aliases for backward compatibility
// These allow existing code to continue working without changes
/// Type alias for `MokaCache` (default L1 backend)
#[cfg(feature = "moka")]
#[cfg_attr(docsrs, doc(cfg(feature = "moka")))]
pub type L1Cache = MokaCache;

/// Type alias for `RedisCache` (default L2 backend)
#[cfg(feature = "redis")]
#[cfg_attr(docsrs, doc(cfg(feature = "redis")))]
pub type L2Cache = RedisCache;

// Future backends will be added here with conditional compilation
// based on feature flags:

// #[cfg(feature = "backend-quickcache")]
// pub mod quickcache_cache;
// #[cfg(feature = "backend-quickcache")]
// pub use quickcache_cache::QuickCacheBackend;

// #[cfg(feature = "backend-dashmap")]
// pub mod dashmap_cache;
// #[cfg(feature = "backend-dashmap")]
// pub use dashmap_cache::DashMapCache;

// #[cfg(feature = "backend-rocksdb")]
// pub mod rocksdb_cache;
// #[cfg(feature = "backend-rocksdb")]
// pub use rocksdb_cache::RocksDBCache;

/// A simple glob-like pattern matching helper supporting '*' wildcard
#[must_use]
#[allow(clippy::indexing_slicing)]
pub fn matches_pattern(key: &str, pattern: &str) -> bool {
    let parts: Vec<&str> = pattern.split('*').collect();
    if parts.len() == 1 {
        return key == pattern;
    }
    if !key.starts_with(parts[0]) {
        return false;
    }
    let mut key_rem = &key[parts[0].len()..];
    for &part in &parts[1..parts.len() - 1] {
        if let Some(idx) = key_rem.find(part) {
            key_rem = &key_rem[idx + part.len()..];
        } else {
            return false;
        }
    }
    key_rem.ends_with(parts[parts.len() - 1])
}

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

    #[test]
    fn test_matches_pattern() {
        assert!(matches_pattern("user:123", "user:123"));
        assert!(matches_pattern("user:123", "user:*"));
        assert!(matches_pattern("user:123:profile", "user:*:profile"));
        assert!(matches_pattern(
            "user:123:profile:details",
            "user:*:details"
        ));
        assert!(!matches_pattern("product:123", "user:*"));
        assert!(matches_pattern("user:123", "*"));
        assert!(matches_pattern("user:123", "user:123*"));
        assert!(matches_pattern("user:123", "*123"));
        assert!(!matches_pattern("user:123", "user:1234*"));
    }
}