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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add layer_fn #491

Merged
merged 6 commits into from Dec 29, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions tower-layer/Cargo.toml
Expand Up @@ -25,3 +25,4 @@ edition = "2018"

[dev-dependencies]
tower-service = { version = "0.3.0" }
tower = { version = "0.3" }
104 changes: 104 additions & 0 deletions tower-layer/src/layer_fn.rs
@@ -0,0 +1,104 @@
use super::Layer;
use std::fmt;

/// Returns a new `LayerFn` with the given closure.
///
hawkw marked this conversation as resolved.
Show resolved Hide resolved
/// # Example
/// ```rust
/// # use tower::Service;
/// # use std::task::{Poll, Context};
/// # use tower_layer::{Layer, layer_fn};
/// # use std::fmt;
/// # use std::convert::Infallible;
/// #
/// // A middleware that logs requests before forwarding them to another service
/// pub struct LogService<S> {
/// target: &'static str,
/// service: S,
/// }
///
/// impl<S, Request> Service<Request> for LogService<S>
/// where
/// S: Service<Request>,
/// Request: fmt::Debug,
/// {
/// type Response = S::Response;
/// type Error = S::Error;
/// type Future = S::Future;
///
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
/// self.service.poll_ready(cx)
/// }
///
/// fn call(&mut self, request: Request) -> Self::Future {
/// // Log the request
/// println!("request = {:?}, target = {:?}", request, self.target);
///
/// self.service.call(request)
/// }
/// }
///
/// // A `Layer` that wraps services in `LogService`
/// let log_layer = layer_fn(|service| {
/// LogService {
/// service,
/// target: "tower-docs",
/// }
/// });
///
/// // An example service. This one uppercases strings
/// let uppercase_service = tower::service_fn(|request: String| async move {
/// Ok::<_, Infallible>(request.to_uppercase())
/// });
///
/// // Wrap our servic in a `LogService` so requests are logged.
/// let wrapped_service = log_layer.layer(uppercase_service);
/// ```
pub fn layer_fn<T>(f: T) -> LayerFn<T> {
hawkw marked this conversation as resolved.
Show resolved Hide resolved
LayerFn { f }
}

/// A `Layer` implemented by a closure. See the docs for `layer_fn` for more details.
hawkw marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Clone, Copy)]
pub struct LayerFn<F> {
f: F,
}

impl<F, S, Out> Layer<S> for LayerFn<F>
where
F: Fn(S) -> Out,
{
type Service = Out;

fn layer(&self, inner: S) -> Self::Service {
(self.f)(inner)
}
}

impl<F> fmt::Debug for LayerFn<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LayerFn")
.field("f", &format_args!("<{}>", std::any::type_name::<F>()))
.finish()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[allow(dead_code)]
hawkw marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn layer_fn_has_useful_debug_impl() {
struct WrappedService<S> {
inner: S,
}
let layer = layer_fn(|svc| WrappedService { inner: svc });
let _svc = layer.layer("foo");

assert_eq!(
"LayerFn { f: <tower_layer::layer_fn::tests::layer_fn_has_useful_debug_impl::{{closure}}> }".to_string(),
format!("{:?}", layer),
);
}
}
7 changes: 6 additions & 1 deletion tower-layer/src/lib.rs
Expand Up @@ -14,9 +14,14 @@
//! A middleware implements the [`Layer`] and [`Service`] trait.

mod identity;
mod layer_fn;
mod stack;

pub use self::{identity::Identity, stack::Stack};
pub use self::{
identity::Identity,
layer_fn::{layer_fn, LayerFn},
stack::Stack,
};

/// Decorates a `Service`, transforming either the request or the response.
///
Expand Down