feat: add QUERY method routing (#3801)

This commit is contained in:
Yunus
2026-08-03 21:29:39 +02:00
committed by GitHub
parent 98884fb7be
commit a2e64a8fc3
9 changed files with 78 additions and 16 deletions
Generated
+2 -2
View File
@@ -678,9 +678,9 @@ dependencies = [
[[package]]
name = "http"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
"itoa",
+2
View File
@@ -22,11 +22,13 @@ and this project adheres to [Semantic Versioning].
.route_with_tsr("/path", get(/* handler */))
.route_with_tsr("/path", post(/* handler */))
```
- **added:** Add `RouterExt::typed_query` ([#3801])
- **fixed:** Escape multipart `Content-Disposition` parameters and reject
newlines in field names and filenames ([#3776])
[#3599]: https://github.com/tokio-rs/axum/pull/3599
[#3586]: https://github.com/tokio-rs/axum/pull/3586
[#3801]: https://github.com/tokio-rs/axum/pull/3801
[#3776]: https://github.com/tokio-rs/axum/pull/3776
# 0.12.6
+23
View File
@@ -234,6 +234,19 @@ pub trait RouterExt<S>: sealed::Sealed {
T: SecondElementIs<P> + 'static,
P: TypedPath;
/// Add a typed `QUERY` route to the router.
///
/// The path will be inferred from the first argument to the handler function which must
/// implement [`TypedPath`].
///
/// See [`TypedPath`] for more details and examples.
#[cfg(feature = "typed-routing")]
fn typed_query<H, T, P>(self, handler: H) -> Self
where
H: axum::handler::Handler<T, S>,
T: SecondElementIs<P> + 'static,
P: TypedPath;
/// Add another route to the router with an additional "trailing slash redirect" route.
///
/// If you add a route _without_ a trailing slash, such as `/foo`, this method will also add a
@@ -368,6 +381,16 @@ where
self.route(P::PATH, axum::routing::connect(handler))
}
#[cfg(feature = "typed-routing")]
fn typed_query<H, T, P>(self, handler: H) -> Self
where
H: axum::handler::Handler<T, S>,
T: SecondElementIs<P> + 'static,
P: TypedPath,
{
self.route(P::PATH, axum::routing::query(handler))
}
#[track_caller]
fn route_with_tsr(mut self, path: &str, method_router: MethodRouter<S>) -> Self
where
+2
View File
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **changed:** `Redirect` constructors now accept any `impl Into<String>` ([#3635])
- **changed:** Updated `matchit` allowing for routes with captures and static prefixes and suffixes ([#3702])
- **fixed:** Responses to `HEAD` will not accidentally reply with `content-length: 0` anymore ([#3742])
- **added:** Add `MethodFilter::QUERY`, `routing::query[_service]` and `MethodRouter::query[_service]` ([#3801])
- **fixed:** `MethodRouter::merge` no longer lists a method twice in the `Allow`
header after merging `get` and `head` ([#3836])
@@ -42,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#3742]: https://github.com/tokio-rs/axum/pull/3742
[#3836]: https://github.com/tokio-rs/axum/pull/3836
[#3757]: https://github.com/tokio-rs/axum/pull/3757
[#3801]: https://github.com/tokio-rs/axum/pull/3801
# 0.8.9
+1 -1
View File
@@ -112,7 +112,7 @@ axum-core = { path = "../axum-core", version = "0.5.6" }
bytes = "1.7"
futures-core = "0.3"
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
http = "1.0.0"
http = "1.5.0"
http-body = "1.0.0"
http-body-util = "0.1.0"
itoa = "1.0.5"
+17 -9
View File
@@ -26,23 +26,25 @@ impl MethodFilter {
/// [extended CONNECT]: https://www.rfc-editor.org/rfc/rfc8441.html#section-4
/// [HTTP Upgrade Token Registry]: https://www.iana.org/assignments/http-upgrade-tokens/http-upgrade-tokens.xhtml
/// [`WebSocketUpgrade`]: crate::extract::WebSocketUpgrade
pub const CONNECT: Self = Self::from_bits(0b0_0000_0001);
pub const CONNECT: Self = Self::from_bits(0b00_0000_0001);
/// Match `DELETE` requests.
pub const DELETE: Self = Self::from_bits(0b0_0000_0010);
pub const DELETE: Self = Self::from_bits(0b00_0000_0010);
/// Match `GET` requests.
pub const GET: Self = Self::from_bits(0b0_0000_0100);
pub const GET: Self = Self::from_bits(0b00_0000_0100);
/// Match `HEAD` requests.
pub const HEAD: Self = Self::from_bits(0b0_0000_1000);
pub const HEAD: Self = Self::from_bits(0b00_0000_1000);
/// Match `OPTIONS` requests.
pub const OPTIONS: Self = Self::from_bits(0b0_0001_0000);
pub const OPTIONS: Self = Self::from_bits(0b00_0001_0000);
/// Match `PATCH` requests.
pub const PATCH: Self = Self::from_bits(0b0_0010_0000);
pub const PATCH: Self = Self::from_bits(0b00_0010_0000);
/// Match `POST` requests.
pub const POST: Self = Self::from_bits(0b0_0100_0000);
pub const POST: Self = Self::from_bits(0b00_0100_0000);
/// Match `PUT` requests.
pub const PUT: Self = Self::from_bits(0b0_1000_0000);
pub const PUT: Self = Self::from_bits(0b00_1000_0000);
/// Match `TRACE` requests.
pub const TRACE: Self = Self::from_bits(0b1_0000_0000);
pub const TRACE: Self = Self::from_bits(0b01_0000_0000);
/// Match `QUERY` requests.
pub const QUERY: Self = Self::from_bits(0b10_0000_0000);
const fn bits(self) -> u16 {
let bits = self;
@@ -99,6 +101,7 @@ impl TryFrom<Method> for MethodFilter {
Method::POST => Ok(Self::POST),
Method::PUT => Ok(Self::PUT),
Method::TRACE => Ok(Self::TRACE),
Method::QUERY => Ok(Self::QUERY),
other => Err(NoMatchingMethodFilter { method: other }),
}
}
@@ -155,6 +158,11 @@ mod tests {
MethodFilter::TRACE
);
assert_eq!(
MethodFilter::try_from(Method::QUERY).unwrap(),
MethodFilter::QUERY
);
assert!(
MethodFilter::try_from(http::Method::from_bytes(b"CUSTOM").unwrap())
.unwrap_err()
+28 -1
View File
@@ -341,6 +341,7 @@ top_level_service_fn!(patch_service, PATCH);
top_level_service_fn!(post_service, POST);
top_level_service_fn!(put_service, PUT);
top_level_service_fn!(trace_service, TRACE);
top_level_service_fn!(query_service, QUERY);
/// Route requests with the given method to the service.
///
@@ -445,6 +446,7 @@ top_level_handler_fn!(patch, PATCH);
top_level_handler_fn!(post, POST);
top_level_handler_fn!(put, PUT);
top_level_handler_fn!(trace, TRACE);
top_level_handler_fn!(query, QUERY);
/// Route requests with the given method to the handler.
///
@@ -554,6 +556,7 @@ pub struct MethodRouter<S = (), E = Infallible> {
put: MethodEndpoint<S, E>,
trace: MethodEndpoint<S, E>,
connect: MethodEndpoint<S, E>,
query: MethodEndpoint<S, E>,
fallback: Fallback<S, E>,
allow_header: AllowHeader,
}
@@ -604,6 +607,7 @@ impl<S, E> fmt::Debug for MethodRouter<S, E> {
.field("put", &self.put)
.field("trace", &self.trace)
.field("connect", &self.connect)
.field("query", &self.query)
.field("fallback", &self.fallback)
.field("allow_header", &self.allow_header)
.finish()
@@ -657,6 +661,7 @@ where
chained_handler_fn!(post, POST);
chained_handler_fn!(put, PUT);
chained_handler_fn!(trace, TRACE);
chained_handler_fn!(query, QUERY);
/// Add a fallback [`Handler`] to the router.
pub fn fallback<H, T>(mut self, handler: H) -> Self
@@ -690,6 +695,7 @@ where
put,
trace,
connect,
query,
fallback,
allow_header: _,
} = self;
@@ -708,6 +714,7 @@ where
(put, MethodFilter::PUT),
(trace, MethodFilter::TRACE),
(connect, MethodFilter::CONNECT),
(query, MethodFilter::QUERY),
]
.into_iter()
.filter_map(|(ep, f)| ep.is_some().then_some(f))
@@ -820,6 +827,7 @@ where
put: MethodEndpoint::None,
trace: MethodEndpoint::None,
connect: MethodEndpoint::None,
query: MethodEndpoint::None,
allow_header: AllowHeader::None,
fallback: Fallback::Default(fallback),
}
@@ -837,6 +845,7 @@ where
put: self.put.with_state(&state),
trace: self.trace.with_state(&state),
connect: self.connect.with_state(&state),
query: self.query.with_state(&state),
allow_header: self.allow_header,
fallback: self.fallback.with_state(state),
}
@@ -995,6 +1004,16 @@ where
&["CONNECT"],
);
set_endpoint(
"QUERY",
&mut self.query,
endpoint,
filter,
MethodFilter::QUERY,
&mut self.allow_header,
&["QUERY"],
);
self
}
@@ -1007,6 +1026,7 @@ where
chained_service_fn!(post_service, POST);
chained_service_fn!(put_service, PUT);
chained_service_fn!(trace_service, TRACE);
chained_service_fn!(query_service, QUERY);
#[doc = include_str!("../docs/method_routing/fallback.md")]
pub fn fallback_service<T>(mut self, svc: T) -> Self
@@ -1043,6 +1063,7 @@ where
put: self.put.map(layer_fn.clone()),
trace: self.trace.map(layer_fn.clone()),
connect: self.connect.map(layer_fn.clone()),
query: self.query.map(layer_fn.clone()),
fallback: self.fallback.map(layer_fn),
allow_header: self.allow_header,
}
@@ -1068,6 +1089,7 @@ where
&& self.put.is_none()
&& self.trace.is_none()
&& self.connect.is_none()
&& self.query.is_none()
{
panic!(
"Adding a route_layer before any routes is a no-op. \
@@ -1085,7 +1107,8 @@ where
self.post = self.post.map(layer_fn.clone());
self.put = self.put.map(layer_fn.clone());
self.trace = self.trace.map(layer_fn.clone());
self.connect = self.connect.map(layer_fn);
self.connect = self.connect.map(layer_fn.clone());
self.query = self.query.map(layer_fn);
self
}
@@ -1131,6 +1154,7 @@ where
self.put = merge_inner(path, "PUT", self.put, other.put)?;
self.trace = merge_inner(path, "TRACE", self.trace, other.trace)?;
self.connect = merge_inner(path, "CONNECT", self.connect, other.connect)?;
self.query = merge_inner(path, "QUERY", self.query, other.query)?;
self.fallback = self
.fallback
@@ -1206,6 +1230,7 @@ where
put,
trace,
connect,
query,
fallback,
allow_header,
} = self;
@@ -1220,6 +1245,7 @@ where
call!(req, DELETE, delete);
call!(req, TRACE, trace);
call!(req, CONNECT, connect);
call!(req, QUERY, query);
let future = fallback.clone().call_with_state(req, state);
@@ -1263,6 +1289,7 @@ impl<S, E> Clone for MethodRouter<S, E> {
put: self.put.clone(),
trace: self.trace.clone(),
connect: self.connect.clone(),
query: self.query.clone(),
fallback: self.fallback.clone(),
allow_header: self.allow_header.clone(),
}
+1 -1
View File
@@ -45,7 +45,7 @@ pub use self::{into_make_service::IntoMakeService, method_filter::MethodFilter,
pub use self::method_routing::{
any, any_service, connect, connect_service, delete, delete_service, get, get_service, head,
head_service, on, on_service, options, options_service, patch, patch_service, post,
post_service, put, put_service, trace, trace_service, MethodRouter,
post_service, put, put_service, query, query_service, trace, trace_service, MethodRouter,
};
macro_rules! panic_on_err {
+2 -2
View File
@@ -2308,9 +2308,9 @@ dependencies = [
[[package]]
name = "http"
version = "1.4.2"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
"itoa",