#![deny(unsafe_code)]
#![deny(missing_debug_implementations)]
#![warn(missing_docs)]
pub mod app;
pub mod error;
#[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};
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());
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);
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(¶ms, "id").unwrap();
assert_eq!(id, 42);
let not_found = extract::path_param_parse::<u64>(¶ms, "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() {
let _app = prelude::App::new();
let _err = prelude::WebError::NotFound("test".to_string());
let _mw = prelude::LoggingMiddleware::new(false);
}
}