Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option_layer #1696

Merged
merged 5 commits into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
5 changes: 4 additions & 1 deletion axum-extra/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning].

# Unreleased

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

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

# 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 @@ -67,6 +67,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 @@ -69,6 +69,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));
davidpdrsn marked this conversation as resolved.
Show resolved Hide resolved
/// # 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()))
}