limiteron 0.3.0-rc.7

Unified flow control framework for Rust
Documentation
// Copyright (c) 2026 Kirky.X🌠
// SPDX-License-Identifier: MIT
//! HTTP服务器启动

#[cfg(feature = "ban-manager")]
use crate::BanManager;
#[cfg(feature = "circuit-breaker")]
use crate::CircuitBreaker;
use crate::Governor;
#[cfg(feature = "quota-control")]
use crate::QuotaController;
use crate::i18n::t;

use super::config::{AdminApiConfig, ConfigError};
use super::routes;
use axum::Router;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;

/// 应用状态
#[derive(Clone)]
pub struct LimiteronState {
    pub governor: Arc<Governor>,
    #[cfg(feature = "ban-manager")]
    pub ban_manager: Option<Arc<BanManager>>,
    #[cfg(feature = "quota-control")]
    pub quota_controller: Option<Arc<QuotaController>>,
    #[cfg(feature = "circuit-breaker")]
    pub circuit_breaker: Option<Arc<CircuitBreaker>>,
    /// Prometheus 指标实例(`/metrics` 数据源;prometheus feature 下存在)
    ///
    /// 未注入时 `/metrics` 回退到全局指标(`telemetry::try_global()`),
    /// 仍无则返回空 exposition(合法 Prometheus 文本),保持端点 200 契约。
    #[cfg(feature = "prometheus")]
    pub metrics: Option<Arc<crate::telemetry::Metrics>>,
}

impl LimiteronState {
    /// 构造协议无关管理服务面(组件引用为廉价 `Arc` 克隆)
    pub fn service(&self) -> super::service::GovernorAdminService {
        super::service::GovernorAdminService::from_state(self)
    }
}

/// 管理API服务器
pub struct AdminServer {
    state: LimiteronState,
    config: AdminApiConfig,
}

impl AdminServer {
    /// 创建新服务器
    pub fn new(governor: Arc<Governor>, config: AdminApiConfig) -> Self {
        Self {
            state: LimiteronState {
                governor,
                #[cfg(feature = "ban-manager")]
                ban_manager: None,
                #[cfg(feature = "quota-control")]
                quota_controller: None,
                #[cfg(feature = "circuit-breaker")]
                circuit_breaker: None,
                #[cfg(feature = "prometheus")]
                metrics: None,
            },
            config,
        }
    }

    /// 设置指标实例(`/metrics` 数据源)
    #[cfg(feature = "prometheus")]
    pub fn with_metrics(mut self, metrics: Arc<crate::telemetry::Metrics>) -> Self {
        self.state.metrics = Some(metrics);
        self
    }

    /// 设置封禁管理器
    #[cfg(feature = "ban-manager")]
    pub fn with_ban_manager(mut self, ban_manager: Arc<BanManager>) -> Self {
        self.state.ban_manager = Some(ban_manager);
        self
    }

    /// 设置配额控制器
    #[cfg(feature = "quota-control")]
    pub fn with_quota_controller(mut self, quota_controller: Arc<QuotaController>) -> Self {
        self.state.quota_controller = Some(quota_controller);
        self
    }

    /// 设置熔断器
    #[cfg(feature = "circuit-breaker")]
    pub fn with_circuit_breaker(mut self, circuit_breaker: Arc<CircuitBreaker>) -> Self {
        self.state.circuit_breaker = Some(circuit_breaker);
        self
    }

    /// 启动服务器
    pub async fn start(&self) -> Result<(), Box<dyn std::error::Error>> {
        if !self.config.enabled {
            log::info!(
                target: "admin-api",
                "{}",
                t("admin-api-disabled", &[])
            );
            return Ok(());
        }

        // 启动前强制校验配置(api_key 非空、长度 ≥16),防止以空 key 暴露管理 API
        self.config.validate()?;

        let router = routes::create_router(self.state.clone(), &self.config);

        let listener = TcpListener::bind(&self.config.address()).await?;
        let address = listener.local_addr()?;

        log::info!(
            target: "admin-api",
            "{}",
            t("admin-api-started", &[("address", address.to_string())])
        );

        // 启用 ConnectInfo<SocketAddr> 注入,
        // 使 routes middleware 可通过 req.extensions() 获取 TCP 连接远端 IP
        // 用于 per-client rate limit 分桶
        axum::serve(
            listener,
            router.into_make_service_with_connect_info::<SocketAddr>(),
        )
        .await?;

        Ok(())
    }

