zenith-web 0.1.0

Zenith Web 应用框架:编译期 Trie 路由、类型化 Extractor、中间件 DAG、静态文件服务、统一错误处理
//! Zenith Web - Web 应用框架
//!
//! 本 crate 提供构建高性能 Web 应用的核心抽象,包括:
//! - 编译期路由系统(静态/动态参数/通配符)
//! - 类型化 Extractor/Responder(路径参数、查询参数、头部提取)
//! - 中间件系统(链式执行、前置/后置处理、短路支持)
//! - 统一错误处理(WebError、panic 保护、错误响应格式化)
//! - 静态文件服务(Range 请求、条件请求、缓存控制)
//! - App 构建器(完整请求链路、中间件集成、路由分发)
//!
//! # 设计原则
//! - 全链路零堆分配热路径
//! - 单线程本地存储、无锁访问
//! - 编译期路由注册、运行期零开销分发

#![deny(unsafe_code)]
#![deny(missing_debug_implementations)]
#![warn(missing_docs)]

pub mod app;
pub mod error;
/// AF_XDP → HTTP/3 数据面桥接
/// (仅 `afxdp` 特性 + Linux:依赖 zenith-net 真实 AF_XDP Worker 与 zenith-ebpf BpfMaps)
#[cfg(all(feature = "afxdp", target_os = "linux"))]
pub mod afxdp_bridge;
pub mod extract;
pub mod middleware;
pub mod normalize;
pub mod quic_server;
pub mod router;
pub mod server;
/// 静态文件服务模块
#[path = "static.rs"]
pub mod static_;

// 重新导出核心类型
pub use app::{App, HandlerFn, HandlerEntry};
pub use error::{
    bad_request, conflict, forbidden, internal_error, method_not_allowed, not_found,
    service_unavailable, too_many_requests, unauthorized, unprocessable,
    catch_panic, WebError, RouterError, MiddlewareError,
};
pub use extract::{
    path_param, path_param_parse, query_param, query_param_or, query_param_parse,
    header_value, header_required, parse_query, ExtractError, Pagination, UserId,
};
pub use middleware::{
    AuthMiddleware, CorsMiddleware, IdentityMiddleware, LoggingMiddleware, Middleware,
    MiddlewareChain, MiddlewareContext, RequestIdMiddleware,
};
pub use normalize::{
    alpn_to_protocol, encode_response_http1, encode_response_http2, encode_response_http3,
    normalize_http1_request, normalize_http2_request, normalize_http3_request, parse_method,
    ProtocolNormalizeError,
};
pub use router::{RouteEntry, RouteMatch, RouteMethod, Router};
pub use server::{FingerprintMatch, ProtocolServer, ServerConfig, ServerError};
pub use static_::{StaticConfig, StaticFileServer};

/// prelude 模块:常用类型和函数
pub mod prelude {
    pub use crate::app::App;
    pub use crate::error::{WebError, RouterError, MiddlewareError, catch_panic};
    pub use crate::extract::{ExtractError, Pagination, UserId};
    pub use crate::middleware::{
        Middleware, MiddlewareChain, MiddlewareContext, CorsMiddleware, IdentityMiddleware,
        LoggingMiddleware, RequestIdMiddleware, AuthMiddleware,
    };
    pub use crate::normalize::{
        alpn_to_protocol, encode_response_http1, encode_response_http2, encode_response_http3,
        normalize_http1_request, normalize_http2_request, normalize_http3_request,
        ProtocolNormalizeError,
    };
    pub use crate::router::{RouteMethod, RouteMatch, Router};
    pub use crate::server::{ProtocolServer, ServerConfig, ServerError};
    pub use crate::static_::{StaticConfig, StaticFileServer};

    pub use zenith_api::{CanonicalRequest, CanonicalResponse, Method, Protocol, Transport};
}

#[cfg(test)]
mod tests {
    use super::*;
    use rustc_hash::FxHashMap;
    use zenith_api::{CanonicalRequest, CanonicalResponse, Method};

