Skip to content

Commit

Permalink
Add option_layer (#1696)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidpdrsn committed Feb 11, 2023
1 parent 0ecf5ee commit 37922ab
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 0 deletions.
3 changes: 3 additions & 0 deletions axum-extra/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ and this project adheres to [Semantic Versioning].

# Unreleased

- **added:** Add `option_layer` for converting an `Option<Layer>` into a `Layer` ([#1696])
- **added:** Implement `Layer` and `Service` for `Either` ([#1696])
- **added:** Add `TypedPath::with_query_params` ([#1744])

[#1696]: https://github.com/tokio-rs/axum/pull/1696
[#1744]: https://github.com/tokio-rs/axum/pull/1744

# 0.4.2 (02. December, 2022)
Expand Down
1 change: 1 addition & 0 deletions axum-extra/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.71"
tokio = { version = "1.14", features = ["full"] }
tower = { version = "0.4", features = ["util"] }
tower-http = { version = "0.3", features = ["map-response-body", "timeout"] }

[package.metadata.docs.rs]
all-features = true
Expand Down
43 changes: 43 additions & 0 deletions axum-extra/src/either.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,16 @@
//! [`BytesRejection`]: axum::extract::rejection::BytesRejection
//! [`IntoResponse::into_response`]: https://docs.rs/axum/0.5/axum/response/index.html#returning-different-response-types

use std::task::{Context, Poll};

use axum::{
async_trait,
extract::FromRequestParts,
response::{IntoResponse, Response},
};
use http::request::Parts;
use tower_layer::Layer;
use tower_service::Service;

/// Combines two extractors or responses into a single type.
///
Expand Down Expand Up @@ -267,3 +271,42 @@ impl_traits_for_either!(Either5 => [E1, E2, E3, E4], E5);
impl_traits_for_either!(Either6 => [E1, E2, E3, E4, E5], E6);
impl_traits_for_either!(Either7 => [E1, E2, E3, E4, E5, E6], E7);
impl_traits_for_either!(Either8 => [E1, E2, E3, E4, E5, E6, E7], E8);

impl<E1, E2, S> Layer<S> for Either<E1, E2>
where
E1: Layer<S>,
E2: Layer<S>,
{
type Service = Either<E1::Service, E2::Service>;

fn layer(&self, inner: S) -> Self::Service {
match self {
Either::E1(layer) => Either::E1(layer.layer(inner)),
Either::E2(layer) => Either::E2(layer.layer(inner)),
}
}
}

impl<R, E1, E2> Service<R> for Either<E1, E2>
where
E1: Service<R>,
E2: Service<R, Response = E1::Response, Error = E1::Error>,
{
type Response = E1::Response;
type Error = E1::Error;
type Future = futures_util::future::Either<E1::Future, E2::Future>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
match self {
Either::E1(inner) => inner.poll_ready(cx),
Either::E2(inner) => inner.poll_ready(cx),
}
}

fn call(&mut self, req: R) -> Self::Future {
match self {
Either::E1(inner) => futures_util::future::Either::Left(inner.call(req)),
Either::E2(inner) => futures_util::future::Either::Right(inner.call(req)),
}
}
}
1 change: 1 addition & 0 deletions axum-extra/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub mod body;
pub mod either;
pub mod extract;
pub mod handler;
pub mod middleware;
pub mod response;
pub mod routing;

Expand Down
44 changes: 44 additions & 0 deletions axum-extra/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Additional middleware utilities.

use crate::either::Either;
use tower_layer::Identity;

/// Convert an `Option<Layer>` into a [`Layer`].
///
/// If the layer is a `Some` it'll be applied, otherwise not.
///
/// # Example
///
/// ```
/// use axum_extra::middleware::option_layer;
/// use axum::{Router, routing::get};
/// use std::time::Duration;
/// use tower_http::timeout::TimeoutLayer;
///
/// # let option_timeout = Some(Duration::new(10, 0));
/// let timeout_layer = option_timeout.map(TimeoutLayer::new);
///
/// let app = Router::new()
/// .route("/", get(|| async {}))
/// .layer(option_layer(timeout_layer));
/// # let _: Router = app;
/// ```
///
/// # Difference between this and [`tower::util::option_layer`]
///
/// [`tower::util::option_layer`] always changes the error type to [`BoxError`] which requires
/// using [`HandleErrorLayer`] when used with axum, even if the layer you're applying uses
/// [`Infallible`].
///
/// `axum_extra::middleware::option_layer` on the other hand doesn't change the error type so can
/// be applied directly.
///
/// [`Layer`]: tower_layer::Layer
/// [`BoxError`]: tower::BoxError
/// [`HandleErrorLayer`]: axum::error_handling::HandleErrorLayer
/// [`Infallible`]: std::convert::Infallible
pub fn option_layer<L>(layer: Option<L>) -> Either<L, Identity> {
layer
.map(Either::E1)
.unwrap_or_else(|| Either::E2(Identity::new()))
}

0 comments on commit 37922ab

Please sign in to comment.