Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions axum-extra/src/routing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions axum/src/routing/method_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ impl MethodFilter {
pub const PUT: Self = Self::from_bits(0b0_1000_0000);
/// Match `TRACE` requests.
pub const TRACE: Self = Self::from_bits(0b1_0000_0000);
/// Match `QUERY` requests.
pub const QUERY: Self = Self::from_bits(0b10_0000_0000);

const fn bits(self) -> u16 {
let bits = self;
Expand Down Expand Up @@ -99,6 +101,7 @@ impl TryFrom<Method> for MethodFilter {
Method::POST => Ok(Self::POST),
Method::PUT => Ok(Self::PUT),
Method::TRACE => Ok(Self::TRACE),
other if other.as_str() == "QUERY" => Ok(Self::QUERY),
other => Err(NoMatchingMethodFilter { method: other }),
}
}
Expand Down Expand Up @@ -155,6 +158,11 @@ mod tests {
MethodFilter::TRACE
);

assert_eq!(
MethodFilter::try_from(Method::from_bytes(b"QUERY").unwrap()).unwrap(),
MethodFilter::QUERY
);

assert!(
MethodFilter::try_from(http::Method::from_bytes(b"CUSTOM").unwrap())
.unwrap_err()
Expand Down
47 changes: 46 additions & 1 deletion axum/src/routing/method_routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -649,6 +653,7 @@ where
}

chained_handler_fn!(connect, CONNECT);
chained_handler_fn!(query, QUERY);
chained_handler_fn!(delete, DELETE);
chained_handler_fn!(get, GET);
chained_handler_fn!(head, HEAD);
Expand Down Expand Up @@ -690,6 +695,7 @@ where
put,
trace,
connect,
query,
fallback,
allow_header: _,
} = self;
Expand All @@ -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))
Expand Down Expand Up @@ -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),
}
Expand All @@ -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),
}
Expand Down Expand Up @@ -995,10 +1004,21 @@ where
&["CONNECT"],
);

set_endpoint(
"QUERY",
&mut self.query,
endpoint,
filter,
MethodFilter::QUERY,
&mut self.allow_header,
&["QUERY"],
);

self
}

chained_service_fn!(connect_service, CONNECT);
chained_service_fn!(query_service, QUERY);
chained_service_fn!(delete_service, DELETE);
chained_service_fn!(get_service, GET);
chained_service_fn!(head_service, HEAD);
Expand Down Expand Up @@ -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,
}
Expand All @@ -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. \
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1175,6 +1199,24 @@ where

pub(crate) fn call_with_state(&self, req: Request, state: S) -> RouteFuture<E> {
macro_rules! call {
(
$req:expr,
QUERY,
$svc:expr
) => {
if req.method().as_str() == "QUERY" {
match $svc {
MethodEndpoint::None => {}
MethodEndpoint::Route(route) => {
return route.clone().oneshot_inner_owned($req);
}
MethodEndpoint::BoxedHandler(handler) => {
let route = handler.clone().into_route(state);
return route.oneshot_inner_owned($req);
}
}
}
};
(
$req:expr,
$method_variant:ident,
Expand Down Expand Up @@ -1206,6 +1248,7 @@ where
put,
trace,
connect,
query,
fallback,
allow_header,
} = self;
Expand All @@ -1220,6 +1263,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);

Expand Down Expand Up @@ -1263,6 +1307,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(),
}
Expand Down
2 changes: 1 addition & 1 deletion axum/src/routing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions axum/src/routing/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1352,3 +1352,18 @@ async fn middleware_adding_body() {

assert_eq!(res.text().await, "…");
}

#[crate::test]
async fn query_method_routing() {
use crate::routing::query;

let app = Router::new().route("/", query(|| async { "query handler" }));

let client = TestClient::new(app);

let query_method = Method::from_bytes(b"QUERY").unwrap();

let res = client.request(query_method, "/").await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "query handler");
}
9 changes: 9 additions & 0 deletions axum/src/test_helpers/test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ impl TestClient {
}
}

#[allow(dead_code)]
pub fn request(&self, method: http::Method, url: &str) -> RequestBuilder {
RequestBuilder {
builder: self
.client
.request(method, format!("http://{}{url}", self.addr)),
}
}

#[allow(dead_code)]
#[must_use]
pub fn server_port(&self) -> u16 {
Expand Down