Skip to content

Commit

Permalink
re-add and prepare tower-async-bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
glendc committed Nov 19, 2023
1 parent 51acb5d commit 6de2713
Show file tree
Hide file tree
Showing 14 changed files with 892 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

members = [
"tower-async",
"tower-async-bridge",
"tower-async-http",
"tower-async-hyper",
"tower-async-layer",
Expand Down
26 changes: 26 additions & 0 deletions tower-async-bridge/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.2.0 (November 20, 2023)

- Adapt to new `tower_async::Service` contract:
- `call` takes now `&self` instead of `&mut self`;
- `call` returns `impl Future` instead of declared as `async fn`;

## 0.1.1 (July 18, 2023)

- Improve, expand and fix documentation;

## 0.1.0 (July 17, 2023)

This is the initial release of `tower-async-bridge`, and is meant to bridge services and/or layers
from the <https://github.com/tower-rs/tower> ecosystem with those from the `tower-async` ecosystem
(meaning written using technology of this repository).

The bridging can go in both directions, but does require the `into_async` feature to be enabled
in case you want to bridge classic (<https://github.com/tower-rs/tower>) services and/or layers
into their `async (static fn) trait` counterparts.
52 changes: 52 additions & 0 deletions tower-async-bridge/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[package]
name = "tower-async-bridge"
# When releasing to crates.io:
# - Update doc url
# - Cargo.toml
# - README.md
# - Update CHANGELOG.md.
# - Create "v0.1.x" git tag.
version = "0.2.0"
authors = ["Glen De Cauwsemaecker <[email protected]>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/plabayo/tower-async"
homepage = "https://github.com/plabayo/tower-async"
description = """
Bridges a `tower-async` `Service` to be used within a `tower` (classic) environment,
and also the other way around.
"""
categories = ["asynchronous", "network-programming"]
edition = "2021"

[features]
default = []
full = [
"into_async",
]

into_async = ["tower/util"]

[dependencies]
async-lock = "3.1"
tower = { version = "0.4", optional = true }
tower-async-layer = { version = "0.2", path = "../tower-async-layer" }
tower-async-service = { version = "0.2", path = "../tower-async-service" }
tower-layer = { version = "0.3" }
tower-service = { version = "0.3" }

[dev-dependencies]
futures-core = "0.3"
hyper = { version = "1", features = ["full"] }
pin-project-lite = "0.2"
tokio = { version = "1.11", features = ["macros", "rt-multi-thread"] }
tokio-test = { version = "0.4" }
tower = { version = "0.4", features = ["full"] }
tower-async = { path = "../tower-async", features = ["full"] }

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[package.metadata.playground]
features = ["full"]
25 changes: 25 additions & 0 deletions tower-async-bridge/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright (c) 2023 Plabayo

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
30 changes: 30 additions & 0 deletions tower-async-bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Tower Async Bridge

Decorates a [`tower::Service`], allowing it to be turned into an [`Service`].

[![Crates.io][crates-badge]][crates-url]
[![Documentation][docs-badge]][docs-url]
[![MIT licensed][mit-badge]][mit-url]
[![Build Status][actions-badge]][actions-url]

[crates-badge]: https://img.shields.io/crates/v/tower_async_bridge.svg
[crates-url]: https://crates.io/crates/tower-async-bridge
[docs-badge]: https://docs.rs/tower-async-bridge/badge.svg
[docs-url]: https://docs.rs/tower-async-bridge
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: LICENSE
[actions-badge]: https://github.com/plabayo/tower-async/workflows/CI/badge.svg
[actions-url]:https://github.com/plabayo/tower-async/actions?query=workflow%3ACI

## License

This project is licensed under the [MIT license](LICENSE).

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Tower Async by you, shall be licensed as MIT, without any additional
terms or conditions.

[`tower::Service`]: https://docs.rs/tower/*/tower/trait.Service.html
[`Service`]: https://docs.rs/tower-async/*/tower_async/trait.Service.html
225 changes: 225 additions & 0 deletions tower-async-bridge/src/into_async/async_layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use crate::{AsyncServiceWrapper, ClassicServiceWrapper};

/// AsyncLayerExt adds a method to _any_ [`tower_layer::Layer`] that
/// wraps it in an [AsyncLayer] so that it can be used within a [`tower_async_layer::Layer`] environment.
///
/// [`tower_layer::Layer`]: https://docs.rs/tower-layer/*/tower_layer/trait.Layer.html
/// [`tower_async_layer::Layer`]: https://docs.rs/tower-async-layer/*/tower_async_layer/trait.Layer.html
pub trait AsyncLayerExt<S>: tower_layer::Layer<S> {
/// Wrap a [`tower_layer::Layer`],
/// so that it can be used within a [`tower_async_layer::Layer`] environment.
///
/// [`tower_layer::Layer`]: https://docs.rs/tower-layer/*/tower_layer/trait.Layer.html
/// [`tower_async_layer::Layer`]: https://docs.rs/tower-async-layer/*/tower_async_layer/trait.Layer.html
fn into_async(self) -> AsyncLayer<Self, S>
where
Self: Sized,
{
AsyncLayer::new(self)
}
}

impl<L, S> AsyncLayerExt<S> for L where L: tower_layer::Layer<S> + Sized {}

impl<L, S> From<L> for AsyncLayer<L, S>
where
L: tower_layer::Layer<S>,
{
fn from(inner: L) -> Self {
Self::new(inner)
}
}

/// A wrapper around a [`tower_layer::Layer`] that implements
/// [`tower_async_layer::Layer`] and is the type returned
/// by [AsyncLayerExt::into_async].
///
/// [`tower_layer::Layer`]: https://docs.rs/tower-layer/*/tower_layer/trait.Layer.html
/// [`tower_async_layer::Layer`]: https://docs.rs/tower-async-layer/*/tower_async_layer/trait.Layer.html
pub struct AsyncLayer<L, S> {
inner: L,
_marker: std::marker::PhantomData<S>,
}

impl<L, S> std::fmt::Debug for AsyncLayer<L, S>
where
L: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncLayer")
.field("inner", &self.inner)
.finish()
}
}

impl<L, S> Clone for AsyncLayer<L, S>
where
L: Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
_marker: std::marker::PhantomData,
}
}
}

