From 72071cf5de3fd28bdcfa2678ec4f0b810fcf8deb Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 7 Aug 2021 23:05:53 +0200 Subject: [PATCH] Implement `MethodFilter` via bitflags (#158) Fixes https://github.com/tokio-rs/axum/issues/107 --- CHANGELOG.md | 9 ++++--- Cargo.toml | 1 + src/handler/mod.rs | 44 ++++++++++++++++----------------- src/routing.rs | 47 +++--------------------------------- src/routing/method_filter.rs | 45 ++++++++++++++++++++++++++++++++++ src/service/mod.rs | 44 ++++++++++++++++----------------- src/tests.rs | 10 ++++---- 7 files changed, 103 insertions(+), 97 deletions(-) create mode 100644 src/routing/method_filter.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e4b15896..184ec119 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- Make `FromRequest` default to being generic over `axum::body::Body` ([#146](https://github.com/tokio-rs/axum/pull/146)) +- Make `FromRequest` default to being generic over `body::Body` ([#146](https://github.com/tokio-rs/axum/pull/146)) - Implement `std::error::Error` for all rejections ([#153](https://github.com/tokio-rs/axum/pull/153)) - Fix `Uri` extractor not being the full URI if using `nest` ([#156](https://github.com/tokio-rs/axum/pull/156)) - Add `RoutingDsl::or` for combining routes ([#108](https://github.com/tokio-rs/axum/pull/108)) @@ -17,13 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add associated `Body` and `BodyError` types to `IntoResponse`. This is required for returning responses with bodies other than `hyper::Body` from handlers. See the docs for advice on how to implement `IntoResponse` ([#86](https://github.com/tokio-rs/axum/pull/86)) -- Replace `axum::body::BoxStdError` with `axum::Error`, which supports downcasting ([#150](https://github.com/tokio-rs/axum/pull/150)) +- Replace `body::BoxStdError` with `Error`, which supports downcasting ([#150](https://github.com/tokio-rs/axum/pull/150)) - Change WebSocket API to use an extractor ([#121](https://github.com/tokio-rs/axum/pull/121)) - Make WebSocket `Message` an enum ([#116](https://github.com/tokio-rs/axum/pull/116)) -- `WebSocket` now uses `axum::Error` as its error type ([#150](https://github.com/tokio-rs/axum/pull/150)) -- Ensure a `HandleError` service created from `axum::ServiceExt::handle_error` +- `WebSocket` now uses `Error` as its error type ([#150](https://github.com/tokio-rs/axum/pull/150)) +- Ensure a `HandleError` service created from `ServiceExt::handle_error` _does not_ implement `RoutingDsl` as that could lead to confusing routing behavior ([#120](https://github.com/tokio-rs/axum/pull/120)) +- Implement `routing::MethodFilter` via [`bitflags`](https://crates.io/crates/bitflags) - Removed `extract::UrlParams` and `extract::UrlParamsMap`. Use `extract::Path` instead - `EmptyRouter` now requires the response body to implement `Send + Sync + 'static'` ([#108](https://github.com/tokio-rs/axum/pull/108)) - These future types have been moved diff --git a/Cargo.toml b/Cargo.toml index 1501c178..572f8795 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ multipart = ["multer", "mime"] [dependencies] async-trait = "0.1" +bitflags = "1.0" bytes = "1.0" futures-util = "0.3" http = "0.2" diff --git a/src/handler/mod.rs b/src/handler/mod.rs index 8f402f75..6968a45e 100644 --- a/src/handler/mod.rs +++ b/src/handler/mod.rs @@ -41,7 +41,7 @@ pub fn any(handler: H) -> OnMethod, EmptyRouter> where H: Handler, { - on(MethodFilter::Any, handler) + on(MethodFilter::all(), handler) } /// Route `CONNECT` requests to the given handler. @@ -51,7 +51,7 @@ pub fn connect(handler: H) -> OnMethod, EmptyRoute where H: Handler, { - on(MethodFilter::Connect, handler) + on(MethodFilter::CONNECT, handler) } /// Route `DELETE` requests to the given handler. @@ -61,7 +61,7 @@ pub fn delete(handler: H) -> OnMethod, EmptyRouter where H: Handler, { - on(MethodFilter::Delete, handler) + on(MethodFilter::DELETE, handler) } /// Route `GET` requests to the given handler. @@ -83,7 +83,7 @@ pub fn get(handler: H) -> OnMethod, EmptyRouter> where H: Handler, { - on(MethodFilter::Get, handler) + on(MethodFilter::GET, handler) } /// Route `HEAD` requests to the given handler. @@ -93,7 +93,7 @@ pub fn head(handler: H) -> OnMethod, EmptyRouter> where H: Handler, { - on(MethodFilter::Head, handler) + on(MethodFilter::HEAD, handler) } /// Route `OPTIONS` requests to the given handler. @@ -103,7 +103,7 @@ pub fn options(handler: H) -> OnMethod, EmptyRoute where H: Handler, { - on(MethodFilter::Options, handler) + on(MethodFilter::OPTIONS, handler) } /// Route `PATCH` requests to the given handler. @@ -113,7 +113,7 @@ pub fn patch(handler: H) -> OnMethod, EmptyRouter> where H: Handler, { - on(MethodFilter::Patch, handler) + on(MethodFilter::PATCH, handler) } /// Route `POST` requests to the given handler. @@ -123,7 +123,7 @@ pub fn post(handler: H) -> OnMethod, EmptyRouter> where H: Handler, { - on(MethodFilter::Post, handler) + on(MethodFilter::POST, handler) } /// Route `PUT` requests to the given handler. @@ -133,7 +133,7 @@ pub fn put(handler: H) -> OnMethod, EmptyRouter> where H: Handler, { - on(MethodFilter::Put, handler) + on(MethodFilter::PUT, handler) } /// Route `TRACE` requests to the given handler. @@ -143,7 +143,7 @@ pub fn trace(handler: H) -> OnMethod, EmptyRouter> where H: Handler, { - on(MethodFilter::Trace, handler) + on(MethodFilter::TRACE, handler) } /// Route requests with the given method to the handler. @@ -156,7 +156,7 @@ where /// async fn handler() {} /// /// // Requests to `POST /` will go to `handler`. -/// let app = route("/", on(MethodFilter::Post, handler)); +/// let app = route("/", on(MethodFilter::POST, handler)); /// # async { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; @@ -467,7 +467,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Any, handler) + self.on(MethodFilter::all(), handler) } /// Chain an additional handler that will only accept `CONNECT` requests. @@ -477,7 +477,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Connect, handler) + self.on(MethodFilter::CONNECT, handler) } /// Chain an additional handler that will only accept `DELETE` requests. @@ -487,7 +487,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Delete, handler) + self.on(MethodFilter::DELETE, handler) } /// Chain an additional handler that will only accept `GET` requests. @@ -512,7 +512,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Get, handler) + self.on(MethodFilter::GET, handler) } /// Chain an additional handler that will only accept `HEAD` requests. @@ -522,7 +522,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Head, handler) + self.on(MethodFilter::HEAD, handler) } /// Chain an additional handler that will only accept `OPTIONS` requests. @@ -532,7 +532,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Options, handler) + self.on(MethodFilter::OPTIONS, handler) } /// Chain an additional handler that will only accept `PATCH` requests. @@ -542,7 +542,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Patch, handler) + self.on(MethodFilter::PATCH, handler) } /// Chain an additional handler that will only accept `POST` requests. @@ -552,7 +552,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Post, handler) + self.on(MethodFilter::POST, handler) } /// Chain an additional handler that will only accept `PUT` requests. @@ -562,7 +562,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Put, handler) + self.on(MethodFilter::PUT, handler) } /// Chain an additional handler that will only accept `TRACE` requests. @@ -572,7 +572,7 @@ impl OnMethod { where H: Handler, { - self.on(MethodFilter::Trace, handler) + self.on(MethodFilter::TRACE, handler) } /// Chain an additional handler that will accept requests matching the given @@ -589,7 +589,7 @@ impl OnMethod { /// /// // Requests to `GET /` will go to `handler` and `DELETE /` will go to /// // `other_handler` - /// let app = route("/", get(handler).on(MethodFilter::Delete, other_handler)); + /// let app = route("/", get(handler).on(MethodFilter::DELETE, other_handler)); /// # async { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; diff --git a/src/routing.rs b/src/routing.rs index 820a1f8b..08375683 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -11,7 +11,7 @@ use crate::{ }; use async_trait::async_trait; use bytes::Bytes; -use http::{Method, Request, Response, StatusCode, Uri}; +use http::{Request, Response, StatusCode, Uri}; use regex::Regex; use std::{ borrow::Cow, @@ -29,50 +29,9 @@ use tower_http::map_response_body::MapResponseBodyLayer; pub mod future; pub mod or; +pub use self::method_filter::MethodFilter; -/// A filter that matches one or more HTTP methods. -#[derive(Debug, Copy, Clone)] -pub enum MethodFilter { - /// Match any method. - Any, - /// Match `CONNECT` requests. - Connect, - /// Match `DELETE` requests. - Delete, - /// Match `GET` requests. - Get, - /// Match `HEAD` requests. - Head, - /// Match `OPTIONS` requests. - Options, - /// Match `PATCH` requests. - Patch, - /// Match `POST` requests. - Post, - /// Match `PUT` requests. - Put, - /// Match `TRACE` requests. - Trace, -} - -impl MethodFilter { - #[allow(clippy::match_like_matches_macro)] - pub(crate) fn matches(self, method: &Method) -> bool { - match (self, method) { - (MethodFilter::Any, _) - | (MethodFilter::Connect, &Method::CONNECT) - | (MethodFilter::Delete, &Method::DELETE) - | (MethodFilter::Get, &Method::GET) - | (MethodFilter::Head, &Method::HEAD) - | (MethodFilter::Options, &Method::OPTIONS) - | (MethodFilter::Patch, &Method::PATCH) - | (MethodFilter::Post, &Method::POST) - | (MethodFilter::Put, &Method::PUT) - | (MethodFilter::Trace, &Method::TRACE) => true, - _ => false, - } - } -} +mod method_filter; /// A route that sends requests to one of two [`Service`]s depending on the /// path. diff --git a/src/routing/method_filter.rs b/src/routing/method_filter.rs new file mode 100644 index 00000000..0381f74b --- /dev/null +++ b/src/routing/method_filter.rs @@ -0,0 +1,45 @@ +use bitflags::bitflags; +use http::Method; + +bitflags! { + /// A filter that matches one or more HTTP methods. + pub struct MethodFilter: u16 { + /// Match `CONNECT` requests. + const CONNECT = 0b000000001; + /// Match `DELETE` requests. + const DELETE = 0b000000010; + /// Match `GET` requests. + const GET = 0b000000100; + /// Match `HEAD` requests. + const HEAD = 0b000001000; + /// Match `OPTIONS` requests. + const OPTIONS = 0b000010000; + /// Match `PATCH` requests. + const PATCH = 0b000100000; + /// Match `POSt` requests. + const POST = 0b001000000; + /// Match `PUT` requests. + const PUT = 0b010000000; + /// Match `TRACE` requests. + const TRACE = 0b100000000; + } +} + +impl MethodFilter { + #[allow(clippy::match_like_matches_macro)] + pub(crate) fn matches(self, method: &Method) -> bool { + let method = match *method { + Method::CONNECT => Self::CONNECT, + Method::DELETE => Self::DELETE, + Method::GET => Self::GET, + Method::HEAD => Self::HEAD, + Method::OPTIONS => Self::OPTIONS, + Method::PATCH => Self::PATCH, + Method::POST => Self::POST, + Method::PUT => Self::PUT, + Method::TRACE => Self::TRACE, + _ => return false, + }; + self.contains(method) + } +} diff --git a/src/service/mod.rs b/src/service/mod.rs index 7995c656..d0bbeb90 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -110,7 +110,7 @@ pub fn any(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Any, svc) + on(MethodFilter::all(), svc) } /// Route `CONNECT` requests to the given service. @@ -120,7 +120,7 @@ pub fn connect(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Connect, svc) + on(MethodFilter::CONNECT, svc) } /// Route `DELETE` requests to the given service. @@ -130,7 +130,7 @@ pub fn delete(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Delete, svc) + on(MethodFilter::DELETE, svc) } /// Route `GET` requests to the given service. @@ -157,7 +157,7 @@ pub fn get(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Get, svc) + on(MethodFilter::GET, svc) } /// Route `HEAD` requests to the given service. @@ -167,7 +167,7 @@ pub fn head(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Head, svc) + on(MethodFilter::HEAD, svc) } /// Route `OPTIONS` requests to the given service. @@ -177,7 +177,7 @@ pub fn options(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Options, svc) + on(MethodFilter::OPTIONS, svc) } /// Route `PATCH` requests to the given service. @@ -187,7 +187,7 @@ pub fn patch(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Patch, svc) + on(MethodFilter::PATCH, svc) } /// Route `POST` requests to the given service. @@ -197,7 +197,7 @@ pub fn post(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Post, svc) + on(MethodFilter::POST, svc) } /// Route `PUT` requests to the given service. @@ -207,7 +207,7 @@ pub fn put(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Put, svc) + on(MethodFilter::PUT, svc) } /// Route `TRACE` requests to the given service. @@ -217,7 +217,7 @@ pub fn trace(svc: S) -> OnMethod, EmptyRouter> + Clone, { - on(MethodFilter::Trace, svc) + on(MethodFilter::TRACE, svc) } /// Route requests with the given method to the service. @@ -235,7 +235,7 @@ where /// }); /// /// // Requests to `POST /` will go to `service`. -/// let app = route("/", service::on(MethodFilter::Post, service)); +/// let app = route("/", service::on(MethodFilter::POST, service)); /// # async { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; @@ -275,7 +275,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Any, svc) + self.on(MethodFilter::all(), svc) } /// Chain an additional service that will only accept `CONNECT` requests. @@ -285,7 +285,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Connect, svc) + self.on(MethodFilter::CONNECT, svc) } /// Chain an additional service that will only accept `DELETE` requests. @@ -295,7 +295,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Delete, svc) + self.on(MethodFilter::DELETE, svc) } /// Chain an additional service that will only accept `GET` requests. @@ -327,7 +327,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Get, svc) + self.on(MethodFilter::GET, svc) } /// Chain an additional service that will only accept `HEAD` requests. @@ -337,7 +337,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Head, svc) + self.on(MethodFilter::HEAD, svc) } /// Chain an additional service that will only accept `OPTIONS` requests. @@ -347,7 +347,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Options, svc) + self.on(MethodFilter::OPTIONS, svc) } /// Chain an additional service that will only accept `PATCH` requests. @@ -357,7 +357,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Patch, svc) + self.on(MethodFilter::PATCH, svc) } /// Chain an additional service that will only accept `POST` requests. @@ -367,7 +367,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Post, svc) + self.on(MethodFilter::POST, svc) } /// Chain an additional service that will only accept `PUT` requests. @@ -377,7 +377,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Put, svc) + self.on(MethodFilter::PUT, svc) } /// Chain an additional service that will only accept `TRACE` requests. @@ -387,7 +387,7 @@ impl OnMethod { where T: Service> + Clone, { - self.on(MethodFilter::Trace, svc) + self.on(MethodFilter::TRACE, svc) } /// Chain an additional service that will accept requests matching the given @@ -410,7 +410,7 @@ impl OnMethod { /// }); /// /// // Requests to `DELETE /` will go to `service` - /// let app = route("/", service::on(MethodFilter::Delete, service)); + /// let app = route("/", service::on(MethodFilter::DELETE, service)); /// # async { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; diff --git a/src/tests.rs b/src/tests.rs index 07fe3990..2b47b3a5 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -299,10 +299,10 @@ async fn extracting_url_params_multiple_times() { async fn boxing() { let app = route( "/", - on(MethodFilter::Get, |_: Request| async { + on(MethodFilter::GET, |_: Request| async { "hi from GET" }) - .on(MethodFilter::Post, |_: Request| async { + .on(MethodFilter::POST, |_: Request| async { "hi from POST" }), ) @@ -343,7 +343,7 @@ async fn service_handlers() { .route( "/static/Cargo.toml", service::on( - MethodFilter::Get, + MethodFilter::GET, ServeFile::new("Cargo.toml").handle_error(|error: std::io::Error| { Ok::<_, Infallible>((StatusCode::INTERNAL_SERVER_ERROR, error.to_string())) }), @@ -391,7 +391,7 @@ async fn routing_between_services() { Ok::<_, Infallible>(Response::new(Body::from("one post"))) })) .on( - MethodFilter::Put, + MethodFilter::PUT, service_fn(|_: Request| async { Ok::<_, Infallible>(Response::new(Body::from("one put"))) }), @@ -399,7 +399,7 @@ async fn routing_between_services() { ) .route( "/two", - service::on(MethodFilter::Get, handle.into_service()), + service::on(MethodFilter::GET, handle.into_service()), ); let addr = run_in_background(app).await;