oxcache 0.1.4

A high-performance multi-level cache library for Rust with L1 (memory) and L2 (Redis) caching.
Documentation
//! Copyright (c) 2025-2026, Kirky.X
//!
//! MIT License
//!
//! 统一配置模块入口
//!
//! Feature-gated 配置系统:
//! - L1 配置需要 l1-moka feature  
//! - L2 配置需要 l2-redis feature
//! - confers 配置需要 confers feature

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub mod builder;
#[cfg(feature = "l1-moka")]
pub mod layer;
pub mod legacy_config;
pub mod service;
pub mod validation;

#[cfg(feature = "confers")]
pub mod confers_macro;

#[cfg(feature = "config-dynamic")]
pub mod dynamic;

#[cfg(feature = "confers")]
pub use confers_macro::confers_load as load_from_file;

#[cfg(feature = "l2-redis")]
pub use crate::config::legacy_config::{ClusterConfig, SentinelConfig};
pub use builder::OxcacheConfigBuilder;
#[cfg(feature = "l1-moka")]
pub use layer::{EvictionPolicy, L1LayerConfig, L2LayerConfig, LayerConfig, TwoLevelLayerConfig};
#[cfg(feature = "bloom-filter")]
pub use service::BloomFilterConfig;
#[cfg(feature = "l1-moka")]
pub use service::L1Config;
pub use service::{CacheType, RedisMode, ServiceConfig};
#[cfg(feature = "l2-redis")]
pub use service::{L2Config, TwoLevelConfig};
pub use validation::ConfigValidation;

pub use self::legacy_config::{
    CacheStrategy, CacheWarmupConfig, Config as LegacyConfig, DynamicConfig,
    EvictionPolicy as LegacyEvictionPolicy, GlobalConfig as LegacyGlobalConfig,
    InvalidationChannelConfig, RedisMode as LegacyRedisMode, SerializationType, WarmupDataSource,
};

/// 配置来源枚举
#[cfg(feature = "confers")]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub enum ConfigSource {
    Code,
    Macro(String),
    File(String),
}

/// 配置版本
pub const CONFIG_VERSION: u32 = 2;
pub const CONFIG_VERSION_FIELD: &str = "config_version";

/// 全局配置(始终可用)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalConfig {
    pub default_ttl: u64,
    pub health_check_interval: u64,
    pub serialization: SerializationType,
    pub enable_metrics: bool,
}

impl Default for GlobalConfig {
    fn default() -> Self {
        Self {
            default_ttl: 300,
            health_check_interval: 60,
            serialization: SerializationType::default(),
            enable_metrics: false,
        }
    }
}

impl GlobalConfig {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_default_ttl(mut self, ttl: u64) -> Self {
        self.default_ttl = ttl;
        self
    }

    pub fn with_health_check_interval(mut self, interval: u64) -> Self {
        self.health_check_interval = interval;
        self
    }

    pub fn with_serialization(mut self, serialization: SerializationType) -> Self {
        self.serialization = serialization;
        self
    }

    pub fn with_enable_metrics(mut self, enable: bool) -> Self {
        self.enable_metrics = enable;
        self
    }
}

/// 统一的配置入口结构体
#[derive(Debug, Clone, Default, Deserialize)]
pub struct OxcacheConfig {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub config_version: Option<u32>,
    pub global: GlobalConfig,
    pub services: HashMap<String, ServiceConfig>,
    #[cfg(feature = "l1-moka")]
    pub layer: Option<LayerConfig>,
    #[cfg(feature = "confers")]
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub extensions: HashMap<String, serde_json::Value>,
    #[cfg(feature = "confers")]
    pub source: Option<ConfigSource>,
}

impl OxcacheConfig {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn builder() -> OxcacheConfigBuilder {
        OxcacheConfigBuilder::new()
    }

    pub fn validate(&self) -> Result<(), String> {
        ConfigValidation::validate(self)
    }

    #[cfg(feature = "confers")]
    pub fn source(&self) -> &Option<ConfigSource> {
        &self.source
    }

    #[cfg(feature = "confers")]
    pub fn set_source(&mut self, source: ConfigSource) {
        self.source = Some(source);
    }

    pub fn is_l1_enabled(&self) -> bool {
        cfg!(feature = "l1-moka")
    }

    pub fn is_l2_enabled(&self) -> bool {
        cfg!(feature = "l2-redis")
    }

    pub fn available_features(&self) -> Vec<&'static str> {
        let mut features = Vec::new();

        add_feature_if_enabled!(features, "l1-moka");
        add_feature_if_enabled!(features, "l2-redis");
        add_feature_if_enabled!(features, "bloom-filter");
        add_feature_if_enabled!(features, "rate-limiting");
        add_feature_if_enabled!(features, "batch-write");
        add_feature_if_enabled!(features, "wal-recovery");
        add_feature_if_enabled!(features, "serialization");
        add_feature_if_enabled!(features, "compression");
        add_feature_if_enabled!(features, "database");
        add_feature_if_enabled!(features, "cli");
        add_feature_if_enabled!(features, "opentelemetry");
        add_feature_if_enabled!(features, "metrics");
        add_feature_if_enabled!(features, "confers");

        features
    }
}

/// 配置入口函数
pub fn oxcache_config() -> OxcacheConfigBuilder {
    OxcacheConfigBuilder::new()
}

/// 从文件加载配置(统一走 confers 路径)
///
/// 此函数在 confers feature 启用时可用
#[cfg(feature = "confers")]
pub fn load_config_from_file(path: &str) -> Result<OxcacheConfig, String> {
    confers_macro::confers_load(path)
}

#[deprecated(since = "0.2.0", note = "请使用 `OxcacheConfig` 替代 `Config`")]
pub type Config = OxcacheConfig;

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

    #[test]
    fn test_oxcache_config_default() {
        let config = OxcacheConfig::default();
        assert!(config.services.is_empty());
        #[cfg(feature = "l1-moka")]
        assert!(config.layer.is_none());
    }

    #[test]
    fn test_oxcache_config_builder() {
        let config = oxcache_config()
            .with_global(GlobalConfig::default())
            .build();
        assert!(!config.services.is_empty() || config.global.default_ttl == 300);
    }

    #[test]
    fn test_global_config_builder() {
        let global = GlobalConfig::new()
            .with_default_ttl(600)
            .with_health_check_interval(30)
            .with_serialization(SerializationType::Json)
            .with_enable_metrics(true);
        assert_eq!(global.default_ttl, 600);
    }

    #[test]
    fn test_feature_flags() {
        let config = OxcacheConfig::new();
        #[cfg(feature = "l1-moka")]
        assert!(config.is_l1_enabled());
        #[cfg(feature = "l2-redis")]
        assert!(config.is_l2_enabled());
    }
}