impl<L, S> AsyncLayer<L, S> {
/// Create a new [AsyncLayer] wrapping `inner`.
pub fn new(inner: L) -> Self {
Self {
inner,
_marker: std::marker::PhantomData,
}
}
}

impl<L, S> tower_async_layer::Layer<S> for AsyncLayer<L, S>
where
L: tower_layer::Layer<ClassicServiceWrapper<S>>,
{
type Service =
AsyncServiceWrapper<<L as tower_layer::Layer<ClassicServiceWrapper<S>>>::Service>;

#[inline]
fn layer(&self, service: S) -> Self::Service {
let service = ClassicServiceWrapper::new(service);
let service = self.inner.layer(service);
AsyncServiceWrapper::new(service)
}
}

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

use pin_project_lite::pin_project;
use std::convert::Infallible;
use tower_async::ServiceExt;

#[derive(Debug)]
struct DelayService<S> {
inner: S,
delay: std::time::Duration,
}

impl<S> DelayService<S> {
fn new(inner: S, delay: std::time::Duration) -> Self {
Self { inner, delay }
}
}

impl<S, Request> tower_service::Service<Request> for DelayService<S>
where
S: tower_service::Service<Request>,
{
type Response = S::Response;
type Error = S::Error;
type Future = DelayFuture<tokio::time::Sleep, S::Future>;

fn poll_ready(
&mut self,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}

fn call(&mut self, request: Request) -> Self::Future {
DelayFuture::new(tokio::time::sleep(self.delay), self.inner.call(request))
}
}

enum DelayFutureState {
Delaying,
Serving,
}

pin_project! {
struct DelayFuture<T, U> {
state: DelayFutureState,
#[pin]
delay: T,
#[pin]
serve: U,
}
}

impl<T, U> DelayFuture<T, U> {
fn new(delay: T, serve: U) -> Self {
Self {
state: DelayFutureState::Delaying,
delay,
serve,
}
}
}

impl<T, U> std::future::Future for DelayFuture<T, U>
where
T: std::future::Future,
U: std::future::Future,
{
type Output = U::Output;

fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let this = self.project();
match this.state {
DelayFutureState::Delaying => {
let _ = futures_core::ready!(this.delay.poll(cx));
*this.state = DelayFutureState::Serving;
this.serve.poll(cx)
}
DelayFutureState::Serving => this.serve.poll(cx),
}
}
}

#[derive(Debug)]
struct DelayLayer {
delay: std::time::Duration,
}

impl DelayLayer {
fn new(delay: std::time::Duration) -> Self {
Self { delay }
}
}

impl<S> tower_layer::Layer<S> for DelayLayer {
type Service = DelayService<S>;

fn layer(&self, service: S) -> Self::Service {
DelayService::new(service, self.delay)
}
}

#[derive(Debug)]
struct AsyncEchoService;

impl<Request> tower_async_service::Service<Request> for AsyncEchoService {
type Response = Request;
type Error = Infallible;

async fn call(&self, req: Request) -> Result<Self::Response, Self::Error> {
Ok(req)
}
}

/// Test that a classic Tower layer can be used in an async tower builder.
/// While this is not the normal use case of this crate, it might as well be supported
/// for those cases where one _has_ to use a classic layer in an async tower envirioment,
/// because for example the functionality was not yet ported to an async trait version.
#[tokio::test]
async fn test_async_layer_in_async_tower_builder() {
let service = tower_async::ServiceBuilder::new()
.timeout(std::time::Duration::from_millis(200))
.layer(DelayLayer::new(std::time::Duration::from_millis(100)).into_async())
.service(AsyncEchoService);

let response = service.oneshot("hello").await.unwrap();
assert_eq!(response, "hello");
}
}
Loading

0 comments on commit 6de2713

Please sign in to comment.