From 34e2aeca9eb74ad80caaf7e10fbaa79125122548 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 16 Jan 2026 01:13:29 +0300 Subject: [PATCH 1/3] Refactor route matching to occur after middleware Moved route matching logic into the middleware chain so that middleware (such as CORS) can intercept requests before route matching. Added a set_path_params method to Request for updating path parameters after route matching. --- crates/rustapi-core/src/request.rs | 5 ++ crates/rustapi-core/src/server.rs | 73 ++++++++++++++++-------------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/crates/rustapi-core/src/request.rs b/crates/rustapi-core/src/request.rs index 762ce03..8f909be 100644 --- a/crates/rustapi-core/src/request.rs +++ b/crates/rustapi-core/src/request.rs @@ -181,6 +181,11 @@ impl Request { &self.state } + /// Set path parameters (used internally after route matching) + pub(crate) fn set_path_params(&mut self, params: PathParams) { + self.path_params = params; + } + /// Create a test request from an http::Request /// /// This is useful for testing middleware and extractors. diff --git a/crates/rustapi-core/src/server.rs b/crates/rustapi-core/src/server.rs index d5536a0..b3cf511 100644 --- a/crates/rustapi-core/src/server.rs +++ b/crates/rustapi-core/src/server.rs @@ -88,54 +88,59 @@ async fn handle_request( // Convert hyper request to our Request type first let (parts, body) = req.into_parts(); - // Match the route to get path params - let (handler, params) = match router.match_route(&path, &method) { - RouteMatch::Found { handler, params } => (handler.clone(), params), - RouteMatch::NotFound => { - let response = ApiError::not_found(format!("No route found for {} {}", method, path)) - .into_response(); - log_request(&method, &path, response.status(), start); - return response; - } - RouteMatch::MethodNotAllowed { allowed } => { - let allowed_str: Vec<&str> = allowed.iter().map(|m| m.as_str()).collect(); - let mut response = ApiError::new( - StatusCode::METHOD_NOT_ALLOWED, - "method_not_allowed", - format!("Method {} not allowed for {}", method, path), - ) - .into_response(); - - response - .headers_mut() - .insert(header::ALLOW, allowed_str.join(", ").parse().unwrap()); - log_request(&method, &path, response.status(), start); - return response; - } - }; - - // Build Request (initially streaming) + // Build Request with empty path params (will be set after route matching) let request = Request::new( parts, crate::request::BodyVariant::Streaming(body), router.state_ref(), - params, + crate::path_params::PathParams::new(), ); // Apply request interceptors (in registration order) let request = interceptors.intercept_request(request); - // Create the final handler as a BoxedNext - let final_handler: BoxedNext = Arc::new(move |req: Request| { - let handler = handler.clone(); - Box::pin(async move { handler(req).await }) + // Create the routing handler that does route matching inside the middleware chain + // This allows CORS and other middleware to intercept requests BEFORE route matching + let router_clone = router.clone(); + let path_clone = path.clone(); + let method_clone = method.clone(); + let routing_handler: BoxedNext = Arc::new(move |mut req: Request| { + let router = router_clone.clone(); + let path = path_clone.clone(); + let method = method_clone.clone(); + Box::pin(async move { + match router.match_route(&path, &method) { + RouteMatch::Found { handler, params } => { + // Set path params on the request + req.set_path_params(params); + handler(req).await + } + RouteMatch::NotFound => { + ApiError::not_found(format!("No route found for {} {}", method, path)) + .into_response() + } + RouteMatch::MethodNotAllowed { allowed } => { + let allowed_str: Vec<&str> = allowed.iter().map(|m| m.as_str()).collect(); + let mut response = ApiError::new( + StatusCode::METHOD_NOT_ALLOWED, + "method_not_allowed", + format!("Method {} not allowed for {}", method, path), + ) + .into_response(); + response + .headers_mut() + .insert(header::ALLOW, allowed_str.join(", ").parse().unwrap()); + response + } + } + }) as std::pin::Pin< Box + Send + 'static>, > }); - // Execute through middleware stack - let response = layers.execute(request, final_handler).await; + // Execute through middleware stack - middleware runs FIRST, then routing + let response = layers.execute(request, routing_handler).await; // Apply response interceptors (in reverse registration order) let response = interceptors.intercept_response(response); From 1b211f92bd92d01f1f916a36885b6b55ba98bc3e Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 16 Jan 2026 01:42:32 +0300 Subject: [PATCH 2/3] ! --- crates/rustapi-rs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rustapi-rs/src/lib.rs b/crates/rustapi-rs/src/lib.rs index 989dd90..18ef9fd 100644 --- a/crates/rustapi-rs/src/lib.rs +++ b/crates/rustapi-rs/src/lib.rs @@ -8,7 +8,7 @@ //! //! ## Quick Start //! -//! ```rust +//! ```rust,no_run //! use rustapi_rs::prelude::*; //! //! #[derive(Serialize, Schema)] From 5ef9b1424522daa688f94512a3b6147d2243bd62 Mon Sep 17 00:00:00 2001 From: Tunay Engin Date: Fri, 16 Jan 2026 01:55:05 +0300 Subject: [PATCH 3/3] Update MSRV to 1.78 and remove MSRV CI job Bump the minimum supported Rust version to 1.78 in Cargo.toml and remove the MSRV check job from the CI workflow. This streamlines CI and aligns the project with the new Rust version requirement. --- .github/workflows/ci.yml | 10 ---------- Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c9df0d..2989519 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,16 +154,6 @@ jobs: env: RUSTDOCFLAGS: -D warnings - msrv: - name: Check MSRV - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install MSRV Rust - uses: dtolnay/rust-toolchain@1.75.0 - - name: Check - run: cargo check --workspace - semver: name: SemVer Checks runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 32e7776..a64bc8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ homepage = "https://github.com/Tuntii/RustAPI" documentation = "https://docs.rs/rustapi-rs" keywords = ["web", "framework", "api", "rest", "http"] categories = ["web-programming::http-server"] -rust-version = "1.75" +rust-version = "1.78" [workspace.dependencies] # Async runtime