Skip to content

Commit 5620ff3

Browse files
authored
Merge pull request #36 from Tuntii/options-method-not-allowed
Refactor route matching to occur after middleware
2 parents d848973 + 5ef9b14 commit 5620ff3

5 files changed

Lines changed: 46 additions & 46 deletions

File tree

.github/workflows/ci.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,6 @@ jobs:
154154
env:
155155
RUSTDOCFLAGS: -D warnings
156156

157-
msrv:
158-
name: Check MSRV
159-
runs-on: ubuntu-latest
160-
steps:
161-
- uses: actions/checkout@v4
162-
- name: Install MSRV Rust
163-
uses: dtolnay/rust-toolchain@1.75.0
164-
- name: Check
165-
run: cargo check --workspace
166-
167157
semver:
168158
name: SemVer Checks
169159
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ homepage = "https://github.com/Tuntii/RustAPI"
3030
documentation = "https://docs.rs/rustapi-rs"
3131
keywords = ["web", "framework", "api", "rest", "http"]
3232
categories = ["web-programming::http-server"]
33-
rust-version = "1.75"
33+
rust-version = "1.78"
3434

3535
[workspace.dependencies]
3636
# Async runtime

crates/rustapi-core/src/request.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ impl Request {
181181
&self.state
182182
}
183183

184+
/// Set path parameters (used internally after route matching)
185+
pub(crate) fn set_path_params(&mut self, params: PathParams) {
186+
self.path_params = params;
187+
}
188+
184189
/// Create a test request from an http::Request
185190
///
186191
/// This is useful for testing middleware and extractors.

crates/rustapi-core/src/server.rs

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -88,54 +88,59 @@ async fn handle_request(
8888
// Convert hyper request to our Request type first
8989
let (parts, body) = req.into_parts();
9090

91-
// Match the route to get path params
92-
let (handler, params) = match router.match_route(&path, &method) {
93-
RouteMatch::Found { handler, params } => (handler.clone(), params),
94-
RouteMatch::NotFound => {
95-
let response = ApiError::not_found(format!("No route found for {} {}", method, path))
96-
.into_response();
97-
log_request(&method, &path, response.status(), start);
98-
return response;
99-
}
100-
RouteMatch::MethodNotAllowed { allowed } => {
101-
let allowed_str: Vec<&str> = allowed.iter().map(|m| m.as_str()).collect();
102-
let mut response = ApiError::new(
103-
StatusCode::METHOD_NOT_ALLOWED,
104-
"method_not_allowed",
105-
format!("Method {} not allowed for {}", method, path),
106-
)
107-
.into_response();
108-
109-
response
110-
.headers_mut()
111-
.insert(header::ALLOW, allowed_str.join(", ").parse().unwrap());
112-
log_request(&method, &path, response.status(), start);
113-
return response;
114-
}
115-
};
116-
117-
// Build Request (initially streaming)
91+
// Build Request with empty path params (will be set after route matching)
11892
let request = Request::new(
11993
parts,
12094
crate::request::BodyVariant::Streaming(body),
12195
router.state_ref(),
122-
params,
96+
crate::path_params::PathParams::new(),
12397
);
12498

12599
// Apply request interceptors (in registration order)
126100
let request = interceptors.intercept_request(request);
127101

128-
// Create the final handler as a BoxedNext
129-
let final_handler: BoxedNext = Arc::new(move |req: Request| {
130-
let handler = handler.clone();
131-
Box::pin(async move { handler(req).await })
102+
// Create the routing handler that does route matching inside the middleware chain
103+
// This allows CORS and other middleware to intercept requests BEFORE route matching
104+
let router_clone = router.clone();
105+
let path_clone = path.clone();
106+
let method_clone = method.clone();
107+
let routing_handler: BoxedNext = Arc::new(move |mut req: Request| {
108+
let router = router_clone.clone();
109+
let path = path_clone.clone();
110+
let method = method_clone.clone();
111+
Box::pin(async move {
112+
match router.match_route(&path, &method) {
113+
RouteMatch::Found { handler, params } => {
114+
// Set path params on the request
115+
req.set_path_params(params);
116+
handler(req).await
117+
}
118+
RouteMatch::NotFound => {
119+
ApiError::not_found(format!("No route found for {} {}", method, path))
120+
.into_response()
121+
}
122+
RouteMatch::MethodNotAllowed { allowed } => {
123+
let allowed_str: Vec<&str> = allowed.iter().map(|m| m.as_str()).collect();
124+
let mut response = ApiError::new(
125+
StatusCode::METHOD_NOT_ALLOWED,
126+
"method_not_allowed",
127+
format!("Method {} not allowed for {}", method, path),
128+
)
129+
.into_response();
130+
response
131+
.headers_mut()
132+
.insert(header::ALLOW, allowed_str.join(", ").parse().unwrap());
133+
response
134+
}
135+
}
136+
})
132137
as std::pin::Pin<
133138
Box<dyn std::future::Future<Output = crate::response::Response> + Send + 'static>,
134139
>
135140
});
136141

137-
// Execute through middleware stack
138-
let response = layers.execute(request, final_handler).await;
142+
// Execute through middleware stack - middleware runs FIRST, then routing
143+
let response = layers.execute(request, routing_handler).await;
139144

140145
// Apply response interceptors (in reverse registration order)
141146
let response = interceptors.intercept_response(response);

crates/rustapi-rs/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
//!
99
//! ## Quick Start
1010
//!
11-
//! ```rust
11+
//! ```rust,no_run
1212
//! use rustapi_rs::prelude::*;
1313
//!
1414
//! #[derive(Serialize, Schema)]

0 commit comments

Comments
 (0)