use crate::middleware::v2::{MiddlewarePipelineV2, NextFuture};
use crate::request::ElifRequest;
use crate::response::{ElifResponse, ElifStatusCode};
use crate::routing::{HttpMethod, RouteMatch, RouteMatcher};
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PipelineError {
#[error("Route not found: {method} {path}")]
RouteNotFound { method: HttpMethod, path: String },
#[error("Method not allowed: {method}")]
MethodNotAllowed { method: String },
#[error("Parameter error: {0}")]
Parameter(#[from] ParamError),
#[error("Middleware error: {0}")]
Middleware(String),
#[error("Handler error: {0}")]
Handler(String),
#[error("Internal pipeline error: {0}")]
Internal(String),
}
#[derive(Error, Debug)]
pub enum ParamError {
#[error("Missing parameter: {0}")]
Missing(String),
#[error("Failed to parse parameter '{param}' with value '{value}': {error}")]
ParseError {
param: String,
value: String,
error: String,
},
}
pub type HandlerFn = dyn Fn(ElifRequest) -> NextFuture<'static> + Send + Sync;
#[derive(Debug, Clone)]
pub struct MiddlewareGroup {
pub name: String,
pub pipeline: MiddlewarePipelineV2,
}
pub struct RequestPipeline {
matcher: Arc<RouteMatcher>,
global_middleware: MiddlewarePipelineV2,
middleware_groups: HashMap<String, MiddlewarePipelineV2>,
handlers: Arc<HashMap<String, Arc<HandlerFn>>>,
}
impl RequestPipeline {
pub fn new(matcher: RouteMatcher) -> Self {
Self {
matcher: Arc::new(matcher),
global_middleware: MiddlewarePipelineV2::new(),
middleware_groups: HashMap::new(),
handlers: Arc::new(HashMap::new()),
}
}
pub fn add_global_middleware<M>(mut self, middleware: M) -> Self
where
M: crate::middleware::v2::Middleware + 'static,
{
self.global_middleware = self.global_middleware.add(middleware);
self
}
pub fn add_middleware_group<S: Into<String>>(
mut self,
name: S,
pipeline: MiddlewarePipelineV2,
) -> Self {
self.middleware_groups.insert(name.into(), pipeline);
self
}
pub fn add_handler<S: Into<String>, F>(mut self, route_id: S, handler: F) -> Self
where
F: Fn(ElifRequest) -> NextFuture<'static> + Send + Sync + 'static,
{
let handlers = Arc::get_mut(&mut self.handlers)
.expect("RequestPipeline handlers should be exclusively owned during building");
handlers.insert(route_id.into(), Arc::new(handler));
self
}
pub async fn process(&self, request: ElifRequest) -> ElifResponse {
match self.process_internal(request).await {
Ok(response) => response,
Err(error) => self.handle_pipeline_error(error),
}
}
async fn process_internal(&self, request: ElifRequest) -> Result<ElifResponse, PipelineError> {
let route_match = self.resolve_route(&request)?;
let request_with_params = self.inject_params(request, &route_match);
let complete_pipeline = self.build_route_pipeline(&route_match)?;
let route_id = route_match.route_id.clone();
let handlers = Arc::clone(&self.handlers);
let response = complete_pipeline.execute(request_with_params, move |req| {
let route_id = route_id.clone();
let handlers = Arc::clone(&handlers);
Box::pin(async move {
match handlers.get(&route_id) {
Some(handler) => handler(req).await,
None => {
ElifResponse::internal_server_error()
.with_json(&serde_json::json!({
"error": {
"code": "handler_not_found",
"message": format!("No handler registered for route: {}", route_id)
}
}))
}
}
})
}).await;
Ok(response)
}
fn resolve_route(&self, request: &ElifRequest) -> Result<RouteMatch, PipelineError> {
let http_method = match request.method.to_axum() {
&axum::http::Method::GET => HttpMethod::GET,
&axum::http::Method::POST => HttpMethod::POST,
&axum::http::Method::PUT => HttpMethod::PUT,
&axum::http::Method::DELETE => HttpMethod::DELETE,
&axum::http::Method::PATCH => HttpMethod::PATCH,
&axum::http::Method::HEAD => HttpMethod::HEAD,
&axum::http::Method::OPTIONS => HttpMethod::OPTIONS,
&axum::http::Method::TRACE => HttpMethod::TRACE,
unsupported => {
return Err(PipelineError::MethodNotAllowed {
method: unsupported.to_string(),
});
}
};
self.matcher
.resolve(&http_method, request.path())
.ok_or_else(|| PipelineError::RouteNotFound {
method: http_method,
path: request.path().to_string(),
})
}
fn inject_params(&self, mut request: ElifRequest, route_match: &RouteMatch) -> ElifRequest {
for (key, value) in &route_match.params {
request.add_path_param(key, value);
}
request
}
fn build_route_pipeline(
&self,
_route_match: &RouteMatch,
) -> Result<MiddlewarePipelineV2, PipelineError> {
let pipeline = self.global_middleware.clone();
Ok(pipeline)
}
fn handle_pipeline_error(&self, error: PipelineError) -> ElifResponse {
match error {
PipelineError::RouteNotFound { .. } => {
ElifResponse::not_found().with_json(&serde_json::json!({
"error": {
"code": "not_found",
"message": "The requested resource was not found"
}
}))
}
PipelineError::MethodNotAllowed { method } => ElifResponse::with_status(
ElifStatusCode::METHOD_NOT_ALLOWED,
)
.with_json(&serde_json::json!({
"error": {
"code": "method_not_allowed",
"message": format!("HTTP method '{}' is not supported", method),
"hint": "Supported methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE"
}
})),
PipelineError::Parameter(param_error) => {
ElifResponse::bad_request().with_json(&serde_json::json!({
"error": {
"code": "parameter_error",
"message": param_error.to_string()
}
}))
}
PipelineError::Middleware(msg)
| PipelineError::Handler(msg)
| PipelineError::Internal(msg) => {
ElifResponse::internal_server_error().with_json(&serde_json::json!({
"error": {
"code": "internal_error",
"message": msg
}
}))
}
}
}
pub fn stats(&self) -> PipelineStats {
PipelineStats {
total_routes: self.matcher.all_routes().len(),
global_middleware_count: self.global_middleware.len(),
middleware_groups: self.middleware_groups.len(),
registered_handlers: self.handlers.len(),
}
}
pub fn matcher(&self) -> &RouteMatcher {
&self.matcher
}
pub fn global_middleware(&self) -> &MiddlewarePipelineV2 {
&self.global_middleware
}
pub fn middleware_groups(&self) -> &HashMap<String, MiddlewarePipelineV2> {
&self.middleware_groups
}
}
impl std::fmt::Debug for RequestPipeline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RequestPipeline")
.field("matcher", &self.matcher)
.field("global_middleware", &self.global_middleware)
.field("middleware_groups", &self.middleware_groups)
.field("handlers", &self.handlers.len())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct PipelineStats {
pub total_routes: usize,
pub global_middleware_count: usize,
pub middleware_groups: usize,
pub registered_handlers: usize,
}
pub struct RequestPipelineBuilder {
matcher: Option<RouteMatcher>,
global_middleware: MiddlewarePipelineV2,
middleware_groups: HashMap<String, MiddlewarePipelineV2>,
handlers: HashMap<String, Arc<HandlerFn>>,
}
impl RequestPipelineBuilder {
pub fn new() -> Self {
Self {
matcher: None,
global_middleware: MiddlewarePipelineV2::new(),
middleware_groups: HashMap::new(),
handlers: HashMap::new(),
}
}
pub fn matcher(mut self, matcher: RouteMatcher) -> Self {
self.matcher = Some(matcher);
self
}
pub fn global_middleware<M>(mut self, middleware: M) -> Self
where
M: crate::middleware::v2::Middleware + 'static,
{
self.global_middleware = self.global_middleware.add(middleware);
self
}
pub fn middleware_group<S: Into<String>>(
mut self,
name: S,
pipeline: MiddlewarePipelineV2,
) -> Self {
self.middleware_groups.insert(name.into(), pipeline);
self
}
pub fn handler<S: Into<String>, F>(mut self, route_id: S, handler: F) -> Self
where
F: Fn(ElifRequest) -> NextFuture<'static> + Send + Sync + 'static,
{
self.handlers.insert(route_id.into(), Arc::new(handler));
self
}
pub fn build(self) -> Result<RequestPipeline, PipelineError> {
let matcher = self
.matcher
.ok_or_else(|| PipelineError::Internal("Route matcher is required".to_string()))?;
Ok(RequestPipeline {
matcher: Arc::new(matcher),
global_middleware: self.global_middleware,
middleware_groups: self.middleware_groups,
handlers: Arc::new(self.handlers),
})
}
}
impl Default for RequestPipelineBuilder {
fn default() -> Self {
Self::new()
}
}
pub mod parameter_extraction {
use super::{ElifRequest, ParamError};
use std::fmt::{Debug, Display};
use std::str::FromStr;
pub fn extract_path_param<T>(request: &ElifRequest, name: &str) -> Result<T, ParamError>
where
T: FromStr,
T::Err: Debug + Display,
{
let param_value = request
.path_param(name)
.ok_or_else(|| ParamError::Missing(name.to_string()))?;
param_value.parse().map_err(|e| ParamError::ParseError {
param: name.to_string(),
value: param_value.clone(),
error: format!("{}", e),
})
}
pub fn extract_query_param<T>(
request: &ElifRequest,
name: &str,
) -> Result<Option<T>, ParamError>
where
T: FromStr,
T::Err: Debug + Display,
{
if let Some(param_value) = request.query_param(name) {
let parsed = param_value.parse().map_err(|e| ParamError::ParseError {
param: name.to_string(),
value: param_value.clone(),
error: format!("{}", e),
})?;
Ok(Some(parsed))
} else {
Ok(None)
}
}
pub fn extract_required_query_param<T>(
request: &ElifRequest,
name: &str,
) -> Result<T, ParamError>
where
T: FromStr,
T::Err: Debug + Display,
{
extract_query_param(request, name)?.ok_or_else(|| ParamError::Missing(name.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::middleware::v2::{LoggingMiddleware, MiddlewarePipelineV2};
use crate::response::{ElifResponse, ElifStatusCode};
use crate::routing::RouteMatcherBuilder;
#[tokio::test]
async fn test_basic_pipeline_processing() {
let matcher = RouteMatcherBuilder::new()
.get("home".to_string(), "/".to_string())
.get("user_show".to_string(), "/users/{id}".to_string())
.build()
.unwrap();
let pipeline = RequestPipelineBuilder::new()
.matcher(matcher)
.handler("home", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("Welcome home!") })
})
.handler("user_show", |req| {
Box::pin(async move {
let user_id: u32 = match parameter_extraction::extract_path_param(&req, "id") {
Ok(id) => id,
Err(_) => return ElifResponse::bad_request().with_text("Invalid user ID"),
};
ElifResponse::ok().with_json(&serde_json::json!({
"user_id": user_id,
"message": "User details"
}))
})
})
.build()
.unwrap();
let home_request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let home_response = pipeline.process(home_request).await;
assert_eq!(home_response.status_code(), ElifStatusCode::OK);
let user_request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/users/123".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let user_response = pipeline.process(user_request).await;
assert_eq!(user_response.status_code(), ElifStatusCode::OK);
}
#[tokio::test]
async fn test_pipeline_with_middleware() {
let matcher = RouteMatcherBuilder::new()
.get("test".to_string(), "/test".to_string())
.build()
.unwrap();
let pipeline = RequestPipelineBuilder::new()
.matcher(matcher)
.global_middleware(LoggingMiddleware)
.handler("test", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("Test response") })
})
.build()
.unwrap();
let request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/test".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let response = pipeline.process(request).await;
assert_eq!(response.status_code(), ElifStatusCode::OK);
}
#[tokio::test]
async fn test_pipeline_route_not_found() {
let matcher = RouteMatcherBuilder::new()
.get("home".to_string(), "/".to_string())
.build()
.unwrap();
let pipeline = RequestPipelineBuilder::new()
.matcher(matcher)
.build()
.unwrap();
let request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/nonexistent".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let response = pipeline.process(request).await;
assert_eq!(response.status_code(), ElifStatusCode::NOT_FOUND);
}
#[tokio::test]
async fn test_pipeline_handler_not_found() {
let matcher = RouteMatcherBuilder::new()
.get("test".to_string(), "/test".to_string())
.build()
.unwrap();
let pipeline = RequestPipelineBuilder::new()
.matcher(matcher)
.build()
.unwrap();
let request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/test".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let response = pipeline.process(request).await;
assert_eq!(
response.status_code(),
ElifStatusCode::INTERNAL_SERVER_ERROR
);
}
#[tokio::test]
async fn test_parameter_extraction_helpers() {
let mut request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/users/123?page=2&limit=10".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
request.add_path_param("id", "123");
request.add_query_param("page", "2");
request.add_query_param("limit", "10");
let user_id: u32 = parameter_extraction::extract_path_param(&request, "id").unwrap();
assert_eq!(user_id, 123);
let page: Option<u32> =
parameter_extraction::extract_query_param(&request, "page").unwrap();
assert_eq!(page, Some(2));
let limit: u32 =
parameter_extraction::extract_required_query_param(&request, "limit").unwrap();
assert_eq!(limit, 10);
let result = parameter_extraction::extract_path_param::<u32>(&request, "nonexistent");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ParamError::Missing(_)));
request.add_path_param("invalid", "not_a_number");
let result = parameter_extraction::extract_path_param::<u32>(&request, "invalid");
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ParamError::ParseError { .. }));
}
#[tokio::test]
async fn test_pipeline_stats() {
let matcher = RouteMatcherBuilder::new()
.get("route1".to_string(), "/route1".to_string())
.get("route2".to_string(), "/route2".to_string())
.build()
.unwrap();
let middleware_group = MiddlewarePipelineV2::new().add(LoggingMiddleware);
let pipeline = RequestPipelineBuilder::new()
.matcher(matcher)
.global_middleware(LoggingMiddleware)
.middleware_group("auth", middleware_group)
.handler("route1", |_req| Box::pin(async move { ElifResponse::ok() }))
.handler("route2", |_req| Box::pin(async move { ElifResponse::ok() }))
.build()
.unwrap();
let stats = pipeline.stats();
assert_eq!(stats.total_routes, 2);
assert_eq!(stats.global_middleware_count, 1);
assert_eq!(stats.middleware_groups, 1);
assert_eq!(stats.registered_handlers, 2);
}
#[tokio::test]
async fn test_arc_optimization_efficiency() {
let matcher = RouteMatcherBuilder::new()
.get("test_route".to_string(), "/test".to_string())
.build()
.unwrap();
let mut builder = RequestPipelineBuilder::new().matcher(matcher);
for i in 0..100 {
builder = builder.handler(format!("handler_{}", i), |_req| {
Box::pin(async move { ElifResponse::ok() })
});
}
builder = builder.handler("test_route", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("Test response") })
});
let pipeline = builder.build().unwrap();
for _ in 0..10 {
let request = ElifRequest::new(
crate::request::ElifMethod::GET,
"/test".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let response = pipeline.process(request).await;
assert_eq!(response.status_code(), ElifStatusCode::OK);
}
let stats = pipeline.stats();
assert_eq!(stats.registered_handlers, 101);
}
#[tokio::test]
async fn test_method_not_allowed_error() {
let matcher = RouteMatcherBuilder::new()
.get("test".to_string(), "/test".to_string())
.build()
.unwrap();
let pipeline = RequestPipelineBuilder::new()
.matcher(matcher)
.handler("test", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("Test response") })
})
.build()
.unwrap();
let connect_method = crate::request::ElifMethod::from_axum(axum::http::Method::CONNECT);
let request = ElifRequest::new(
connect_method,
"/test".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let response = pipeline.process(request).await;
assert_eq!(response.status_code(), ElifStatusCode::METHOD_NOT_ALLOWED);
}
#[tokio::test]
async fn test_supported_methods_work() {
let matcher = RouteMatcherBuilder::new()
.get("get_test".to_string(), "/test".to_string())
.post("post_test".to_string(), "/test".to_string())
.put("put_test".to_string(), "/test".to_string())
.delete("delete_test".to_string(), "/test".to_string())
.build()
.unwrap();
let pipeline = RequestPipelineBuilder::new()
.matcher(matcher)
.handler("get_test", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("GET") })
})
.handler("post_test", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("POST") })
})
.handler("put_test", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("PUT") })
})
.handler("delete_test", |_req| {
Box::pin(async move { ElifResponse::ok().with_text("DELETE") })
})
.build()
.unwrap();
let methods = [
(crate::request::ElifMethod::GET, "GET"),
(crate::request::ElifMethod::POST, "POST"),
(crate::request::ElifMethod::PUT, "PUT"),
(crate::request::ElifMethod::DELETE, "DELETE"),
];
for (method, _expected_response) in methods {
let request = ElifRequest::new(
method,
"/test".parse().unwrap(),
crate::response::ElifHeaderMap::new(),
);
let response = pipeline.process(request).await;
assert_eq!(response.status_code(), ElifStatusCode::OK);
}
}
}