    /// 创建Router(不启动服务器)
    ///
    /// 与 [`Self::start`] 一致地先执行配置校验:以无效 api_key(空或过短)
    /// 构建的路由会让 "Bearer " 之类的畸形凭证通过鉴权,因此校验失败时
    /// 返回 Err 而非产出可被外部 runner 挂载的 Router。
    pub fn into_router(self) -> Result<Router, ConfigError> {
        self.config.validate()?;
        Ok(routes::create_router(self.state.clone(), &self.config))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::admin::AdminApiConfig;
    use crate::admin::handlers::tests::make_governor;

    #[tokio::test]
    async fn test_admin_server_new() {
        let governor = Arc::new(make_governor().await);
        let config = AdminApiConfig::new("test-api-key-16chars!!");
        let server = AdminServer::new(governor, config);
        // 验证 state 字段存在
        assert!(Arc::strong_count(&server.state.governor) >= 1);
        #[cfg(feature = "ban-manager")]
        assert!(server.state.ban_manager.is_none());
        #[cfg(feature = "quota-control")]
        assert!(server.state.quota_controller.is_none());
        #[cfg(feature = "circuit-breaker")]
        assert!(server.state.circuit_breaker.is_none());
    }

    #[cfg(feature = "ban-manager")]
    #[tokio::test]
    async fn test_admin_server_with_ban_manager() {
        use crate::BanManager;
        let governor = Arc::new(make_governor().await);
        let ban_manager = Arc::new(BanManager::new().await.unwrap());
        let config = AdminApiConfig::new("test-api-key-16chars!!");
        let server = AdminServer::new(governor, config).with_ban_manager(ban_manager);
        assert!(server.state.ban_manager.is_some());
    }

    #[cfg(feature = "quota-control")]
    #[tokio::test]
    async fn test_admin_server_with_quota_controller() {
        use crate::QuotaController;
        use crate::cache::CacheQuotaStorage;
        use crate::quota::QuotaConfig;
        use crate::storage::QuotaStorage;
        use oxcache::backend::memory::DashMapMemoryBackend;
        let governor = Arc::new(make_governor().await);
        let storage: Arc<dyn QuotaStorage> = Arc::new(CacheQuotaStorage::new(Arc::new(
            DashMapMemoryBackend::new(),
        )));
        let quota_controller = Arc::new(QuotaController::with_dependencies(
            storage,
            QuotaConfig::default(),
        ));
        let config = AdminApiConfig::new("test-api-key-16chars!!");
        let server = AdminServer::new(governor, config).with_quota_controller(quota_controller);
        assert!(server.state.quota_controller.is_some());
    }

    #[cfg(feature = "circuit-breaker")]
    #[tokio::test]
    async fn test_admin_server_with_circuit_breaker() {
        use crate::circuit::{CircuitBreaker, CircuitBreakerConfig};
        let governor = Arc::new(make_governor().await);
        let cb = Arc::new(CircuitBreaker::with_dependencies(
            CircuitBreakerConfig::default(),
        ));
        let config = AdminApiConfig::new("test-api-key-16chars!!");
        let server = AdminServer::new(governor, config).with_circuit_breaker(cb);
        assert!(server.state.circuit_breaker.is_some());
    }

    #[tokio::test]
    async fn test_admin_server_start_disabled() {
        let governor = Arc::new(make_governor().await);
        // disabled = true 的配置
        let config = AdminApiConfig::default();
        let server = AdminServer::new(governor, config);
        // start() 在 disabled 时应立即返回 Ok(())
        let result = server.start().await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_admin_server_into_router() {
        let governor = Arc::new(make_governor().await);
        let config = AdminApiConfig::new("test-api-key-16chars!!");
        let server = AdminServer::new(governor, config);
        // into_router 应返回一个 Router 而不启动服务器
        let _router = server.into_router().expect("valid config yields router");
        // 如果没有 panic 则说明路由创建成功
    }

    #[tokio::test]
    async fn test_admin_server_into_router_rejects_invalid_config() {
        let governor = Arc::new(make_governor().await);
        // enabled 但 api_key 为空 → 校验必须失败,不允许产出无凭证路由
        // (默认配置 enabled=false,空 key 合法,不触发校验)
        let config = AdminApiConfig {
            enabled: true,
            ..Default::default()
        };
        let server = AdminServer::new(governor, config);
        match server.into_router() {
            Err(ConfigError::ApiKeyRequired) => {}
            other => panic!("expected ApiKeyRequired, got {:?}", other),
        }
    }

    #[cfg(feature = "ban-manager")]
    #[tokio::test]
    async fn test_admin_server_chained_builders() {
        use crate::cache::CacheQuotaStorage;
        use crate::circuit::{CircuitBreaker, CircuitBreakerConfig};
        use crate::quota::QuotaConfig;
        use crate::storage::QuotaStorage;
        use crate::{BanManager, QuotaController};
        use oxcache::backend::memory::DashMapMemoryBackend;
        let governor = Arc::new(make_governor().await);
        let ban_manager = Arc::new(BanManager::new().await.unwrap());
        let storage: Arc<dyn QuotaStorage> = Arc::new(CacheQuotaStorage::new(Arc::new(
            DashMapMemoryBackend::new(),
        )));
        let quota_controller = Arc::new(QuotaController::with_dependencies(
            storage,
            QuotaConfig::default(),
        ));
        let cb = Arc::new(CircuitBreaker::with_dependencies(
            CircuitBreakerConfig::default(),
        ));
        let config = AdminApiConfig::new("test-api-key-16chars!!");
        let server = AdminServer::new(governor, config)
            .with_ban_manager(ban_manager)
            .with_quota_controller(quota_controller)
            .with_circuit_breaker(cb);
        assert!(server.state.ban_manager.is_some());
        assert!(server.state.quota_controller.is_some());
        assert!(server.state.circuit_breaker.is_some());
    }
}