    #[test]
    fn test_app_full_lifecycle() {
        let mut app = App::new();

        // 注册路由
        app.get("/", |_req, _params| {
            Ok(CanonicalResponse::new(200))
        });
        app.get("/health", |_req, _params| {
            Ok(CanonicalResponse::new(204))
        });
        app.post("/api/data", |_req, _params| {
            Ok(CanonicalResponse::new(201))
        });
        app.get("/users/:id", |_req, params| {
            let id = params.get("id").unwrap_or_default();
            let mut resp = CanonicalResponse::new(200);
            resp.set_body(format!("User: {}", id).into_bytes());
            Ok(resp)
        });

        // 验证路由数量
        assert_eq!(app.route_count(), 4);
        assert!(app.validate().is_ok());

        // 测试各种请求(handle 拿走 request 所有权,每次需重新构造)
        let mut req = CanonicalRequest::empty();
        let _ = req.set_path("/");
        assert_eq!(app.handle(req).status_code, 200);

        let mut req = CanonicalRequest::empty();
        let _ = req.set_path("/health");
        assert_eq!(app.handle(req).status_code, 204);

        let mut req = CanonicalRequest::empty();
        req.method = Method::Post;
        let _ = req.set_path("/api/data");
        assert_eq!(app.handle(req).status_code, 201);
    }

    #[test]
    fn test_error_handling() {
        let mut app = App::new();

        app.get("/error", |_req, _params| {
            Err(WebError::InternalError("Test error".to_string()))
        });

        app.get("/panic", |_req, _params| {
            let result = std::panic::catch_unwind(|| {
                panic!("test panic");
            });
            match result {
                Ok(_) => Ok(CanonicalResponse::new(200)),
                Err(_) => Err(WebError::InternalError("Panic recovered".to_string())),
            }
        });

        // 错误处理
        let mut req = CanonicalRequest::empty();
        let _ = req.set_path("/error");
        let resp = app.handle(req);
        assert_eq!(resp.status_code, 500);

        // NotFound
        let mut req = CanonicalRequest::empty();
        let _ = req.set_path("/nonexistent");
        let resp = app.handle(req);
        assert_eq!(resp.status_code, 404);
    }

    #[test]
    fn test_middleware_chain_full() {
        let mut app = App::new();

        app.middleware(LoggingMiddleware::new(false));
        app.middleware(CorsMiddleware::new().with_origin("*"));
        app.middleware(RequestIdMiddleware::new());

        app.get("/api", |_req, _params| {
            Ok(CanonicalResponse::new(200))
        });

        let mut req = CanonicalRequest::empty();
        let _ = req.set_path("/api");
        let resp = app.handle(req);

        assert_eq!(resp.status_code, 200);
        assert!(resp.find_header("access-control-allow-origin").is_some());
        assert!(resp.find_header("x-request-id").is_some());
    }

    #[test]
    fn test_extractors_integration() {
        // 测试参数提取逻辑
        let mut params = FxHashMap::default();
        params.insert("id".to_string(), "42".to_string());

        let id: u64 = extract::path_param_parse(&params, "id").unwrap();
        assert_eq!(id, 42);

        let not_found = extract::path_param_parse::<u64>(&params, "missing");
        assert!(not_found.is_err());
    }

    #[test]
    fn test_static_config() {
        let config = StaticConfig::new("/var/www")
            .with_default_file("index.html")
            .with_cache_max_age(3600)
            .with_range(true)
            .with_conditional(true);

        assert_eq!(config.root, std::path::PathBuf::from("/var/www"));
        assert_eq!(config.default_file, "index.html");
        assert_eq!(config.cache_max_age, 3600);
        assert!(config.enable_range);
        assert!(config.enable_conditional);
    }

    #[test]
    fn test_web_error_types() {
        let errors = vec![
            WebError::BadRequest("test".to_string()),
            WebError::Unauthorized("test".to_string()),
            WebError::Forbidden("test".to_string()),
            WebError::NotFound("test".to_string()),
            WebError::MethodNotAllowed("test".to_string()),
            WebError::Conflict("test".to_string()),
            WebError::UnprocessableEntity("test".to_string()),
            WebError::TooManyRequests("test".to_string()),
            WebError::InternalError("test".to_string()),
            WebError::NotImplemented("test".to_string()),
            WebError::ServiceUnavailable("test".to_string()),
        ];

        for err in errors {
            let resp = err.into_response();
            assert!(resp.status_code >= 400);
            assert!(resp.find_header("content-type").is_some());
        }
    }

    #[test]
    fn test_prelude_access() {
        // 确保 prelude 模块可访问
        let _app = prelude::App::new();
        let _err = prelude::WebError::NotFound("test".to_string());
        let _mw = prelude::LoggingMiddleware::new(false);
    }
}